clay-server 5.2.0-beta.4 → 5.2.0-beta.6
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/account-auth-socket.js +1 -0
- package/lib/builtin-mates.js +17 -14
- package/lib/canonical-clay-record-authority.js +15 -0
- package/lib/canonical-clay-scheduling-authority.js +19 -0
- package/lib/claude-hook-installer.js +1 -239
- package/lib/claude-jsonl-watcher.js +1 -204
- package/lib/clay-mate-chat-receipts.js +26 -0
- package/lib/clay-mate-chat.js +87 -0
- package/lib/clay-mate-knowledge-input.js +26 -0
- package/lib/clay-mate-knowledge.js +95 -0
- package/lib/clay-mate-lifecycle.js +95 -0
- package/lib/clay-product-control-input.js +33 -0
- package/lib/clay-product-control-tools.js +51 -0
- package/lib/clay-product-control.js +237 -0
- package/lib/clay-project-lifecycle.js +154 -0
- package/lib/clay-project-record-input.js +44 -0
- package/lib/clay-project-records.js +83 -0
- package/lib/clay-scheduling-input.js +21 -0
- package/lib/clay-scheduling-receipts.js +26 -0
- package/lib/clay-scheduling.js +119 -0
- package/lib/clay-screen-controls.js +96 -0
- package/lib/clay-skill-install.js +1 -33
- package/lib/clay-workspace-controls.js +83 -0
- package/lib/clay-workspace-input.js +17 -0
- package/lib/clay-workspace-receipts.js +26 -0
- package/lib/cli-sessions.js +1 -300
- package/lib/codex-defaults.js +1 -28
- package/lib/context-usage.js +3 -2
- package/lib/daemon-project-access.js +11 -0
- package/lib/daemon-project-lifecycle-callbacks.js +15 -0
- package/lib/daemon-project-lifecycle.js +262 -0
- package/lib/daemon-projects.js +1 -1
- package/lib/daemon.js +78 -276
- package/lib/default-ai-runtime.js +4 -8
- package/lib/email-mcp-server.js +1 -1
- package/lib/issues-service.js +29 -15
- package/lib/kiro-defaults.js +1 -22
- package/lib/knowledge-db-import.js +5 -4
- package/lib/knowledge-db-service.js +9 -6
- package/lib/knowledge-document-index.js +2 -1
- package/lib/knowledge-product-operations.js +46 -0
- package/lib/mate-creation-mcp-server.js +2 -0
- package/lib/mate-knowledge-canonical-access.js +10 -0
- package/lib/mate-knowledge-service.js +7 -4
- package/lib/mate-lifecycle-service.js +94 -0
- package/lib/mate-message-delivery.js +43 -0
- package/lib/mate-ready-creation.js +9 -1
- package/lib/mate-schedule-input.js +29 -0
- package/lib/mate-schedule-lifecycle.js +151 -0
- package/lib/mate-scheduling.js +103 -0
- package/lib/mates.js +4 -3
- package/lib/mcp-proxy.js +2 -32
- package/lib/mcp-query-bridge.js +1 -1
- package/lib/mcp-source-adapters.js +1 -91
- package/lib/mcp-source-context.js +1 -42
- package/lib/mcp-source-discovery.js +1 -169
- package/lib/mcp-source-plugins.js +1 -68
- package/lib/mcp-source-restrictions.js +1 -47
- package/lib/mcp-tool-policy.js +1 -26
- package/lib/notes-checklist.js +50 -0
- package/lib/notes.js +49 -3
- package/lib/os-user-diagnostics.js +2 -4
- package/lib/os-users.js +24 -70
- package/lib/project-admin-overview.js +51 -0
- package/lib/project-ask-user.js +4 -2
- package/lib/project-connection.js +8 -7
- package/lib/project-delegated-follow-up.js +2 -1
- package/lib/project-directory-claim.js +65 -0
- package/lib/project-file-operations.js +61 -0
- package/lib/project-filesystem.js +13 -87
- package/lib/project-full-access.js +23 -2
- package/lib/project-http.js +4 -4
- package/lib/project-knowledge.js +32 -18
- package/lib/project-lifecycle-operations.js +98 -0
- package/lib/project-lifecycle-process-witness.js +32 -0
- package/lib/project-lifecycle-runtime.js +50 -0
- package/lib/project-linux-access.js +22 -0
- package/lib/project-logs-canonical-clay.js +27 -0
- package/lib/project-logs-clay-query.js +16 -0
- package/lib/project-logs-schema.js +4 -2
- package/lib/project-logs-service.js +8 -7
- package/lib/project-logs.js +1 -0
- package/lib/project-loop.js +1 -0
- package/lib/project-mate-chat-control.js +55 -0
- package/lib/project-mate-creation-proposal.js +18 -3
- package/lib/project-mcp.js +2 -2
- package/lib/project-models.js +14 -31
- package/lib/project-notifications.js +74 -16
- package/lib/project-ownership.js +77 -0
- package/lib/project-pair-result-delivery.js +14 -3
- package/lib/project-pair-usage.js +12 -13
- package/lib/project-pending-message-queue.js +4 -1
- package/lib/project-product-control.js +87 -0
- package/lib/project-product-session-queue.js +46 -0
- package/lib/project-push-access.js +28 -0
- package/lib/project-scheduled-message-queue.js +30 -0
- package/lib/project-scheduled-messages.js +63 -64
- package/lib/project-scheduled-task-control.js +83 -0
- package/lib/project-scheduled-tasks.js +13 -2
- package/lib/project-scheduling-access.js +10 -0
- package/lib/project-session-actions.js +65 -0
- package/lib/project-session-delete.js +8 -1
- package/lib/project-session-github.js +47 -12
- package/lib/project-session-history-actions.js +167 -0
- package/lib/project-session-pair.js +1 -1
- package/lib/project-session-runtime-actions.js +64 -0
- package/lib/project-session-spawn.js +2 -3
- package/lib/project-session-vendor.js +55 -0
- package/lib/project-sessions.js +82 -267
- package/lib/project-user-message.js +31 -1
- package/lib/project-vendor-login.js +6 -36
- package/lib/project-workspace-operations.js +126 -0
- package/lib/project-workspace-query.js +9 -2
- package/lib/project.js +95 -3
- package/lib/public/app.js +1 -0
- package/lib/public/css/clay-tool-activity.css +51 -0
- package/lib/public/css/home-mate-creation.css +5 -0
- package/lib/public/css/messages.css +6 -2
- package/lib/public/css/overlays.css +4 -2
- package/lib/public/css/project-admin-overview.css +13 -0
- package/lib/public/css/session-folders.css +11 -0
- package/lib/public/css/session-github.css +3 -0
- package/lib/public/css/sticky-notes.css +61 -1
- package/lib/public/index.html +1 -1
- package/lib/public/modules/admin-settings-projects.js +6 -2
- package/lib/public/modules/app-connection.js +8 -1
- package/lib/public/modules/app-favicon.js +11 -2
- package/lib/public/modules/app-header.js +4 -2
- package/lib/public/modules/app-loop-ui.js +2 -0
- package/lib/public/modules/app-message-router.js +1 -1
- package/lib/public/modules/app-messages.js +54 -8
- package/lib/public/modules/app-notifications.js +1 -2
- package/lib/public/modules/app-panels.js +61 -123
- package/lib/public/modules/app-projects.js +5 -0
- package/lib/public/modules/app-rate-limit.js +7 -27
- package/lib/public/modules/chat-render-runtime.js +3 -0
- package/lib/public/modules/clay-owner-navigation.js +27 -0
- package/lib/public/modules/clay-tool-activity.js +140 -0
- package/lib/public/modules/clay-tool-presentation.js +338 -0
- package/lib/public/modules/command-palette.js +1 -1
- package/lib/public/modules/default-ai.js +11 -3
- package/lib/public/modules/home-debate-planning.js +5 -5
- package/lib/public/modules/home-mate-chat.js +8 -6
- package/lib/public/modules/home-mate-creation.js +73 -1
- package/lib/public/modules/mcp-connection-view.js +1 -1
- package/lib/public/modules/permission-control.js +5 -4
- package/lib/public/modules/personal-settings-dialog.js +7 -1
- package/lib/public/modules/project-access-recovery.js +17 -0
- package/lib/public/modules/project-admin-overview.js +21 -0
- package/lib/public/modules/scheduler.js +2 -0
- package/lib/public/modules/session-create-form.js +10 -10
- package/lib/public/modules/session-folder-layout.js +1 -1
- package/lib/public/modules/session-folder-view-menu.js +9 -0
- package/lib/public/modules/session-github.js +65 -8
- package/lib/public/modules/settings-defaults.js +27 -28
- package/lib/public/modules/sidebar-session-folders-ui.js +1 -1
- package/lib/public/modules/sticky-note-markdown.js +72 -6
- package/lib/public/modules/sticky-notes-actions.js +30 -0
- package/lib/public/modules/sticky-notes-browser.js +9 -2
- package/lib/public/modules/sticky-notes-card.js +23 -7
- package/lib/public/modules/sticky-notes-checklist.js +71 -0
- package/lib/public/modules/sticky-notes-editor.js +108 -44
- package/lib/public/modules/sticky-notes-shared.js +50 -2
- package/lib/public/modules/sticky-notes-state.js +66 -0
- package/lib/public/modules/sticky-notes.js +34 -51
- package/lib/public/modules/thinking-lifecycle.js +33 -3
- package/lib/public/modules/thinking-view.js +23 -1
- package/lib/public/modules/tools.js +36 -7
- package/lib/public/modules/tui-grab.js +1 -2
- package/lib/public/modules/user-settings-appearance.js +10 -3
- package/lib/public/modules/user-settings-editors.js +1 -1
- package/lib/public/modules/user-settings-layout.js +11 -2
- package/lib/public/modules/user-settings-preferences.js +6 -2
- package/lib/public/modules/user-settings-profile.js +12 -2
- package/lib/public/modules/user-settings.js +3 -2
- package/lib/public/modules/vendor-login-browser.js +5 -3
- package/lib/public/modules/vendor-login.js +2 -2
- package/lib/public/modules/vendor-priority.js +15 -1
- package/lib/public/muse-avatar.svg +4 -0
- package/lib/public/style.css +2 -0
- package/lib/safe-bash-commands.js +3 -45
- package/lib/scheduled-task-records.js +3 -1
- package/lib/scheduler.js +2 -0
- package/lib/sdk-bridge.js +334 -403
- package/lib/sdk-foreground-admission.js +16 -0
- package/lib/sdk-mcp-descriptors.js +2 -37
- package/lib/sdk-message-processor.js +52 -17
- package/lib/sdk-quota-snapshot.js +1 -1
- package/lib/sdk-response-recovery.js +1 -0
- package/lib/sdk-worker.js +2 -677
- package/lib/server-admin.js +63 -16
- package/lib/server-dm.js +1 -1
- package/lib/server-global-ws.js +10 -20
- package/lib/server-home-chat-events.js +7 -3
- package/lib/server-home-chat.js +4 -16
- package/lib/server-home-mate-creation.js +1 -0
- package/lib/server-home-mcp-consent.js +3 -3
- package/lib/server-mates.js +25 -67
- package/lib/server-owner-ui.js +77 -0
- package/lib/server-path-access.js +38 -0
- package/lib/server-project-removal.js +22 -0
- package/lib/server.js +100 -30
- package/lib/session-config-transition.js +45 -0
- package/lib/session-folders.js +7 -2
- package/lib/session-fork-history.js +21 -0
- package/lib/session-github.js +9 -2
- package/lib/session-hygiene.js +3 -35
- package/lib/session-permission-mode.js +4 -3
- package/lib/session-skill-scope.js +11 -0
- package/lib/session-visibility.js +5 -2
- package/lib/sessions.js +27 -89
- package/lib/terminal-manager.js +52 -0
- package/lib/tool-llm.js +1 -1
- package/lib/tui-transcript-index.js +1 -132
- package/lib/users-permissions.js +2 -8
- package/lib/users.js +4 -0
- package/lib/vendor-login-browser.js +1 -11
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +8 -2
- package/lib/yoke/acp-agent-profiles.js +5 -3
- package/lib/yoke/adapters/antigravity.js +3 -14
- package/lib/yoke/adapters/claude-query-contract.js +9 -0
- package/lib/yoke/adapters/claude.js +54 -50
- package/lib/yoke/adapters/codex-query-contract.js +24 -0
- package/lib/yoke/adapters/codex.js +53 -111
- package/lib/yoke/adapters/kiro.js +3 -1
- package/lib/yoke/adapters/muse-query-contract.js +7 -0
- package/lib/yoke/adapters/muse.js +122 -0
- package/lib/yoke/auth-events.js +17 -0
- package/lib/yoke/claude-hook-installer.js +267 -0
- package/lib/yoke/claude-jsonl-watcher.js +204 -0
- package/lib/yoke/claude-maintenance.js +55 -0
- package/lib/yoke/claude-permission-patterns.js +53 -0
- package/lib/yoke/claude-response-recovery.js +155 -0
- package/lib/yoke/claude-transcript-hygiene.js +34 -0
- package/lib/yoke/claude-worker-startup.js +28 -0
- package/lib/yoke/clay-skill-install.js +33 -0
- package/lib/yoke/cli-sessions.js +300 -0
- package/lib/yoke/codex-app-server.js +7 -12
- package/lib/yoke/codex-idle-recovery.js +81 -1
- package/lib/yoke/codex-recovery-admission.js +164 -0
- package/lib/yoke/configuration.js +13 -0
- package/lib/yoke/discovery.js +70 -0
- package/lib/yoke/errors.js +8 -0
- package/lib/yoke/host-environment.js +7 -0
- package/lib/yoke/index.js +9 -51
- package/lib/yoke/interface.js +116 -0
- package/lib/yoke/kiro-acp-server.js +5 -5
- package/lib/yoke/legacy-sessions.js +89 -0
- package/lib/yoke/login-protocol.js +25 -0
- package/lib/yoke/maintenance.js +12 -0
- package/lib/yoke/mcp-proxy.js +32 -0
- package/lib/yoke/mcp-source-adapters.js +91 -0
- package/lib/yoke/mcp-source-context.js +42 -0
- package/lib/yoke/mcp-source-discovery.js +169 -0
- package/lib/yoke/mcp-source-plugins.js +68 -0
- package/lib/yoke/mcp-source-restrictions.js +47 -0
- package/lib/yoke/mcp-tool-policy-normalize.js +28 -0
- package/lib/yoke/model-preferences.js +6 -0
- package/lib/yoke/muse-artifacts.js +36 -0
- package/lib/yoke/muse-controls.js +50 -0
- package/lib/yoke/muse-input.js +64 -0
- package/lib/yoke/muse-items.js +157 -0
- package/lib/yoke/muse-policy.js +52 -0
- package/lib/yoke/muse-query.js +428 -0
- package/lib/yoke/muse-runtime.js +211 -0
- package/lib/yoke/muse-services.js +79 -0
- package/lib/yoke/muse-skills.js +79 -0
- package/lib/yoke/muse-turns.js +71 -0
- package/lib/yoke/options/acp.js +2 -0
- package/lib/yoke/options/antigravity.js +2 -0
- package/lib/yoke/options/claude.js +21 -0
- package/lib/yoke/options/codex-defaults.js +29 -0
- package/lib/yoke/options/codex-settings.js +21 -0
- package/lib/yoke/options/codex.js +6 -0
- package/lib/yoke/options/kiro-defaults.js +22 -0
- package/lib/yoke/options/kiro.js +5 -0
- package/lib/yoke/options/muse.js +5 -0
- package/lib/yoke/provider-contract.js +5 -0
- package/lib/yoke/providers/antigravity-executable.js +8 -0
- package/lib/yoke/providers/claude-executable.js +40 -0
- package/lib/yoke/providers/claude-model-descriptions.js +14 -0
- package/lib/yoke/providers/claude-process.js +97 -0
- package/lib/yoke/providers/claude-provision.js +75 -0
- package/lib/yoke/providers/codex-errors.js +8 -0
- package/lib/yoke/providers/muse-executable.js +17 -0
- package/lib/yoke/provisioning.js +6 -0
- package/lib/yoke/query-options.js +10 -0
- package/lib/yoke/response-recovery.js +41 -0
- package/lib/yoke/sdk-mcp-descriptors.js +37 -0
- package/lib/yoke/sdk-server-factory.js +7 -0
- package/lib/yoke/session-history.js +8 -0
- package/lib/yoke/session-tool-transport.js +34 -13
- package/lib/yoke/settings.js +23 -0
- package/lib/yoke/source-locations.js +4 -0
- package/lib/yoke/title-query.js +18 -7
- package/lib/yoke/tui-transcript-index.js +132 -0
- package/lib/yoke/usage.js +48 -0
- package/lib/yoke/user-input.js +13 -0
- package/lib/yoke/vendor-registry.js +81 -1
- package/package.json +3 -2
|
@@ -11,7 +11,8 @@ import { store } from './store.js';
|
|
|
11
11
|
import { iconHtml, refreshIcons } from './icons.js';
|
|
12
12
|
import { renderMiniMarkdown, getTitle } from './sticky-note-markdown.js';
|
|
13
13
|
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
14
|
-
import {
|
|
14
|
+
import { closeHumanNote } from './sticky-notes-actions.js';
|
|
15
|
+
import { send, getNoteUiState } from './sticky-notes-shared.js';
|
|
15
16
|
import { listNoteData, focusNoteOnCanvas, hideNotes, showNotes } from './sticky-notes.js';
|
|
16
17
|
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
17
18
|
|
|
@@ -215,7 +216,7 @@ function buildCard(data) {
|
|
|
215
216
|
action.setAttribute("aria-label", "Close note: " + title.textContent);
|
|
216
217
|
action.addEventListener("click", function (e) {
|
|
217
218
|
e.stopPropagation();
|
|
218
|
-
|
|
219
|
+
closeHumanNote(data.id, data);
|
|
219
220
|
});
|
|
220
221
|
}
|
|
221
222
|
header.appendChild(action);
|
|
@@ -229,6 +230,11 @@ function buildCard(data) {
|
|
|
229
230
|
enhanceClayLogLinks(body);
|
|
230
231
|
}
|
|
231
232
|
card.appendChild(body);
|
|
233
|
+
var ui = getNoteUiState(data.id, data);
|
|
234
|
+
if (ui.error || ui.pending.length) {
|
|
235
|
+
var status = document.createElement('p'); status.className = 'settings-status'; status.setAttribute('role', 'status');
|
|
236
|
+
status.textContent = ui.error || 'Saving…'; card.appendChild(status);
|
|
237
|
+
}
|
|
232
238
|
|
|
233
239
|
if (closed) {
|
|
234
240
|
var deleteAction = document.createElement("button");
|
|
@@ -427,6 +433,7 @@ export function initNotesBrowser() {
|
|
|
427
433
|
closeNotesBrowser();
|
|
428
434
|
});
|
|
429
435
|
store.subscribe(function (state, previous) {
|
|
436
|
+
if (state.notesBrowserOpen && state.stickyNoteUiState !== previous.stickyNoteUiState) renderNotesBrowser();
|
|
430
437
|
if (state.connected !== previous.connected || state.myUserId !== previous.myUserId ||
|
|
431
438
|
state.isMultiUserMode !== previous.isMultiUserMode) closeDeleteDialog(true);
|
|
432
439
|
if (state.currentSlug === previous.currentSlug) return;
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
clampSize,
|
|
18
18
|
debouncedUpdate,
|
|
19
19
|
} from './sticky-notes-shared.js';
|
|
20
|
+
import { closeHumanNote } from './sticky-notes-actions.js';
|
|
20
21
|
import { setupTextEdit } from './sticky-notes-editor.js';
|
|
21
22
|
|
|
22
23
|
var colorPickerEl = null;
|
|
@@ -74,7 +75,7 @@ export function renderNote(data) {
|
|
|
74
75
|
addBtn.innerHTML = iconHtml("plus");
|
|
75
76
|
addBtn.addEventListener("click", function (e) {
|
|
76
77
|
e.stopPropagation();
|
|
77
|
-
|
|
78
|
+
send({ type: "note_create", x: 60, y: 60, color: "purple", opacity: 0.64 });
|
|
78
79
|
});
|
|
79
80
|
header.appendChild(addBtn);
|
|
80
81
|
|
|
@@ -90,6 +91,21 @@ export function renderNote(data) {
|
|
|
90
91
|
mdBtn.innerHTML = "<span class='sn-md-label'>MD</span>";
|
|
91
92
|
header.appendChild(mdBtn);
|
|
92
93
|
|
|
94
|
+
var checklistBtn = document.createElement("button");
|
|
95
|
+
checklistBtn.className = "sticky-note-btn sticky-note-checklist-btn";
|
|
96
|
+
checklistBtn.title = data.checklistMode ? "Show note" : "Show checklist";
|
|
97
|
+
checklistBtn.setAttribute("aria-label", checklistBtn.title);
|
|
98
|
+
checklistBtn.setAttribute("aria-pressed", data.checklistMode === true ? "true" : "false");
|
|
99
|
+
checklistBtn.innerHTML = iconHtml("list-check");
|
|
100
|
+
if (data.checklistMode) checklistBtn.classList.add("active");
|
|
101
|
+
header.appendChild(checklistBtn);
|
|
102
|
+
|
|
103
|
+
var saveStatus = document.createElement("span");
|
|
104
|
+
saveStatus.className = "sticky-note-save-status";
|
|
105
|
+
saveStatus.setAttribute("role", "status");
|
|
106
|
+
saveStatus.setAttribute("aria-live", "polite");
|
|
107
|
+
header.appendChild(saveStatus);
|
|
108
|
+
|
|
93
109
|
var opacityWrap = document.createElement("div");
|
|
94
110
|
opacityWrap.className = "sticky-note-opacity";
|
|
95
111
|
var opacitySlider = document.createElement("input");
|
|
@@ -139,11 +155,12 @@ export function renderNote(data) {
|
|
|
139
155
|
rendered.contentEditable = "true";
|
|
140
156
|
rendered.spellcheck = true;
|
|
141
157
|
if (data.text && data.text.trim()) {
|
|
142
|
-
rendered.innerHTML = renderMiniMarkdown(data.text);
|
|
158
|
+
rendered.innerHTML = renderMiniMarkdown(data.text, { checklistMode: data.checklistMode === true, checklistState: data.checklistState || [] });
|
|
143
159
|
enhanceClayLogLinks(rendered);
|
|
144
160
|
} else {
|
|
145
161
|
rendered.classList.add("is-empty");
|
|
146
162
|
}
|
|
163
|
+
rendered.contentEditable = data.checklistMode === true ? "false" : "true";
|
|
147
164
|
body.appendChild(rendered);
|
|
148
165
|
|
|
149
166
|
el.appendChild(body);
|
|
@@ -156,7 +173,7 @@ export function renderNote(data) {
|
|
|
156
173
|
// --- Event handlers ---
|
|
157
174
|
setupDrag(el, spacer, data.id);
|
|
158
175
|
setupResize(el, resizeHandle, data.id);
|
|
159
|
-
setupTextEdit(textarea, rendered, data.id, mdBtn);
|
|
176
|
+
setupTextEdit(textarea, rendered, data.id, mdBtn, checklistBtn, data);
|
|
160
177
|
setupColorPicker(colorBtn, el, data.id);
|
|
161
178
|
setupMinimize(minBtn, el, data.id);
|
|
162
179
|
setupClose(closeBtn, el, data.id);
|
|
@@ -336,14 +353,13 @@ function setupMinimize(btn, noteEl, noteId) {
|
|
|
336
353
|
|
|
337
354
|
// --- Close note ---
|
|
338
355
|
//
|
|
339
|
-
//
|
|
340
|
-
//
|
|
356
|
+
// The human close transaction retains nonempty notes in Closed and removes
|
|
357
|
+
// truly empty notes after checking the canonical source and visible draft.
|
|
341
358
|
|
|
342
359
|
function setupClose(btn, noteEl, noteId) {
|
|
343
360
|
btn.addEventListener("click", function (e) {
|
|
344
361
|
e.stopPropagation();
|
|
345
|
-
|
|
346
|
-
send({ type: "note_close", id: noteId });
|
|
362
|
+
closeHumanNote(noteId);
|
|
347
363
|
});
|
|
348
364
|
}
|
|
349
365
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Plain task controls use Markdown only as the internal persistence format.
|
|
2
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
3
|
+
import { getChecklistItems } from './sticky-note-markdown.js';
|
|
4
|
+
import { getNoteUiState, setNoteUiState } from './sticky-notes-shared.js';
|
|
5
|
+
import { mutateChecklist } from './sticky-notes-state.js';
|
|
6
|
+
|
|
7
|
+
export function checklistDraftText(ui) {
|
|
8
|
+
var fields = ui.checklistInputs || {};
|
|
9
|
+
var lines = (ui.text || '').split('\n');
|
|
10
|
+
if (fields.title !== undefined) lines[0] = fields.title;
|
|
11
|
+
getChecklistItems(ui.text).forEach(function (item) {
|
|
12
|
+
if (fields[item.key] !== undefined) {
|
|
13
|
+
var marker = item.text.match(/^\s*(?:[-*+]\s+)?\[([ xX])\]/);
|
|
14
|
+
var checked = ui.checklistState[item.key];
|
|
15
|
+
if (checked === undefined) checked = marker && marker[1].toLowerCase() === 'x';
|
|
16
|
+
lines[item.index + 1] = '- [' + (checked ? 'x' : ' ') + '] ' + fields[item.key];
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
if (fields.entry && fields.entry.trim()) {
|
|
20
|
+
if (!lines[0].trim()) lines[0] = 'Checklist';
|
|
21
|
+
lines.push('- [ ] ' + fields.entry.trim());
|
|
22
|
+
}
|
|
23
|
+
return lines.join('\n');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function renderTaskControls(root, id) {
|
|
27
|
+
var ui = getNoteUiState(id);
|
|
28
|
+
var fields = ui.checklistInputs || {};
|
|
29
|
+
var busy = ui.pending.length > 0;
|
|
30
|
+
root.replaceChildren();
|
|
31
|
+
var form = document.createElement('form'); form.className = 'sn-task-form';
|
|
32
|
+
function input(label, value, key) {
|
|
33
|
+
var control = document.createElement('input'); control.type = 'text'; control.value = fields[key] !== undefined ? fields[key] : value;
|
|
34
|
+
control.setAttribute('aria-label', label); control.placeholder = label; control.maxLength = 400;
|
|
35
|
+
control.disabled = busy;
|
|
36
|
+
control.oninput = function () {
|
|
37
|
+
var current = getNoteUiState(id);
|
|
38
|
+
var draft = Object.assign({}, current.checklistInputs || {}); draft[key] = control.value;
|
|
39
|
+
setNoteUiState(id, { checklistInputs: draft });
|
|
40
|
+
};
|
|
41
|
+
return control;
|
|
42
|
+
}
|
|
43
|
+
var title = input('Checklist title', (ui.text || '').split('\n')[0], 'title'); title.className = 'sn-task-title'; form.appendChild(title);
|
|
44
|
+
getChecklistItems(ui.text).forEach(function (item, position) {
|
|
45
|
+
var row = document.createElement('div'); row.className = 'sn-task-row';
|
|
46
|
+
var marker = item.text.match(/^\s*(?:[-*+]\s+)?\[([ xX])\]\s*(.*)$/);
|
|
47
|
+
var check = document.createElement('button'); check.type = 'button'; check.className = 'sn-check';
|
|
48
|
+
var checked = ui.checklistState[item.key];
|
|
49
|
+
if (checked === undefined) checked = marker && marker[1].toLowerCase() === 'x';
|
|
50
|
+
check.classList.toggle('checked', !!checked); check.textContent = checked ? '✓' : '☐';
|
|
51
|
+
check.setAttribute('role', 'checkbox'); check.setAttribute('aria-checked', checked ? 'true' : 'false'); check.setAttribute('aria-label', 'Complete task ' + (position + 1));
|
|
52
|
+
check.dataset.checklistIndex = item.index; check.dataset.checklistKey = encodeURIComponent(item.key);
|
|
53
|
+
check.disabled = busy;
|
|
54
|
+
var label = input('Task ' + (position + 1), marker ? marker[2] : item.text, item.key);
|
|
55
|
+
var remove = document.createElement('button'); remove.type = 'button'; remove.className = 'sn-task-remove'; remove.innerHTML = iconHtml('x'); remove.setAttribute('aria-label', 'Remove task ' + (position + 1)); remove.disabled = busy;
|
|
56
|
+
remove.onclick = function () {
|
|
57
|
+
var current = getNoteUiState(id); var lines = checklistDraftText(current).split('\n'); lines.splice(item.index + 1, 1);
|
|
58
|
+
save(lines.join('\n'));
|
|
59
|
+
};
|
|
60
|
+
row.append(check, label, remove); form.appendChild(row);
|
|
61
|
+
});
|
|
62
|
+
var entry = input('New task', '', 'entry'); entry.className = 'sn-task-entry'; form.appendChild(entry);
|
|
63
|
+
var saveButton = document.createElement('button'); saveButton.type = 'submit'; saveButton.className = 'sn-task-save'; saveButton.textContent = 'Save tasks'; saveButton.disabled = busy; form.appendChild(saveButton);
|
|
64
|
+
function save(text) {
|
|
65
|
+
var current = getNoteUiState(id);
|
|
66
|
+
if (current.pending.length) return;
|
|
67
|
+
mutateChecklist(id, { checklistEdit: true, checklistSource: current.canonical.text, checklistVersion: current.canonical.updatedAt, text: text });
|
|
68
|
+
}
|
|
69
|
+
form.onsubmit = function (event) { event.preventDefault(); save(checklistDraftText(getNoteUiState(id))); };
|
|
70
|
+
root.appendChild(form); refreshIcons(root);
|
|
71
|
+
}
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
import { refreshIcons, iconHtml } from './icons.js';
|
|
9
9
|
import { extractMarkdown, renderMiniMarkdown } from './sticky-note-markdown.js';
|
|
10
10
|
import { enhanceClayLogLinks } from './clay-log-links.js';
|
|
11
|
-
import { debouncedTextUpdate, syncTitle } from './sticky-notes-shared.js';
|
|
11
|
+
import { debouncedTextUpdate, syncTitle, getNoteUiState, setNoteUiState, subscribeNoteUiState } from './sticky-notes-shared.js';
|
|
12
|
+
|
|
13
|
+
import { renderTaskControls } from './sticky-notes-checklist.js';
|
|
14
|
+
import { requestChecklistMode } from './sticky-notes-actions.js';
|
|
15
|
+
import { mutateChecklist } from './sticky-notes-state.js';
|
|
12
16
|
|
|
13
17
|
var formatToolbarEl = null;
|
|
14
18
|
|
|
@@ -145,51 +149,112 @@ function positionToolbarAtRange(toolbar, range) {
|
|
|
145
149
|
});
|
|
146
150
|
}
|
|
147
151
|
|
|
148
|
-
export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
152
|
+
export function setupTextEdit(textarea, rendered, noteId, mdBtn, checklistBtn, noteData) {
|
|
149
153
|
var noteEl = textarea.closest(".sticky-note");
|
|
150
154
|
var mdMode = false;
|
|
155
|
+
var disposed = false;
|
|
156
|
+
var ui = getNoteUiState(noteId, noteData);
|
|
157
|
+
var checklistMode = ui.checklistMode === true;
|
|
158
|
+
var checklistState = ui.checklistState || {};
|
|
159
|
+
var renderedDirty = false;
|
|
160
|
+
var sourceText = ui.text !== undefined ? ui.text : textarea.value;
|
|
161
|
+
textarea.value = sourceText;
|
|
162
|
+
|
|
163
|
+
function renderChecklist() {
|
|
164
|
+
var active = document.activeElement;
|
|
165
|
+
var focusKey = active && rendered.contains(active) && active.dataset ? active.dataset.checklistKey : undefined;
|
|
166
|
+
if (checklistMode) renderTaskControls(rendered, noteId);
|
|
167
|
+
else rendered.innerHTML = renderMiniMarkdown(sourceText, { checklistMode: checklistMode, checklistState: checklistState });
|
|
168
|
+
if (sourceText.trim()) enhanceClayLogLinks(rendered);
|
|
169
|
+
rendered.classList.toggle("is-empty", !checklistMode && !sourceText.trim());
|
|
170
|
+
rendered.contentEditable = checklistMode || getNoteUiState(noteId).pending.length ? "false" : "true";
|
|
171
|
+
if (focusKey !== undefined) {
|
|
172
|
+
var checks = rendered.querySelectorAll("[data-checklist-key]");
|
|
173
|
+
for (var i = 0; i < checks.length; i++) {
|
|
174
|
+
if (checks[i].dataset.checklistKey === focusKey) { checks[i].focus({ preventScroll: true }); break; }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
151
178
|
|
|
152
179
|
function saveRenderedMarkdown() {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
180
|
+
if (checklistMode) return;
|
|
181
|
+
renderedDirty = true;
|
|
182
|
+
sourceText = extractMarkdown(rendered);
|
|
183
|
+
textarea.value = sourceText;
|
|
184
|
+
setNoteUiState(noteId, { text: sourceText, draft: true });
|
|
185
|
+
debouncedTextUpdate(noteId, sourceText);
|
|
186
|
+
syncTitle(noteEl, sourceText);
|
|
187
|
+
rendered.classList.toggle("is-empty", !checklistMode && !sourceText.trim());
|
|
158
188
|
}
|
|
159
189
|
|
|
160
190
|
function toggleChecklistItem(check) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
191
|
+
if (checklistMode && check.dataset.checklistIndex !== undefined) {
|
|
192
|
+
var index = parseInt(check.dataset.checklistIndex, 10);
|
|
193
|
+
var checked = !check.classList.contains("checked");
|
|
194
|
+
mutateChecklist(noteId, { checklistIndex: index, checklistKey: check.dataset.checklistKey || "", checklistSource: sourceText, checklistChecked: checked });
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
var normalChecked = !check.classList.contains("checked");
|
|
198
|
+
check.classList.toggle("checked", normalChecked);
|
|
199
|
+
check.textContent = normalChecked ? "✓" : "☐";
|
|
200
|
+
check.setAttribute("aria-checked", normalChecked ? "true" : "false");
|
|
165
201
|
saveRenderedMarkdown();
|
|
166
202
|
}
|
|
167
203
|
|
|
168
|
-
|
|
204
|
+
var unsubscribeUiState = subscribeNoteUiState(function (all) {
|
|
205
|
+
var next = all[noteId];
|
|
206
|
+
if (!next) return;
|
|
207
|
+
var changed = next.checklistMode !== checklistMode ||
|
|
208
|
+
(next.text !== undefined && next.text !== sourceText) ||
|
|
209
|
+
next.pending.length !== ui.pending.length ||
|
|
210
|
+
next.checklistInputs !== ui.checklistInputs && !next.checklistInputs ||
|
|
211
|
+
JSON.stringify(next.checklistState || {}) !== JSON.stringify(checklistState || {});
|
|
212
|
+
ui = next;
|
|
213
|
+
checklistMode = next.checklistMode === true;
|
|
214
|
+
checklistState = next.checklistState || {};
|
|
215
|
+
if (next.text !== undefined && next.text !== sourceText) {
|
|
216
|
+
sourceText = next.text;
|
|
217
|
+
textarea.value = sourceText;
|
|
218
|
+
}
|
|
219
|
+
mdBtn.hidden = checklistMode;
|
|
220
|
+
mdBtn.disabled = checklistMode;
|
|
221
|
+
if (checklistMode && mdMode) {
|
|
222
|
+
mdMode = false; textarea.style.display = 'none'; rendered.style.display = ''; mdBtn.classList.remove('active');
|
|
223
|
+
}
|
|
224
|
+
checklistBtn.classList.toggle("active", checklistMode);
|
|
225
|
+
checklistBtn.title = checklistMode ? "Show note" : "Show checklist";
|
|
226
|
+
checklistBtn.setAttribute("aria-label", checklistBtn.title);
|
|
227
|
+
checklistBtn.setAttribute("aria-pressed", checklistMode ? "true" : "false");
|
|
228
|
+
var status = noteEl.querySelector(".sticky-note-save-status");
|
|
229
|
+
if (status) status.textContent = next.error || (next.pending && next.pending.length ? "Saving…" : "");
|
|
230
|
+
if (changed && !mdMode) { renderChecklist(); syncTitle(noteEl, sourceText); }
|
|
231
|
+
});
|
|
232
|
+
noteEl._stickyNoteDispose = function () {
|
|
233
|
+
disposed = true;
|
|
234
|
+
closeFormatToolbar();
|
|
235
|
+
unsubscribeUiState();
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
checklistBtn.addEventListener("click", function (e) {
|
|
239
|
+
e.stopPropagation();
|
|
240
|
+
requestChecklistMode(noteId);
|
|
241
|
+
});
|
|
242
|
+
|
|
169
243
|
mdBtn.addEventListener("click", function (e) {
|
|
170
244
|
e.stopPropagation();
|
|
245
|
+
if (checklistMode) return;
|
|
171
246
|
mdMode = !mdMode;
|
|
172
247
|
if (mdMode) {
|
|
173
|
-
|
|
174
|
-
var md = extractMarkdown(rendered);
|
|
175
|
-
textarea.value = md;
|
|
248
|
+
textarea.value = sourceText;
|
|
176
249
|
textarea.style.display = "";
|
|
177
250
|
rendered.style.display = "none";
|
|
178
251
|
mdBtn.classList.add("active");
|
|
179
252
|
textarea.focus();
|
|
180
253
|
} else {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
-
}
|
|
254
|
+
sourceText = textarea.value;
|
|
255
|
+
if (getNoteUiState(noteId).draft) debouncedTextUpdate(noteId, sourceText);
|
|
256
|
+
syncTitle(noteEl, sourceText);
|
|
257
|
+
renderChecklist();
|
|
193
258
|
textarea.style.display = "none";
|
|
194
259
|
rendered.style.display = "";
|
|
195
260
|
mdBtn.classList.remove("active");
|
|
@@ -197,11 +262,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
197
262
|
}
|
|
198
263
|
});
|
|
199
264
|
|
|
200
|
-
|
|
201
|
-
rendered.addEventListener("input", function () {
|
|
202
|
-
saveRenderedMarkdown();
|
|
203
|
-
});
|
|
204
|
-
|
|
265
|
+
rendered.addEventListener("input", saveRenderedMarkdown);
|
|
205
266
|
rendered.addEventListener("click", function (e) {
|
|
206
267
|
var check = e.target.closest && e.target.closest(".sn-check");
|
|
207
268
|
if (!check || !rendered.contains(check)) return;
|
|
@@ -210,27 +271,23 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
210
271
|
toggleChecklistItem(check);
|
|
211
272
|
});
|
|
212
273
|
|
|
213
|
-
// On blur, re-render to normalize HTML structure
|
|
214
274
|
rendered.addEventListener("blur", function (e) {
|
|
215
|
-
|
|
275
|
+
if (checklistMode || !renderedDirty) return;
|
|
276
|
+
renderedDirty = false;
|
|
216
277
|
if (e.relatedTarget && e.relatedTarget.closest && e.relatedTarget.closest(".sn-format-toolbar")) return;
|
|
217
278
|
closeFormatToolbar();
|
|
218
279
|
var md = extractMarkdown(rendered);
|
|
280
|
+
sourceText = md;
|
|
219
281
|
textarea.value = md;
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
enhanceClayLogLinks(rendered);
|
|
223
|
-
rendered.classList.remove("is-empty");
|
|
224
|
-
} else {
|
|
225
|
-
rendered.innerHTML = "";
|
|
226
|
-
rendered.classList.add("is-empty");
|
|
227
|
-
}
|
|
282
|
+
setNoteUiState(noteId, { text: sourceText, draft: true });
|
|
283
|
+
renderChecklist();
|
|
228
284
|
});
|
|
229
285
|
|
|
230
286
|
// Show format toolbar when selecting text
|
|
231
287
|
rendered.addEventListener("mouseup", function (e) {
|
|
232
288
|
if (e.target.tagName === "A") return;
|
|
233
289
|
setTimeout(function () {
|
|
290
|
+
if (disposed) return;
|
|
234
291
|
var sel = window.getSelection();
|
|
235
292
|
if (sel && !sel.isCollapsed && sel.toString().trim()) {
|
|
236
293
|
showFormatToolbar(rendered);
|
|
@@ -260,6 +317,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
260
317
|
toggleChecklistItem(e.target);
|
|
261
318
|
return;
|
|
262
319
|
}
|
|
320
|
+
if (checklistMode) return;
|
|
263
321
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
264
322
|
e.preventDefault();
|
|
265
323
|
document.execCommand("insertLineBreak");
|
|
@@ -268,6 +326,7 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
268
326
|
|
|
269
327
|
// Paste as plain text to avoid importing HTML formatting
|
|
270
328
|
rendered.addEventListener("paste", function (e) {
|
|
329
|
+
if (checklistMode) return;
|
|
271
330
|
e.preventDefault();
|
|
272
331
|
var text = (e.clipboardData || window.clipboardData).getData("text/plain");
|
|
273
332
|
document.execCommand("insertText", false, text);
|
|
@@ -281,10 +340,15 @@ export function setupTextEdit(textarea, rendered, noteId, mdBtn) {
|
|
|
281
340
|
// Sync textarea edits when in MD mode
|
|
282
341
|
textarea.addEventListener("input", function () {
|
|
283
342
|
if (!mdMode) return;
|
|
284
|
-
|
|
285
|
-
|
|
343
|
+
sourceText = textarea.value;
|
|
344
|
+
setNoteUiState(noteId, { text: sourceText, draft: true });
|
|
345
|
+
debouncedTextUpdate(noteId, sourceText);
|
|
346
|
+
syncTitle(noteEl, sourceText);
|
|
286
347
|
});
|
|
287
348
|
|
|
349
|
+
mdBtn.hidden = checklistMode; mdBtn.disabled = checklistMode;
|
|
350
|
+
renderChecklist();
|
|
351
|
+
|
|
288
352
|
textarea.addEventListener("mousedown", function (e) {
|
|
289
353
|
e.stopPropagation();
|
|
290
354
|
});
|
|
@@ -55,8 +55,51 @@ export function clampSize(x, y, w, h) {
|
|
|
55
55
|
|
|
56
56
|
export function send(obj) {
|
|
57
57
|
var ws = getWs();
|
|
58
|
-
if (!ws || !store.get('connected')) return;
|
|
59
|
-
ws.send(JSON.stringify(obj));
|
|
58
|
+
if (!ws || !store.get('connected')) return false;
|
|
59
|
+
try { ws.send(JSON.stringify(obj)); return true; } catch (error) { return false; }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getNoteUiState(id, fallback) {
|
|
63
|
+
var all = store.get("stickyNoteUiState") || {};
|
|
64
|
+
if (!all[id]) {
|
|
65
|
+
var initial = {
|
|
66
|
+
canonical: Object.assign({}, fallback),
|
|
67
|
+
canonicalRevision: 0,
|
|
68
|
+
draft: false,
|
|
69
|
+
text: fallback && fallback.text ? fallback.text : "",
|
|
70
|
+
checklistMode: fallback && fallback.checklistMode === true,
|
|
71
|
+
checklistState: fallback && fallback.checklistState ? fallback.checklistState : {},
|
|
72
|
+
pending: [],
|
|
73
|
+
};
|
|
74
|
+
var next = Object.assign({}, all);
|
|
75
|
+
next[id] = initial;
|
|
76
|
+
store.set({ stickyNoteUiState: next });
|
|
77
|
+
return initial;
|
|
78
|
+
}
|
|
79
|
+
return all[id];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function setNoteUiState(id, changes) {
|
|
83
|
+
var all = store.get("stickyNoteUiState") || {};
|
|
84
|
+
var current = all[id] || {};
|
|
85
|
+
var next = Object.assign({}, all);
|
|
86
|
+
next[id] = Object.assign({}, current, changes || {});
|
|
87
|
+
store.set({ stickyNoteUiState: next });
|
|
88
|
+
return next[id];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function deleteNoteUiState(id) {
|
|
92
|
+
var all = store.get("stickyNoteUiState") || {};
|
|
93
|
+
if (!all[id]) return;
|
|
94
|
+
var next = Object.assign({}, all);
|
|
95
|
+
delete next[id];
|
|
96
|
+
store.set({ stickyNoteUiState: next });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function subscribeNoteUiState(listener) {
|
|
100
|
+
return store.subscribe(function (next, previous) {
|
|
101
|
+
if (next.stickyNoteUiState !== previous.stickyNoteUiState) listener(next.stickyNoteUiState || {});
|
|
102
|
+
});
|
|
60
103
|
}
|
|
61
104
|
|
|
62
105
|
export function debouncedUpdate(id, changes, delay) {
|
|
@@ -82,6 +125,11 @@ export function syncTitle(noteEl, text) {
|
|
|
82
125
|
|
|
83
126
|
|
|
84
127
|
// Drop any pending writes for a note this client no longer has.
|
|
128
|
+
export function clearNoteTextTimer(id) {
|
|
129
|
+
clearTimeout(textTimers[id]);
|
|
130
|
+
delete textTimers[id];
|
|
131
|
+
}
|
|
132
|
+
|
|
85
133
|
export function clearNoteTimers(id) {
|
|
86
134
|
clearTimeout(updateTimers[id]);
|
|
87
135
|
clearTimeout(textTimers[id]);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Reconcile canonical broadcasts with optimistic checklist actions and text drafts.
|
|
2
|
+
import { store } from './store.js';
|
|
3
|
+
import { getNoteUiState, setNoteUiState, send } from './sticky-notes-shared.js';
|
|
4
|
+
|
|
5
|
+
function project(ui, pending, error) {
|
|
6
|
+
var canonical = ui.canonical || {};
|
|
7
|
+
var text = ui.draft ? ui.text : canonical.text || "";
|
|
8
|
+
var mode = canonical.checklistMode === true;
|
|
9
|
+
var state = Object.assign({}, canonical.checklistState || {});
|
|
10
|
+
pending.forEach(function (request) {
|
|
11
|
+
var changes = request.changes;
|
|
12
|
+
if (changes.text !== undefined) text = changes.text;
|
|
13
|
+
if (changes.checklistMode !== undefined) mode = changes.checklistMode;
|
|
14
|
+
if (changes.checklistKey) state[decodeURIComponent(changes.checklistKey)] = changes.checklistChecked;
|
|
15
|
+
});
|
|
16
|
+
return setNoteUiState(canonical.id, {
|
|
17
|
+
text: text,
|
|
18
|
+
checklistMode: mode, checklistState: state, pending: pending, error: error || '',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function reconcileNote(data) {
|
|
23
|
+
var ui = getNoteUiState(data.id, data);
|
|
24
|
+
var draft = ui.draft && ui.text !== (data.text || '');
|
|
25
|
+
ui = setNoteUiState(data.id, { canonical: data, canonicalRevision: (ui.canonicalRevision || 0) + 1, draft: draft });
|
|
26
|
+
project(ui, ui.pending || [], ui.error);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mutateChecklist(id, changes) {
|
|
30
|
+
var ui = getNoteUiState(id);
|
|
31
|
+
var requestId = 'note-update-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
32
|
+
var pending = (ui.pending || []).concat([{ requestId: requestId, changes: changes, revision: ui.canonicalRevision, draftText: ui.text }]);
|
|
33
|
+
project(ui, pending, '');
|
|
34
|
+
if (!send(Object.assign({}, changes, { type: changes.humanClose ? 'note_close' : 'note_update', id: id, requestId: requestId }))) {
|
|
35
|
+
project(getNoteUiState(id), pending.slice(0, -1), 'Connection unavailable');
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function reconcileNoteResult(msg) {
|
|
40
|
+
var ui = getNoteUiState(msg.noteId);
|
|
41
|
+
var request = (ui.pending || []).find(function (item) { return item.requestId === msg.requestId; });
|
|
42
|
+
if (!request) return;
|
|
43
|
+
// The server broadcasts before acknowledgement. Use the returned note when
|
|
44
|
+
// that broadcast was absent, but never replace a newer remote canonical note.
|
|
45
|
+
if (msg.ok && msg.note && ui.canonicalRevision === request.revision) {
|
|
46
|
+
reconcileNote(msg.note);
|
|
47
|
+
ui = getNoteUiState(msg.noteId);
|
|
48
|
+
}
|
|
49
|
+
if (msg.ok && request.changes.checklistConvert && ui.text === request.draftText) {
|
|
50
|
+
ui = setNoteUiState(msg.noteId, { draft: false, text: ui.canonical.text || '' });
|
|
51
|
+
}
|
|
52
|
+
if (msg.ok && (request.changes.checklistEdit || request.changes.checklistConvert || request.changes.humanClose)) {
|
|
53
|
+
ui = setNoteUiState(msg.noteId, { checklistInputs: null });
|
|
54
|
+
}
|
|
55
|
+
var remaining = ui.pending.filter(function (item) { return item.requestId !== msg.requestId; });
|
|
56
|
+
project(ui, remaining, msg.ok ? '' : msg.error || 'Could not save checklist change');
|
|
57
|
+
return ui.canonical;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
store.subscribe(function (next, previous) {
|
|
61
|
+
if (next.connected || next.connected === previous.connected) return;
|
|
62
|
+
var all = next.stickyNoteUiState || {};
|
|
63
|
+
Object.keys(all).forEach(function (id) {
|
|
64
|
+
if (all[id].pending && all[id].pending.length) project(all[id], [], 'Connection unavailable');
|
|
65
|
+
});
|
|
66
|
+
});
|