1/** 2 * CitrusOS Changelog desktop widget â condensed preview, opens full changelog window. 3 */ 4(function (global) { 5 'use strict'; 6 7 var CHANGELOG_SCRIPTS = [ 8 '/scripts/dependencies/changelogData.js', 9 '/scripts/dependencies/siteChangelog.js' 10 ]; 11 var DATA_OPTS = { includeTodoActivity: false }; 12 var scriptsPromise = null; 13 var storageHandler = null; 14 15 function loadScriptOnce(src) { 16 if (global.PupwifeScriptLoad && typeof global.PupwifeScriptLoad.once === 'function') { 17 return global.PupwifeScriptLoad.once(src); 18 } 19 return new Promise(function (resolve, reject) { 20 if (document.querySelector('script[src="' + src + '"]')) { 21 resolve(); 22 return; 23 } 24 var script = document.createElement('script'); 25 script.src = src; 26 script.async = false; 27 script.onload = function () { resolve(); }; 28 script.onerror = function () { reject(new Error('Failed to load ' + src)); }; 29 document.head.appendChild(script); 30 }); 31 } 32 33 function ensureChangelogReady() { 34 if (global.SiteChangelog && global.PUPWIFE_CHANGELOG) { 35 return Promise.resolve(); 36 } 37 if (!scriptsPromise) { 38 scriptsPromise = CHANGELOG_SCRIPTS.reduce(function (chain, src) { 39 return chain.then(function () { return loadScriptOnce(src); }); 40 }, Promise.resolve()).catch(function (err) { 41 scriptsPromise = null; 42 throw err; 43 }); 44 } 45 return scriptsPromise; 46 } 47 48 function openChangelogWindow(ev) { 49 if (ev) { 50 ev.preventDefault(); 51 ev.stopPropagation(); 52 } 53 var panel = ev && ev.currentTarget; 54 var registry = global.CitrusAppRegistry; 55 var desktop = global.CitrusDesktop; 56 if (registry && desktop && typeof registry.getAppByHref === 'function') { 57 var app = registry.getAppByHref('/changelog.html'); 58 if (app && typeof desktop.openWindow === 'function') { 59 desktop.openWindow(app); 60 if (panel) { 61 global.setTimeout(function () { refreshPanel(panel); }, 400); 62 global.setTimeout(function () { refreshPanel(panel); }, 1200); 63 } 64 return; 65 } 66 if (typeof desktop.openHref === 'function') { 67 desktop.openHref('/changelog.html'); 68 } 69 } 70 } 71 72 function refreshPanel(panel) { 73 if (!panel || !global.SiteChangelog) return; 74 if (typeof global.SiteChangelog.renderPreview === 'function') { 75 global.SiteChangelog.renderPreview(panel, DATA_OPTS); 76 } 77 if (typeof global.SiteChangelog.syncUnread === 'function') { 78 global.SiteChangelog.syncUnread(panel, DATA_OPTS); 79 } 80 } 81 82 function renderWidget(mountEl, statusText) { 83 mountEl.innerHTML = 84 '<button type="button" class="citrus-widget-changelog__panel" data-changelog-widget-open>' + 85 '<span class="citrus-widget-changelog__head">' + 86 '<span class="citrus-widget-changelog__title">' + 87 '<i class="fa-solid fa-clock-rotate-left" aria-hidden="true"></i>' + 88 '<span>Changelog</span>' + 89 '</span>' + 90 '<span class="citrus-widget-changelog__badge" data-changelog-badge hidden aria-label="Unread updates"></span>' + 91 '</span>' + 92 '<span class="citrus-widget-changelog__preview">' + 93 '<span class="citrus-widget-changelog__date" data-changelog-preview-date>Loading...</span>' + 94 '<span class="citrus-widget-changelog__summary" data-changelog-preview-summary></span>' + 95 '<span class="citrus-widget-changelog__chips" data-changelog-preview-pages></span>' + 96 '</span>' + 97 '<span class="citrus-widget-changelog__foot">' + 98 '<span class="citrus-widget-changelog__hint">' + (statusText || 'Open full changelog') + '</span>' + 99 '<i class="fa-solid fa-up-right-from-square citrus-widget-changelog__foot-icon" aria-hidden="true"></i>' + 100 '</span>' + 101 '</button>'; 102 103 var panel = mountEl.querySelector('[data-changelog-widget-open]'); 104 if (panel) { 105 panel.addEventListener('click', openChangelogWindow); 106 panel.addEventListener('pointerdown', function (ev) { ev.stopPropagation(); }); 107 refreshPanel(panel); 108 } 109 } 110 111 function bindStorageSync(mountEl) { 112 if (storageHandler) return; 113 storageHandler = function (ev) { 114 if (ev.key !== 'pupwife_changelog_seen_revision') return; 115 var panel = mountEl.querySelector('[data-changelog-widget-open]'); 116 if (panel) refreshPanel(panel); 117 }; 118 global.addEventListener('storage', storageHandler); 119 } 120 121 function mount(body) { 122 body.classList.add('citrus-widget-changelog'); 123 var mountEl = document.createElement('div'); 124 mountEl.className = 'citrus-widget-changelog__mount'; 125 body.innerHTML = ''; 126 body.appendChild(mountEl); 127 128 renderWidget(mountEl, 'Loading...'); 129 bindStorageSync(mountEl); 130 131 ensureChangelogReady().then(function () { 132 if (!mountEl.isConnected) return; 133 renderWidget(mountEl); 134 }).catch(function () { 135 if (!mountEl.isConnected) return; 136 renderWidget(mountEl, 'Changelog unavailable'); 137 }); 138 139 return { 140 destroy: function () { 141 if (storageHandler) { 142 global.removeEventListener('storage', storageHandler); 143 storageHandler = null; 144 } 145 }, 146 refresh: function () { 147 var panel = mountEl.querySelector('[data-changelog-widget-open]'); 148 if (panel) refreshPanel(panel); 149 } 150 }; 151 } 152 153 if (global.CitrusWidgetRegistry && typeof global.CitrusWidgetRegistry.register === 'function') { 154 global.CitrusWidgetRegistry.register({ 155 type: 'changelog', 156 label: 'Changelog', 157 icon: 'fa-solid fa-clock-rotate-left', 158 blurb: 'Latest site updates at a glance. Click to open the full changelog.', 159 defaultSize: { w: 252, h: 176 }, 160 minSize: { w: 240, h: 168 }, 161 resizable: false, 162 persistSettings: false, 163 mount: mount 164 }); 165 } 166})(typeof window !== 'undefined' ? window : this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.