clay-server 2.5.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/LICENSE +21 -0
- package/README.md +281 -0
- package/bin/cli.js +2385 -0
- package/lib/cli-sessions.js +270 -0
- package/lib/config.js +237 -0
- package/lib/daemon.js +489 -0
- package/lib/ipc.js +112 -0
- package/lib/notes.js +120 -0
- package/lib/pages.js +664 -0
- package/lib/project.js +1433 -0
- package/lib/public/app.js +2795 -0
- package/lib/public/apple-touch-icon-dark.png +0 -0
- package/lib/public/apple-touch-icon.png +0 -0
- package/lib/public/css/base.css +264 -0
- package/lib/public/css/diff.css +128 -0
- package/lib/public/css/filebrowser.css +1114 -0
- package/lib/public/css/highlight.css +144 -0
- package/lib/public/css/icon-strip.css +296 -0
- package/lib/public/css/input.css +573 -0
- package/lib/public/css/menus.css +856 -0
- package/lib/public/css/messages.css +1445 -0
- package/lib/public/css/mobile-nav.css +354 -0
- package/lib/public/css/overlays.css +697 -0
- package/lib/public/css/rewind.css +505 -0
- package/lib/public/css/server-settings.css +761 -0
- package/lib/public/css/sidebar.css +936 -0
- package/lib/public/css/sticky-notes.css +358 -0
- package/lib/public/css/title-bar.css +314 -0
- package/lib/public/favicon-dark.svg +1 -0
- package/lib/public/favicon.svg +1 -0
- package/lib/public/icon-192-dark.png +0 -0
- package/lib/public/icon-192.png +0 -0
- package/lib/public/icon-512-dark.png +0 -0
- package/lib/public/icon-512.png +0 -0
- package/lib/public/icon-mono.svg +1 -0
- package/lib/public/index.html +762 -0
- package/lib/public/manifest.json +27 -0
- package/lib/public/modules/diff.js +398 -0
- package/lib/public/modules/events.js +21 -0
- package/lib/public/modules/filebrowser.js +1411 -0
- package/lib/public/modules/fileicons.js +172 -0
- package/lib/public/modules/icons.js +54 -0
- package/lib/public/modules/input.js +584 -0
- package/lib/public/modules/markdown.js +356 -0
- package/lib/public/modules/notifications.js +649 -0
- package/lib/public/modules/qrcode.js +70 -0
- package/lib/public/modules/rewind.js +345 -0
- package/lib/public/modules/server-settings.js +510 -0
- package/lib/public/modules/sidebar.js +1083 -0
- package/lib/public/modules/state.js +3 -0
- package/lib/public/modules/sticky-notes.js +688 -0
- package/lib/public/modules/terminal.js +697 -0
- package/lib/public/modules/theme.js +738 -0
- package/lib/public/modules/tools.js +1608 -0
- package/lib/public/modules/utils.js +56 -0
- package/lib/public/style.css +15 -0
- package/lib/public/sw.js +75 -0
- package/lib/push.js +124 -0
- package/lib/sdk-bridge.js +989 -0
- package/lib/server.js +582 -0
- package/lib/sessions.js +424 -0
- package/lib/terminal-manager.js +187 -0
- package/lib/terminal.js +24 -0
- package/lib/themes/ayu-light.json +9 -0
- package/lib/themes/catppuccin-latte.json +9 -0
- package/lib/themes/catppuccin-mocha.json +9 -0
- package/lib/themes/clay-light.json +10 -0
- package/lib/themes/clay.json +10 -0
- package/lib/themes/dracula.json +9 -0
- package/lib/themes/everforest-light.json +9 -0
- package/lib/themes/everforest.json +9 -0
- package/lib/themes/github-light.json +9 -0
- package/lib/themes/gruvbox-dark.json +9 -0
- package/lib/themes/gruvbox-light.json +9 -0
- package/lib/themes/monokai.json +9 -0
- package/lib/themes/nord-light.json +9 -0
- package/lib/themes/nord.json +9 -0
- package/lib/themes/one-dark.json +9 -0
- package/lib/themes/one-light.json +9 -0
- package/lib/themes/rose-pine-dawn.json +9 -0
- package/lib/themes/rose-pine.json +9 -0
- package/lib/themes/solarized-dark.json +9 -0
- package/lib/themes/solarized-light.json +9 -0
- package/lib/themes/tokyo-night-light.json +9 -0
- package/lib/themes/tokyo-night.json +9 -0
- package/lib/updater.js +97 -0
- package/package.json +47 -0
|
@@ -0,0 +1,688 @@
|
|
|
1
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
2
|
+
|
|
3
|
+
var ctx;
|
|
4
|
+
var notes = new Map(); // id -> { data, el }
|
|
5
|
+
var notesVisible = false;
|
|
6
|
+
var updateTimers = {};
|
|
7
|
+
var textTimers = {};
|
|
8
|
+
var colorPickerEl = null;
|
|
9
|
+
|
|
10
|
+
var NOTE_COLORS = ["yellow", "blue", "green", "pink", "orange", "purple"];
|
|
11
|
+
|
|
12
|
+
function getContainerBounds() {
|
|
13
|
+
var c = document.getElementById("sticky-notes-container");
|
|
14
|
+
if (!c || c.clientWidth === 0 || c.clientHeight === 0) return null;
|
|
15
|
+
return { w: c.clientWidth, h: c.clientHeight };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function clampPos(x, y, noteW, noteH) {
|
|
19
|
+
var b = getContainerBounds();
|
|
20
|
+
if (!b) return { x: x, y: y };
|
|
21
|
+
return {
|
|
22
|
+
x: Math.max(0, Math.min(x, b.w - noteW)),
|
|
23
|
+
y: Math.max(0, Math.min(y, b.h - noteH)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function clampSize(x, y, w, h) {
|
|
28
|
+
var b = getContainerBounds();
|
|
29
|
+
if (!b) return { w: w, h: h };
|
|
30
|
+
return {
|
|
31
|
+
w: Math.min(w, b.w - x),
|
|
32
|
+
h: Math.min(h, b.h - y),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function reclampAllNotes() {
|
|
37
|
+
notes.forEach(function (entry) {
|
|
38
|
+
var el = entry.el;
|
|
39
|
+
var noteW = el.offsetWidth;
|
|
40
|
+
var noteH = el.offsetHeight;
|
|
41
|
+
var curX = parseInt(el.style.left) || 0;
|
|
42
|
+
var curY = parseInt(el.style.top) || 0;
|
|
43
|
+
var c = clampPos(curX, curY, noteW, noteH);
|
|
44
|
+
el.style.left = c.x + "px";
|
|
45
|
+
el.style.top = c.y + "px";
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function initStickyNotes(_ctx) {
|
|
50
|
+
ctx = _ctx;
|
|
51
|
+
|
|
52
|
+
var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
|
|
53
|
+
if (toggleBtn) {
|
|
54
|
+
toggleBtn.addEventListener("click", function () {
|
|
55
|
+
if (!notesVisible && notes.size > 0) {
|
|
56
|
+
// Hidden with existing notes → just show them
|
|
57
|
+
showNotes();
|
|
58
|
+
} else {
|
|
59
|
+
// Visible or no notes → create a new one
|
|
60
|
+
showNotes();
|
|
61
|
+
createNote();
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// Long-press or right-click to toggle hide
|
|
66
|
+
toggleBtn.addEventListener("contextmenu", function (e) {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
if (notesVisible) hideNotes();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Re-clamp note positions on window resize so notes stay visible
|
|
73
|
+
var resizeTimer;
|
|
74
|
+
window.addEventListener("resize", function () {
|
|
75
|
+
clearTimeout(resizeTimer);
|
|
76
|
+
resizeTimer = setTimeout(function () {
|
|
77
|
+
if (notesVisible && notes.size > 0) {
|
|
78
|
+
reclampAllNotes();
|
|
79
|
+
}
|
|
80
|
+
}, 100);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// --- Visibility ---
|
|
85
|
+
|
|
86
|
+
function showNotes() {
|
|
87
|
+
notesVisible = true;
|
|
88
|
+
var container = document.getElementById("sticky-notes-container");
|
|
89
|
+
var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
|
|
90
|
+
if (container) container.classList.remove("hidden");
|
|
91
|
+
if (toggleBtn) toggleBtn.classList.add("active");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hideNotes() {
|
|
95
|
+
notesVisible = false;
|
|
96
|
+
var container = document.getElementById("sticky-notes-container");
|
|
97
|
+
var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
|
|
98
|
+
if (container) container.classList.add("hidden");
|
|
99
|
+
if (toggleBtn) toggleBtn.classList.remove("active");
|
|
100
|
+
closeColorPicker();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function createNote() {
|
|
104
|
+
var container = document.getElementById("sticky-notes-container");
|
|
105
|
+
if (!container) return;
|
|
106
|
+
// Scatter position so notes don't stack exactly
|
|
107
|
+
var offset = (notes.size % 5) * 30;
|
|
108
|
+
wsSend({
|
|
109
|
+
type: "note_create",
|
|
110
|
+
x: 60 + offset,
|
|
111
|
+
y: 60 + offset,
|
|
112
|
+
color: "yellow",
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function updateBadge() {
|
|
117
|
+
var badge = document.querySelector(".sticky-notes-count");
|
|
118
|
+
if (!badge) return;
|
|
119
|
+
if (notes.size > 0) {
|
|
120
|
+
badge.textContent = notes.size;
|
|
121
|
+
badge.classList.remove("hidden");
|
|
122
|
+
} else {
|
|
123
|
+
badge.classList.add("hidden");
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// --- WS send helpers ---
|
|
128
|
+
|
|
129
|
+
function wsSend(obj) {
|
|
130
|
+
if (ctx && ctx.ws && ctx.connected) {
|
|
131
|
+
ctx.ws.send(JSON.stringify(obj));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function debouncedUpdate(id, changes, delay) {
|
|
136
|
+
clearTimeout(updateTimers[id]);
|
|
137
|
+
updateTimers[id] = setTimeout(function () {
|
|
138
|
+
changes.type = "note_update";
|
|
139
|
+
changes.id = id;
|
|
140
|
+
wsSend(changes);
|
|
141
|
+
}, delay || 300);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function debouncedTextUpdate(id, text) {
|
|
145
|
+
clearTimeout(textTimers[id]);
|
|
146
|
+
textTimers[id] = setTimeout(function () {
|
|
147
|
+
wsSend({ type: "note_update", id: id, text: text });
|
|
148
|
+
}, 500);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Simple markdown ---
|
|
152
|
+
|
|
153
|
+
function getTitle(text) {
|
|
154
|
+
if (!text) return "";
|
|
155
|
+
var idx = text.indexOf("\n");
|
|
156
|
+
return idx === -1 ? text : text.substring(0, idx);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function renderMiniMarkdown(text) {
|
|
160
|
+
if (!text) return "";
|
|
161
|
+
var lines = text.split("\n");
|
|
162
|
+
var title = lines[0];
|
|
163
|
+
var body = lines.slice(1).join("\n");
|
|
164
|
+
|
|
165
|
+
function fmt(s) {
|
|
166
|
+
var escaped = s
|
|
167
|
+
.replace(/&/g, "&")
|
|
168
|
+
.replace(/</g, "<")
|
|
169
|
+
.replace(/>/g, ">");
|
|
170
|
+
return escaped
|
|
171
|
+
.replace(/(https?:\/\/[^\s<]+)/g, '<a href="$1" target="_blank" rel="noopener">$1</a>')
|
|
172
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
173
|
+
.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<em>$1</em>")
|
|
174
|
+
.replace(/`([^`]+)`/g, "<code>$1</code>")
|
|
175
|
+
.replace(/~~(.+?)~~/g, "<del>$1</del>")
|
|
176
|
+
.replace(/^- \[x\]/gm, '<span class="sn-check checked">✓</span>')
|
|
177
|
+
.replace(/^- \[ \]/gm, '<span class="sn-check">☐</span>')
|
|
178
|
+
.replace(/\n/g, "<br>");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
var html = '<div class="sn-title">' + fmt(title) + '</div>';
|
|
182
|
+
if (body.trim()) {
|
|
183
|
+
html += fmt(body);
|
|
184
|
+
}
|
|
185
|
+
return html;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function syncTitle(noteEl, text) {
|
|
189
|
+
var spacer = noteEl.querySelector(".sticky-note-spacer");
|
|
190
|
+
if (spacer) spacer.textContent = getTitle(text);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// --- Note rendering ---
|
|
194
|
+
|
|
195
|
+
function renderNote(data) {
|
|
196
|
+
var el = document.createElement("div");
|
|
197
|
+
el.className = "sticky-note";
|
|
198
|
+
el.dataset.noteId = data.id;
|
|
199
|
+
var clamped = clampPos(data.x, data.y, data.w, data.h);
|
|
200
|
+
el.style.left = clamped.x + "px";
|
|
201
|
+
el.style.top = clamped.y + "px";
|
|
202
|
+
el.style.width = data.w + "px";
|
|
203
|
+
el.style.height = data.h + "px";
|
|
204
|
+
el.style.zIndex = 100 + (data.zIndex || 0);
|
|
205
|
+
el.dataset.color = data.color || "yellow";
|
|
206
|
+
|
|
207
|
+
if (data.minimized) el.classList.add("minimized");
|
|
208
|
+
|
|
209
|
+
// Header
|
|
210
|
+
var header = document.createElement("div");
|
|
211
|
+
header.className = "sticky-note-header";
|
|
212
|
+
|
|
213
|
+
var deleteBtn = document.createElement("button");
|
|
214
|
+
deleteBtn.className = "sticky-note-btn sticky-note-delete";
|
|
215
|
+
deleteBtn.title = "Delete";
|
|
216
|
+
deleteBtn.innerHTML = iconHtml("x");
|
|
217
|
+
header.appendChild(deleteBtn);
|
|
218
|
+
|
|
219
|
+
var minBtn = document.createElement("button");
|
|
220
|
+
minBtn.className = "sticky-note-btn sticky-note-min-btn";
|
|
221
|
+
minBtn.title = data.minimized ? "Expand" : "Minimize";
|
|
222
|
+
minBtn.innerHTML = data.minimized ? iconHtml("maximize-2") : iconHtml("minus");
|
|
223
|
+
header.appendChild(minBtn);
|
|
224
|
+
|
|
225
|
+
var spacer = document.createElement("div");
|
|
226
|
+
spacer.className = "sticky-note-spacer";
|
|
227
|
+
spacer.textContent = getTitle(data.text);
|
|
228
|
+
header.appendChild(spacer);
|
|
229
|
+
|
|
230
|
+
var addBtn = document.createElement("button");
|
|
231
|
+
addBtn.className = "sticky-note-btn";
|
|
232
|
+
addBtn.title = "New note";
|
|
233
|
+
addBtn.innerHTML = iconHtml("plus");
|
|
234
|
+
addBtn.addEventListener("click", function (e) {
|
|
235
|
+
e.stopPropagation();
|
|
236
|
+
createNote();
|
|
237
|
+
});
|
|
238
|
+
header.appendChild(addBtn);
|
|
239
|
+
|
|
240
|
+
var colorBtn = document.createElement("button");
|
|
241
|
+
colorBtn.className = "sticky-note-color-btn";
|
|
242
|
+
colorBtn.title = "Change color";
|
|
243
|
+
colorBtn.innerHTML = iconHtml("palette");
|
|
244
|
+
header.appendChild(colorBtn);
|
|
245
|
+
|
|
246
|
+
el.appendChild(header);
|
|
247
|
+
|
|
248
|
+
// Body
|
|
249
|
+
var body = document.createElement("div");
|
|
250
|
+
body.className = "sticky-note-body";
|
|
251
|
+
|
|
252
|
+
var textarea = document.createElement("textarea");
|
|
253
|
+
textarea.className = "sticky-note-text";
|
|
254
|
+
textarea.value = data.text || "";
|
|
255
|
+
textarea.placeholder = "Type a note...";
|
|
256
|
+
body.appendChild(textarea);
|
|
257
|
+
|
|
258
|
+
var rendered = document.createElement("div");
|
|
259
|
+
rendered.className = "sticky-note-rendered";
|
|
260
|
+
body.appendChild(rendered);
|
|
261
|
+
|
|
262
|
+
// Show rendered view if there's content
|
|
263
|
+
if (data.text) {
|
|
264
|
+
rendered.innerHTML = renderMiniMarkdown(data.text);
|
|
265
|
+
textarea.style.display = "none";
|
|
266
|
+
rendered.style.display = "";
|
|
267
|
+
} else {
|
|
268
|
+
textarea.style.display = "";
|
|
269
|
+
rendered.style.display = "none";
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
el.appendChild(body);
|
|
273
|
+
|
|
274
|
+
// Resize handle
|
|
275
|
+
var resizeHandle = document.createElement("div");
|
|
276
|
+
resizeHandle.className = "sticky-note-resize";
|
|
277
|
+
el.appendChild(resizeHandle);
|
|
278
|
+
|
|
279
|
+
// --- Event handlers ---
|
|
280
|
+
setupDrag(el, spacer, data.id);
|
|
281
|
+
setupResize(el, resizeHandle, data.id);
|
|
282
|
+
setupTextEdit(textarea, rendered, data.id);
|
|
283
|
+
setupColorPicker(colorBtn, el, data.id);
|
|
284
|
+
setupMinimize(minBtn, el, data.id);
|
|
285
|
+
setupDelete(deleteBtn, data.id);
|
|
286
|
+
setupBringToFront(el, data.id);
|
|
287
|
+
|
|
288
|
+
refreshIcons();
|
|
289
|
+
return el;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// --- Drag ---
|
|
293
|
+
|
|
294
|
+
function setupDrag(noteEl, spacerEl, noteId) {
|
|
295
|
+
var dragging = false;
|
|
296
|
+
var startX, startY, origX, origY;
|
|
297
|
+
|
|
298
|
+
spacerEl.addEventListener("mousedown", function (e) {
|
|
299
|
+
if (e.button !== 0) return;
|
|
300
|
+
e.preventDefault();
|
|
301
|
+
dragging = true;
|
|
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.addEventListener("mousemove", onMove);
|
|
308
|
+
document.addEventListener("mouseup", onUp);
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
function onMove(e) {
|
|
312
|
+
if (!dragging) 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() {
|
|
321
|
+
if (!dragging) return;
|
|
322
|
+
dragging = false;
|
|
323
|
+
noteEl.classList.remove("dragging");
|
|
324
|
+
document.removeEventListener("mousemove", onMove);
|
|
325
|
+
document.removeEventListener("mouseup", onUp);
|
|
326
|
+
debouncedUpdate(noteId, {
|
|
327
|
+
x: parseInt(noteEl.style.left),
|
|
328
|
+
y: parseInt(noteEl.style.top),
|
|
329
|
+
}, 200);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Touch support
|
|
333
|
+
spacerEl.addEventListener("touchstart", function (e) {
|
|
334
|
+
if (e.touches.length !== 1) return;
|
|
335
|
+
var touch = e.touches[0];
|
|
336
|
+
dragging = true;
|
|
337
|
+
startX = touch.clientX;
|
|
338
|
+
startY = touch.clientY;
|
|
339
|
+
origX = parseInt(noteEl.style.left) || 0;
|
|
340
|
+
origY = parseInt(noteEl.style.top) || 0;
|
|
341
|
+
noteEl.classList.add("dragging");
|
|
342
|
+
document.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
343
|
+
document.addEventListener("touchend", onTouchEnd);
|
|
344
|
+
}, { passive: true });
|
|
345
|
+
|
|
346
|
+
function onTouchMove(e) {
|
|
347
|
+
if (!dragging) return;
|
|
348
|
+
e.preventDefault();
|
|
349
|
+
var touch = e.touches[0];
|
|
350
|
+
var dx = touch.clientX - startX;
|
|
351
|
+
var dy = touch.clientY - startY;
|
|
352
|
+
var c = clampPos(origX + dx, origY + dy, noteEl.offsetWidth, noteEl.offsetHeight);
|
|
353
|
+
noteEl.style.left = c.x + "px";
|
|
354
|
+
noteEl.style.top = c.y + "px";
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function onTouchEnd() {
|
|
358
|
+
if (!dragging) return;
|
|
359
|
+
dragging = false;
|
|
360
|
+
noteEl.classList.remove("dragging");
|
|
361
|
+
document.removeEventListener("touchmove", onTouchMove);
|
|
362
|
+
document.removeEventListener("touchend", onTouchEnd);
|
|
363
|
+
debouncedUpdate(noteId, {
|
|
364
|
+
x: parseInt(noteEl.style.left),
|
|
365
|
+
y: parseInt(noteEl.style.top),
|
|
366
|
+
}, 200);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// --- Resize ---
|
|
371
|
+
|
|
372
|
+
function setupResize(noteEl, handle, noteId) {
|
|
373
|
+
var resizing = false;
|
|
374
|
+
var startX, startY, origW, origH;
|
|
375
|
+
var MIN_W = 160;
|
|
376
|
+
var MIN_H = 80;
|
|
377
|
+
|
|
378
|
+
handle.addEventListener("mousedown", function (e) {
|
|
379
|
+
if (e.button !== 0) return;
|
|
380
|
+
e.preventDefault();
|
|
381
|
+
e.stopPropagation();
|
|
382
|
+
resizing = true;
|
|
383
|
+
startX = e.clientX;
|
|
384
|
+
startY = e.clientY;
|
|
385
|
+
origW = noteEl.offsetWidth;
|
|
386
|
+
origH = noteEl.offsetHeight;
|
|
387
|
+
noteEl.classList.add("resizing");
|
|
388
|
+
document.addEventListener("mousemove", onMove);
|
|
389
|
+
document.addEventListener("mouseup", onUp);
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
function onMove(e) {
|
|
393
|
+
if (!resizing) return;
|
|
394
|
+
var rawW = Math.max(MIN_W, origW + (e.clientX - startX));
|
|
395
|
+
var rawH = Math.max(MIN_H, origH + (e.clientY - startY));
|
|
396
|
+
var cs = clampSize(parseInt(noteEl.style.left) || 0, parseInt(noteEl.style.top) || 0, rawW, rawH);
|
|
397
|
+
noteEl.style.width = Math.max(MIN_W, cs.w) + "px";
|
|
398
|
+
noteEl.style.height = Math.max(MIN_H, cs.h) + "px";
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function onUp() {
|
|
402
|
+
if (!resizing) return;
|
|
403
|
+
resizing = false;
|
|
404
|
+
noteEl.classList.remove("resizing");
|
|
405
|
+
document.removeEventListener("mousemove", onMove);
|
|
406
|
+
document.removeEventListener("mouseup", onUp);
|
|
407
|
+
debouncedUpdate(noteId, {
|
|
408
|
+
w: noteEl.offsetWidth,
|
|
409
|
+
h: noteEl.offsetHeight,
|
|
410
|
+
}, 200);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// Touch resize
|
|
414
|
+
handle.addEventListener("touchstart", function (e) {
|
|
415
|
+
if (e.touches.length !== 1) return;
|
|
416
|
+
e.stopPropagation();
|
|
417
|
+
var touch = e.touches[0];
|
|
418
|
+
resizing = true;
|
|
419
|
+
startX = touch.clientX;
|
|
420
|
+
startY = touch.clientY;
|
|
421
|
+
origW = noteEl.offsetWidth;
|
|
422
|
+
origH = noteEl.offsetHeight;
|
|
423
|
+
noteEl.classList.add("resizing");
|
|
424
|
+
document.addEventListener("touchmove", onTouchMove, { passive: false });
|
|
425
|
+
document.addEventListener("touchend", onTouchEnd);
|
|
426
|
+
}, { passive: true });
|
|
427
|
+
|
|
428
|
+
function onTouchMove(e) {
|
|
429
|
+
if (!resizing) return;
|
|
430
|
+
e.preventDefault();
|
|
431
|
+
var touch = e.touches[0];
|
|
432
|
+
var rawW = Math.max(MIN_W, origW + (touch.clientX - startX));
|
|
433
|
+
var rawH = Math.max(MIN_H, origH + (touch.clientY - startY));
|
|
434
|
+
var cs = clampSize(parseInt(noteEl.style.left) || 0, parseInt(noteEl.style.top) || 0, rawW, rawH);
|
|
435
|
+
noteEl.style.width = Math.max(MIN_W, cs.w) + "px";
|
|
436
|
+
noteEl.style.height = Math.max(MIN_H, cs.h) + "px";
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function onTouchEnd() {
|
|
440
|
+
if (!resizing) return;
|
|
441
|
+
resizing = false;
|
|
442
|
+
noteEl.classList.remove("resizing");
|
|
443
|
+
document.removeEventListener("touchmove", onTouchMove);
|
|
444
|
+
document.removeEventListener("touchend", onTouchEnd);
|
|
445
|
+
debouncedUpdate(noteId, {
|
|
446
|
+
w: noteEl.offsetWidth,
|
|
447
|
+
h: noteEl.offsetHeight,
|
|
448
|
+
}, 200);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// --- Text edit ---
|
|
453
|
+
|
|
454
|
+
function switchToEdit(textarea, rendered) {
|
|
455
|
+
rendered.style.display = "none";
|
|
456
|
+
textarea.style.display = "";
|
|
457
|
+
textarea.focus();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function setupTextEdit(textarea, rendered, noteId) {
|
|
461
|
+
var noteEl = textarea.closest(".sticky-note");
|
|
462
|
+
var spacer = noteEl.querySelector(".sticky-note-spacer");
|
|
463
|
+
|
|
464
|
+
textarea.addEventListener("input", function () {
|
|
465
|
+
debouncedTextUpdate(noteId, textarea.value);
|
|
466
|
+
syncTitle(noteEl, textarea.value);
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
// Click spacer → switch to edit
|
|
470
|
+
if (spacer) {
|
|
471
|
+
spacer.addEventListener("click", function () {
|
|
472
|
+
if (noteEl.classList.contains("minimized")) return;
|
|
473
|
+
switchToEdit(textarea, rendered);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Click rendered → switch to edit
|
|
478
|
+
rendered.addEventListener("mousedown", function (e) {
|
|
479
|
+
e.stopPropagation();
|
|
480
|
+
});
|
|
481
|
+
rendered.addEventListener("click", function (e) {
|
|
482
|
+
if (e.target.tagName === "A") return;
|
|
483
|
+
switchToEdit(textarea, rendered);
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
// Blur textarea → switch to rendered (if has content)
|
|
487
|
+
textarea.addEventListener("blur", function () {
|
|
488
|
+
if (textarea.value.trim()) {
|
|
489
|
+
rendered.innerHTML = renderMiniMarkdown(textarea.value);
|
|
490
|
+
textarea.style.display = "none";
|
|
491
|
+
rendered.style.display = "";
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Prevent drag when clicking textarea
|
|
496
|
+
textarea.addEventListener("mousedown", function (e) {
|
|
497
|
+
e.stopPropagation();
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// --- Color picker ---
|
|
502
|
+
|
|
503
|
+
function closeColorPicker() {
|
|
504
|
+
if (colorPickerEl) {
|
|
505
|
+
colorPickerEl.remove();
|
|
506
|
+
colorPickerEl = null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function setupColorPicker(btn, noteEl, noteId) {
|
|
511
|
+
btn.addEventListener("click", function (e) {
|
|
512
|
+
e.stopPropagation();
|
|
513
|
+
showColorPicker(btn, noteEl, noteId);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function showColorPicker(anchor, noteEl, noteId) {
|
|
518
|
+
closeColorPicker();
|
|
519
|
+
|
|
520
|
+
var picker = document.createElement("div");
|
|
521
|
+
picker.className = "sticky-note-color-picker";
|
|
522
|
+
|
|
523
|
+
for (var i = 0; i < NOTE_COLORS.length; i++) {
|
|
524
|
+
(function (color) {
|
|
525
|
+
var dot = document.createElement("button");
|
|
526
|
+
dot.className = "sticky-note-color-dot";
|
|
527
|
+
dot.dataset.color = color;
|
|
528
|
+
if (noteEl.dataset.color === color) dot.classList.add("active");
|
|
529
|
+
dot.addEventListener("click", function (e) {
|
|
530
|
+
e.stopPropagation();
|
|
531
|
+
noteEl.dataset.color = color;
|
|
532
|
+
wsSend({ type: "note_update", id: noteId, color: color });
|
|
533
|
+
closeColorPicker();
|
|
534
|
+
});
|
|
535
|
+
picker.appendChild(dot);
|
|
536
|
+
})(NOTE_COLORS[i]);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
document.body.appendChild(picker);
|
|
540
|
+
colorPickerEl = picker;
|
|
541
|
+
|
|
542
|
+
// Position relative to anchor
|
|
543
|
+
var rect = anchor.getBoundingClientRect();
|
|
544
|
+
picker.style.left = rect.left + "px";
|
|
545
|
+
picker.style.top = (rect.bottom + 4) + "px";
|
|
546
|
+
|
|
547
|
+
// Close on outside click
|
|
548
|
+
setTimeout(function () {
|
|
549
|
+
document.addEventListener("click", function closeHandler() {
|
|
550
|
+
closeColorPicker();
|
|
551
|
+
document.removeEventListener("click", closeHandler);
|
|
552
|
+
});
|
|
553
|
+
}, 0);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// --- Minimize ---
|
|
557
|
+
|
|
558
|
+
function setupMinimize(btn, noteEl, noteId) {
|
|
559
|
+
btn.addEventListener("click", function (e) {
|
|
560
|
+
e.stopPropagation();
|
|
561
|
+
var isMinimized = noteEl.classList.toggle("minimized");
|
|
562
|
+
btn.innerHTML = isMinimized ? iconHtml("maximize-2") : iconHtml("minus");
|
|
563
|
+
btn.title = isMinimized ? "Expand" : "Minimize";
|
|
564
|
+
refreshIcons();
|
|
565
|
+
wsSend({ type: "note_update", id: noteId, minimized: isMinimized });
|
|
566
|
+
});
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// --- Delete ---
|
|
570
|
+
|
|
571
|
+
function setupDelete(btn, noteId) {
|
|
572
|
+
btn.addEventListener("click", function (e) {
|
|
573
|
+
e.stopPropagation();
|
|
574
|
+
wsSend({ type: "note_delete", id: noteId });
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// --- Bring to front ---
|
|
579
|
+
|
|
580
|
+
function setupBringToFront(noteEl, noteId) {
|
|
581
|
+
noteEl.addEventListener("mousedown", function (e) {
|
|
582
|
+
// Skip bring-to-front when clicking header buttons to avoid
|
|
583
|
+
// race condition where server response replaces innerHTML
|
|
584
|
+
// between mousedown and click, causing the click event to be lost
|
|
585
|
+
if (e.target.closest("button")) return;
|
|
586
|
+
wsSend({ type: "note_bring_front", id: noteId });
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// --- WS message handlers ---
|
|
591
|
+
|
|
592
|
+
export function handleNotesList(msg) {
|
|
593
|
+
var container = document.getElementById("sticky-notes-container");
|
|
594
|
+
if (!container) return;
|
|
595
|
+
|
|
596
|
+
// Clear existing
|
|
597
|
+
container.innerHTML = "";
|
|
598
|
+
notes.clear();
|
|
599
|
+
|
|
600
|
+
var list = msg.notes || [];
|
|
601
|
+
for (var i = 0; i < list.length; i++) {
|
|
602
|
+
var el = renderNote(list[i]);
|
|
603
|
+
notes.set(list[i].id, { data: list[i], el: el });
|
|
604
|
+
container.appendChild(el);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
updateBadge();
|
|
608
|
+
|
|
609
|
+
// Auto-show if there are notes
|
|
610
|
+
if (list.length > 0 && !notesVisible) {
|
|
611
|
+
notesVisible = true;
|
|
612
|
+
container.classList.remove("hidden");
|
|
613
|
+
var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
|
|
614
|
+
if (toggleBtn) toggleBtn.classList.add("active");
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
export function handleNoteCreated(msg) {
|
|
619
|
+
var container = document.getElementById("sticky-notes-container");
|
|
620
|
+
if (!container || !msg.note) return;
|
|
621
|
+
|
|
622
|
+
// Don't duplicate
|
|
623
|
+
if (notes.has(msg.note.id)) return;
|
|
624
|
+
|
|
625
|
+
var el = renderNote(msg.note);
|
|
626
|
+
notes.set(msg.note.id, { data: msg.note, el: el });
|
|
627
|
+
container.appendChild(el);
|
|
628
|
+
updateBadge();
|
|
629
|
+
|
|
630
|
+
// Show container if hidden
|
|
631
|
+
if (!notesVisible) {
|
|
632
|
+
notesVisible = true;
|
|
633
|
+
container.classList.remove("hidden");
|
|
634
|
+
var toggleBtn = document.getElementById("sticky-notes-toggle-btn");
|
|
635
|
+
if (toggleBtn) toggleBtn.classList.add("active");
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
export function handleNoteUpdated(msg) {
|
|
640
|
+
if (!msg.note) return;
|
|
641
|
+
var entry = notes.get(msg.note.id);
|
|
642
|
+
if (!entry) return;
|
|
643
|
+
|
|
644
|
+
entry.data = msg.note;
|
|
645
|
+
|
|
646
|
+
// Update DOM
|
|
647
|
+
entry.el.style.left = msg.note.x + "px";
|
|
648
|
+
entry.el.style.top = msg.note.y + "px";
|
|
649
|
+
entry.el.style.width = msg.note.w + "px";
|
|
650
|
+
entry.el.style.height = msg.note.h + "px";
|
|
651
|
+
entry.el.style.zIndex = 100 + (msg.note.zIndex || 0);
|
|
652
|
+
entry.el.dataset.color = msg.note.color || "yellow";
|
|
653
|
+
|
|
654
|
+
// Update text only if not actively editing
|
|
655
|
+
var textarea = entry.el.querySelector(".sticky-note-text");
|
|
656
|
+
var rendered = entry.el.querySelector(".sticky-note-rendered");
|
|
657
|
+
if (textarea && textarea !== document.activeElement) {
|
|
658
|
+
textarea.value = msg.note.text || "";
|
|
659
|
+
if (rendered && msg.note.text) {
|
|
660
|
+
rendered.innerHTML = renderMiniMarkdown(msg.note.text);
|
|
661
|
+
}
|
|
662
|
+
syncTitle(entry.el, msg.note.text);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
var minBtn = entry.el.querySelector(".sticky-note-min-btn");
|
|
666
|
+
if (msg.note.minimized) {
|
|
667
|
+
entry.el.classList.add("minimized");
|
|
668
|
+
if (minBtn) { minBtn.innerHTML = iconHtml("maximize-2"); minBtn.title = "Expand"; }
|
|
669
|
+
} else {
|
|
670
|
+
entry.el.classList.remove("minimized");
|
|
671
|
+
if (minBtn) { minBtn.innerHTML = iconHtml("minus"); minBtn.title = "Minimize"; }
|
|
672
|
+
}
|
|
673
|
+
refreshIcons();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function handleNoteDeleted(msg) {
|
|
677
|
+
var entry = notes.get(msg.id);
|
|
678
|
+
if (!entry) return;
|
|
679
|
+
entry.el.remove();
|
|
680
|
+
notes.delete(msg.id);
|
|
681
|
+
updateBadge();
|
|
682
|
+
|
|
683
|
+
// Clear debounce timers
|
|
684
|
+
clearTimeout(updateTimers[msg.id]);
|
|
685
|
+
clearTimeout(textTimers[msg.id]);
|
|
686
|
+
delete updateTimers[msg.id];
|
|
687
|
+
delete textTimers[msg.id];
|
|
688
|
+
}
|