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
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// Sticky-note text editing: the WYSIWYG format toolbar and the contenteditable
|
|
2
|
+
// surface behind it.
|
|
3
|
+
//
|
|
4
|
+
// Split out of sticky-notes.js to keep every module under the size limit. It
|
|
5
|
+
// owns the toolbar element and nothing else, and depends only on the leaf
|
|
6
|
+
// helpers, so it never has to reach back into the canvas.
|
|
7
|
+
|
|
8
|
+
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
|
+
import { extractMarkdown, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
|
+
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
11
|
+
import { debouncedTextUpdate, syncTitle } from './sticky-notes-shared.js';
|
|
12
|
+
|
|
13
|
+
var formatToolbarEl = null;
|
|
14
|
+
|
|
15
|
+
// --- Text edit (contenteditable) ---
|
|
16
|
+
|
|
17
|
+
// --- Format toolbar (WYSIWYG) ---
|
|
18
|
+
|
|
19
|
+
var FORMAT_BUTTONS = [
|
|
20
|
+
{ label: "B", title: "Bold", cls: "sn-fmt-bold", command: "bold" },
|
|
21
|
+
{ label: "I", title: "Italic", cls: "sn-fmt-italic", command: "italic" },
|
|
22
|
+
{ label: "S", title: "Strikethrough", cls: "sn-fmt-strike", command: "strikethrough" },
|
|
23
|
+
{ label: "code-2", title: "Code", cls: "sn-fmt-code", command: "code", isIcon: true },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
// The canvas asks this before deciding an outside click should dismiss the
|
|
27
|
+
// toolbar, so the element itself stays private to this module.
|
|
28
|
+
export function isFormatToolbarOpen() {
|
|
29
|
+
return !!formatToolbarEl;
|
|
30
|
+
}
|
|
31
|
+
export function closeFormatToolbar() {
|
|
32
|
+
if (formatToolbarEl) {
|
|
33
|
+
formatToolbarEl.remove();
|
|
34
|
+
formatToolbarEl = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function applyFormat(command, rendered) {
|
|
39
|
+
if (command === "code") {
|
|
40
|
+
var sel = window.getSelection();
|
|
41
|
+
if (!sel.rangeCount || sel.isCollapsed) return;
|
|
42
|
+
var range = sel.getRangeAt(0);
|
|
43
|
+
var ancestor = range.commonAncestorContainer;
|
|
44
|
+
var codeParent = (ancestor.nodeType === 3 ? ancestor.parentElement : ancestor);
|
|
45
|
+
if (codeParent && codeParent.closest && codeParent.closest("code")) {
|
|
46
|
+
// Unwrap: replace <code> with its text content
|
|
47
|
+
var codeEl = codeParent.closest("code");
|
|
48
|
+
var textNode = document.createTextNode(codeEl.textContent);
|
|
49
|
+
codeEl.parentNode.replaceChild(textNode, codeEl);
|
|
50
|
+
var newRange = document.createRange();
|
|
51
|
+
newRange.selectNodeContents(textNode);
|
|
52
|
+
sel.removeAllRanges();
|
|
53
|
+
sel.addRange(newRange);
|
|
54
|
+
} else {
|
|
55
|
+
// Wrap selection in <code>
|
|
56
|
+
var code = document.createElement("code");
|
|
57
|
+
try { range.surroundContents(code); } catch (e) {
|
|
58
|
+
var frag = range.extractContents();
|
|
59
|
+
code.appendChild(frag);
|
|
60
|
+
range.insertNode(code);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
document.execCommand(command, false, null);
|
|
65
|
+
}
|
|
66
|
+
// Trigger sync
|
|
67
|
+
rendered.dispatchEvent(new Event("input", { bubbles: true }));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function showFormatToolbar(rendered) {
|
|
71
|
+
closeFormatToolbar();
|
|
72
|
+
|
|
73
|
+
var sel = window.getSelection();
|
|
74
|
+
if (!sel || !sel.rangeCount || sel.isCollapsed) return;
|
|
75
|
+
if (!sel.toString().trim()) return;
|
|
76
|
+
|
|
77
|
+
var range = sel.getRangeAt(0);
|
|
78
|
+
// When dragging outside the note, commonAncestorContainer may be a parent
|
|
79
|
+
// of rendered. Clamp the range to the rendered element so the toolbar shows.
|
|
80
|
+
if (!rendered.contains(range.commonAncestorContainer)) {
|
|
81
|
+
try {
|
|
82
|
+
var clampedRange = range.cloneRange();
|
|
83
|
+
if (range.startContainer === rendered || rendered.contains(range.startContainer)) {
|
|
84
|
+
clampedRange.selectNodeContents(rendered);
|
|
85
|
+
clampedRange.setStart(range.startContainer, range.startOffset);
|
|
86
|
+
} else if (range.endContainer === rendered || rendered.contains(range.endContainer)) {
|
|
87
|
+
clampedRange.selectNodeContents(rendered);
|
|
88
|
+
clampedRange.setEnd(range.endContainer, range.endOffset);
|
|
89
|
+
} else {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
range = clampedRange;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
var toolbar = document.createElement("div");
|
|
99
|
+
toolbar.className = "sn-format-toolbar";
|
|
100
|
+
|
|
101
|
+
for (var i = 0; i < FORMAT_BUTTONS.length; i++) {
|
|
102
|
+
(function (cfg) {
|
|
103
|
+
var btn = document.createElement("button");
|
|
104
|
+
btn.className = "sn-fmt-btn " + cfg.cls;
|
|
105
|
+
btn.title = cfg.title;
|
|
106
|
+
btn.innerHTML = cfg.isIcon ? iconHtml(cfg.label) : cfg.label;
|
|
107
|
+
btn.addEventListener("mousedown", function (e) {
|
|
108
|
+
e.preventDefault();
|
|
109
|
+
e.stopPropagation();
|
|
110
|
+
applyFormat(cfg.command, rendered);
|
|
111
|
+
setTimeout(function () {
|
|
112
|
+
var s = window.getSelection();
|
|
113
|
+
if (s && !s.isCollapsed) {
|
|
114
|
+
showFormatToolbar(rendered);
|
|
115
|
+
} else {
|
|
116
|
+
closeFormatToolbar();
|
|
117
|
+
}
|
|
118
|
+
}, 0);
|
|
119
|
+
});
|
|
120
|
+
toolbar.appendChild(btn);
|
|
121
|
+
})(FORMAT_BUTTONS[i]);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
refreshIcons();
|
|
125
|
+
document.body.appendChild(toolbar);
|
|
126
|
+
formatToolbarEl = toolbar;
|
|
127
|
+
positionToolbarAtRange(toolbar, range);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function positionToolbarAtRange(toolbar, range) {
|
|
131
|
+
var rect = range.getBoundingClientRect();
|
|
132
|
+
var toolbarX = rect.left + rect.width / 2;
|
|
133
|
+
var toolbarY = rect.top - 4;
|
|
134
|
+
|
|
135
|
+
toolbar.style.left = toolbarX + "px";
|
|
136
|
+
toolbar.style.top = toolbarY + "px";
|
|
137
|
+
|
|
138
|
+
requestAnimationFrame(function () {
|
|
139
|
+
var tw = toolbar.offsetWidth;
|
|
140
|
+
var th = toolbar.offsetHeight;
|
|
141
|
+
var x = Math.max(8, Math.min(toolbarX - tw / 2, window.innerWidth - tw - 8));
|
|
142
|
+
var y = Math.max(8, toolbarY - th);
|
|
143
|
+
toolbar.style.left = x + "px";
|
|
144
|
+
toolbar.style.top = y + "px";
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
149
|
+
var noteEl = textarea.closest(".sticky-note");
|
|
150
|
+
var mdMode = false;
|
|
151
|
+
|
|
152
|
+
function saveRenderedMarkdown() {
|
|
153
|
+
var markdown = extractMarkdown(rendered);
|
|
154
|
+
textarea.value = markdown;
|
|
155
|
+
debouncedTextUpdate(noteId, markdown);
|
|
156
|
+
syncTitle(noteEl, markdown);
|
|
157
|
+
rendered.classList.toggle("is-empty", !markdown.trim());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function toggleChecklistItem(check) {
|
|
161
|
+
var checked = !check.classList.contains("checked");
|
|
162
|
+
check.classList.toggle("checked", checked);
|
|
163
|
+
check.textContent = checked ? "✓" : "☐";
|
|
164
|
+
check.setAttribute("aria-checked", checked ? "true" : "false");
|
|
165
|
+
saveRenderedMarkdown();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// MD button: toggle between contenteditable rendered view and raw textarea
|
|
169
|
+
mdBtn.addEventListener("click", function (e) {
|
|
170
|
+
e.stopPropagation();
|
|
171
|
+
mdMode = !mdMode;
|
|
172
|
+
if (mdMode) {
|
|
173
|
+
// Switch to raw markdown editing
|
|
174
|
+
var md = extractMarkdown(rendered);
|
|
175
|
+
textarea.value = md;
|
|
176
|
+
textarea.style.display = "";
|
|
177
|
+
rendered.style.display = "none";
|
|
178
|
+
mdBtn.classList.add("active");
|
|
179
|
+
textarea.focus();
|
|
180
|
+
} else {
|
|
181
|
+
// Switch back to contenteditable rendered view
|
|
182
|
+
var md = textarea.value;
|
|
183
|
+
debouncedTextUpdate(noteId, md);
|
|
184
|
+
syncTitle(noteEl, md);
|
|
185
|
+
if (md.trim()) {
|
|
186
|
+
rendered.innerHTML = renderMiniMarkdown(md);
|
|
187
|
+
enhanceClayLogLinks(rendered);
|
|
188
|
+
rendered.classList.remove("is-empty");
|
|
189
|
+
} else {
|
|
190
|
+
rendered.innerHTML = "";
|
|
191
|
+
rendered.classList.add("is-empty");
|
|
192
|
+
}
|
|
193
|
+
textarea.style.display = "none";
|
|
194
|
+
rendered.style.display = "";
|
|
195
|
+
mdBtn.classList.remove("active");
|
|
196
|
+
rendered.focus();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Sync contenteditable changes to textarea (data store) and save
|
|
201
|
+
rendered.addEventListener("input", function () {
|
|
202
|
+
saveRenderedMarkdown();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
rendered.addEventListener("click", function (e) {
|
|
206
|
+
var check = e.target.closest && e.target.closest(".sn-check");
|
|
207
|
+
if (!check || !rendered.contains(check)) return;
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
e.stopPropagation();
|
|
210
|
+
toggleChecklistItem(check);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// On blur, re-render to normalize HTML structure
|
|
214
|
+
rendered.addEventListener("blur", function (e) {
|
|
215
|
+
// Don't re-render if clicking format toolbar (it prevents default, but just in case)
|
|
216
|
+
if (e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest(".sn-format-toolbar")) return;
|
|
217
|
+
closeFormatToolbar();
|
|
218
|
+
var md = extractMarkdown(rendered);
|
|
219
|
+
textarea.value = md;
|
|
220
|
+
if (md.trim()) {
|
|
221
|
+
rendered.innerHTML = renderMiniMarkdown(md);
|
|
222
|
+
enhanceClayLogLinks(rendered);
|
|
223
|
+
rendered.classList.remove("is-empty");
|
|
224
|
+
} else {
|
|
225
|
+
rendered.innerHTML = "";
|
|
226
|
+
rendered.classList.add("is-empty");
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Show format toolbar when selecting text
|
|
231
|
+
rendered.addEventListener("mouseup", function (e) {
|
|
232
|
+
if (e.target.tagName === "A") return;
|
|
233
|
+
setTimeout(function () {
|
|
234
|
+
var sel = window.getSelection();
|
|
235
|
+
if (sel && !sel.isCollapsed && sel.toString().trim()) {
|
|
236
|
+
showFormatToolbar(rendered);
|
|
237
|
+
} else {
|
|
238
|
+
closeFormatToolbar();
|
|
239
|
+
}
|
|
240
|
+
}, 10);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Keyboard selection
|
|
244
|
+
rendered.addEventListener("keyup", function (e) {
|
|
245
|
+
if (e.shiftKey || e.key === "Shift") {
|
|
246
|
+
var sel = window.getSelection();
|
|
247
|
+
if (sel && !sel.isCollapsed) {
|
|
248
|
+
showFormatToolbar(rendered);
|
|
249
|
+
} else {
|
|
250
|
+
closeFormatToolbar();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Insert <br> on Enter instead of <div>
|
|
256
|
+
rendered.addEventListener("keydown", function (e) {
|
|
257
|
+
if (e.target.classList && e.target.classList.contains("sn-check") &&
|
|
258
|
+
(e.key === "Enter" || e.key === " ")) {
|
|
259
|
+
e.preventDefault();
|
|
260
|
+
toggleChecklistItem(e.target);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
264
|
+
e.preventDefault();
|
|
265
|
+
document.execCommand("insertLineBreak");
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Paste as plain text to avoid importing HTML formatting
|
|
270
|
+
rendered.addEventListener("paste", function (e) {
|
|
271
|
+
e.preventDefault();
|
|
272
|
+
var text = (e.clipboardData || window.clipboardData).getData("text/plain");
|
|
273
|
+
document.execCommand("insertText", false, text);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Prevent drag when clicking body
|
|
277
|
+
rendered.addEventListener("mousedown", function (e) {
|
|
278
|
+
e.stopPropagation();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Sync textarea edits when in MD mode
|
|
282
|
+
textarea.addEventListener("input", function () {
|
|
283
|
+
if (!mdMode) return;
|
|
284
|
+
debouncedTextUpdate(noteId, textarea.value);
|
|
285
|
+
syncTitle(noteEl, textarea.value);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
textarea.addEventListener("mousedown", function (e) {
|
|
289
|
+
e.stopPropagation();
|
|
290
|
+
});
|
|
291
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Leaf helpers shared by every sticky-note module.
|
|
2
|
+
//
|
|
3
|
+
// This module imports nothing from the other sticky-note modules, so the canvas,
|
|
4
|
+
// the card, and the editor can all depend on it without depending on each other.
|
|
5
|
+
// Keep it that way: anything added here must stay dependency-neutral.
|
|
6
|
+
|
|
7
|
+
import { getWs } from './ws-ref.js';
|
|
8
|
+
import { store } from './store.js';
|
|
9
|
+
import { getTitle } from './sticky-note-markdown.js';
|
|
10
|
+
|
|
11
|
+
export var NOTE_COLORS = ["purple", "green", "yellow", "blue", "pink", "orange"];
|
|
12
|
+
|
|
13
|
+
// The debounce timers live here because both the card and the editor schedule
|
|
14
|
+
// writes, and a note must never have two competing pending writes.
|
|
15
|
+
var updateTimers = {};
|
|
16
|
+
var textTimers = {};
|
|
17
|
+
|
|
18
|
+
// A note is closed when its lifecycle says so. Legacy notes carry no state and
|
|
19
|
+
// used `hidden` for the same meaning, so both are honoured.
|
|
20
|
+
export function isClosedNote(data) {
|
|
21
|
+
if (!data) return false;
|
|
22
|
+
if (data.state === "closed") return true;
|
|
23
|
+
if (data.state === "open") return false;
|
|
24
|
+
return data.hidden === true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// --- Canvas geometry ---
|
|
28
|
+
|
|
29
|
+
export function getContainerBounds() {
|
|
30
|
+
var c = document.getElementById("sticky-notes-container");
|
|
31
|
+
if (!c || c.clientWidth === 0 || c.clientHeight === 0) return null;
|
|
32
|
+
return { w: c.clientWidth, h: c.clientHeight };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function clampPos(x, y, noteW, noteH) {
|
|
36
|
+
var b = getContainerBounds();
|
|
37
|
+
if (!b) return { x: x, y: y };
|
|
38
|
+
return {
|
|
39
|
+
x: Math.max(0, Math.min(x, b.w - noteW)),
|
|
40
|
+
y: Math.max(0, Math.min(y, b.h - noteH)),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function clampSize(x, y, w, h) {
|
|
45
|
+
var b = getContainerBounds();
|
|
46
|
+
if (!b) return { w: w, h: h };
|
|
47
|
+
return {
|
|
48
|
+
w: Math.min(w, b.w - x),
|
|
49
|
+
h: Math.min(h, b.h - y),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
// --- WebSocket and debounced persistence ---
|
|
55
|
+
|
|
56
|
+
export function send(obj) {
|
|
57
|
+
var ws = getWs();
|
|
58
|
+
if (!ws || !store.get('connected')) return;
|
|
59
|
+
ws.send(JSON.stringify(obj));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function debouncedUpdate(id, changes, delay) {
|
|
63
|
+
clearTimeout(updateTimers[id]);
|
|
64
|
+
updateTimers[id] = setTimeout(function () {
|
|
65
|
+
changes.type = "note_update";
|
|
66
|
+
changes.id = id;
|
|
67
|
+
send(changes);
|
|
68
|
+
}, delay || 300);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function debouncedTextUpdate(id, text) {
|
|
72
|
+
clearTimeout(textTimers[id]);
|
|
73
|
+
textTimers[id] = setTimeout(function () {
|
|
74
|
+
send({ type: "note_update", id: id, text: text });
|
|
75
|
+
}, 500);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function syncTitle(noteEl, text) {
|
|
79
|
+
var spacer = noteEl.querySelector(".sticky-note-spacer");
|
|
80
|
+
if (spacer) spacer.textContent = getTitle(text) || "Untitled";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
// Drop any pending writes for a note this client no longer has.
|
|
85
|
+
export function clearNoteTimers(id) {
|
|
86
|
+
clearTimeout(updateTimers[id]);
|
|
87
|
+
clearTimeout(textTimers[id]);
|
|
88
|
+
delete updateTimers[id];
|
|
89
|
+
delete textTimers[id];
|
|
90
|
+
}
|