clay-server 4.0.0-beta.8 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/capsule-display-floor.js +51 -0
- package/lib/capsule-frame-server.js +255 -0
- package/lib/capsule-mate-turn.js +105 -0
- package/lib/capsule-pig-logic.js +300 -0
- package/lib/capsule-server-runtimes.js +39 -0
- package/lib/capsules/pig/display.js +198 -0
- package/lib/capsules/pig/manifest.json +9 -0
- package/lib/capsules/pig/ui.json +84 -0
- package/lib/config.js +25 -1
- package/lib/daemon-projects.js +13 -8
- package/lib/daemon.js +127 -14
- package/lib/git-cli.js +95 -1
- package/lib/knowledge-import.js +436 -0
- package/lib/knowledge-record-store.js +188 -0
- package/lib/knowledge-search.js +240 -0
- package/lib/mate-knowledge-mcp-server.js +151 -0
- package/lib/mate-knowledge-migration.js +283 -0
- package/lib/mate-knowledge-service.js +488 -0
- package/lib/mate-knowledge-sync.js +482 -0
- package/lib/migrate-single-user.js +2 -0
- package/lib/notes-lifecycle.js +149 -0
- package/lib/notes.js +60 -3
- package/lib/os-users.js +3 -1
- package/lib/project-capsule-catalog.js +9 -1
- package/lib/project-capsule-turn.js +96 -0
- package/lib/project-connection.js +15 -1
- package/lib/project-file-history.js +223 -0
- package/lib/project-file-watch.js +77 -49
- package/lib/project-filesystem.js +31 -220
- package/lib/project-http.js +15 -1
- package/lib/project-knowledge.js +22 -0
- package/lib/project-log-context.js +172 -0
- package/lib/project-log-feedback-delivery.js +118 -0
- package/lib/project-logs-comments.js +137 -0
- package/lib/project-logs-context-state.js +42 -0
- package/lib/project-logs-mcp-server.js +328 -0
- package/lib/project-logs-query.js +185 -0
- package/lib/project-logs-root.js +75 -0
- package/lib/project-logs-schema.js +259 -0
- package/lib/project-logs-service.js +463 -0
- package/lib/project-logs-snapshot.js +205 -0
- package/lib/project-logs-store.js +495 -0
- package/lib/project-logs-versioning.js +132 -0
- package/lib/project-logs.js +400 -0
- package/lib/project-mate-interaction.js +14 -0
- package/lib/project-mate-knowledge.js +110 -0
- package/lib/project-memory.js +22 -0
- package/lib/project-message-delivery.js +70 -0
- package/lib/project-pair-lifecycle.js +460 -0
- package/lib/project-request-access.js +66 -0
- package/lib/project-session-handoff.js +1 -1
- package/lib/project-session-notes.js +64 -13
- package/lib/project-session-pair.js +159 -146
- package/lib/project-session-spawn.js +1 -1
- package/lib/project-sessions.js +58 -13
- package/lib/project-user-message.js +53 -4
- package/lib/project-worker-permission.js +430 -0
- package/lib/project-worker-proposal.js +208 -62
- package/lib/project.js +185 -6
- package/lib/public/app.js +63 -35
- package/lib/public/css/admin.css +2 -2
- package/lib/public/css/capsule-ui.css +38 -0
- package/lib/public/css/command-palette.css +19 -9
- package/lib/public/css/git-placard.css +154 -0
- package/lib/public/css/home-hub.css +4 -0
- package/lib/public/css/home-session-actions.css +66 -0
- package/lib/public/css/home-sidebar.css +66 -0
- package/lib/public/css/input.css +84 -3
- package/lib/public/css/messages.css +40 -1
- package/lib/public/css/mobile-nav.css +80 -0
- package/lib/public/css/notifications-center.css +67 -0
- package/lib/public/css/pane.css +97 -10
- package/lib/public/css/project-logs.css +198 -0
- package/lib/public/css/scheduler.css +1 -1
- package/lib/public/css/session-actions.css +16 -101
- package/lib/public/css/sidebar.css +103 -0
- package/lib/public/css/sticky-notes.css +237 -229
- package/lib/public/css/worker-proposal.css +36 -3
- package/lib/public/index.html +43 -11
- package/lib/public/modules/admin.js +8 -8
- package/lib/public/modules/app-connection.js +124 -6
- package/lib/public/modules/app-home-hub.js +12 -8
- package/lib/public/modules/app-message-cards.js +2 -1
- package/lib/public/modules/app-messages.js +92 -11
- package/lib/public/modules/app-misc.js +2 -14
- package/lib/public/modules/app-notifications.js +11 -7
- package/lib/public/modules/app-panels.js +3 -2
- package/lib/public/modules/app-projects.js +56 -18
- package/lib/public/modules/app-rendering.js +1 -0
- package/lib/public/modules/capsule-preference.js +44 -0
- package/lib/public/modules/chat-bubble-renderer.js +1 -1
- package/lib/public/modules/clay-log-links.js +70 -0
- package/lib/public/modules/command-palette.js +52 -15
- package/lib/public/modules/context-sources.js +0 -3
- package/lib/public/modules/git-agent-sessions.js +85 -0
- package/lib/public/modules/git-panel.js +111 -84
- package/lib/public/modules/git-placard.js +161 -0
- package/lib/public/modules/home-chat-empty-state.js +39 -0
- package/lib/public/modules/home-chat-identity.js +9 -0
- package/lib/public/modules/home-conversations-sheet.js +102 -48
- package/lib/public/modules/home-mate-chat.js +15 -35
- package/lib/public/modules/home-session-actions.js +6 -0
- package/lib/public/modules/home-sidebar-chat-list.js +101 -41
- package/lib/public/modules/home-sidebar.js +7 -0
- package/lib/public/modules/home-surface-boot.js +7 -3
- package/lib/public/modules/home-tool-frame.js +182 -0
- package/lib/public/modules/home-tools.js +138 -1
- package/lib/public/modules/input.js +38 -7
- package/lib/public/modules/markdown.js +2 -0
- package/lib/public/modules/mate-sidebar.js +0 -8
- package/lib/public/modules/message-delivery.js +97 -0
- package/lib/public/modules/paste-modal.js +84 -0
- package/lib/public/modules/project-activation.js +45 -0
- package/lib/public/modules/project-logs-render.js +405 -0
- package/lib/public/modules/project-logs.js +461 -0
- package/lib/public/modules/project-removal-target.js +26 -0
- package/lib/public/modules/scheduler.js +17 -3
- package/lib/public/modules/session-actions.js +29 -105
- package/lib/public/modules/session-hierarchy.js +54 -0
- package/lib/public/modules/sidebar-mobile.js +40 -15
- package/lib/public/modules/sidebar-session-hierarchy.js +214 -0
- package/lib/public/modules/sidebar-sessions.js +77 -11
- package/lib/public/modules/sidebar.js +17 -5
- package/lib/public/modules/split-group-helpers.js +8 -0
- package/lib/public/modules/split-pair-ui.js +15 -4
- package/lib/public/modules/split-view.js +5 -2
- package/lib/public/modules/split-worker-runtime.js +24 -0
- package/lib/public/modules/sticky-note-markdown.js +5 -0
- package/lib/public/modules/sticky-notes-browser.js +326 -0
- package/lib/public/modules/sticky-notes-card.js +360 -0
- package/lib/public/modules/sticky-notes-editor.js +291 -0
- package/lib/public/modules/sticky-notes-shared.js +90 -0
- package/lib/public/modules/sticky-notes.js +170 -946
- package/lib/public/modules/thinking-lifecycle.js +175 -0
- package/lib/public/modules/thinking-summary.js +28 -0
- package/lib/public/modules/thinking-view.js +75 -0
- package/lib/public/modules/tool-palette-order.js +143 -0
- package/lib/public/modules/tool-palette-overlays.js +159 -0
- package/lib/public/modules/tool-palette.js +21 -188
- package/lib/public/modules/tools.js +31 -123
- package/lib/public/modules/update-snooze.js +169 -0
- package/lib/public/modules/user-settings.js +4 -0
- package/lib/public/modules/worker-pane-lock.js +176 -0
- package/lib/public/modules/worker-proposal-state.js +16 -0
- package/lib/public/modules/worker-proposal.js +49 -20
- package/lib/public/style.css +3 -1
- package/lib/sdk-bridge.js +135 -46
- package/lib/sdk-message-processor.js +36 -19
- package/lib/server-experimental-settings.js +49 -0
- package/lib/server-global-ws.js +11 -0
- package/lib/server-home-chat-events.js +31 -1
- package/lib/server-home-chat.js +27 -4
- package/lib/server-home-clay-entry.js +1 -1
- package/lib/server-home-clay-session-links.js +34 -5
- package/lib/server-settings.js +5 -0
- package/lib/server-tools.js +127 -9
- package/lib/server.js +137 -22
- package/lib/session-driver-eligibility.js +38 -0
- package/lib/session-driver-orchestration.js +31 -0
- package/lib/session-hygiene.js +3 -0
- package/lib/session-notes-mcp-server.js +25 -5
- package/lib/session-pair-factory.js +247 -0
- package/lib/session-pair-mcp-server.js +46 -8
- package/lib/session-pair-prompts.js +79 -0
- package/lib/session-pair-turn-control.js +153 -0
- package/lib/session-provenance.js +119 -0
- package/lib/session-spawn-mcp-server.js +1 -1
- package/lib/session-split-groups.js +4 -1
- package/lib/session-title-policy.js +60 -0
- package/lib/session-visibility.js +41 -0
- package/lib/sessions.js +57 -10
- package/lib/tools-registry.js +89 -10
- package/lib/update-snooze.js +437 -0
- package/lib/users-experimental-preferences.js +30 -0
- package/lib/users-permissions.js +6 -6
- package/lib/users.js +22 -16
- package/lib/workspace-query-access.js +102 -0
- package/lib/workspace-query-service.js +24 -17
- package/lib/worktree.js +25 -38
- package/lib/ws-schema.js +38 -7
- package/lib/yoke/adapters/claude-worker.js +16 -6
- package/lib/yoke/adapters/claude.js +12 -0
- package/lib/yoke/adapters/codex.js +24 -14
- package/lib/yoke/vendor-registry.js +1 -1
- package/package.json +2 -2
|
@@ -1,43 +1,62 @@
|
|
|
1
|
+
// The sticky-note canvas: the board of floating cards over the conversation.
|
|
2
|
+
//
|
|
3
|
+
// This module owns the notes map, visibility, the badges, and the WebSocket
|
|
4
|
+
// handlers. Card markup and interactions live in sticky-notes-card.js, text
|
|
5
|
+
// editing in sticky-notes-editor.js, and the leaf helpers in
|
|
6
|
+
// sticky-notes-shared.js. The browse/index pane is sticky-notes-browser.js.
|
|
7
|
+
|
|
1
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
9
|
+
import { renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
11
|
+
import { isClosedNote, send, clampPos, clearNoteTimers, syncTitle } from './sticky-notes-shared.js';
|
|
12
|
+
import { renderNote, closeColorPicker } from './sticky-notes-card.js';
|
|
13
|
+
import { closeFormatToolbar, isFormatToolbarOpen } from './sticky-notes-editor.js';
|
|
4
14
|
|
|
5
|
-
var ctx;
|
|
6
15
|
var notes = new Map(); // id -> { data, el }
|
|
7
16
|
var notesVisible = false;
|
|
8
|
-
var archiveOpen = false;
|
|
9
|
-
var updateTimers = {};
|
|
10
|
-
var textTimers = {};
|
|
11
|
-
var colorPickerEl = null;
|
|
12
|
-
var formatToolbarEl = null;
|
|
13
17
|
|
|
14
|
-
|
|
18
|
+
// The canvas shows open notes only. Closed notes leave the canvas and live in
|
|
19
|
+
// the browser's Closed tab until they are reopened.
|
|
20
|
+
function openCount() {
|
|
21
|
+
var count = 0;
|
|
22
|
+
notes.forEach(function (entry) {
|
|
23
|
+
if (entry && !isClosedNote(entry.data)) count++;
|
|
24
|
+
});
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The browser pane registers here rather than being imported, so the canvas
|
|
29
|
+
// module and the browser module never form an import cycle.
|
|
30
|
+
var browserRefresh = null;
|
|
15
31
|
|
|
32
|
+
export function setBrowserRefresh(fn) {
|
|
33
|
+
browserRefresh = typeof fn === "function" ? fn : null;
|
|
34
|
+
}
|
|
16
35
|
|
|
17
|
-
function
|
|
18
|
-
|
|
19
|
-
if (!c || c.clientWidth === 0 || c.clientHeight === 0) return null;
|
|
20
|
-
return { w: c.clientWidth, h: c.clientHeight };
|
|
36
|
+
function refreshBrowser() {
|
|
37
|
+
if (browserRefresh) browserRefresh();
|
|
21
38
|
}
|
|
22
39
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
y: Math.max(0, Math.min(y, b.h - noteH)),
|
|
29
|
-
};
|
|
40
|
+
// Read-only view of every note the client knows about, for the browser pane.
|
|
41
|
+
export function listNoteData() {
|
|
42
|
+
var out = [];
|
|
43
|
+
notes.forEach(function (entry) { if (entry && entry.data) out.push(entry.data); });
|
|
44
|
+
return out;
|
|
30
45
|
}
|
|
31
46
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
};
|
|
47
|
+
// Bring one open note to the front of the canvas and flash it.
|
|
48
|
+
export function focusNoteOnCanvas(id) {
|
|
49
|
+
var entry = notes.get(id);
|
|
50
|
+
if (!entry || isClosedNote(entry.data)) return;
|
|
51
|
+
showNotes();
|
|
52
|
+
send({ type: "note_bring_front", id: id });
|
|
53
|
+
if (entry.data.minimized) send({ type: "note_update", id: id, minimized: false });
|
|
54
|
+
if (!entry.el) return;
|
|
55
|
+
entry.el.classList.add("note-flash");
|
|
56
|
+
setTimeout(function () { if (entry.el) entry.el.classList.remove("note-flash"); }, 600);
|
|
39
57
|
}
|
|
40
58
|
|
|
59
|
+
|
|
41
60
|
function reclampAllNotes() {
|
|
42
61
|
notes.forEach(function (entry) {
|
|
43
62
|
var el = entry.el;
|
|
@@ -51,12 +70,10 @@ function reclampAllNotes() {
|
|
|
51
70
|
});
|
|
52
71
|
}
|
|
53
72
|
|
|
54
|
-
export function initStickyNotes(
|
|
55
|
-
ctx = _ctx;
|
|
56
|
-
|
|
73
|
+
export function initStickyNotes() {
|
|
57
74
|
// Close format toolbar on outside click
|
|
58
75
|
document.addEventListener("mousedown", function (e) {
|
|
59
|
-
if (
|
|
76
|
+
if (isFormatToolbarOpen() && !e.target.closest(".sn-format-toolbar") && !e.target.closest(".sticky-note-text") && !e.target.closest(".sticky-note-rendered")) {
|
|
60
77
|
closeFormatToolbar();
|
|
61
78
|
}
|
|
62
79
|
});
|
|
@@ -94,7 +111,7 @@ export function createNote() {
|
|
|
94
111
|
if (!container) return;
|
|
95
112
|
// Scatter position so notes don't stack exactly
|
|
96
113
|
var offset = (notes.size % 5) * 30;
|
|
97
|
-
|
|
114
|
+
send({
|
|
98
115
|
type: "note_create",
|
|
99
116
|
x: 60 + offset,
|
|
100
117
|
y: 60 + offset,
|
|
@@ -106,673 +123,66 @@ export function createNote() {
|
|
|
106
123
|
function updateBadge() {
|
|
107
124
|
var badge = document.querySelector(".sticky-notes-count");
|
|
108
125
|
if (!badge) return;
|
|
109
|
-
|
|
110
|
-
|
|
126
|
+
var count = openCount();
|
|
127
|
+
if (count > 0) {
|
|
128
|
+
badge.textContent = count;
|
|
111
129
|
badge.classList.remove("hidden");
|
|
112
130
|
} else {
|
|
113
131
|
badge.classList.add("hidden");
|
|
114
132
|
}
|
|
115
133
|
}
|
|
116
134
|
|
|
117
|
-
// --- WS send helpers ---
|
|
118
|
-
|
|
119
|
-
function wsSend(obj) {
|
|
120
|
-
if (ctx && ctx.ws && ctx.connected) {
|
|
121
|
-
ctx.ws.send(JSON.stringify(obj));
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function debouncedUpdate(id, changes, delay) {
|
|
126
|
-
clearTimeout(updateTimers[id]);
|
|
127
|
-
updateTimers[id] = setTimeout(function () {
|
|
128
|
-
changes.type = "note_update";
|
|
129
|
-
changes.id = id;
|
|
130
|
-
wsSend(changes);
|
|
131
|
-
}, delay || 300);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function debouncedTextUpdate(id, text) {
|
|
135
|
-
clearTimeout(textTimers[id]);
|
|
136
|
-
textTimers[id] = setTimeout(function () {
|
|
137
|
-
wsSend({ type: "note_update", id: id, text: text });
|
|
138
|
-
}, 500);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
function syncTitle(noteEl, text) {
|
|
142
|
-
var spacer = noteEl.querySelector(".sticky-note-spacer");
|
|
143
|
-
if (spacer) spacer.textContent = getTitle(text) || "Untitled";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// --- Note rendering ---
|
|
147
|
-
|
|
148
|
-
function renderNote(data) {
|
|
149
|
-
var el = document.createElement("div");
|
|
150
|
-
el.className = "sticky-note";
|
|
151
|
-
el.dataset.noteId = data.id;
|
|
152
|
-
var clamped = clampPos(data.x, data.y, data.w, data.h);
|
|
153
|
-
el.style.left = clamped.x + "px";
|
|
154
|
-
el.style.top = clamped.y + "px";
|
|
155
|
-
el.style.width = data.w + "px";
|
|
156
|
-
el.style.height = data.h + "px";
|
|
157
|
-
el.style.zIndex = 100 + (data.zIndex || 0);
|
|
158
|
-
el.dataset.color = data.color || "purple";
|
|
159
|
-
|
|
160
|
-
if (data.minimized) el.classList.add("minimized");
|
|
161
|
-
if (data.hidden) el.classList.add("hidden");
|
|
162
|
-
|
|
163
|
-
// Header
|
|
164
|
-
var header = document.createElement("div");
|
|
165
|
-
header.className = "sticky-note-header";
|
|
166
|
-
|
|
167
|
-
if (data.origin && VENDOR_AVATARS[data.origin.vendor]) {
|
|
168
|
-
var originBadge = document.createElement("img");
|
|
169
|
-
originBadge.className = "sticky-note-agent-badge";
|
|
170
|
-
originBadge.src = VENDOR_AVATARS[data.origin.vendor];
|
|
171
|
-
originBadge.alt = "";
|
|
172
|
-
originBadge.title = "Created by " + data.origin.vendor;
|
|
173
|
-
header.appendChild(originBadge);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
var closeBtn = document.createElement("button");
|
|
177
|
-
closeBtn.className = "sticky-note-btn sticky-note-close";
|
|
178
|
-
closeBtn.title = "Close";
|
|
179
|
-
closeBtn.innerHTML = iconHtml("x");
|
|
180
|
-
header.appendChild(closeBtn);
|
|
181
|
-
|
|
182
|
-
var minBtn = document.createElement("button");
|
|
183
|
-
minBtn.className = "sticky-note-btn sticky-note-min-btn";
|
|
184
|
-
minBtn.title = data.minimized ? "Expand" : "Minimize";
|
|
185
|
-
minBtn.innerHTML = data.minimized ? iconHtml("maximize-2") : iconHtml("minus");
|
|
186
|
-
header.appendChild(minBtn);
|
|
187
|
-
|
|
188
|
-
var spacer = document.createElement("div");
|
|
189
|
-
spacer.className = "sticky-note-spacer";
|
|
190
|
-
spacer.textContent = getTitle(data.text) || "Untitled";
|
|
191
|
-
header.appendChild(spacer);
|
|
192
|
-
|
|
193
|
-
var addBtn = document.createElement("button");
|
|
194
|
-
addBtn.className = "sticky-note-btn";
|
|
195
|
-
addBtn.title = "New note";
|
|
196
|
-
addBtn.innerHTML = iconHtml("plus");
|
|
197
|
-
addBtn.addEventListener("click", function (e) {
|
|
198
|
-
e.stopPropagation();
|
|
199
|
-
createNote();
|
|
200
|
-
});
|
|
201
|
-
header.appendChild(addBtn);
|
|
202
|
-
|
|
203
|
-
var colorBtn = document.createElement("button");
|
|
204
|
-
colorBtn.className = "sticky-note-color-btn";
|
|
205
|
-
colorBtn.title = "Change color";
|
|
206
|
-
colorBtn.innerHTML = iconHtml("palette");
|
|
207
|
-
header.appendChild(colorBtn);
|
|
208
|
-
|
|
209
|
-
var mdBtn = document.createElement("button");
|
|
210
|
-
mdBtn.className = "sticky-note-btn sticky-note-md-btn";
|
|
211
|
-
mdBtn.title = "Edit markdown";
|
|
212
|
-
mdBtn.innerHTML = "<span class='sn-md-label'>MD</span>";
|
|
213
|
-
header.appendChild(mdBtn);
|
|
214
|
-
|
|
215
|
-
var opacityWrap = document.createElement("div");
|
|
216
|
-
opacityWrap.className = "sticky-note-opacity";
|
|
217
|
-
var opacitySlider = document.createElement("input");
|
|
218
|
-
opacitySlider.type = "range";
|
|
219
|
-
opacitySlider.min = "20";
|
|
220
|
-
opacitySlider.max = "100";
|
|
221
|
-
opacitySlider.value = String(Math.round((typeof data.opacity === "number" ? data.opacity : 0.64) * 100));
|
|
222
|
-
opacitySlider.className = "sticky-note-opacity-slider";
|
|
223
|
-
opacitySlider.title = "Opacity";
|
|
224
|
-
opacityWrap.appendChild(opacitySlider);
|
|
225
|
-
header.appendChild(opacityWrap);
|
|
226
|
-
|
|
227
|
-
// Apply saved opacity via CSS variable (not element opacity, so header stays visible on hover)
|
|
228
|
-
if (typeof data.opacity === "number") {
|
|
229
|
-
el.style.setProperty("--note-opacity", data.opacity);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
opacitySlider.addEventListener("input", function (e) {
|
|
233
|
-
e.stopPropagation();
|
|
234
|
-
var val = parseInt(opacitySlider.value, 10) / 100;
|
|
235
|
-
el.style.setProperty("--note-opacity", val);
|
|
236
|
-
});
|
|
237
|
-
opacitySlider.addEventListener("change", function (e) {
|
|
238
|
-
e.stopPropagation();
|
|
239
|
-
var val = parseInt(opacitySlider.value, 10) / 100;
|
|
240
|
-
el.style.setProperty("--note-opacity", val);
|
|
241
|
-
debouncedUpdate(data.id, { opacity: val }, 300);
|
|
242
|
-
});
|
|
243
|
-
opacitySlider.addEventListener("mousedown", function (e) { e.stopPropagation(); });
|
|
244
|
-
|
|
245
|
-
el.appendChild(header);
|
|
246
|
-
|
|
247
|
-
// Body
|
|
248
|
-
var body = document.createElement("div");
|
|
249
|
-
body.className = "sticky-note-body";
|
|
250
|
-
|
|
251
|
-
// Hidden textarea as markdown data store
|
|
252
|
-
var textarea = document.createElement("textarea");
|
|
253
|
-
textarea.className = "sticky-note-text";
|
|
254
|
-
textarea.value = data.text || "";
|
|
255
|
-
textarea.style.display = "none";
|
|
256
|
-
body.appendChild(textarea);
|
|
257
|
-
|
|
258
|
-
// Contenteditable rendered view (primary editing surface)
|
|
259
|
-
var rendered = document.createElement("div");
|
|
260
|
-
rendered.className = "sticky-note-rendered";
|
|
261
|
-
rendered.contentEditable = "true";
|
|
262
|
-
rendered.spellcheck = true;
|
|
263
|
-
if (data.text && data.text.trim()) {
|
|
264
|
-
rendered.innerHTML = renderMiniMarkdown(data.text);
|
|
265
|
-
} else {
|
|
266
|
-
rendered.classList.add("is-empty");
|
|
267
|
-
}
|
|
268
|
-
body.appendChild(rendered);
|
|
269
|
-
|
|
270
|
-
el.appendChild(body);
|
|
271
|
-
|
|
272
|
-
// Resize handle
|
|
273
|
-
var resizeHandle = document.createElement("div");
|
|
274
|
-
resizeHandle.className = "sticky-note-resize";
|
|
275
|
-
el.appendChild(resizeHandle);
|
|
276
|
-
|
|
277
|
-
// --- Event handlers ---
|
|
278
|
-
setupDrag(el, spacer, data.id);
|
|
279
|
-
setupResize(el, resizeHandle, data.id);
|
|
280
|
-
setupTextEdit(textarea, rendered, data.id, mdBtn);
|
|
281
|
-
setupColorPicker(colorBtn, el, data.id);
|
|
282
|
-
setupMinimize(minBtn, el, data.id);
|
|
283
|
-
setupClose(closeBtn, el, data.id);
|
|
284
|
-
setupBringToFront(el, data.id);
|
|
285
|
-
|
|
286
|
-
refreshIcons();
|
|
287
|
-
return el;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// --- Drag ---
|
|
291
|
-
|
|
292
|
-
function setupDrag(noteEl, spacerEl, noteId) {
|
|
293
|
-
var dragging = false;
|
|
294
|
-
var pointerId = null;
|
|
295
|
-
var startX, startY, origX, origY;
|
|
296
|
-
|
|
297
|
-
spacerEl.addEventListener("pointerdown", function (e) {
|
|
298
|
-
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
299
|
-
e.preventDefault();
|
|
300
|
-
dragging = true;
|
|
301
|
-
pointerId = e.pointerId;
|
|
302
|
-
startX = e.clientX;
|
|
303
|
-
startY = e.clientY;
|
|
304
|
-
origX = parseInt(noteEl.style.left) || 0;
|
|
305
|
-
origY = parseInt(noteEl.style.top) || 0;
|
|
306
|
-
noteEl.classList.add("dragging");
|
|
307
|
-
document.body.classList.add("sticky-note-interacting");
|
|
308
|
-
spacerEl.setPointerCapture(pointerId);
|
|
309
|
-
});
|
|
310
|
-
|
|
311
|
-
function onMove(e) {
|
|
312
|
-
if (!dragging || e.pointerId !== pointerId) return;
|
|
313
|
-
var dx = e.clientX - startX;
|
|
314
|
-
var dy = e.clientY - startY;
|
|
315
|
-
var c = clampPos(origX + dx, origY + dy, noteEl.offsetWidth, noteEl.offsetHeight);
|
|
316
|
-
noteEl.style.left = c.x + "px";
|
|
317
|
-
noteEl.style.top = c.y + "px";
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function onUp(e) {
|
|
321
|
-
if (!dragging || (e && e.pointerId !== pointerId)) return;
|
|
322
|
-
dragging = false;
|
|
323
|
-
noteEl.classList.remove("dragging");
|
|
324
|
-
document.body.classList.remove("sticky-note-interacting");
|
|
325
|
-
if (pointerId !== null && spacerEl.hasPointerCapture(pointerId)) spacerEl.releasePointerCapture(pointerId);
|
|
326
|
-
pointerId = null;
|
|
327
|
-
debouncedUpdate(noteId, {
|
|
328
|
-
x: parseInt(noteEl.style.left),
|
|
329
|
-
y: parseInt(noteEl.style.top),
|
|
330
|
-
}, 200);
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
spacerEl.addEventListener("pointermove", onMove);
|
|
334
|
-
spacerEl.addEventListener("pointerup", onUp);
|
|
335
|
-
spacerEl.addEventListener("pointercancel", onUp);
|
|
336
|
-
spacerEl.addEventListener("lostpointercapture", onUp);
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// --- Resize ---
|
|
340
|
-
|
|
341
|
-
function setupResize(noteEl, handle, noteId) {
|
|
342
|
-
var resizing = false;
|
|
343
|
-
var pointerId = null;
|
|
344
|
-
var startX, startY, origW, origH;
|
|
345
|
-
var MIN_W = 160;
|
|
346
|
-
var MIN_H = 80;
|
|
347
|
-
|
|
348
|
-
handle.addEventListener("pointerdown", function (e) {
|
|
349
|
-
if (e.pointerType === "mouse" && e.button !== 0) return;
|
|
350
|
-
e.preventDefault();
|
|
351
|
-
e.stopPropagation();
|
|
352
|
-
resizing = true;
|
|
353
|
-
pointerId = e.pointerId;
|
|
354
|
-
startX = e.clientX;
|
|
355
|
-
startY = e.clientY;
|
|
356
|
-
origW = noteEl.offsetWidth;
|
|
357
|
-
origH = noteEl.offsetHeight;
|
|
358
|
-
noteEl.classList.add("resizing");
|
|
359
|
-
document.body.classList.add("sticky-note-interacting");
|
|
360
|
-
handle.setPointerCapture(pointerId);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
function onMove(e) {
|
|
364
|
-
if (!resizing || e.pointerId !== pointerId) return;
|
|
365
|
-
var rawW = Math.max(MIN_W, origW + (e.clientX - startX));
|
|
366
|
-
var rawH = Math.max(MIN_H, origH + (e.clientY - startY));
|
|
367
|
-
var cs = clampSize(parseInt(noteEl.style.left) || 0, parseInt(noteEl.style.top) || 0, rawW, rawH);
|
|
368
|
-
noteEl.style.width = Math.max(MIN_W, cs.w) + "px";
|
|
369
|
-
noteEl.style.height = Math.max(MIN_H, cs.h) + "px";
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
function onUp(e) {
|
|
373
|
-
if (!resizing || (e && e.pointerId !== pointerId)) return;
|
|
374
|
-
resizing = false;
|
|
375
|
-
noteEl.classList.remove("resizing");
|
|
376
|
-
document.body.classList.remove("sticky-note-interacting");
|
|
377
|
-
if (pointerId !== null && handle.hasPointerCapture(pointerId)) handle.releasePointerCapture(pointerId);
|
|
378
|
-
pointerId = null;
|
|
379
|
-
debouncedUpdate(noteId, {
|
|
380
|
-
w: noteEl.offsetWidth,
|
|
381
|
-
h: noteEl.offsetHeight,
|
|
382
|
-
}, 200);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
handle.addEventListener("pointermove", onMove);
|
|
386
|
-
handle.addEventListener("pointerup", onUp);
|
|
387
|
-
handle.addEventListener("pointercancel", onUp);
|
|
388
|
-
handle.addEventListener("lostpointercapture", onUp);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// --- Text edit (contenteditable) ---
|
|
392
|
-
|
|
393
|
-
// --- Format toolbar (WYSIWYG) ---
|
|
394
|
-
|
|
395
|
-
var FORMAT_BUTTONS = [
|
|
396
|
-
{ label: "B", title: "Bold", cls: "sn-fmt-bold", command: "bold" },
|
|
397
|
-
{ label: "I", title: "Italic", cls: "sn-fmt-italic", command: "italic" },
|
|
398
|
-
{ label: "S", title: "Strikethrough", cls: "sn-fmt-strike", command: "strikethrough" },
|
|
399
|
-
{ label: "code-2", title: "Code", cls: "sn-fmt-code", command: "code", isIcon: true },
|
|
400
|
-
];
|
|
401
|
-
|
|
402
|
-
function closeFormatToolbar() {
|
|
403
|
-
if (formatToolbarEl) {
|
|
404
|
-
formatToolbarEl.remove();
|
|
405
|
-
formatToolbarEl = null;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
function applyFormat(command, rendered) {
|
|
410
|
-
if (command === "code") {
|
|
411
|
-
var sel = window.getSelection();
|
|
412
|
-
if (!sel.rangeCount || sel.isCollapsed) return;
|
|
413
|
-
var range = sel.getRangeAt(0);
|
|
414
|
-
var ancestor = range.commonAncestorContainer;
|
|
415
|
-
var codeParent = (ancestor.nodeType === 3 ? ancestor.parentElement : ancestor);
|
|
416
|
-
if (codeParent && codeParent.closest && codeParent.closest("code")) {
|
|
417
|
-
// Unwrap: replace <code> with its text content
|
|
418
|
-
var codeEl = codeParent.closest("code");
|
|
419
|
-
var textNode = document.createTextNode(codeEl.textContent);
|
|
420
|
-
codeEl.parentNode.replaceChild(textNode, codeEl);
|
|
421
|
-
var newRange = document.createRange();
|
|
422
|
-
newRange.selectNodeContents(textNode);
|
|
423
|
-
sel.removeAllRanges();
|
|
424
|
-
sel.addRange(newRange);
|
|
425
|
-
} else {
|
|
426
|
-
// Wrap selection in <code>
|
|
427
|
-
var code = document.createElement("code");
|
|
428
|
-
try { range.surroundContents(code); } catch (e) {
|
|
429
|
-
var frag = range.extractContents();
|
|
430
|
-
code.appendChild(frag);
|
|
431
|
-
range.insertNode(code);
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
} else {
|
|
435
|
-
document.execCommand(command, false, null);
|
|
436
|
-
}
|
|
437
|
-
// Trigger sync
|
|
438
|
-
rendered.dispatchEvent(new Event("input", { bubbles: true }));
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
function showFormatToolbar(rendered) {
|
|
442
|
-
closeFormatToolbar();
|
|
443
|
-
|
|
444
|
-
var sel = window.getSelection();
|
|
445
|
-
if (!sel || !sel.rangeCount || sel.isCollapsed) return;
|
|
446
|
-
if (!sel.toString().trim()) return;
|
|
447
|
-
|
|
448
|
-
var range = sel.getRangeAt(0);
|
|
449
|
-
// When dragging outside the note, commonAncestorContainer may be a parent
|
|
450
|
-
// of rendered. Clamp the range to the rendered element so the toolbar shows.
|
|
451
|
-
if (!rendered.contains(range.commonAncestorContainer)) {
|
|
452
|
-
try {
|
|
453
|
-
var clampedRange = range.cloneRange();
|
|
454
|
-
if (range.startContainer === rendered || rendered.contains(range.startContainer)) {
|
|
455
|
-
clampedRange.selectNodeContents(rendered);
|
|
456
|
-
clampedRange.setStart(range.startContainer, range.startOffset);
|
|
457
|
-
} else if (range.endContainer === rendered || rendered.contains(range.endContainer)) {
|
|
458
|
-
clampedRange.selectNodeContents(rendered);
|
|
459
|
-
clampedRange.setEnd(range.endContainer, range.endOffset);
|
|
460
|
-
} else {
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
range = clampedRange;
|
|
464
|
-
} catch (e) {
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
var toolbar = document.createElement("div");
|
|
470
|
-
toolbar.className = "sn-format-toolbar";
|
|
471
|
-
|
|
472
|
-
for (var i = 0; i < FORMAT_BUTTONS.length; i++) {
|
|
473
|
-
(function (cfg) {
|
|
474
|
-
var btn = document.createElement("button");
|
|
475
|
-
btn.className = "sn-fmt-btn " + cfg.cls;
|
|
476
|
-
btn.title = cfg.title;
|
|
477
|
-
btn.innerHTML = cfg.isIcon ? iconHtml(cfg.label) : cfg.label;
|
|
478
|
-
btn.addEventListener("mousedown", function (e) {
|
|
479
|
-
e.preventDefault();
|
|
480
|
-
e.stopPropagation();
|
|
481
|
-
applyFormat(cfg.command, rendered);
|
|
482
|
-
setTimeout(function () {
|
|
483
|
-
var s = window.getSelection();
|
|
484
|
-
if (s && !s.isCollapsed) {
|
|
485
|
-
showFormatToolbar(rendered);
|
|
486
|
-
} else {
|
|
487
|
-
closeFormatToolbar();
|
|
488
|
-
}
|
|
489
|
-
}, 0);
|
|
490
|
-
});
|
|
491
|
-
toolbar.appendChild(btn);
|
|
492
|
-
})(FORMAT_BUTTONS[i]);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
refreshIcons();
|
|
496
|
-
document.body.appendChild(toolbar);
|
|
497
|
-
formatToolbarEl = toolbar;
|
|
498
|
-
positionToolbarAtRange(toolbar, range);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
function positionToolbarAtRange(toolbar, range) {
|
|
502
|
-
var rect = range.getBoundingClientRect();
|
|
503
|
-
var toolbarX = rect.left + rect.width / 2;
|
|
504
|
-
var toolbarY = rect.top - 4;
|
|
505
|
-
|
|
506
|
-
toolbar.style.left = toolbarX + "px";
|
|
507
|
-
toolbar.style.top = toolbarY + "px";
|
|
508
|
-
|
|
509
|
-
requestAnimationFrame(function () {
|
|
510
|
-
var tw = toolbar.offsetWidth;
|
|
511
|
-
var th = toolbar.offsetHeight;
|
|
512
|
-
var x = Math.max(8, Math.min(toolbarX - tw / 2, window.innerWidth - tw - 8));
|
|
513
|
-
var y = Math.max(8, toolbarY - th);
|
|
514
|
-
toolbar.style.left = x + "px";
|
|
515
|
-
toolbar.style.top = y + "px";
|
|
516
|
-
});
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
520
|
-
var noteEl = textarea.closest(".sticky-note");
|
|
521
|
-
var mdMode = false;
|
|
522
|
-
|
|
523
|
-
function saveRenderedMarkdown() {
|
|
524
|
-
var markdown = extractMarkdown(rendered);
|
|
525
|
-
textarea.value = markdown;
|
|
526
|
-
debouncedTextUpdate(noteId, markdown);
|
|
527
|
-
syncTitle(noteEl, markdown);
|
|
528
|
-
rendered.classList.toggle("is-empty", !markdown.trim());
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
function toggleChecklistItem(check) {
|
|
532
|
-
var checked = !check.classList.contains("checked");
|
|
533
|
-
check.classList.toggle("checked", checked);
|
|
534
|
-
check.textContent = checked ? "✓" : "☐";
|
|
535
|
-
check.setAttribute("aria-checked", checked ? "true" : "false");
|
|
536
|
-
saveRenderedMarkdown();
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// MD button: toggle between contenteditable rendered view and raw textarea
|
|
540
|
-
mdBtn.addEventListener("click", function (e) {
|
|
541
|
-
e.stopPropagation();
|
|
542
|
-
mdMode = !mdMode;
|
|
543
|
-
if (mdMode) {
|
|
544
|
-
// Switch to raw markdown editing
|
|
545
|
-
var md = extractMarkdown(rendered);
|
|
546
|
-
textarea.value = md;
|
|
547
|
-
textarea.style.display = "";
|
|
548
|
-
rendered.style.display = "none";
|
|
549
|
-
mdBtn.classList.add("active");
|
|
550
|
-
textarea.focus();
|
|
551
|
-
} else {
|
|
552
|
-
// Switch back to contenteditable rendered view
|
|
553
|
-
var md = textarea.value;
|
|
554
|
-
debouncedTextUpdate(noteId, md);
|
|
555
|
-
syncTitle(noteEl, md);
|
|
556
|
-
if (md.trim()) {
|
|
557
|
-
rendered.innerHTML = renderMiniMarkdown(md);
|
|
558
|
-
rendered.classList.remove("is-empty");
|
|
559
|
-
} else {
|
|
560
|
-
rendered.innerHTML = "";
|
|
561
|
-
rendered.classList.add("is-empty");
|
|
562
|
-
}
|
|
563
|
-
textarea.style.display = "none";
|
|
564
|
-
rendered.style.display = "";
|
|
565
|
-
mdBtn.classList.remove("active");
|
|
566
|
-
rendered.focus();
|
|
567
|
-
}
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
// Sync contenteditable changes to textarea (data store) and save
|
|
571
|
-
rendered.addEventListener("input", function () {
|
|
572
|
-
saveRenderedMarkdown();
|
|
573
|
-
});
|
|
574
|
-
|
|
575
|
-
rendered.addEventListener("click", function (e) {
|
|
576
|
-
var check = e.target.closest && e.target.closest(".sn-check");
|
|
577
|
-
if (!check || !rendered.contains(check)) return;
|
|
578
|
-
e.preventDefault();
|
|
579
|
-
e.stopPropagation();
|
|
580
|
-
toggleChecklistItem(check);
|
|
581
|
-
});
|
|
582
|
-
|
|
583
|
-
// On blur, re-render to normalize HTML structure
|
|
584
|
-
rendered.addEventListener("blur", function (e) {
|
|
585
|
-
// Don't re-render if clicking format toolbar (it prevents default, but just in case)
|
|
586
|
-
if (e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest(".sn-format-toolbar")) return;
|
|
587
|
-
closeFormatToolbar();
|
|
588
|
-
var md = extractMarkdown(rendered);
|
|
589
|
-
textarea.value = md;
|
|
590
|
-
if (md.trim()) {
|
|
591
|
-
rendered.innerHTML = renderMiniMarkdown(md);
|
|
592
|
-
rendered.classList.remove("is-empty");
|
|
593
|
-
} else {
|
|
594
|
-
rendered.innerHTML = "";
|
|
595
|
-
rendered.classList.add("is-empty");
|
|
596
|
-
}
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
// Show format toolbar when selecting text
|
|
600
|
-
rendered.addEventListener("mouseup", function (e) {
|
|
601
|
-
if (e.target.tagName === "A") return;
|
|
602
|
-
setTimeout(function () {
|
|
603
|
-
var sel = window.getSelection();
|
|
604
|
-
if (sel && !sel.isCollapsed && sel.toString().trim()) {
|
|
605
|
-
showFormatToolbar(rendered);
|
|
606
|
-
} else {
|
|
607
|
-
closeFormatToolbar();
|
|
608
|
-
}
|
|
609
|
-
}, 10);
|
|
610
|
-
});
|
|
611
|
-
|
|
612
|
-
// Keyboard selection
|
|
613
|
-
rendered.addEventListener("keyup", function (e) {
|
|
614
|
-
if (e.shiftKey || e.key === "Shift") {
|
|
615
|
-
var sel = window.getSelection();
|
|
616
|
-
if (sel && !sel.isCollapsed) {
|
|
617
|
-
showFormatToolbar(rendered);
|
|
618
|
-
} else {
|
|
619
|
-
closeFormatToolbar();
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
});
|
|
623
|
-
|
|
624
|
-
// Insert <br> on Enter instead of <div>
|
|
625
|
-
rendered.addEventListener("keydown", function (e) {
|
|
626
|
-
if (e.target.classList && e.target.classList.contains("sn-check") &&
|
|
627
|
-
(e.key === "Enter" || e.key === " ")) {
|
|
628
|
-
e.preventDefault();
|
|
629
|
-
toggleChecklistItem(e.target);
|
|
630
|
-
return;
|
|
631
|
-
}
|
|
632
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
633
|
-
e.preventDefault();
|
|
634
|
-
document.execCommand("insertLineBreak");
|
|
635
|
-
}
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
// Paste as plain text to avoid importing HTML formatting
|
|
639
|
-
rendered.addEventListener("paste", function (e) {
|
|
640
|
-
e.preventDefault();
|
|
641
|
-
var text = (e.clipboardData || window.clipboardData).getData("text/plain");
|
|
642
|
-
document.execCommand("insertText", false, text);
|
|
643
|
-
});
|
|
644
|
-
|
|
645
|
-
// Prevent drag when clicking body
|
|
646
|
-
rendered.addEventListener("mousedown", function (e) {
|
|
647
|
-
e.stopPropagation();
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
// Sync textarea edits when in MD mode
|
|
651
|
-
textarea.addEventListener("input", function () {
|
|
652
|
-
if (!mdMode) return;
|
|
653
|
-
debouncedTextUpdate(noteId, textarea.value);
|
|
654
|
-
syncTitle(noteEl, textarea.value);
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
textarea.addEventListener("mousedown", function (e) {
|
|
658
|
-
e.stopPropagation();
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// --- Color picker ---
|
|
663
|
-
|
|
664
|
-
function closeColorPicker() {
|
|
665
|
-
if (colorPickerEl) {
|
|
666
|
-
colorPickerEl.remove();
|
|
667
|
-
colorPickerEl = null;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function setupColorPicker(btn, noteEl, noteId) {
|
|
672
|
-
btn.addEventListener("click", function (e) {
|
|
673
|
-
e.stopPropagation();
|
|
674
|
-
showColorPicker(btn, noteEl, noteId);
|
|
675
|
-
});
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
function showColorPicker(anchor, noteEl, noteId) {
|
|
679
|
-
closeColorPicker();
|
|
680
|
-
|
|
681
|
-
var picker = document.createElement("div");
|
|
682
|
-
picker.className = "sticky-note-color-picker";
|
|
683
|
-
|
|
684
|
-
for (var i = 0; i < NOTE_COLORS.length; i++) {
|
|
685
|
-
(function (color) {
|
|
686
|
-
var dot = document.createElement("button");
|
|
687
|
-
dot.className = "sticky-note-color-dot";
|
|
688
|
-
dot.dataset.color = color;
|
|
689
|
-
if (noteEl.dataset.color === color) dot.classList.add("active");
|
|
690
|
-
dot.addEventListener("click", function (e) {
|
|
691
|
-
e.stopPropagation();
|
|
692
|
-
noteEl.dataset.color = color;
|
|
693
|
-
wsSend({ type: "note_update", id: noteId, color: color });
|
|
694
|
-
closeColorPicker();
|
|
695
|
-
});
|
|
696
|
-
picker.appendChild(dot);
|
|
697
|
-
})(NOTE_COLORS[i]);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
document.body.appendChild(picker);
|
|
701
|
-
colorPickerEl = picker;
|
|
702
|
-
|
|
703
|
-
// Position relative to anchor
|
|
704
|
-
var rect = anchor.getBoundingClientRect();
|
|
705
|
-
picker.style.left = rect.left + "px";
|
|
706
|
-
picker.style.top = (rect.bottom + 4) + "px";
|
|
707
|
-
|
|
708
|
-
// Close on outside click
|
|
709
|
-
setTimeout(function () {
|
|
710
|
-
document.addEventListener("click", function closeHandler() {
|
|
711
|
-
closeColorPicker();
|
|
712
|
-
document.removeEventListener("click", closeHandler);
|
|
713
|
-
});
|
|
714
|
-
}, 0);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// --- Minimize ---
|
|
718
|
-
|
|
719
|
-
function setupMinimize(btn, noteEl, noteId) {
|
|
720
|
-
btn.addEventListener("click", function (e) {
|
|
721
|
-
e.stopPropagation();
|
|
722
|
-
var isMinimized = noteEl.classList.toggle("minimized");
|
|
723
|
-
btn.innerHTML = isMinimized ? iconHtml("maximize-2") : iconHtml("minus");
|
|
724
|
-
btn.title = isMinimized ? "Expand" : "Minimize";
|
|
725
|
-
refreshIcons();
|
|
726
|
-
wsSend({ type: "note_update", id: noteId, minimized: isMinimized });
|
|
727
|
-
});
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
// --- Close (hide) note ---
|
|
731
|
-
|
|
732
|
-
function setupClose(btn, noteEl, noteId) {
|
|
733
|
-
btn.addEventListener("click", function (e) {
|
|
734
|
-
e.stopPropagation();
|
|
735
|
-
noteEl.classList.add("hidden");
|
|
736
|
-
wsSend({ type: "note_update", id: noteId, hidden: true });
|
|
737
|
-
});
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
// --- Bring to front ---
|
|
741
|
-
|
|
742
|
-
function setupBringToFront(noteEl, noteId) {
|
|
743
|
-
noteEl.addEventListener("mousedown", function (e) {
|
|
744
|
-
// Skip bring-to-front when clicking header buttons to avoid
|
|
745
|
-
// race condition where server response replaces innerHTML
|
|
746
|
-
// between mousedown and click, causing the click event to be lost
|
|
747
|
-
if (e.target.closest("button")) return;
|
|
748
|
-
wsSend({ type: "note_bring_front", id: noteId });
|
|
749
|
-
});
|
|
750
|
-
}
|
|
751
|
-
|
|
752
135
|
// --- WS message handlers ---
|
|
753
136
|
|
|
754
137
|
export function handleNotesList(msg) {
|
|
755
138
|
var container = document.getElementById("sticky-notes-container");
|
|
756
139
|
if (!container) return;
|
|
757
140
|
|
|
758
|
-
//
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
141
|
+
// Keyed reconciliation, never a clear-and-rebuild. A reconnect re-sends this
|
|
142
|
+
// list unchanged; tearing every note down and recreating it made all the
|
|
143
|
+
// floating cards vanish and reappear, and destroyed any open editor.
|
|
762
144
|
var list = msg.notes || [];
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
145
|
+
var seen = {};
|
|
146
|
+
var created = false;
|
|
147
|
+
var i;
|
|
148
|
+
|
|
149
|
+
for (i = 0; i < list.length; i++) {
|
|
150
|
+
var data = list[i];
|
|
151
|
+
if (!data || !data.id) continue;
|
|
152
|
+
seen[data.id] = true;
|
|
153
|
+
var entry = notes.get(data.id);
|
|
154
|
+
if (entry && entry.el && entry.el.parentNode === container) {
|
|
155
|
+
patchNote(entry, data);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
var el = renderNote(data);
|
|
159
|
+
notes.set(data.id, { data: data, el: el });
|
|
766
160
|
container.appendChild(el);
|
|
161
|
+
created = true;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Remove notes the server no longer lists.
|
|
165
|
+
var staleIds = [];
|
|
166
|
+
notes.forEach(function (value, id) {
|
|
167
|
+
if (!seen[id]) staleIds.push(id);
|
|
168
|
+
});
|
|
169
|
+
for (i = 0; i < staleIds.length; i++) {
|
|
170
|
+
var stale = notes.get(staleIds[i]);
|
|
171
|
+
if (stale && stale.el && stale.el.parentNode) stale.el.parentNode.removeChild(stale.el);
|
|
172
|
+
notes.delete(staleIds[i]);
|
|
767
173
|
}
|
|
768
174
|
|
|
175
|
+
// renderNote already refreshes icons for the nodes it builds; an all-patch
|
|
176
|
+
// pass introduces no new Lucide markup and must not trigger a document scan.
|
|
177
|
+
if (created) refreshIcons();
|
|
178
|
+
|
|
769
179
|
updateBadge();
|
|
770
180
|
updateSidebarBadge();
|
|
181
|
+
refreshBrowser();
|
|
771
182
|
|
|
772
|
-
//
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
if (list.length > 0 && !notesVisible) {
|
|
183
|
+
// Auto-show only when something is actually asking for attention. A board of
|
|
184
|
+
// nothing but closed notes must not pop the canvas open.
|
|
185
|
+
if (openCount() > 0 && !notesVisible) {
|
|
776
186
|
notesVisible = true;
|
|
777
187
|
container.classList.remove("hidden");
|
|
778
188
|
}
|
|
@@ -790,9 +200,7 @@ export function handleNoteCreated(msg) {
|
|
|
790
200
|
container.appendChild(el);
|
|
791
201
|
updateBadge();
|
|
792
202
|
updateSidebarBadge();
|
|
793
|
-
|
|
794
|
-
// Re-render archive if open
|
|
795
|
-
if (archiveOpen) renderArchiveCards();
|
|
203
|
+
refreshBrowser();
|
|
796
204
|
|
|
797
205
|
// Show container if hidden
|
|
798
206
|
if (!notesVisible) {
|
|
@@ -801,62 +209,91 @@ export function handleNoteCreated(msg) {
|
|
|
801
209
|
}
|
|
802
210
|
}
|
|
803
211
|
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
entry
|
|
814
|
-
|
|
815
|
-
entry.
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
212
|
+
// Patch one existing note element in place from a server payload.
|
|
213
|
+
//
|
|
214
|
+
// Geometry, z-order, colour, and opacity are plain style writes and are always
|
|
215
|
+
// applied. The rendered markdown is only replaced when the text actually
|
|
216
|
+
// changed and the note is not being edited, so a geometry-only or z-only
|
|
217
|
+
// update never destroys the user's caret, selection, or in-progress draft.
|
|
218
|
+
// Returns true when icon markup was replaced, so the caller can decide whether
|
|
219
|
+
// a Lucide pass is needed at all.
|
|
220
|
+
function patchNote(entry, next) {
|
|
221
|
+
if (!entry || !next) return false;
|
|
222
|
+
var previous = entry.data || {};
|
|
223
|
+
entry.data = next;
|
|
224
|
+
var el = entry.el;
|
|
225
|
+
|
|
226
|
+
el.style.left = next.x + "px";
|
|
227
|
+
el.style.top = next.y + "px";
|
|
228
|
+
el.style.width = next.w + "px";
|
|
229
|
+
el.style.height = next.h + "px";
|
|
230
|
+
el.style.zIndex = 100 + (next.zIndex || 0);
|
|
231
|
+
el.dataset.color = next.color || "purple";
|
|
232
|
+
|
|
233
|
+
var textarea = el.querySelector(".sticky-note-text");
|
|
234
|
+
var rendered = el.querySelector(".sticky-note-rendered");
|
|
235
|
+
var nextText = next.text || "";
|
|
236
|
+
var editing = rendered === document.activeElement || textarea === document.activeElement;
|
|
237
|
+
// A draft the user typed but has not committed must survive a redraw.
|
|
238
|
+
var hasDraft = !!(textarea && textarea.value !== (previous.text || ""));
|
|
239
|
+
var textChanged = nextText !== (previous.text || "");
|
|
240
|
+
if (rendered && !editing && !hasDraft && textChanged) {
|
|
241
|
+
if (textarea) textarea.value = nextText;
|
|
242
|
+
if (nextText.trim()) {
|
|
243
|
+
rendered.innerHTML = renderMiniMarkdown(nextText);
|
|
244
|
+
enhanceClayLogLinks(rendered);
|
|
826
245
|
rendered.classList.remove("is-empty");
|
|
827
246
|
} else {
|
|
828
247
|
rendered.innerHTML = "";
|
|
829
248
|
rendered.classList.add("is-empty");
|
|
830
249
|
}
|
|
831
|
-
syncTitle(
|
|
250
|
+
syncTitle(el, nextText);
|
|
832
251
|
}
|
|
833
252
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
if (slider) slider.value = String(Math.round(msg.note.opacity * 100));
|
|
253
|
+
if (typeof next.opacity === "number") {
|
|
254
|
+
el.style.setProperty("--note-opacity", next.opacity);
|
|
255
|
+
var slider = el.querySelector(".sticky-note-opacity-slider");
|
|
256
|
+
if (slider) slider.value = String(Math.round(next.opacity * 100));
|
|
839
257
|
}
|
|
840
258
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
259
|
+
if (isClosedNote(next)) el.classList.add("hidden");
|
|
260
|
+
else el.classList.remove("hidden");
|
|
261
|
+
|
|
262
|
+
// The minimize button swaps its icon, which is the only path here that can
|
|
263
|
+
// introduce fresh Lucide markup.
|
|
264
|
+
var iconsReplaced = false;
|
|
265
|
+
var wasMinimized = el.classList.contains("minimized");
|
|
266
|
+
var minBtn = el.querySelector(".sticky-note-min-btn");
|
|
267
|
+
if (next.minimized) {
|
|
268
|
+
el.classList.add("minimized");
|
|
269
|
+
if (minBtn && !wasMinimized) {
|
|
270
|
+
minBtn.innerHTML = iconHtml("maximize-2");
|
|
271
|
+
minBtn.title = "Expand";
|
|
272
|
+
iconsReplaced = true;
|
|
273
|
+
}
|
|
844
274
|
} else {
|
|
845
|
-
|
|
275
|
+
el.classList.remove("minimized");
|
|
276
|
+
if (minBtn && wasMinimized) {
|
|
277
|
+
minBtn.innerHTML = iconHtml("minus");
|
|
278
|
+
minBtn.title = "Minimize";
|
|
279
|
+
iconsReplaced = true;
|
|
280
|
+
}
|
|
846
281
|
}
|
|
282
|
+
return iconsReplaced;
|
|
283
|
+
}
|
|
847
284
|
|
|
848
|
-
|
|
849
|
-
if (msg.note
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
} else {
|
|
853
|
-
entry.el.classList.remove("minimized");
|
|
854
|
-
if (minBtn) { minBtn.innerHTML = iconHtml("minus"); minBtn.title = "Minimize"; }
|
|
855
|
-
}
|
|
856
|
-
refreshIcons();
|
|
285
|
+
export function handleNoteUpdated(msg) {
|
|
286
|
+
if (!msg.note) return;
|
|
287
|
+
var entry = notes.get(msg.note.id);
|
|
288
|
+
if (!entry) return;
|
|
857
289
|
|
|
858
|
-
|
|
859
|
-
|
|
290
|
+
if (patchNote(entry, msg.note)) refreshIcons();
|
|
291
|
+
|
|
292
|
+
// A close or reopen arrives as an ordinary update, so the canvas count and
|
|
293
|
+
// the browser both follow from the same message.
|
|
294
|
+
updateBadge();
|
|
295
|
+
updateSidebarBadge();
|
|
296
|
+
refreshBrowser();
|
|
860
297
|
}
|
|
861
298
|
|
|
862
299
|
export function handleNoteWritten(msg) {
|
|
@@ -870,6 +307,8 @@ export function handleNoteWritten(msg) {
|
|
|
870
307
|
}, 2100);
|
|
871
308
|
}
|
|
872
309
|
|
|
310
|
+
// Defensive only. The server no longer deletes notes and never sends this, but
|
|
311
|
+
// an older daemon on the other end of the socket still must not corrupt state.
|
|
873
312
|
export function handleNoteDeleted(msg) {
|
|
874
313
|
var entry = notes.get(msg.id);
|
|
875
314
|
if (!entry) return;
|
|
@@ -878,14 +317,8 @@ export function handleNoteDeleted(msg) {
|
|
|
878
317
|
updateBadge();
|
|
879
318
|
updateSidebarBadge();
|
|
880
319
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
clearTimeout(textTimers[msg.id]);
|
|
884
|
-
delete updateTimers[msg.id];
|
|
885
|
-
delete textTimers[msg.id];
|
|
886
|
-
|
|
887
|
-
// Re-render archive if open
|
|
888
|
-
if (archiveOpen) renderArchiveCards();
|
|
320
|
+
clearNoteTimers(msg.id);
|
|
321
|
+
refreshBrowser();
|
|
889
322
|
}
|
|
890
323
|
|
|
891
324
|
// --- Sidebar badge ---
|
|
@@ -893,224 +326,15 @@ export function handleNoteDeleted(msg) {
|
|
|
893
326
|
function updateSidebarBadge() {
|
|
894
327
|
var badge = document.getElementById("sticky-notes-sidebar-count");
|
|
895
328
|
if (!badge) return;
|
|
896
|
-
|
|
897
|
-
|
|
329
|
+
var count = openCount();
|
|
330
|
+
if (count > 0) {
|
|
331
|
+
badge.textContent = count;
|
|
898
332
|
badge.classList.remove("hidden");
|
|
899
333
|
} else {
|
|
900
334
|
badge.classList.add("hidden");
|
|
901
335
|
}
|
|
902
336
|
}
|
|
903
337
|
|
|
904
|
-
// --- Notes Archive View ---
|
|
905
|
-
|
|
906
|
-
function renderArchiveCards() {
|
|
907
|
-
var grid = document.getElementById("notes-archive-grid");
|
|
908
|
-
if (!grid) return;
|
|
909
|
-
|
|
910
|
-
grid.innerHTML = "";
|
|
911
|
-
|
|
912
|
-
if (notes.size === 0) {
|
|
913
|
-
var empty = document.createElement("div");
|
|
914
|
-
empty.className = "notes-archive-empty";
|
|
915
|
-
empty.innerHTML = iconHtml("sticky-note") + "<p>Keep what matters across sessions</p><p class=\"notes-archive-empty-sub\">Use sticky notes for checklists, goals, handoffs, and durable project knowledge. You and every Clay agent can find them in future sessions.</p><p class=\"notes-archive-empty-sub\">Create one with the " + iconHtml("sticky-note") + " button in the title bar</p>";
|
|
916
|
-
grid.appendChild(empty);
|
|
917
|
-
refreshIcons();
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
// Sort by creation time (id is timestamp-based) — newest first
|
|
922
|
-
var sorted = Array.from(notes.values()).sort(function (a, b) {
|
|
923
|
-
return (b.data.id || "").localeCompare(a.data.id || "");
|
|
924
|
-
});
|
|
925
|
-
|
|
926
|
-
for (var i = 0; i < sorted.length; i++) {
|
|
927
|
-
(function (noteData) {
|
|
928
|
-
var card = document.createElement("div");
|
|
929
|
-
card.className = "notes-archive-card" + (noteData.data.hidden ? " archived" : "");
|
|
930
|
-
card.dataset.color = noteData.data.color || "purple";
|
|
931
|
-
|
|
932
|
-
// Header with title + delete
|
|
933
|
-
var header = document.createElement("div");
|
|
934
|
-
header.className = "notes-archive-card-header";
|
|
935
|
-
|
|
936
|
-
var title = document.createElement("div");
|
|
937
|
-
title.className = "notes-archive-card-title";
|
|
938
|
-
title.textContent = getTitle(noteData.data.text) || "Untitled";
|
|
939
|
-
header.appendChild(title);
|
|
940
|
-
|
|
941
|
-
var deleteBtn = document.createElement("button");
|
|
942
|
-
deleteBtn.className = "notes-archive-card-delete";
|
|
943
|
-
deleteBtn.title = "Delete permanently";
|
|
944
|
-
deleteBtn.innerHTML = iconHtml("trash-2");
|
|
945
|
-
deleteBtn.addEventListener("click", function (e) {
|
|
946
|
-
e.stopPropagation();
|
|
947
|
-
// Confirm before permanent delete
|
|
948
|
-
if (card.classList.contains("confirm-delete")) {
|
|
949
|
-
wsSend({ type: "note_delete", id: noteData.data.id });
|
|
950
|
-
card.classList.add("deleting");
|
|
951
|
-
return;
|
|
952
|
-
}
|
|
953
|
-
card.classList.add("confirm-delete");
|
|
954
|
-
deleteBtn.title = "Click again to confirm";
|
|
955
|
-
setTimeout(function () {
|
|
956
|
-
card.classList.remove("confirm-delete");
|
|
957
|
-
deleteBtn.title = "Delete permanently";
|
|
958
|
-
}, 2000);
|
|
959
|
-
});
|
|
960
|
-
// Restore button (only for archived/hidden notes)
|
|
961
|
-
if (noteData.data.hidden) {
|
|
962
|
-
var restoreBtn = document.createElement("button");
|
|
963
|
-
restoreBtn.className = "notes-archive-card-restore";
|
|
964
|
-
restoreBtn.title = "Restore to canvas";
|
|
965
|
-
restoreBtn.innerHTML = iconHtml("rotate-ccw") + "<span>Restore</span>";
|
|
966
|
-
restoreBtn.addEventListener("click", function (e) {
|
|
967
|
-
e.stopPropagation();
|
|
968
|
-
wsSend({ type: "note_update", id: noteData.data.id, hidden: false });
|
|
969
|
-
noteData.el.classList.remove("hidden");
|
|
970
|
-
noteData.data.hidden = false;
|
|
971
|
-
renderArchiveCards();
|
|
972
|
-
});
|
|
973
|
-
header.appendChild(restoreBtn);
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
header.appendChild(deleteBtn);
|
|
977
|
-
card.appendChild(header);
|
|
978
|
-
|
|
979
|
-
// Body (rendered markdown)
|
|
980
|
-
var body = document.createElement("div");
|
|
981
|
-
body.className = "notes-archive-card-body";
|
|
982
|
-
var bodyLines = (noteData.data.text || "").split("\n").slice(1).join("\n").trim();
|
|
983
|
-
if (bodyLines) {
|
|
984
|
-
body.innerHTML = renderMiniMarkdown("_\n" + bodyLines).replace('<div class="sn-title">_</div>', "");
|
|
985
|
-
}
|
|
986
|
-
card.appendChild(body);
|
|
987
|
-
|
|
988
|
-
// Color strip at bottom
|
|
989
|
-
var colorStrip = document.createElement("div");
|
|
990
|
-
colorStrip.className = "notes-archive-card-color";
|
|
991
|
-
card.appendChild(colorStrip);
|
|
992
|
-
|
|
993
|
-
// Click card → close archive, jump to note on canvas
|
|
994
|
-
card.addEventListener("click", function () {
|
|
995
|
-
closeArchive();
|
|
996
|
-
showNotes();
|
|
997
|
-
// Un-hide if needed
|
|
998
|
-
if (noteData.data.hidden) {
|
|
999
|
-
wsSend({ type: "note_update", id: noteData.data.id, hidden: false });
|
|
1000
|
-
noteData.el.classList.remove("hidden");
|
|
1001
|
-
}
|
|
1002
|
-
// Bring note to front
|
|
1003
|
-
wsSend({ type: "note_bring_front", id: noteData.data.id });
|
|
1004
|
-
// Un-minimize if needed
|
|
1005
|
-
if (noteData.data.minimized) {
|
|
1006
|
-
wsSend({ type: "note_update", id: noteData.data.id, minimized: false });
|
|
1007
|
-
}
|
|
1008
|
-
// Flash the note
|
|
1009
|
-
var noteEl = noteData.el;
|
|
1010
|
-
if (noteEl) {
|
|
1011
|
-
noteEl.classList.add("note-flash");
|
|
1012
|
-
setTimeout(function () { noteEl.classList.remove("note-flash"); }, 600);
|
|
1013
|
-
}
|
|
1014
|
-
});
|
|
1015
|
-
|
|
1016
|
-
grid.appendChild(card);
|
|
1017
|
-
})(sorted[i]);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
refreshIcons();
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
export function openArchive() {
|
|
1024
|
-
if (archiveOpen) return;
|
|
1025
|
-
archiveOpen = true;
|
|
1026
|
-
|
|
1027
|
-
var messagesEl = document.getElementById("messages");
|
|
1028
|
-
var appEl = document.getElementById("app");
|
|
1029
|
-
var inputArea = document.getElementById("input-area");
|
|
1030
|
-
var titleBar = document.querySelector("#main-column > .title-bar-content");
|
|
1031
|
-
var notesContainer = document.getElementById("sticky-notes-container");
|
|
1032
|
-
|
|
1033
|
-
// Hide messages, input area, session title bar, and floating notes
|
|
1034
|
-
if (messagesEl) messagesEl.classList.add("hidden");
|
|
1035
|
-
if (inputArea) inputArea.classList.add("hidden");
|
|
1036
|
-
if (titleBar) titleBar.classList.add("hidden");
|
|
1037
|
-
if (notesContainer) notesContainer.classList.add("hidden");
|
|
1038
|
-
|
|
1039
|
-
// Create or show archive container
|
|
1040
|
-
var archive = document.getElementById("notes-archive");
|
|
1041
|
-
if (!archive) {
|
|
1042
|
-
archive = document.createElement("div");
|
|
1043
|
-
archive.id = "notes-archive";
|
|
1044
|
-
|
|
1045
|
-
var header = document.createElement("div");
|
|
1046
|
-
header.className = "notes-archive-header";
|
|
1047
|
-
|
|
1048
|
-
var titleWrap = document.createElement("div");
|
|
1049
|
-
titleWrap.className = "notes-archive-title-wrap";
|
|
1050
|
-
titleWrap.innerHTML = iconHtml("sticky-note") + "<h2>Sticky Notes</h2><span class=\"notes-archive-count\"></span>";
|
|
1051
|
-
header.appendChild(titleWrap);
|
|
1052
|
-
|
|
1053
|
-
var closeBtn = document.createElement("button");
|
|
1054
|
-
closeBtn.className = "notes-archive-close";
|
|
1055
|
-
closeBtn.title = "Back to chat";
|
|
1056
|
-
closeBtn.innerHTML = iconHtml("x");
|
|
1057
|
-
closeBtn.addEventListener("click", function () {
|
|
1058
|
-
closeArchive();
|
|
1059
|
-
});
|
|
1060
|
-
header.appendChild(closeBtn);
|
|
1061
|
-
|
|
1062
|
-
archive.appendChild(header);
|
|
1063
|
-
|
|
1064
|
-
var grid = document.createElement("div");
|
|
1065
|
-
grid.id = "notes-archive-grid";
|
|
1066
|
-
grid.className = "notes-archive-grid";
|
|
1067
|
-
archive.appendChild(grid);
|
|
1068
|
-
|
|
1069
|
-
if (appEl) appEl.appendChild(archive);
|
|
1070
|
-
}
|
|
1071
|
-
|
|
1072
|
-
archive.classList.remove("hidden");
|
|
1073
|
-
|
|
1074
|
-
// Update count
|
|
1075
|
-
var countEl = archive.querySelector(".notes-archive-count");
|
|
1076
|
-
if (countEl) countEl.textContent = notes.size + " note" + (notes.size !== 1 ? "s" : "");
|
|
1077
|
-
|
|
1078
|
-
renderArchiveCards();
|
|
1079
|
-
|
|
1080
|
-
// Mark sidebar button active
|
|
1081
|
-
var sidebarBtn = document.getElementById("sticky-notes-sidebar-btn");
|
|
1082
|
-
if (sidebarBtn) sidebarBtn.classList.add("active");
|
|
1083
|
-
|
|
1084
|
-
refreshIcons();
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
export function closeArchive() {
|
|
1088
|
-
if (!archiveOpen) return;
|
|
1089
|
-
archiveOpen = false;
|
|
1090
|
-
|
|
1091
|
-
var archive = document.getElementById("notes-archive");
|
|
1092
|
-
var messagesEl = document.getElementById("messages");
|
|
1093
|
-
var inputArea = document.getElementById("input-area");
|
|
1094
|
-
var titleBar = document.querySelector("#main-column > .title-bar-content");
|
|
1095
|
-
var notesContainer = document.getElementById("sticky-notes-container");
|
|
1096
|
-
|
|
1097
|
-
if (archive) archive.classList.add("hidden");
|
|
1098
|
-
if (messagesEl) messagesEl.classList.remove("hidden");
|
|
1099
|
-
if (inputArea) inputArea.classList.remove("hidden");
|
|
1100
|
-
if (titleBar) titleBar.classList.remove("hidden");
|
|
1101
|
-
|
|
1102
|
-
// Restore floating notes if they were visible before
|
|
1103
|
-
if (notesContainer && notesVisible) notesContainer.classList.remove("hidden");
|
|
1104
|
-
|
|
1105
|
-
// Un-mark sidebar button
|
|
1106
|
-
var sidebarBtn = document.getElementById("sticky-notes-sidebar-btn");
|
|
1107
|
-
if (sidebarBtn) sidebarBtn.classList.remove("active");
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
export function isArchiveOpen() {
|
|
1111
|
-
return archiveOpen;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
338
|
export function isNotesVisible() {
|
|
1115
339
|
return notesVisible;
|
|
1116
340
|
}
|