PageSourceSearch

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

js pupwife.com collected 2026-09-30 15:05:52 UTC 4,900 bytes, 134 lines download raw bytes

1/**
2 * CitrusOS tool lifecycle: render → bind → destroy inside window mounts.
3 * Tools export init(scopeEl) / destroy(scopeEl); init calls render then bind.
4 */
5(function (global) {
6    'use strict';
7
8    function findRouteMount(root) {
9        if (!root || !root.querySelector) return null;
10        return root.querySelector('[data-citrus-mount]') ||
11            root.querySelector('#content') ||
12            null;
13    }
14
15    function findRouteContainer(scopeEl) {
16        var mount = getMount(scopeEl);
17        if (!mount) return null;
18        if (mount.classList && mount.classList.contains('citrus-window__content')) return mount;
19        return mount.closest ? mount.closest('.citrus-window__content') : null;
20    }
21
22    function attachPrefs(refs, scopeEl) {
23        if (!refs) return;
24        if (!global.CitrusPrefs) {
25            refs.prefs = { settings: {}, save: function () { return refs.prefs.settings; } };
26            return;
27        }
28        var routeId = null;
29        if (global.CitrusWindowLoader && typeof global.CitrusWindowLoader.getRouteIdForContainer === 'function') {
30            routeId = global.CitrusWindowLoader.getRouteIdForContainer(findRouteContainer(scopeEl));
31        }
32        refs.prefs = global.CitrusPrefs.createAppContext(routeId);
33    }
34
35    function getMount(scopeEl) {
36        if (!scopeEl) {
37            if (global.CitrusWindowLoader && typeof global.CitrusWindowLoader.getFocusedContainer === 'function') {
38                scopeEl = global.CitrusWindowLoader.getFocusedContainer();
39            }
40            if (!scopeEl && global.CitrusRouteScope && typeof global.CitrusRouteScope.getActiveScope === 'function') {
41                scopeEl = global.CitrusRouteScope.getActiveScope();
42            }
43        }
44        if (!scopeEl) return null;
45
46        if (scopeEl.matches && scopeEl.matches('[data-citrus-mount]')) return scopeEl;
47        if (scopeEl.id === 'content') return scopeEl;
48
49        var appRoot = scopeEl.querySelector('[data-citrus-app-root]');
50        if (appRoot) {
51            var nested = findRouteMount(appRoot);
52            return nested || appRoot;
53        }
54
55        var content = findRouteMount(scopeEl);
56        return content || scopeEl;
57    }
58
59    function queryById(id, scopeEl) {
60        if (!id) return null;
61        var mount = getMount(scopeEl);
62        if (mount && mount.querySelector) {
63            var scoped = mount.querySelector('#' + id);
64            if (scoped) return scoped;
65        }
66        return document.getElementById(id);
67    }
68
69    /**
70     * @param {object} spec
71     * @param {function(HTMLElement): object} spec.render
72     * @param {function(object, HTMLElement): void} [spec.bind]
73     * @param {function(object): void} [spec.teardown]
74     */
75    function create(spec) {
76        var state = { refs: null, mount: null, bound: false, bindInFlight: null };
77
78        function destroy(scopeEl) {
79            if (spec.teardown && state.refs) {
80                try { spec.teardown(state.refs); } catch (e) { console.warn('[CitrusToolContract] teardown', e); }
81            }
82            var mount = state.mount || getMount(scopeEl);
83            if (mount) mount.innerHTML = '';
84            state.refs = null;
85            state.mount = null;
86            state.bound = false;
87            state.bindInFlight = null;
88        }
89
90        function init(scopeEl) {
91            var mount = getMount(scopeEl);
92            if (!mount) return Promise.resolve();
93            if (state.bindInFlight) return state.bindInFlight;
94            if (state.bound) destroy(scopeEl);
95
96            mount.innerHTML = '';
97            state.mount = mount;
98            state.refs = spec.render(mount) || {};
99            attachPrefs(state.refs, scopeEl);
100            if (typeof spec.bind === 'function') {
101                var bindResult = spec.bind(state.refs, mount);
102                if (bindResult && typeof bindResult.then === 'function') {
103                    state.bindInFlight = bindResult.then(function () {
104                        state.bound = true;
105                    }).catch(function (e) {
106                        state.bound = false;
107                        console.warn('[CitrusToolContract] bind failed', e);
108                    }).finally(function () {
109                        state.bindInFlight = null;
110                    });
111                    return state.bindInFlight;
112                }
113                state.bound = true;
114                return Promise.resolve();
115            }
116            state.bound = true;
117            return Promise.resolve();
118        }
119
120        return {
121            init: init,
122            destroy: destroy,
123            render: spec.render,
124            bind: spec.bind,
125            getMount: getMount
126        };
127    }
128
129    global.CitrusToolContract = {
130        create: create,
131        getMount: getMount,
132        queryById: queryById
133    };
134})(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.