Skip to content

Instantly share code, notes, and snippets.

@adnanzameer
Created September 2, 2025 15:01
Show Gist options
  • Select an option

  • Save adnanzameer/f98e1be37934299f23b8a6b93d5195ed to your computer and use it in GitHub Desktop.

Select an option

Save adnanzameer/f98e1be37934299f23b8a6b93d5195ed to your computer and use it in GitHub Desktop.
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