1/** 2 * CitrusOS user-created folders â virtual filesystem lite. 3 */ 4(function (global) { 5 'use strict'; 6 7 var STORAGE_KEY = 'citrusos_user_folders_v1'; 8 9 function readStore() { 10 try { 11 var raw = global.localStorage.getItem(STORAGE_KEY); 12 if (!raw) return { version: 1, folders: {}, rootOrder: [] }; 13 var parsed = JSON.parse(raw); 14 if (!parsed || typeof parsed !== 'object') return { version: 1, folders: {}, rootOrder: [] }; 15 if (!parsed.folders || typeof parsed.folders !== 'object') parsed.folders = {}; 16 if (!Array.isArray(parsed.rootOrder)) parsed.rootOrder = []; 17 parsed.version = 1; 18 return parsed; 19 } catch (e) { 20 return { version: 1, folders: {}, rootOrder: [] }; 21 } 22 } 23 24 function writeStore(data) { 25 try { 26 global.localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); 27 } catch (e) { /* ignore */ } 28 emitChanged(); 29 } 30 31 function emitChanged() { 32 try { 33 global.dispatchEvent(new CustomEvent('pupwife:citrus-user-folders-changed')); 34 } catch (e) { /* ignore */ } 35 } 36 37 function uid() { 38 return 'uf-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 6); 39 } 40 41 function containerIdFor(folderId) { 42 return 'user:' + folderId; 43 } 44 45 function parseContainerId(containerId) { 46 if (!containerId || containerId.indexOf('user:') !== 0) return null; 47 return containerId.slice(5); 48 } 49 50 function getFolder(folderId) { 51 var store = readStore(); 52 return store.folders[folderId] || null; 53 } 54 55 function listRootFolders() { 56 var store = readStore(); 57 var list = []; 58 for (var i = 0; i < store.rootOrder.length; i++) { 59 var f = store.folders[store.rootOrder[i]]; 60 if (f) list.push(f); 61 } 62 return list; 63 } 64 65 function listChildFolders(parentId) { 66 var store = readStore(); 67 var parent = parentId ? store.folders[parentId] : null; 68 var childIds = parent ? (parent.children || []) : store.rootOrder; 69 var list = []; 70 for (var i = 0; i < childIds.length; i++) { 71 var f = store.folders[childIds[i]]; 72 if (f) list.push(f); 73 } 74 return list; 75 } 76 77 function listAnchoredInHub(hubId, pathSegments) { 78 var store = readStore(); 79 var pathKey = (pathSegments && pathSegments.length) ? pathSegments.join('/') : ''; 80 var list = []; 81 var keys = Object.keys(store.folders); 82 for (var i = 0; i < keys.length; i++) { 83 var f = store.folders[keys[i]]; 84 if (!f || f.parent) continue; 85 if (f.anchorContainer === hubId && (f.anchorPathKey || '') === pathKey) { 86 list.push(f); 87 } 88 } 89 list.sort(function (a, b) { return (a.sortIndex || 0) - (b.sortIndex || 0); }); 90 return list; 91 } 92 93 function createFolder(opts) { 94 opts = opts || {}; 95 var store = readStore(); 96 var id = uid(); 97 var label = (opts.label || 'New folder').trim() || 'New folder'; 98 var folder = { 99 id: id, 100 label: label, 101 icon: opts.icon || 'fa-solid fa-folder', 102 parent: opts.parent || null, 103 anchorContainer: opts.anchorContainer || null, 104 anchorPath: (opts.anchorPath || []).slice(), 105 anchorPathKey: pathKeyFrom(opts.anchorPath), 106 children: [], 107 sortIndex: typeof opts.sortIndex === 'number' ? opts.sortIndex : nextSortIndex(opts) 108 }; 109 store.folders[id] = folder; 110 111 if (folder.parent) { 112 var parent = store.folders[folder.parent]; 113 if (parent) { 114 if (!Array.isArray(parent.children)) parent.children = []; 115 parent.children.push(id); 116 } 117 } else if (folder.anchorContainer) { 118 /* anchored inside static hub */ 119 } else { 120 store.rootOrder.push(id); 121 } 122 123 writeStore(store); 124 return folder; 125 } 126 127 function pathKeyFrom(path) { 128 return (path && path.length) ? path.join('/') : ''; 129 } 130 131 function nextSortIndex(opts) { 132 if (opts.parent) { 133 var siblings = listChildFolders(opts.parent); 134 return siblings.length; 135 } 136 if (opts.anchorContainer) { 137 return listAnchoredInHub(opts.anchorContainer, opts.anchorPath).length; 138 } 139 return readStore().rootOrder.length; 140 } 141 142 function renameFolder(folderId, label) { 143 var store = readStore(); 144 var folder = store.folders[folderId]; 145 if (!folder) return null; 146 folder.label = (label || '').trim() || folder.label; 147 writeStore(store); 148 return folder; 149 } 150 151 function countContents(folderId) { 152 var layout = global.CitrusFolderLayout; 153 var childFolders = listChildFolders(folderId).length; 154 var items = layout && layout.listEntriesForLocation 155 ? layout.listEntriesForLocation(containerIdFor(folderId), []).length 156 : 0; 157 return childFolders + items; 158 } 159 160 function deleteFolder(folderId, force) { 161 var store = readStore(); 162 var folder = store.folders[folderId]; 163 if (!folder) return false;
164 if (!force && countContents(folderId) > 0) return false; 165 166 if (folder.parent) { 167 var parent = store.folders[folder.parent]; 168 if (parent && parent.children) { 169 parent.children = parent.children.filter(function (id) { return id !== folderId; }); 170 } 171 } else if (!folder.anchorContainer) { 172 store.rootOrder = store.rootOrder.filter(function (id) { return id !== folderId; }); 173 } 174 175 delete store.folders[folderId]; 176 writeStore(store); 177 return true; 178 } 179 180 function moveFolderNode(folderId, targetParentId, targetAnchorContainer, targetAnchorPath) { 181 var store = readStore(); 182 var folder = store.folders[folderId]; 183 if (!folder) return false; 184 185 if (targetParentId && isDescendant(targetParentId, folderId)) return false; 186 187 if (folder.parent) { 188 var oldParent = store.folders[folder.parent]; 189 if (oldParent && oldParent.children) { 190 oldParent.children = oldParent.children.filter(function (id) { return id !== folderId; }); 191 } 192 } else if (!folder.anchorContainer) { 193 store.rootOrder = store.rootOrder.filter(function (id) { return id !== folderId; }); 194 } 195 196 folder.parent = targetParentId || null; 197 folder.anchorContainer = targetParentId ? null : (targetAnchorContainer || null); 198 folder.anchorPath = targetParentId ? [] : (targetAnchorPath || []).slice(); 199 folder.anchorPathKey = targetParentId ? '' : pathKeyFrom(targetAnchorPath); 200 201 if (folder.parent) { 202 var newParent = store.folders[folder.parent]; 203 if (newParent) { 204 if (!Array.isArray(newParent.children)) newParent.children = []; 205 if (newParent.children.indexOf(folderId) === -1) newParent.children.push(folderId); 206 } 207 } else if (!folder.anchorContainer) { 208 if (store.rootOrder.indexOf(folderId) === -1) store.rootOrder.push(folderId); 209 } 210 211 writeStore(store); 212 return true; 213 } 214 215 function isDescendant(candidateId, ancestorId) { 216 if (!candidateId || !ancestorId) return false; 217 if (candidateId === ancestorId) return true; 218 var current = getFolder(candidateId); 219 while (current && current.parent) { 220 if (current.parent === ancestorId) return true; 221 current = getFolder(current.parent); 222 } 223 return false; 224 } 225 226 function clearAll() { 227 writeStore({ version: 1, folders: {}, rootOrder: [] }); 228 } 229 230 function folderToListingNode(folder) { 231 return { 232 id: folder.id, 233 label: folder.label, 234 icon: folder.icon || 'fa-solid fa-folder', 235 iconType: 'fa', 236 type: 'folder', 237 userFolder: true, 238 userFolderId: folder.id, 239 containerId: containerIdFor(folder.id) 240 }; 241 } 242 243 global.CitrusUserFolders = { 244 STORAGE_KEY: STORAGE_KEY, 245 readStore: readStore, 246 containerIdFor: containerIdFor, 247 parseContainerId: parseContainerId, 248 getFolder: getFolder, 249 listRootFolders: listRootFolders, 250 listChildFolders: listChildFolders, 251 listAnchoredInHub: listAnchoredInHub, 252 createFolder: createFolder, 253 renameFolder: renameFolder, 254 deleteFolder: deleteFolder, 255 moveFolderNode: moveFolderNode, 256 countContents: countContents, 257 isDescendant: isDescendant, 258 clearAll: clearAll, 259 folderToListingNode: folderToListingNode 260 }; 261})(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.