Created
September 2, 2025 15:01
-
-
Save adnanzameer/f98e1be37934299f23b8a6b93d5195ed to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| define([ | |
| "dojo/_base/declare", | |
| "epi-cms/contentediting/editors/ContentAreaEditor", | |
| "dojo/dom-class", | |
| "dojo/dom-construct", | |
| "dojo/request", | |
| "dojo/_base/lang" | |
| ], function (declare, ContentAreaEditor, domClass, domConstruct, request, lang) { | |
| // === Config === | |
| // Set to false for badge-only; true for badge + tinted row | |
| var TINT_ROW = true; | |
| // Per-status CSS classes (badge + optional row tint) | |
| var statusCss = { | |
| NotCreated: { badge: "poc-badge--gray", row: "poc-row--gray" }, | |
| Rejected: { badge: "poc-badge--red", row: "poc-row--red" }, | |
| CheckedOut: { badge: "poc-badge--yellow", row: "poc-row--yellow" }, | |
| CheckedIn: { badge: "poc-badge--yellow", row: "poc-row--yellow" }, | |
| PreviouslyPublished: { badge: "poc-badge--gray", row: "poc-row--gray" }, | |
| DelayedPublish: { badge: "poc-badge--blue", row: "poc-row--blue" }, | |
| AwaitingApproval: { badge: "poc-badge--orange", row: "poc-row--orange" } | |
| // Published => no badge | |
| }; | |
| return declare([ContentAreaEditor], { | |
| _statusCache: null, // { [id: string]: { code: string, label: string, saved?: string, changedBy?: string } } | |
| _retryTimers: null, // number[] timeout IDs | |
| postMixInProperties: function () { | |
| this.inherited(arguments); | |
| this._statusCache = Object.create(null); | |
| this._retryTimers = []; | |
| }, | |
| destroy: function () { | |
| for (var i = 0; i < this._retryTimers.length; i++) { | |
| clearTimeout(this._retryTimers[i]); | |
| } | |
| this._retryTimers.length = 0; | |
| this.inherited(arguments); | |
| }, | |
| _setValueAttr: function (value) { | |
| this.inherited(arguments); | |
| if (!value || !value.length) return; | |
| var self = this; | |
| var idToNodes = {}; // { [id: string]: HTMLElement[] } | |
| var ids = []; // distinct numeric IDs for batch API | |
| value.forEach(function (item) { | |
| var cl = (item && item.contentLink) || ""; | |
| if (!cl) return; | |
| var id = String(cl).split("_")[0]; | |
| if (!id) return; | |
| if (!idToNodes[id]) idToNodes[id] = []; | |
| // find and register node (with a small retry loop) | |
| self._findNodeWithRetry(id, cl, 5, 120, function (node) { | |
| if (!node) return; | |
| if (idToNodes[id].indexOf(node) === -1) { | |
| idToNodes[id].push(node); | |
| } | |
| var cached = self._statusCache[id]; | |
| if (cached && typeof cached.label === "string") { | |
| self._applyDecoration(node, cached.code, cached.label, cached.saved, cached.changedBy); | |
| } | |
| }); | |
| if (!self._statusCache[id] && ids.indexOf(+id) === -1) { | |
| ids.push(+id); | |
| } | |
| }); | |
| if (!ids.length) return; | |
| // Batch fetch with meta for tooltip | |
| request.post("/api/blockstatus/batch", { | |
| handleAs: "json", | |
| headers: { "Content-Type": "application/json" }, | |
| data: JSON.stringify({ ids: ids, includeMeta: true }) | |
| }).then(lang.hitch(this, function (resp) { | |
| // Expected shape: { statuses: { "123": { code, label, saved?, changedBy? }, ... } } | |
| var map = (resp && resp.statuses) || {}; | |
| for (var key in map) if (map.hasOwnProperty(key)) { | |
| var info = map[key] || {}; | |
| var code = info.code || ""; | |
| var label = info.label || ""; | |
| var saved = info.saved || null; | |
| var by = info.changedBy || null; | |
| this._statusCache[key] = { code: code, label: label, saved: saved, changedBy: by }; | |
| var nodes = idToNodes[key] || []; | |
| for (var i = 0; i < nodes.length; i++) { | |
| this._applyDecoration(nodes[i], code, label, saved, by); | |
| } | |
| } | |
| })); | |
| }, | |
| _applyDecoration: function (node, statusCode, statusLabel, savedIso, changedBy) { | |
| // No label => Published => no badge | |
| if (!statusLabel) return; | |
| domClass.add(node, "poc-unpublished-block"); | |
| if (TINT_ROW && statusCss[statusCode] && statusCss[statusCode].row) { | |
| domClass.add(node, statusCss[statusCode].row); | |
| } | |
| // Build tooltip text: "<Label> · Last edited by X on 1 Sep 2025, 15:22" | |
| var tooltip = statusLabel; | |
| var savedText = ""; | |
| if (savedIso) { | |
| try { | |
| var d = new Date(savedIso); | |
| savedText = d.toLocaleString([], { | |
| year: "numeric", month: "short", day: "numeric", | |
| hour: "2-digit", minute: "2-digit" | |
| }); | |
| } catch (e) { /* ignore */ } | |
| } | |
| if (changedBy && savedText) { | |
| tooltip += " · Last edited by " + changedBy + " on " + savedText; | |
| } else if (changedBy) { | |
| tooltip += " · Last edited by " + changedBy; | |
| } else if (savedText) { | |
| tooltip += " · Last edited on " + savedText; | |
| } | |
| var badgeClass = (statusCss[statusCode] && statusCss[statusCode].badge) || "poc-badge--yellow"; | |
| var indicator = node.querySelector(".poc-indicator"); | |
| if (!indicator) { | |
| indicator = domConstruct.create("span", { | |
| className: "poc-indicator " + badgeClass, | |
| innerHTML: "⚠ " + statusLabel, | |
| title: tooltip, | |
| role: "status", | |
| "aria-label": tooltip | |
| }, node, "last"); | |
| } else { | |
| indicator.className = "poc-indicator " + badgeClass; | |
| indicator.innerHTML = "⚠ " + statusLabel; | |
| indicator.title = tooltip; | |
| indicator.setAttribute("aria-label", tooltip); | |
| } | |
| node.setAttribute("data-poc-decorated", "1"); | |
| }, | |
| // ---- Node finding (fast + small retry) ---- | |
| _findNodeWithRetry: function (idOnly, fullLink, maxAttempts, delayMs, onDone) { | |
| var attempt = 0, self = this; | |
| function tryFind() { | |
| var node = self._findNodeFast(self.domNode, idOnly, fullLink); | |
| if (node || attempt >= maxAttempts) { onDone(node || null); return; } | |
| attempt++; | |
| var t = setTimeout(tryFind, delayMs); | |
| self._retryTimers.push(t); | |
| } | |
| tryFind(); | |
| }, | |
| _findNodeFast: function (root, idOnly, fullLink) { | |
| if (!root) return null; | |
| // 1) Exact match on common attributes | |
| var exact = [ | |
| '[data-epi-contentlink="' + fullLink + '"]', | |
| '[data-epi-contentLink="' + fullLink + '"]', | |
| '[data-epi-relatedcontentlink="' + fullLink + '"]', | |
| '[data-epi-relatedContentLink="' + fullLink + '"]', | |
| '[data-epi-block-link="' + fullLink + '"]', | |
| '[data-epi-blockLink="' + fullLink + '"]' | |
| ]; | |
| for (var i = 0; i < exact.length; i++) { | |
| var n = root.querySelector(exact[i]); | |
| if (n) return n.closest(".epi-contentArea-editorItem, .epi-content-area__item, .epi-contentAreaItem") || n; | |
| } | |
| // 2) Contains numeric id | |
| var contains = [ | |
| '[data-epi-contentlink*="' + idOnly + '"]', | |
| '[data-epi-contentLink*="' + idOnly + '"]', | |
| '[data-epi-relatedcontentlink*="' + idOnly + '"]', | |
| '[data-epi-relatedContentLink*="' + idOnly + '"]', | |
| '[data-epi-block-link*="' + idOnly + '"]', | |
| '[data-epi-blockLink*="' + idOnly + '"]' | |
| ]; | |
| for (var j = 0; j < contains.length; j++) { | |
| var n2 = root.querySelector(contains[j]); | |
| if (n2) return n2.closest(".epi-contentArea-editorItem, .epi-content-area__item, .epi-contentAreaItem") || n2; | |
| } | |
| // 3) Fallback: scan any attribute for id/fullLink | |
| var all = root.querySelectorAll("*"); | |
| for (var k = 0; k < all.length; k++) { | |
| var el = all[k]; | |
| for (var a = 0; a < el.attributes.length; a++) { | |
| var v = el.attributes[a].value; | |
| if (v && (v.indexOf(fullLink) !== -1 || v.indexOf(idOnly) !== -1)) { | |
| return el.closest(".epi-contentArea-editorItem, .epi-content-area__item, .epi-contentAreaItem") || el; | |
| } | |
| } | |
| } | |
| return null; | |
| } | |
| }); | |
| }); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment