PageSourceSearch

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

js pupwife.com collected 2026-09-30 15:04:23 UTC 5,596 bytes, 171 lines download raw bytes

1/**
2 * CitrusOS folder layout — user item placement overrides (moves between containers).
3 */
4(function (global) {
5    'use strict';
6
7    var STORAGE_KEY = 'citrusos_folder_layout_v1';
8
9    function readStore() {
10        try {
11            var raw = global.localStorage.getItem(STORAGE_KEY);
12            if (!raw) return { version: 1, entries: {} };
13            var parsed = JSON.parse(raw);
14            if (!parsed || typeof parsed !== 'object') return { version: 1, entries: {} };
15            if (!parsed.entries || typeof parsed.entries !== 'object') parsed.entries = {};
16            parsed.version = 1;
17            return parsed;
18        } catch (e) {
19            return { version: 1, entries: {} };
20        }
21    }
22
23    function writeStore(data) {
24        try {
25            global.localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
26        } catch (e) { /* ignore */ }
27    }
28
29    function normalizeItemKey(key) {
30        if (!key) return '';
31        if (key.indexOf('note:') === 0) return key;
32        if (global.CitrusFolderRegistry && typeof global.CitrusFolderRegistry.normalizeHref === 'function') {
33            return global.CitrusFolderRegistry.normalizeHref(key).replace(/^\//, '');
34        }
35        var k = String(key).replace(/^\//, '');
36        if (!/\.html$/i.test(k) && k.indexOf('.') === -1) k += '.html';
37        return k;
38    }
39
40    function itemKeyFromNode(node) {
41        if (!node) return '';
42        if (node.type === 'note') return 'note:' + (node.noteId || node.id);
43        if (node.href) return normalizeItemKey(node.href);
44        return node.id || '';
45    }
46
47    function itemKeyFromHref(href) {
48        return normalizeItemKey(href);
49    }
50
51    function noteItemKey(noteId) {
52        return noteId ? 'note:' + noteId : '';
53    }
54
55    function parseContainerId(containerId) {
56        var id = containerId || '';
57        if (id.indexOf('user:') === 0) {
58            return { type: 'user', id: id.slice(5), containerId: id };
59        }
60        return { type: 'hub', id: id, containerId: id };
61    }
62
63    function pathKey(path) {
64        return (path && path.length) ? path.join('/') : '';
65    }
66
67    function locationKey(containerId, path) {
68        return (containerId || '') + '|' + pathKey(path || []);
69    }
70
71    function getPlacement(itemKey) {
72        var store = readStore();
73        var key = normalizeItemKey(itemKey);
74        if (key.indexOf('note:') !== 0 && key.indexOf('href:') === 0) {
75            key = normalizeItemKey(key.slice(5));
76        }
77        return store.entries[key] || null;
78    }
79
80    function setPlacement(itemKey, containerId, path, sortIndex) {
81        var store = readStore();
82        var key = normalizeItemKey(itemKey);
83        store.entries[key] = {
84            containerId: containerId,
85            path: (path || []).slice(),
86            sortIndex: typeof sortIndex === 'number' ? sortIndex : 0
87        };
88        writeStore(store);
89        emitLayoutChanged();
90        return store.entries[key];
91    }
92
93    function removePlacement(itemKey) {
94        var store = readStore();
95        var key = normalizeItemKey(itemKey);
96        if (!store.entries[key]) return false;
97        delete store.entries[key];
98        writeStore(store);
99        emitLayoutChanged();
100        return true;
101    }
102
103    function clearAll() {
104        writeStore({ version: 1, entries: {} });
105        emitLayoutChanged();
106    }
107
108    function listEntriesForLocation(containerId, path) {
109        var store = readStore();
110        var loc = locationKey(containerId, path);
111        var results = [];
112        var keys = Object.keys(store.entries);
113        for (var i = 0; i < keys.length; i++) {
114            var entry = store.entries[keys[i]];
115            if (!entry) continue;
116            if (locationKey(entry.containerId, entry.path) === loc) {
117                results.push({ itemKey: keys[i], entry: entry });
118            }
119        }
120        results.sort(function (a, b) {
121            return (a.entry.sortIndex || 0) - (b.entry.sortIndex || 0);
122        });
123        return results;
124    }
125
126    function isItemRelocated(itemKey) {
127        return !!getPlacement(itemKey);
128    }
129
130    function nextSortIndex(containerId, path) {
131        var list = listEntriesForLocation(containerId, path);
132        if (!list.length) return 0;
133        var max = 0;
134        for (var i = 0; i < list.length; i++) {
135            max = Math.max(max, list[i].entry.sortIndex || 0);
136        }
137        return max + 1;
138    }
139
140    function moveItem(itemKey, containerId, path, sortIndex) {
141        return setPlacement(itemKey, containerId, path,
142            typeof sortIndex === 'number' ? sortIndex : nextSortIndex(containerId, path));
143    }
144
145    function emitLayoutChanged() {
146        try {
147            global.dispatchEvent(new CustomEvent('pupwife:citrus-folder-layout-changed'));
148        } catch (e) { /* ignore */ }
149    }
150
151    global.CitrusFolderLayout = {
152        STORAGE_KEY: STORAGE_KEY,
153        readStore: readStore,
154        writeStore: writeStore,
155        normalizeItemKey: normalizeItemKey,
156        itemKeyFromNode: itemKeyFromNode,
157        itemKeyFromHref: itemKeyFromHref,
158        noteItemKey: noteItemKey,
159        parseContainerId: parseContainerId,
160        pathKey: pathKey,
161        locationKey: locationKey,
162        getPlacement: getPlacement,
163        setPlacement: setPlacement,
164        removePlacement: removePlacement,
165        clearAll: clearAll,
166        listEntriesForLocation: listEntriesForLocation,
167        isItemRelocated: isItemRelocated,
168        nextSortIndex: nextSortIndex,
169        moveItem: moveItem
170    };
171})(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.