1/** 2 * CitrusOS desktop shortcuts â persisted shortcut list, factory defaults, resolve to apps. 3 */ 4(function (global) { 5 'use strict'; 6 7 var SESSION_KEY = 'citrusos_session_v3'; 8 var GRID_COL = 96; 9 var GRID_ROW = 104; 10 var GRID_PAD_X = 16; 11 var GRID_PAD_Y = 16; 12 13 var PINNED_CORNER_KEYS = { 14 siteinfo: 'bottom-right' 15 }; 16 17 function readSession() { 18 try { 19 var raw = global.localStorage.getItem(SESSION_KEY); 20 return raw ? JSON.parse(raw) : {}; 21 } catch (e) { 22 return {}; 23 } 24 } 25 26 function writeSession(session) { 27 try { 28 global.localStorage.setItem(SESSION_KEY, JSON.stringify(session)); 29 } catch (e) { /* ignore */ } 30 } 31 32 function uid() { 33 return 'sc-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7); 34 } 35 36 function snapToGrid(x, y) { 37 return { 38 x: GRID_PAD_X + Math.round((x - GRID_PAD_X) / GRID_COL) * GRID_COL, 39 y: GRID_PAD_Y + Math.round((y - GRID_PAD_Y) / GRID_ROW) * GRID_ROW 40 }; 41 } 42 43 function getDesktopEl() { 44 return document.getElementById('citrusos-desktop'); 45 } 46 47 function desktopBottomRight() { 48 var desktop = getDesktopEl(); 49 var w = desktop ? (desktop.clientWidth || desktop.offsetWidth) : 0; 50 var h = desktop ? (desktop.clientHeight || desktop.offsetHeight) : 0; 51 if (!w || !h) { 52 return snapToGrid(GRID_PAD_X + GRID_COL * 8, GRID_PAD_Y + GRID_ROW * 4); 53 } 54 return snapToGrid( 55 Math.max(GRID_PAD_X, w - GRID_COL - GRID_PAD_X), 56 Math.max(GRID_PAD_Y, h - GRID_ROW - GRID_PAD_Y) 57 ); 58 } 59 60 function targetKeyFromApp(app) { 61 if (!app) return ''; 62 if (app.launchTier === 'note' && app.noteId) return 'note:' + app.noteId; 63 if (app.launchTier === 'userFolder' || (app.userFolderId && app.folderId && String(app.folderId).indexOf('user:') === 0)) { 64 var ufId = app.userFolderId || String(app.folderId).replace(/^user:/, ''); 65 return 'user:' + ufId; 66 } 67 if (app.launchTier === 'folder' && app.folderPath && app.folderPath.length) { 68 return 'folder:' + (app.folderId || app.id) + ':' + app.folderPath.join('/'); 69 } 70 if (app.launchTier === 'folder' && app.folderId) { 71 return app.folderId; 72 } 73 if (app.href && global.CitrusAppRegistry) { 74 var registry = global.CitrusAppRegistry; 75 var byHref = registry.getAppByHref ? registry.getAppByHref(app.href) : null; 76 if (byHref && byHref.id && byHref.id === app.id) return app.id; 77 if (app.id && registry.getApp && registry.getApp(app.id)) return app.id; 78 return 'href:' + (registry.getAppByHref ? normalizeHref(app.href) : app.href); 79 } 80 return app.id || ''; 81 } 82 83 function normalizeHref(href) { 84 if (global.CitrusFolderRegistry && global.CitrusFolderRegistry.normalizeHref) { 85 return global.CitrusFolderRegistry.normalizeHref(href); 86 } 87 if (!href) return ''; 88 var key = String(href).replace(/^\//, '').split('?')[0].split('#')[0]; 89 if (!/\.html$/i.test(key) && key.indexOf('.') === -1) key += '.html'; 90 return '/' + key; 91 } 92 93 function targetTypeFromKey(targetKey) { 94 if (!targetKey) return 'app'; 95 if (targetKey.indexOf('note:') === 0) return 'note'; 96 if (targetKey.indexOf('user:') === 0) return 'userFolder'; 97 if (targetKey.indexOf('folder:') === 0) return 'folder'; 98 if (targetKey.indexOf('href:') === 0) return 'app'; 99 if (global.CitrusAppRegistry && global.CitrusAppRegistry.getApp) { 100 var app = global.CitrusAppRegistry.getApp(targetKey); 101 if (app) { 102 if (app.launchTier === 'folder') return 'folder'; 103 if (app.launchTier === 'note') return 'note'; 104 return 'app'; 105 } 106 } 107 return 'app'; 108 } 109 110 function factoryDefaults() { 111 var registry = global.CitrusAppRegistry; 112 if (!registry || !registry.getApps) return []; 113 var apps = registry.getApps(); 114 var shortcuts = []; 115 var col = 0; 116 var row = 0; 117 for (var i = 0; i < apps.length; i++) { 118 var app = apps[i]; 119 if (app.ownerOnly && registry.isSiteOwner && !registry.isSiteOwner()) continue; 120 var targetKey = targetKeyFromApp(app); 121 var pos;
122 if (PINNED_CORNER_KEYS[app.id] === 'bottom-right') { 123 pos = desktopBottomRight(); 124 } else { 125 pos = snapToGrid(GRID_PAD_X + col * GRID_COL, GRID_PAD_Y + row * GRID_ROW); 126 row += 1; 127 if (row > 5) { 128 row = 0; 129 col += 1; 130 } 131 } 132 shortcuts.push({ 133 id: uid(), 134 targetType: targetTypeFromKey(targetKey), 135 targetKey: targetKey, 136 label: null, 137 x: pos.x, 138 y: pos.y 139 }); 140 } 141 return shortcuts; 142 } 143 144 function migrateFromLegacySession(session) { 145 session = session || readSession(); 146 var registry = global.CitrusAppRegistry; 147 if (!registry) return factoryDefaults(); 148 149 var apps = registry.getApps(); 150 var iconMap = {}; 151 var icons = Array.isArray(session.icons) ? session.icons : []; 152 for (var i = 0; i < icons.length; i++) { 153 if (icons[i] && icons[i].appId) { 154 iconMap[icons[i].appId] = { x: icons[i].x, y: icons[i].y }; 155 } 156 } 157 158 var shortcuts = []; 159 for (var j = 0; j < apps.length; j++) { 160 var app = apps[j]; 161 if (app.ownerOnly && registry.isSiteOwner && !registry.isSiteOwner()) continue; 162 var targetKey = targetKeyFromApp(app); 163 var pos = iconMap[app.id]; 164 if (!pos) { 165 if (PINNED_CORNER_KEYS[app.id] === 'bottom-right') { 166 pos = desktopBottomRight(); 167 } else { 168 var slot = nextFreeSlot(shortcuts); 169 pos = slot; 170 } 171 } 172 shortcuts.push({ 173 id: uid(), 174 targetType: targetTypeFromKey(targetKey), 175 targetKey: targetKey, 176 label: null, 177 x: pos.x, 178 y: pos.y 179 }); 180 } 181 return shortcuts; 182 } 183 184 function getShortcuts(session) { 185 session = session || readSession(); 186 if (Array.isArray(session.desktopShortcuts) && session.desktopShortcuts.length) { 187 return session.desktopShortcuts.slice(); 188 } 189 return null; 190 } 191 192 function ensureShortcuts(session) { 193 session = session || readSession(); 194 var list = getShortcuts(session); 195 if (!list) { 196 list = migrateFromLegacySession(session); 197 session.desktopShortcuts = list; 198 writeSession(session); 199 } 200 return session.desktopShortcuts; 201 } 202 203 function saveShortcuts(list) { 204 var session = readSession(); 205 session.desktopShortcuts = list; 206 writeSession(session); 207 } 208 209 function nextFreeSlot(existing) { 210 existing = existing || ensureShortcuts(); 211 var used = {}; 212 for (var i = 0; i < existing.length; i++) { 213 var sc = existing[i]; 214 var col = Math.round((sc.x - GRID_PAD_X) / GRID_COL); 215 var row = Math.round((sc.y - GRID_PAD_Y) / GRID_ROW); 216 if (col >= 0 && row >= 0) used[col + ':' + row] = true; 217 } 218 var col = 0; 219 var row = 0; 220 while (used[col + ':' + row]) { 221 row += 1; 222 if (row > 5) { 223 row = 0; 224 col += 1; 225 } 226 } 227 return snapToGrid(GRID_PAD_X + col * GRID_COL, GRID_PAD_Y + row * GRID_ROW); 228 } 229 230 function isGridSlotUsed(x, y, list, ignoreShortcutId) { 231 for (var i = 0; i < list.length; i++) { 232 if (ignoreShortcutId && list[i].id === ignoreShortcutId) continue; 233 if (list[i].x === x && list[i].y === y) return true; 234 } 235 return false; 236 } 237 238 function clampToDesktopBounds(x, y, desktopEl) { 239 desktopEl = desktopEl || getDesktopEl(); 240 var maxX = GRID_PAD_X; 241 var maxY = GRID_PAD_Y; 242 if (desktopEl) { 243 maxX = Math.max(GRID_PAD_X, (desktopEl.clientWidth || 0) - GRID_COL - GRID_PAD_X); 244 maxY = Math.max(GRID_PAD_Y, (desktopEl.clientHeight || 0
244) - GRID_ROW - GRID_PAD_Y); 245 } 246 var snapped = snapToGrid(x, y); 247 return { 248 x: Math.max(GRID_PAD_X, Math.min(snapped.x, maxX)), 249 y: Math.max(GRID_PAD_Y, Math.min(snapped.y, maxY)) 250 }; 251 } 252 253 /** Snap to grid near a drop point; nudge to a free cell if occupied. */ 254 function resolveDropPosition(preferredX, preferredY, opts) { 255 opts = opts || {}; 256 var list = opts.list || ensureShortcuts(); 257 var desktopEl = opts.desktopEl || getDesktopEl(); 258 var centered = clampToDesktopBounds( 259 preferredX - GRID_COL / 2, 260 preferredY - GRID_ROW / 2, 261 desktopEl 262 ); 263 if (!isGridSlotUsed(centered.x, centered.y, list, opts.ignoreShortcutId)) { 264 return centered; 265 } 266 267 var col = Math.round((centered.x - GRID_PAD_X) / GRID_COL); 268 var row = Math.round((centered.y - GRID_PAD_Y) / GRID_ROW); 269 var radius; 270 for (radius = 1; radius <= 24; radius++) { 271 for (var dc = -radius; dc <= radius; dc++) { 272 for (var dr = -radius; dr <= radius; dr++) { 273 if (Math.abs(dc) !== radius && Math.abs(dr) !== radius) continue; 274 var candidate = clampToDesktopBounds( 275 GRID_PAD_X + (col + dc) * GRID_COL, 276 GRID_PAD_Y + (row + dr) * GRID_ROW, 277 desktopEl 278 ); 279 if (!isGridSlotUsed(candidate.x, candidate.y, list, opts.ignoreShortcutId)) { 280 return candidate; 281 } 282 } 283 } 284 } 285 return nextFreeSlot(list); 286 } 287 288 function clientPointToDesktop(clientX, clientY, desktopEl) { 289 desktopEl = desktopEl || getDesktopEl(); 290 if (!desktopEl) return null; 291 var rect = desktopEl.getBoundingClientRect(); 292 var x = clientX - rect.left; 293 var y = clientY - rect.top; 294 if (global.CitrusBrowserZoom && typeof global.CitrusBrowserZoom.isCompensating === 'function' && 295 global.CitrusBrowserZoom.isCompensating()) { 296 var scale = global.CitrusBrowserZoom.getScale(); 297 x *= scale; 298 y *= scale; 299 } 300 return { x: x, y: y }; 301 } 302 303 function resolveShortcut(sc) { 304 if (!sc) return null; 305 var registry = global.CitrusAppRegistry; 306 if (!registry) return null; 307 var key = sc.targetKey || ''; 308 309 if (key.indexOf('note:') === 0) { 310 var noteId = key.slice(5); 311 return registry.getAppById 312 ? registry.getAppById('note:' + noteId) 313 : { 314 id: 'note:' + noteId, 315 label: 'Note', 316 href: '/stickynotes.html', 317 icon: 'fa-solid fa-file-lines', 318 iconType: 'fa', 319 launchTier: 'note', 320 noteId: noteId, 321 singleton: false 322 }; 323 } 324 325 if (key.indexOf('user:') === 0) { 326 var ufId = key.slice(5); 327 var uf = global.CitrusUserFolders && global.CitrusUserFolders.getFolder 328 ? global.CitrusUserFolders.getFolder(ufId) 329 : null; 330 var ufLabel = uf ? uf.label : 'Folder'; 331 return { 332 id: 'userfolder:' + ufId, 333 label: sc.label || ufLabel, 334 icon: (uf && uf.icon) || 'fa-solid fa-folder', 335 iconType: 'fa', 336 launchTier: 'folder', 337 folderId: 'user:' + ufId, 338 userFolderId: ufId, 339 singleton: true, 340 defaultSize: { w: 680, h: 500 }, 341 resizable: false 342 }; 343 } 344 345 if (key.indexOf('folder:') === 0) { 346 var parts = key.split(':'); 347 var hubId = parts[1]; 348 var path = parts.length > 2 ? parts.slice(2).join(':').split('/').filter(Boolean) : []; 349 var hubMeta = global.CitrusFolderRegistry && global.CitrusFolderRegistry.FOLDER_META 350 ? global.CitrusFolderRegistry.FOLDER_META[hubId] 351 : null; 352 var subLabel = path.length ? path[path.length - 1] : (hubMeta ? hubMeta.label : hubId); 353 return { 354 id: 'folder:' + hubId + (path.length ? ':' + path.join('/') : ''), 355 label: sc.label || subLabel, 356 href: '/' + hubId + '.html', 357 icon: (hubMeta && hubMeta.icon) || 'fa-solid fa-folder', 358 iconType: 'citrus-folder', 359 launchTier: 'folder', 360 folderId: hubId, 361 folderPath: path, 362 singleton: true, 363 defaultSize: { w: 680, h: 500 }, 364 resizable: false 365 }; 366 } 367 368 if (key.indexOf('href:') === 0) { 369 var href = normalizeHref(key.slice(5)); 370 var leaf = registry.getAppByHref ? registry.getAppByHref(href) : null; 371 if (leaf) return applyLabelOverride(leaf, sc.label); 372 } 373 374 var app = registry.getAppById ? registry.getAppById(key) : registry.getApp(key); 375 if (app) return applyLabelOverride(app, sc.label); 376 377 app = registry.getAppByHref ? registry.getAppByHref('/' + key + '.html') : null; 378 if (app) return applyLabelOverride(app, sc.label); 379 380 return null; 381 } 382 383 function applyLabelOverride(app, label) { 384 if (!app || !label) return app; 385 return Object.assign({}, app, { label: label, shortcutLabel: label }); 386 } 387 388 function getShortcutById(shortcutId) { 389 var list = ensureShortcuts(); 390 for (var i = 0; i < list.length; i++) { 391 if (list[i].id === shortcutId) return list[i]; 392 } 393 return null; 394 } 395
396 function addShortcut(targetKey, opts) { 397 opts = opts || {}; 398 var list = ensureShortcuts().slice(); 399 var pos = opts.x != null && opts.y != null 400 ? resolveDropPosition(opts.x, opts.y, { list: list, ignoreShortcutId: opts.ignoreShortcutId }) 401 : nextFreeSlot(list); 402 var sc = { 403 id: uid(), 404 targetType: opts.targetType || targetTypeFromKey(targetKey), 405 targetKey: targetKey, 406 label: opts.label || null, 407 x: pos.x, 408 y: pos.y 409 }; 410 list.push(sc); 411 saveShortcuts(list); 412 return sc; 413 } 414 415 function removeShortcut(shortcutId) { 416 var list = ensureShortcuts().slice(); 417 var next = list.filter(function (sc) { return sc.id !== shortcutId; }); 418 if (next.length === list.length) return false; 419 saveShortcuts(next); 420 return true; 421 } 422 423 function removeShortcutsByTargetKey(targetKey) { 424 var list = ensureShortcuts().slice(); 425 var next = list.filter(function (sc) { return sc.targetKey !== targetKey; }); 426 if (next.length === list.length) return false; 427 saveShortcuts(next); 428 return true; 429 } 430 431 function findFreeSlotNear(preferredX, preferredY, occupied, placed, desktopEl) { 432 var key = preferredX + ':' + preferredY; 433 if (!occupied[key] && !placed[key]) { 434 return { x: preferredX, y: preferredY }; 435 } 436 437 var col = Math.round((preferredX - GRID_PAD_X) / GRID_COL); 438 var row = Math.round((preferredY - GRID_PAD_Y) / GRID_ROW); 439 var radius; 440 for (radius = 1; radius <= 24; radius++) { 441 for (var dc = -radius; dc <= radius; dc++) { 442 for (var dr = -radius; dr <= radius; dr++) { 443 if (Math.abs(dc) !== radius && Math.abs(dr) !== radius) continue; 444 var candidate = clampToDesktopBounds( 445 GRID_PAD_X + (col + dc) * GRID_COL, 446 GRID_PAD_Y + (row + dr) * GRID_ROW, 447 desktopEl 448 ); 449 var ck = candidate.x + ':' + candidate.y; 450 if (!occupied[ck] && !placed[ck]) return candidate; 451 } 452 } 453 } 454 455 var scratch = []; 456 var keys = Object.keys(occupied); 457 for (var i = 0; i < keys.length; i++) { 458 var parts = keys[i].split(':'); 459 scratch.push({ x: parseInt(parts[0], 10), y: parseInt(parts[1], 10) }); 460 } 461 var placedKeys = Object.keys(placed); 462 for (var j = 0; j < placedKeys.length; j++) { 463 var pp = placedKeys[j].split(':'); 464 scratch.push({ x: parseInt(pp[0], 10), y: parseInt(pp[1], 10) }); 465 } 466 return nextFreeSlot(scratch); 467 } 468 469 /** 470 * Snap a dragged icon group to the grid without stacking on the same cell. 471 * @param {{ id: string, x: number, y: number }[]} members current pixel positions 472 */ 473 function resolveGroupDropPositions(members, desktopEl) { 474 if (!members || !members.length) return []; 475 desktopEl = desktopEl || getDesktopEl(); 476 var list = ensureShortcuts(); 477 var movingIds = {}; 478 for (var i = 0; i < members.length; i++) movingIds[members[i].id] = true; 479 480 var occupied = {}; 481 for (var j = 0; j < list.length; j++) { 482 if (movingIds[list[j].id]) continue; 483 occupied[list[j].x + ':' + list[j].y] = true; 484 } 485 486 var sorted = members.map(function (member) { 487 var snapped = snapToGrid(member.x, member.y); 488 var clamped = clampToDesktopBounds(snapped.x, snapped.y, desktopEl); 489 return { id: member.id, x: clamped.x, y: clamped.y }; 490 }); 491 sorted.sort(function (a, b) { 492 if (a.y !== b.y) return a.y - b.y; 493 return a.x - b.x; 494 }); 495 496 var placed = {}; 497 var results = []; 498 for (var k = 0; k < sorted.length; k++) { 499 var item = sorted[k]; 500 var slot = findFreeSlotNear(item.x, item.y, occupied, placed, desktopEl); 501 var slotKey = slot.x + ':' + slot.y; 502 placed[slotKey] = item.id; 503 results.push({ id: item.id, x: slot.x, y: slot.y }); 504 } 505 return results; 506 } 507 508 function updateGroupPositions(updates) { 509 if (!updates || !updates.length) return; 510 var list = ensureShortcuts().slice(); 511 var map = {}; 512 for (var i = 0; i < updates.length; i++) map[updates[i].id] = updates[i]; 513 for (var j = 0; j < list.length; j++) { 514 if (map[list[j].id]) { 515 list[j].x = map[list[j].id].x; 516 list[j].y = map[list[j].id].y; 517 } 518 } 519 saveShortcuts(list); 520 } 521 522 function clampGroupDelta(group, dx, dy, desktopEl) { 523 desktopEl = desktopEl || getDesktopEl(); 524 var maxX = GRID_PAD_X; 525 var maxY = GRID_PAD_Y; 526 if (desktopEl) { 527 maxX = Math.max(GRID_PAD_X, (desktopEl.clientWidth || 0) - GRID_COL - GRID_PAD_X); 528 maxY = Math.max(GRID_PAD_Y, (desktopEl.clientHeight || 0
528) - GRID_ROW - GRID_PAD_Y); 529 } 530 531 var minDx = -Infinity; 532 var maxDx = Infinity; 533 var minDy = -Infinity; 534 var maxDy = Infinity; 535 for (var i = 0; i < group.length; i++) { 536 var member = group[i]; 537 var origLeft = member.origLeft; 538 var origTop = member.origTop; 539 if (typeof origLeft !== 'number') origLeft = parseInt(member.el && member.el.style.left, 10) || 0; 540 if (typeof origTop !== 'number') origTop = parseInt(member.el && member.el.style.top, 10) || 0; 541 minDx = Math.max(minDx, GRID_PAD_X - origLeft); 542 maxDx = Math.min(maxDx, maxX - origLeft); 543 minDy = Math.max(minDy, GRID_PAD_Y - origTop); 544 maxDy = Math.min(maxDy, maxY - origTop); 545 } 546 547 return { 548 dx: Math.max(minDx, Math.min(maxDx, dx)), 549 dy: Math.max(minDy, Math.min(maxDy, dy)) 550 }; 551 } 552 553 function renameShortcut(shortcutId, label) { 554 var list = ensureShortcuts().slice(); 555 for (var i = 0; i < list.length; i++) { 556 if (list[i].id === shortcutId) { 557 list[i].label = (label || '').trim() || null; 558 saveShortcuts(list); 559 return list[i]; 560 } 561 } 562 return null; 563 } 564 565 function updateShortcutPosition(shortcutId, x, y) { 566 var list = ensureShortcuts().slice(); 567 for (var i = 0; i < list.length; i++) { 568 if (list[i].id === shortcutId) { 569 var snapped = snapToGrid(x, y); 570 list[i].x = snapped.x; 571 list[i].y = snapped.y; 572 saveShortcuts(list); 573 return list[i]; 574 } 575 } 576 return null; 577 } 578 579 function syncPositionsFromDom() { 580 var desktop = getDesktopEl(); 581 if (!desktop) return; 582 var icons = desktop.querySelectorAll('.citrus-desktop-icon'); 583 var list = ensureShortcuts().slice(); 584 var map = {}; 585 for (var i = 0; i < list.length; i++) map[list[i].id] = list[i]; 586 for (var j = 0; j < icons.length; j++) { 587 var id = icons[j].getAttribute('data-shortcut-id'); 588 if (!id || !map[id]) continue; 589 map[id].x = parseInt(icons[j].style.left, 10) || map[id].x; 590 map[id].y = parseInt(icons[j].style.top, 10) || map[id].y; 591 } 592 saveShortcuts(list); 593 } 594 595 function resetToFactory() { 596 var list = factoryDefaults(); 597 saveShortcuts(list); 598 return list; 599 } 600 601 function resetLayoutOnly() { 602 var list = ensureShortcuts().slice(); 603 var sorted = list.slice().sort(function (a, b) { 604 if (a.y !== b.y) return a.y - b.y; 605 return a.x - b.x; 606 }); 607 var col = 0; 608 var row = 0; 609 for (var i = 0; i < sorted.length; i++) { 610 var snapped = snapToGrid(GRID_PAD_X + col * GRID_COL, GRID_PAD_Y + row * GRID_ROW); 611 sorted[i].x = snapped.x; 612 sorted[i].y = snapped.y; 613 row += 1; 614 if (row > 5) { 615 row = 0; 616 col += 1; 617 } 618 } 619 saveShortcuts(sorted); 620 return sorted; 621 } 622 623 function removeUserFolderShortcuts() { 624 var list = ensureShortcuts().slice(); 625 var next = list.filter(function (sc) { 626 return !(sc.targetKey && sc.targetKey.indexOf('user:') === 0); 627 }); 628 saveShortcuts(next); 629 return next; 630 } 631 632 function isShortcutAllowed(sc) { 633 var app = resolveShortcut(sc); 634 if (!app) return false; 635 if (app.ownerOnly && global.CitrusAppRegistry && global.CitrusAppRegistry.isSiteOwner && 636 !global.CitrusAppRegistry.isSiteOwner()) { 637 return false; 638 } 639 return true; 640 } 641 642 function listVisibleShortcuts() { 643 return ensureShortcuts().filter(isShortcutAllowed); 644 } 645 646 function layoutNormalizeKey(key) { 647 if (global.CitrusFolderLayout && global.CitrusFolderLayout.normalizeItemKey) { 648 return global.CitrusFolderLayout.normalizeItemKey(key); 649 } 650 return normalizeHref(key).replace(/^\//, ''); 651 } 652 653 function resolveLayoutItemKey(targetKey) { 654 if (!targetKey) return ''; 655 if (targetKey.indexOf('note:') === 0) return targetKey; 656 if (targetKey.indexOf('href:') === 0) { 657 return layoutNormalizeKey(targetKey.slice(5)); 658 } 659 if (global.CitrusAppRegistry) { 660 var app = global.CitrusAppRegistry.getAppById 661 ? global.CitrusAppRegistry.getAppById(targetKey) 662 : (global.CitrusAppRegistry.getApp ? global.CitrusAppRegistry.getApp(targetKey) : null); 663 if (app && app.href) return layoutNormalizeKey(app.href); 664 } 665 return layoutNormalizeKey(targetKey); 666 } 667 668 function targetKeyFromFolderEntry(entry) { 669 if (!entry) return ''; 670 if (entry.type === 'note') return 'note:' + (entry.noteId || entry.id); 671 if (entry.userFolder || entry.userFolderId) return 'user:' + entry.userFolderId; 672 if (entry.type === 'folder' && !entry.userFolder) { 673 return entry.id; 674 } 675 if (entry.href) return 'href:' + normalizeHref(entry.href).replace(/^\//, ''); 676 return entry.id || ''; 677 } 678 679 global.CitrusDesktopShortcuts = { 680 SESSION_KEY: SESSION_KEY, 681 GRID_COL: GRID_COL, 682 GRID_ROW: GRID_ROW, 683 GRID_PAD_X: GRID_PAD_X, 684 GRID_PAD_Y: GRID_PAD_Y, 685 readSession: readSession, 686 writeSession: writeSession,
687 snapToGrid: snapToGrid, 688 targetKeyFromApp: targetKeyFromApp, 689 resolveLayoutItemKey: resolveLayoutItemKey, 690 targetKeyFromFolderEntry: targetKeyFromFolderEntry, 691 targetTypeFromKey: targetTypeFromKey, 692 factoryDefaults: factoryDefaults, 693 migrateFromLegacySession: migrateFromLegacySession, 694 ensureShortcuts: ensureShortcuts, 695 getShortcuts: getShortcuts, 696 saveShortcuts: saveShortcuts, 697 resolveShortcut: resolveShortcut, 698 getShortcutById: getShortcutById, 699 addShortcut: addShortcut, 700 removeShortcut: removeShortcut, 701 removeShortcutsByTargetKey: removeShortcutsByTargetKey, 702 renameShortcut: renameShortcut, 703 updateShortcutPosition: updateShortcutPosition, 704 syncPositionsFromDom: syncPositionsFromDom, 705 nextFreeSlot: nextFreeSlot, 706 resolveDropPosition: resolveDropPosition, 707 resolveGroupDropPositions: resolveGroupDropPositions, 708 updateGroupPositions: updateGroupPositions, 709 clampGroupDelta: clampGroupDelta, 710 clampToDesktopBounds: clampToDesktopBounds, 711 clientPointToDesktop: clientPointToDesktop, 712 resetToFactory: resetToFactory, 713 resetLayoutOnly: resetLayoutOnly, 714 removeUserFolderShortcuts: removeUserFolderShortcuts, 715 listVisibleShortcuts: listVisibleShortcuts, 716 isShortcutAllowed: isShortcutAllowed 717 }; 718})(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.