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.