PageSourceSearch

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

js pupwife.com collected 2026-09-30 15:04:21 UTC 4,508 bytes, 141 lines download raw bytes

1/**
2 * CitrusOS sticky notes — local document store (browser only, no sync).
3 * Used by the Sticky Notes folder explorer and note widgets.
4 */
5(function (global) {
6    'use strict';
7
8    var STORAGE_KEY = 'citrusos_sticky_notes_v1';
9    var DEFAULT_TITLE = 'Untitled note';
10
11    function uid() {
12        return 'note-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 7);
13    }
14
15    function readStore() {
16        try {
17            var raw = localStorage.getItem(STORAGE_KEY);
18            if (!raw) return { version: 1, notes: [] };
19            var data = JSON.parse(raw);
20            if (!data || !Array.isArray(data.notes)) return { version: 1, notes: [] };
21            return data;
22        } catch (e) {
23            return { version: 1, notes: [] };
24        }
25    }
26
27    function writeStore(data) {
28        try {
29            localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
30        } catch (e) { /* ignore */ }
31    }
32
33    function emitChanged() {
34        try {
35            global.dispatchEvent(new CustomEvent('pupwife:citrus-sticky-notes-changed'));
36        } catch (e) { /* ignore */ }
37    }
38
39    function deriveTitle(text, fallback) {
40        var fb = fallback || DEFAULT_TITLE;
41        if (typeof text !== 'string') return fb;
42        var line = text.split('\n')[0].replace(/\s+/g, ' ').trim();
43        if (!line) return fb;
44        if (line.length > 48) line = line.slice(0, 45) + '...';
45        return line;
46    }
47
48    function normalizeNote(note) {
49        if (!note || !note.id) return null;
50        return {
51            id: note.id,
52            title: note.title || DEFAULT_TITLE,
53            text: typeof note.text === 'string' ? note.text : '',
54            createdAt: note.createdAt || new Date().toISOString(),
55            updatedAt: note.updatedAt || note.createdAt || new Date().toISOString()
56        };
57    }
58
59    function listNotes() {
60        var store = readStore();
61        return store.notes.map(normalizeNote).filter(Boolean).sort(function (a, b) {
62            return (b.updatedAt || '').localeCompare(a.updatedAt || '');
63        });
64    }
65
66    function getNote(id) {
67        if (!id) return null;
68        var store = readStore();
69        for (var i = 0; i < store.notes.length; i++) {
70            if (store.notes[i].id === id) {
71                return normalizeNote(store.notes[i]);
72            }
73        }
74        return null;
75    }
76
77    function createNote(opts) {
78        opts = opts || {};
79        var now = new Date().toISOString();
80        var text = typeof opts.text === 'string' ? opts.text : '';
81        var note = {
82            id: uid(),
83            title: deriveTitle(text, opts.title || DEFAULT_TITLE),
84            text: text,
85            createdAt: now,
86            updatedAt: now
87        };
88        var store = readStore();
89        store.notes.unshift(note);
90        writeStore(store);
91        emitChanged();
92        return normalizeNote(note);
93    }
94
95    function updateNote(id, patch) {
96        if (!id) return null;
97        var store = readStore();
98        var updated = null;
99        for (var i = 0; i < store.notes.length; i++) {
100            if (store.notes[i].id !== id) continue;
101            var next = Object.assign({}, store.notes[i]);
102            if (patch && typeof patch.text === 'string') {
103                next.text = patch.text;
104                next.title = deriveTitle(next.text, next.title || DEFAULT_TITLE);
105            }
106            if (patch && typeof patch.title === 'string' && patch.title.trim()) {
107                next.title = patch.title.trim();
108            }
109            next.updatedAt = new Date().toISOString();
110            store.notes[i] = next;
111            updated = normalizeNote(next);
112            break;
113        }
114        if (updated) {
115            writeStore(store);
116            emitChanged();
117        }
118        return updated;
119    }
120
121    function deleteNote(id) {
122        if (!id) return false;
123        var store = readStore();
124        var nextNotes = store.notes.filter(function (n) { return n.id !== id; });
125        if (nextNotes.length === store.notes.length) return false;
126        store.notes = nextNotes;
127        writeStore(store);
128        emitChanged();
129        return true;
130    }
131
132    global.CitrusStickyNotes = {
133        listNotes: listNotes,
134        getNote: getNote,
135        createNote: createNote,
136        updateNote: updateNote,
137        deleteNote: deleteNote,
138        deriveTitle: deriveTitle,
139        DEFAULT_TITLE: DEFAULT_TITLE
140    };
141})(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.