PageSourceSearch

https://pupwife.com/scripts/desktop/citrusChangelogWidget.js

js pupwife.com collected 2026-09-30 15:04:39 UTC 6,736 bytes, 166 lines download raw bytes

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.