clay-server 5.1.0-beta.1 → 5.1.0-beta.3
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/LICENSES/README.md +4 -0
- package/LICENSES/dicebear-avatars.md +16 -0
- package/bin/cli.js +9 -91
- package/lib/builtin-mates.js +7 -7
- package/lib/daemon-project-access.js +7 -1
- package/lib/daemon-settings.js +11 -0
- package/lib/daemon.js +10 -37
- package/lib/dicebear-avatar.js +62 -0
- package/lib/knowledge-db-service.js +175 -0
- package/lib/knowledge-db-validation.js +0 -0
- package/lib/knowledge-document-index.js +49 -0
- package/lib/knowledge-draft-store.js +56 -0
- package/lib/knowledge-file-store.js +199 -0
- package/lib/knowledge-markdown.js +128 -0
- package/lib/linear-store.js +35 -8
- package/lib/mate-identity-file.js +39 -0
- package/lib/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +37 -22
- package/lib/mate-ready-creation.js +2 -1
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates-identity.js +4 -4
- package/lib/mates-prompts.js +4 -3
- package/lib/mates.js +30 -11
- package/lib/os-users.js +5 -16
- package/lib/project-connection.js +36 -9
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +4 -2
- package/lib/project-filesystem.js +21 -39
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-mate-interaction.js +7 -6
- package/lib/project-server-settings.js +31 -0
- package/lib/project-sessions.js +92 -182
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +62 -21
- package/lib/public/app.js +79 -71
- package/lib/public/clay-studio-wordmark-bold.svg +20 -0
- package/lib/public/css/clay-workspace.css +105 -0
- package/lib/public/css/debates-workbench.css +47 -0
- package/lib/public/css/home-debate-live.css +26 -0
- package/lib/public/css/home-hub.css +3 -1
- package/lib/public/css/home-mate-model-picker.css +15 -131
- package/lib/public/css/home-mate-settings.css +168 -16
- package/lib/public/css/icon-strip.css +249 -32
- package/lib/public/css/input.css +3 -1
- package/lib/public/css/mate-conversation.css +176 -0
- package/lib/public/css/mate-knowledge-workbench.css +118 -0
- package/lib/public/css/mate-workspace-color.css +16 -0
- package/lib/public/css/mates.css +28 -28
- package/lib/public/css/menus.css +3 -0
- package/lib/public/css/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +15 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/project-settings-modal.css +56 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/server-settings-system.css +125 -0
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/session-github.css +25 -16
- package/lib/public/css/settings-ui.css +247 -22
- package/lib/public/css/shared-browser.css +7 -3
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +156 -1
- package/lib/public/css/worker-delegation.css +47 -0
- package/lib/public/css/worker-proposal.css +72 -150
- package/lib/public/css/you.css +74 -0
- package/lib/public/index.html +206 -548
- package/lib/public/modules/account-island.js +232 -0
- package/lib/public/modules/admin-settings-email.js +197 -0
- package/lib/public/modules/admin-settings-invitations.js +183 -0
- package/lib/public/modules/admin-settings-projects.js +198 -0
- package/lib/public/modules/admin-settings-ui.js +100 -0
- package/lib/public/modules/admin-settings-users.js +331 -0
- package/lib/public/modules/admin.js +19 -949
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- package/lib/public/modules/app-dm.js +0 -2
- package/lib/public/modules/app-header.js +106 -0
- package/lib/public/modules/app-message-router.js +12 -3
- package/lib/public/modules/app-messages.js +47 -61
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rate-limit.js +70 -12
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +30 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- package/lib/public/modules/context-sources.js +27 -18
- package/lib/public/modules/cursor-sharing-preference.js +3 -3
- package/lib/public/modules/debate-controls.js +299 -0
- package/lib/public/modules/debate.js +2 -0
- package/lib/public/modules/debates-workbench.js +339 -0
- package/lib/public/modules/default-ai.js +88 -65
- package/lib/public/modules/header-tui-font.js +10 -20
- package/lib/public/modules/home-debate-controls.js +18 -289
- package/lib/public/modules/home-mate-avatar-editor.js +228 -0
- package/lib/public/modules/home-mate-color-picker.js +66 -0
- package/lib/public/modules/home-mate-creation.js +1 -1
- package/lib/public/modules/home-mate-instructions.js +90 -0
- package/lib/public/modules/home-mate-model-picker.js +80 -271
- package/lib/public/modules/home-mate-profile-editor.js +72 -0
- package/lib/public/modules/home-mate-settings.js +118 -125
- package/lib/public/modules/home-shell.js +3 -0
- package/lib/public/modules/home-surface-boot.js +1 -9
- package/lib/public/modules/icon-strip-tooltip.js +95 -0
- package/lib/public/modules/knowledge-db-edit-state.js +35 -0
- package/lib/public/modules/knowledge-db-workbench.js +234 -0
- package/lib/public/modules/knowledge-history-state.js +55 -0
- package/lib/public/modules/knowledge-markdown-editor.js +57 -0
- package/lib/public/modules/knowledge-preview-links.js +70 -0
- package/lib/public/modules/knowledge-workbench-shell.js +21 -0
- package/lib/public/modules/linear-settings.js +3 -2
- package/lib/public/modules/markdown.js +5 -6
- package/lib/public/modules/mate-knowledge-workbench-state.js +174 -0
- package/lib/public/modules/mate-knowledge-workbench.js +500 -0
- package/lib/public/modules/mate-knowledge.js +11 -3
- package/lib/public/modules/mate-management.js +54 -10
- package/lib/public/modules/mate-sidebar.js +11 -1
- package/lib/public/modules/mate-workspace-color.js +33 -0
- package/lib/public/modules/mention.js +5 -13
- package/lib/public/modules/notification-settings.js +81 -0
- package/lib/public/modules/notifications.js +44 -28
- package/lib/public/modules/personal-settings-dialog.js +51 -0
- package/lib/public/modules/profile.js +66 -24
- package/lib/public/modules/project-chat-identity.js +79 -0
- package/lib/public/modules/project-debate-planning-workspace.js +250 -0
- package/lib/public/modules/project-debate-workspace.js +185 -0
- package/lib/public/modules/project-header-identity.js +127 -0
- package/lib/public/modules/project-logs-render.js +9 -4
- package/lib/public/modules/project-mate-creation-workspace.js +414 -0
- package/lib/public/modules/project-mate-navigation.js +238 -0
- package/lib/public/modules/project-session-navigation.js +29 -0
- package/lib/public/modules/project-settings-environment.js +106 -0
- package/lib/public/modules/project-settings.js +72 -628
- package/lib/public/modules/project-switcher.js +6 -8
- package/lib/public/modules/projects-hub-view.js +5 -6
- package/lib/public/modules/projects-hub.js +2 -6
- package/lib/public/modules/qrcode.js +27 -8
- package/lib/public/modules/search-clay-chat.js +8 -11
- package/lib/public/modules/server-settings-controls.js +316 -0
- package/lib/public/modules/server-settings-environment.js +194 -0
- package/lib/public/modules/server-settings-navigation.js +39 -0
- package/lib/public/modules/server-settings-state.js +54 -0
- package/lib/public/modules/server-settings.js +116 -788
- package/lib/public/modules/server-storage-settings.js +84 -0
- package/lib/public/modules/session-activity-tooltip.js +12 -0
- package/lib/public/modules/session-create-form.js +86 -14
- package/lib/public/modules/session-create.js +50 -7
- package/lib/public/modules/session-github.js +2 -1
- package/lib/public/modules/session-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +3 -0
- package/lib/public/modules/session-title-marquee.js +51 -0
- package/lib/public/modules/settings-env-values.js +34 -0
- package/lib/public/modules/settings-select.js +121 -0
- package/lib/public/modules/shared-browser.js +22 -12
- package/lib/public/modules/sidebar-mates.js +3 -50
- package/lib/public/modules/sidebar-mobile.js +63 -60
- package/lib/public/modules/sidebar-projects.js +84 -97
- package/lib/public/modules/sidebar-session-folders-ui.js +2 -2
- package/lib/public/modules/sidebar-session-hierarchy.js +149 -70
- package/lib/public/modules/sidebar-sessions.js +49 -50
- package/lib/public/modules/sidebar.js +11 -13
- package/lib/public/modules/stt-picker.js +14 -5
- package/lib/public/modules/terminal-prefs.js +22 -0
- package/lib/public/modules/theme.js +88 -52
- package/lib/public/modules/thinking-lifecycle.js +7 -6
- package/lib/public/modules/tool-palette-order.js +4 -3
- package/lib/public/modules/tool-palette.js +13 -3
- package/lib/public/modules/tools.js +22 -22
- package/lib/public/modules/user-settings-account.js +318 -0
- package/lib/public/modules/user-settings-appearance.js +55 -0
- package/lib/public/modules/user-settings-editors.js +24 -0
- package/lib/public/modules/user-settings-integrations.js +104 -0
- package/lib/public/modules/user-settings-layout.js +80 -0
- package/lib/public/modules/user-settings-preferences.js +48 -0
- package/lib/public/modules/user-settings-profile.js +53 -0
- package/lib/public/modules/user-settings-themes.js +38 -0
- package/lib/public/modules/user-settings-voice.js +75 -0
- package/lib/public/modules/user-settings.js +53 -480
- package/lib/public/modules/worker-proposal.js +42 -8
- package/lib/public/modules/worker-runtime-picker.js +115 -0
- package/lib/public/modules/you-activity.js +121 -0
- package/lib/public/modules/you.js +251 -0
- package/lib/public/style.css +20 -8
- package/lib/public/vendor/codemirror/LICENSE +21 -0
- package/lib/public/vendor/codemirror/codemirror.css +344 -0
- package/lib/public/vendor/codemirror/codemirror.js +9884 -0
- package/lib/public/vendor/codemirror/continuelist.js +101 -0
- package/lib/public/vendor/codemirror/markdown.js +886 -0
- package/lib/public/vendor/codemirror/show-hint.css +37 -0
- package/lib/public/vendor/codemirror/show-hint.js +523 -0
- package/lib/sdk-bridge.js +15 -3
- package/lib/sdk-message-processor.js +16 -4
- package/lib/sdk-quota-snapshot.js +37 -0
- package/lib/server-admin.js +11 -0
- package/lib/server-appearance-settings.js +71 -0
- package/lib/server-home-chat-events.js +1 -1
- package/lib/server-home-chat.js +25 -3
- package/lib/server-home-debate-planning.js +4 -1
- package/lib/server-home-debates.js +2 -0
- package/lib/server-home-mate-creation.js +4 -1
- package/lib/server-linear.js +14 -8
- package/lib/server-mate-instructions.js +44 -0
- package/lib/server-mates.js +14 -4
- package/lib/server-notification-settings.js +67 -0
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +69 -1
- package/lib/server-storage.js +57 -0
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +31 -3
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +35 -10
- package/lib/startup-projects.js +40 -0
- package/lib/storage-native-walk.js +85 -0
- package/lib/storage-usage.js +144 -0
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-preferences.js +0 -74
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -4
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +52 -4
- package/lib/yoke/acp-process-manager.js +2 -2
- package/lib/yoke/adapters/codex.js +12 -52
- package/lib/yoke/codex-app-server.js +2 -2
- package/lib/yoke/codex-model-catalog.js +52 -0
- package/lib/yoke/codex-rate-limits.js +26 -0
- package/package.json +12 -3
|
@@ -0,0 +1,500 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
4
|
+
import { renderMarkdown, highlightCodeBlocks } from './markdown.js';
|
|
5
|
+
import { isMateWorkspace } from './project-mate-navigation.js';
|
|
6
|
+
import { claimRightWorkbench, registerRightWorkbench, releaseRightWorkbench } from './right-workbench.js';
|
|
7
|
+
import { mountKnowledgeEditor, captureKnowledgeEditor, destroyKnowledgeEditor, scrollKnowledgeEditor } from './knowledge-markdown-editor.js';
|
|
8
|
+
import { decorateKnowledgePreview } from './knowledge-preview-links.js';
|
|
9
|
+
import { setKnowledgeNarrowBackground } from './knowledge-workbench-shell.js';
|
|
10
|
+
import { configureKnowledgeDbWorkbench, openKnowledgeDatabase, showKnowledgeCreateMenu, beginKnowledgeDatabaseRecovery, handleKnowledgeDbClick, handleKnowledgeDbInput, handleKnowledgeDbKeydown, renderKnowledgeDatabase, handleKnowledgeDbMessage } from './knowledge-db-workbench.js';
|
|
11
|
+
import {
|
|
12
|
+
liveHistoryTarget, deletedHistoryTarget, beginHistoryRequest, receiveHistoryVersions,
|
|
13
|
+
beginHistoryPreview, receiveHistoryPreview, clearHistoryView, historyToolbarHtml, historyVersionsHtml,
|
|
14
|
+
} from './knowledge-history-state.js';
|
|
15
|
+
import {
|
|
16
|
+
documentIdentity, blankKnowledgeState, selectKnowledgeDocument, editKnowledgeDraft,
|
|
17
|
+
failKnowledgeOffline, closeKnowledgeTab,
|
|
18
|
+
beginKnowledgeRead, applyKnowledgeRead, beginKnowledgeSave, applyKnowledgeSave,
|
|
19
|
+
beginKnowledgeRename, applyKnowledgeRename, beginKnowledgeDelete, applyKnowledgeDelete,
|
|
20
|
+
applyKnowledgeDiscard, failKnowledgeRequest,
|
|
21
|
+
} from './mate-knowledge-workbench-state.js';
|
|
22
|
+
|
|
23
|
+
var panel = null; var requestCounter = 0; var pending = {}; var searchTimer = null; var searchTimerKey = null;
|
|
24
|
+
var returnFocus = null; var deleteTarget = null; var draftSaveTimers = {}; var pendingHeading = null;
|
|
25
|
+
|
|
26
|
+
function workspaceKey() { return store.get('currentSlug') || ''; }
|
|
27
|
+
function allStates() { return store.get('mateKnowledgeWorkspaces') || {}; }
|
|
28
|
+
function stateFor(key) { return allStates()[key] || blankKnowledgeState(); }
|
|
29
|
+
function setState(key, next) {
|
|
30
|
+
var states = Object.assign({}, allStates());
|
|
31
|
+
states[key] = next;
|
|
32
|
+
store.set({ mateKnowledgeWorkspaces: states });
|
|
33
|
+
}
|
|
34
|
+
function patchState(key, patch) { setState(key, Object.assign({}, stateFor(key), patch)); }
|
|
35
|
+
function patchDraft(key, id, patch) {
|
|
36
|
+
var state = stateFor(key); var draft = state.drafts[id]; if (!draft) return;
|
|
37
|
+
var drafts = Object.assign({}, state.drafts); drafts[id] = Object.assign({}, draft, patch); setState(key, Object.assign({}, state, { drafts: drafts }));
|
|
38
|
+
}
|
|
39
|
+
function currentDraft(state) { return state.selected ? state.drafts[state.selected] || null : null; }
|
|
40
|
+
function fileForId(state, id) {
|
|
41
|
+
for (var i = 0; i < state.files.length; i++) if (documentIdentity(state.files[i]) === id) return state.files[i];
|
|
42
|
+
var draft = state.drafts[id]; return draft ? draft.file : null;
|
|
43
|
+
}
|
|
44
|
+
function nextRequestId() { requestCounter += 1; return 'knowledge-' + Date.now() + '-' + requestCounter; }
|
|
45
|
+
function readySocket(key) {
|
|
46
|
+
if (key !== workspaceKey()) return null;
|
|
47
|
+
var ws = getWs();
|
|
48
|
+
if (ws && ws.readyState === 1) return ws;
|
|
49
|
+
patchState(key, { error: 'Reconnect to use Knowledge.' }); render(); return null;
|
|
50
|
+
}
|
|
51
|
+
function sendPrepared(key, type, details, tracked, nextState) {
|
|
52
|
+
var ws = readySocket(key); if (!ws) return false;
|
|
53
|
+
pending[tracked.requestId] = Object.assign({ key: key, type: type }, tracked);
|
|
54
|
+
if (nextState) setState(key, nextState);
|
|
55
|
+
try { ws.send(JSON.stringify(Object.assign({ type: type, requestId: tracked.requestId }, details || {}))); }
|
|
56
|
+
catch (error) {
|
|
57
|
+
delete pending[tracked.requestId]; setState(key, failKnowledgeRequest(stateFor(key), tracked, { error: error.message })); render(); return false;
|
|
58
|
+
}
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
function simpleRequest(type, details, tracked) {
|
|
62
|
+
var key = workspaceKey(); var requestId = nextRequestId();
|
|
63
|
+
return sendPrepared(key, type, details, Object.assign({ requestId: requestId }, tracked || {}), null) ? requestId : null;
|
|
64
|
+
}
|
|
65
|
+
function requestForKey(key, type, details, tracked, nextState) {
|
|
66
|
+
var requestId = nextRequestId();
|
|
67
|
+
return sendPrepared(key, type, details, Object.assign({ requestId: requestId }, tracked || {}), nextState) ? requestId : null;
|
|
68
|
+
}
|
|
69
|
+
function safeName(value) {
|
|
70
|
+
var name = String(value || '').trim(); if (!name) return '';
|
|
71
|
+
if (!/\.md$/i.test(name)) name += '.md';
|
|
72
|
+
return /^(?!\/)(?!.*(?:^|\/)\.\.?\/)(?!.*\/\.\.?$)[^\\\x00-\x1f\x7f]{1,500}\.md$/i.test(name) ? name.replace(/^\.\//, '') : '';
|
|
73
|
+
}
|
|
74
|
+
function saveScroll(keyOverride) {
|
|
75
|
+
if (!panel) return; var key = keyOverride || workspaceKey(); var state = stateFor(key); var id = state.selected; if (!id) return;
|
|
76
|
+
var draft = state.drafts[id]; if (!draft) return;
|
|
77
|
+
var editorState = captureKnowledgeEditor(); var preview = panel.querySelector('[data-knowledge-preview]');
|
|
78
|
+
patchDraft(key, id, { content: editorState ? editorState.content : draft.content, cursor: editorState ? editorState.cursor : draft.cursor,
|
|
79
|
+
editorHistory: editorState ? editorState.history : draft.editorHistory, editScroll: editorState ? editorState.scroll : draft.editScroll || 0,
|
|
80
|
+
readScroll: preview ? preview.scrollTop : draft.readScroll || 0 });
|
|
81
|
+
patchState(key, { explorerScroll: panel.querySelector('.knowledge-explorer-list').scrollTop });
|
|
82
|
+
}
|
|
83
|
+
function headings(content) {
|
|
84
|
+
var lines = String(content || '').split('\n'); var out = [];
|
|
85
|
+
for (var i = 0; i < lines.length; i++) { var match = /^(#{1,4})\s+(.+?)\s*$/.exec(lines[i]); if (match) out.push({ depth: match[1].length, text: match[2].replace(/[*_`]/g, '') }); }
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function ensurePanel() {
|
|
90
|
+
if (panel) return; var host = document.getElementById('main-panels'); if (!host) return;
|
|
91
|
+
panel = document.createElement('section'); panel.id = 'mate-knowledge-workbench'; panel.className = 'hidden'; panel.setAttribute('aria-label', 'Mate Knowledge');
|
|
92
|
+
panel.innerHTML = '<header class="knowledge-topbar"><span class="knowledge-title">' + iconHtml('book-open') + 'Knowledge</span><span class="knowledge-subtitle">Documents in this Mate project</span><button type="button" data-knowledge-deleted title="Recover deleted documents" aria-label="Recover deleted documents">' + iconHtml('history') + '</button><button type="button" data-knowledge-new title="New document" aria-label="New document">' + iconHtml('file-plus-2') + '</button><button type="button" data-knowledge-wide title="Widen Knowledge" aria-label="Widen Knowledge" aria-pressed="false">' + iconHtml('chevrons-left-right') + '</button><button type="button" data-knowledge-full title="Toggle fullscreen" aria-label="Toggle Knowledge fullscreen" aria-pressed="false">' + iconHtml('maximize-2') + '</button><button type="button" data-knowledge-close title="Close Knowledge" aria-label="Close Knowledge">' + iconHtml('x') + '</button></header><div class="knowledge-status" role="status"></div><div class="knowledge-layout"><aside class="knowledge-explorer"><label class="knowledge-search">' + iconHtml('search') + '<input type="search" placeholder="Search names and contents" aria-label="Search Knowledge document names and contents"></label><div class="knowledge-explorer-heading"><span>Documents</span><span data-knowledge-count></span></div><div class="knowledge-explorer-list" role="list"></div></aside><main class="knowledge-document"><div class="knowledge-tabs" role="tablist" aria-label="Open Knowledge documents"></div><div class="knowledge-document-toolbar"></div><div class="knowledge-document-body"></div></main><aside class="knowledge-details"><div class="knowledge-detail-section"><button type="button" class="knowledge-detail-heading" aria-expanded="true" data-detail-toggle="outline">Outline' + iconHtml('chevron-down') + '</button><div data-detail-content="outline"></div></div><div class="knowledge-detail-section"><button type="button" class="knowledge-detail-heading" aria-expanded="true" data-detail-toggle="links">Links' + iconHtml('chevron-down') + '</button><div data-detail-content="links"></div></div><div class="knowledge-detail-section"><button type="button" class="knowledge-detail-heading" aria-expanded="true" data-detail-toggle="history">History' + iconHtml('chevron-down') + '</button><div data-detail-content="history"></div></div></aside></div><div class="knowledge-delete-dialog hidden" role="dialog" aria-modal="true" aria-labelledby="knowledge-delete-title"><div><h2 id="knowledge-delete-title">Delete this document?</h2><p>The file will be removed from this Mate project. Existing record history remains available to shared Knowledge.</p><div><button type="button" data-delete-cancel>Cancel</button><button type="button" class="danger" data-delete-confirm>Delete</button></div></div></div>';
|
|
93
|
+
var newButton = panel.querySelector('[data-knowledge-new]'); newButton.insertAdjacentHTML('beforebegin', '<button type="button" data-knowledge-back title="Back" aria-label="Previous document">' + iconHtml('arrow-left') + '</button><button type="button" data-knowledge-forward title="Forward" aria-label="Next document">' + iconHtml('arrow-right') + '</button>');
|
|
94
|
+
host.appendChild(panel); bindPanel(); refreshIcons(panel);
|
|
95
|
+
}
|
|
96
|
+
function ensureButton() {
|
|
97
|
+
var button = document.getElementById('knowledge-workbench-btn'); if (button) return button;
|
|
98
|
+
var tools = document.getElementById('session-actions'); if (!tools) return null;
|
|
99
|
+
button = document.createElement('button'); button.id = 'knowledge-workbench-btn'; button.type = 'button'; button.className = 'palette-tile'; button.title = 'Knowledge'; button.setAttribute('aria-label', 'Knowledge'); button.innerHTML = '<i data-lucide="book-open"></i><span class="tool-btn-label">Knowledge</span>';
|
|
100
|
+
var fixed = tools.querySelector('[data-fixed="true"]');
|
|
101
|
+
tools.insertBefore(button, fixed ? fixed.nextSibling : tools.firstChild); refreshIcons(button); return button;
|
|
102
|
+
}
|
|
103
|
+
function bindPanel() {
|
|
104
|
+
panel.querySelector('[data-knowledge-close]').addEventListener('click', closeKnowledgeWorkbench);
|
|
105
|
+
panel.querySelector('[data-knowledge-new]').addEventListener('click', function () { showKnowledgeCreateMenu(panel); });
|
|
106
|
+
panel.querySelector('[data-knowledge-deleted]').addEventListener('click', function () { simpleRequest('knowledge_deleted_list'); });
|
|
107
|
+
panel.querySelector('[data-knowledge-back]').addEventListener('click', function () { navigateHistory('back'); });
|
|
108
|
+
panel.querySelector('[data-knowledge-forward]').addEventListener('click', function () { navigateHistory('forward'); });
|
|
109
|
+
panel.querySelector('.knowledge-search input').addEventListener('input', scheduleSearch);
|
|
110
|
+
panel.querySelector('[data-knowledge-wide]').addEventListener('click', function () { var key = workspaceKey(); patchState(key, { wide: !stateFor(key).wide }); renderShell(); });
|
|
111
|
+
panel.querySelector('[data-knowledge-full]').addEventListener('click', exitEditor);
|
|
112
|
+
panel.querySelector('[data-delete-cancel]').addEventListener('click', closeDelete);
|
|
113
|
+
panel.querySelector('[data-delete-confirm]').addEventListener('click', confirmDelete);
|
|
114
|
+
panel.addEventListener('click', handlePanelClick);
|
|
115
|
+
panel.addEventListener('input', function (event) {
|
|
116
|
+
if (handleKnowledgeDbInput(event)) return;
|
|
117
|
+
var key = workspaceKey(); var state = stateFor(key); var id = state.selected;
|
|
118
|
+
if (event.target.matches('[data-document-name]')) { setState(key, editKnowledgeDraft(state, id, { name: event.target.value })); scheduleDraftSave(key, id); }
|
|
119
|
+
});
|
|
120
|
+
panel.addEventListener('keydown', handlePanelKeydown);
|
|
121
|
+
}
|
|
122
|
+
function handlePanelClick(event) {
|
|
123
|
+
var dbAction = handleKnowledgeDbClick(event, panel); if (dbAction === true) return; if (dbAction === 'document') { createDocument(); return; }
|
|
124
|
+
var close = event.target.closest('[data-close-tab]'); if (close) { closeTab(close.dataset.closeTab); return; }
|
|
125
|
+
var select = event.target.closest('[data-select-tab]'); if (select) { selectTab(select.dataset.selectTab, true); return; }
|
|
126
|
+
var open = event.target.closest('[data-open-document]'); if (open) { pendingHeading = open.dataset.openHeading || null; openDocument(open.dataset.openDocument); if (pendingHeading) setTimeout(scrollPendingHeading, 0); return; }
|
|
127
|
+
var outlineTarget = event.target.closest('[data-outline-heading]'); if (outlineTarget) { pendingHeading = outlineTarget.dataset.outlineHeading; scrollPendingHeading(); return; }
|
|
128
|
+
var action = event.target.closest('[data-document-action]'); if (action) { handleAction(action.dataset.documentAction, action); return; }
|
|
129
|
+
var createLink = event.target.closest('[data-create-linked]'); if (createLink) { createDocument(createLink.dataset.createLinked); return; }
|
|
130
|
+
var historyLoad = event.target.closest('[data-history-load]'); if (historyLoad) { loadHistory(); return; }
|
|
131
|
+
var historyPreview = event.target.closest('[data-history-preview]'); if (historyPreview) { previewHistory(historyPreview.dataset.historyPreview); return; }
|
|
132
|
+
var historyRestore = event.target.closest('[data-history-restore]'); if (historyRestore) { restoreHistory(historyRestore.dataset.historyRestore); return; }
|
|
133
|
+
var recoverDatabase = event.target.closest('[data-db-recover-database]'); if (recoverDatabase) { beginKnowledgeDatabaseRecovery(panel, { id: recoverDatabase.dataset.dbRecoverDatabase, name: recoverDatabase.dataset.dbRecoverName, revision: Number(recoverDatabase.dataset.dbRecoverRevision) }); return; }
|
|
134
|
+
var deletedHistory = event.target.closest('[data-deleted-history]'); if (deletedHistory) { loadDeletedHistory(deletedHistory.dataset.deletedHistory, deletedHistory.dataset.deletedName); return; }
|
|
135
|
+
if (event.target.closest('[data-search-more]')) { loadMoreSearch(); return; }
|
|
136
|
+
if (event.target.closest('[data-history-review]')) { reviewHistoryRestore(); return; }
|
|
137
|
+
var toggle = event.target.closest('[data-detail-toggle]'); if (toggle) { var content = panel.querySelector('[data-detail-content="' + toggle.dataset.detailToggle + '"]'); var expanded = toggle.getAttribute('aria-expanded') !== 'false'; toggle.setAttribute('aria-expanded', String(!expanded)); content.hidden = expanded; return; }
|
|
138
|
+
if (event.target.closest('[data-sync-retry]')) retrySync();
|
|
139
|
+
}
|
|
140
|
+
function handlePanelKeydown(event) {
|
|
141
|
+
if (handleKnowledgeDbKeydown(event, panel)) return;
|
|
142
|
+
if (deleteTarget) { trapDeleteFocus(event); return; }
|
|
143
|
+
var tab = event.target.closest('[data-select-tab]');
|
|
144
|
+
if (event.target.matches('.knowledge-search input') && event.key === 'Enter') { var searchState = stateFor(workspaceKey()); var results = searchState.query.trim() ? searchState.searchFiles : searchState.files; if (results.length) { event.preventDefault(); openDocument(documentIdentity(results[0])); } return; }
|
|
145
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'p') { event.preventDefault(); panel.querySelector('.knowledge-search input').focus(); return; }
|
|
146
|
+
if (tab && ['ArrowLeft', 'ArrowRight', 'Home', 'End'].indexOf(event.key) !== -1) { event.preventDefault(); moveTabFocus(tab.dataset.selectTab, event.key); return; }
|
|
147
|
+
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 's') { event.preventDefault(); saveDocument(); }
|
|
148
|
+
else if (event.key === 'Escape' && window.matchMedia('(max-width: 1023px)').matches) closeKnowledgeWorkbench();
|
|
149
|
+
}
|
|
150
|
+
function trapDeleteFocus(event) {
|
|
151
|
+
if (event.key === 'Escape') { event.preventDefault(); closeDelete(); return; }
|
|
152
|
+
if (event.key !== 'Tab') return;
|
|
153
|
+
var controls = panel.querySelectorAll('.knowledge-delete-dialog button:not(:disabled)'); if (!controls.length) return;
|
|
154
|
+
var first = controls[0]; var last = controls[controls.length - 1];
|
|
155
|
+
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
|
|
156
|
+
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
|
|
157
|
+
}
|
|
158
|
+
function moveTabFocus(id, keyName) {
|
|
159
|
+
var state = stateFor(workspaceKey()); var index = state.tabs.indexOf(id); if (index === -1) return;
|
|
160
|
+
if (keyName === 'Home') index = 0; else if (keyName === 'End') index = state.tabs.length - 1; else index = (index + (keyName === 'ArrowRight' ? 1 : -1) + state.tabs.length) % state.tabs.length;
|
|
161
|
+
selectTab(state.tabs[index], true);
|
|
162
|
+
}
|
|
163
|
+
function cancelScheduledSearch(key) {
|
|
164
|
+
if (!searchTimer || (key && searchTimerKey !== key)) return;
|
|
165
|
+
clearTimeout(searchTimer); searchTimer = null;
|
|
166
|
+
var cancelledKey = searchTimerKey; searchTimerKey = null;
|
|
167
|
+
if (cancelledKey) patchState(cancelledKey, { searching: false });
|
|
168
|
+
}
|
|
169
|
+
function requestSearch(key, query, offset, append) {
|
|
170
|
+
if (key !== workspaceKey() || !panel || panel.classList.contains('hidden')) { patchState(key, { searching: false }); return; }
|
|
171
|
+
var requestId = nextRequestId(); var tracked = { requestId: requestId, query: query, offset: offset || 0, append: append === true };
|
|
172
|
+
if (!sendPrepared(key, 'knowledge_search', { query: query, offset: offset || 0, limit: 50 }, tracked, null)) patchState(key, { searching: false });
|
|
173
|
+
}
|
|
174
|
+
function loadMoreSearch() {
|
|
175
|
+
var key = workspaceKey(); var state = stateFor(key); if (state.searching || !state.query.trim() || state.searchFiles.length >= state.searchTotal) return;
|
|
176
|
+
patchState(key, { searching: true }); requestSearch(key, state.query, state.searchFiles.length, true); renderExplorer();
|
|
177
|
+
}
|
|
178
|
+
function scheduleSearch(event) {
|
|
179
|
+
var key = workspaceKey(); var query = event.target.value; cancelScheduledSearch();
|
|
180
|
+
patchState(key, { query: query, searchFiles: query.trim() ? stateFor(key).searchFiles : [], searching: !!query.trim() }); renderExplorer();
|
|
181
|
+
if (!query.trim()) return;
|
|
182
|
+
searchTimerKey = key;
|
|
183
|
+
searchTimer = setTimeout(function () { searchTimer = null; searchTimerKey = null; if (key !== workspaceKey() || stateFor(key).query !== query) { patchState(key, { searching: false }); return; } requestSearch(key, query, 0, false); }, 160);
|
|
184
|
+
}
|
|
185
|
+
function createDocument(suggestedName) {
|
|
186
|
+
saveScroll(); var key = workspaceKey(); var state = stateFor(key); var id = 'draft:' + Date.now() + ':' + requestCounter; var drafts = Object.assign({}, state.drafts);
|
|
187
|
+
var suggestion = typeof suggestedName === 'string' ? suggestedName.replace(/\.md$/i, '') : '';
|
|
188
|
+
if (suggestion && state.selected && state.drafts[state.selected] && suggestion.indexOf('/') !== 0) {
|
|
189
|
+
var currentName = state.drafts[state.selected].file.name || ''; var folder = currentName.indexOf('/') === -1 ? '' : currentName.slice(0, currentName.lastIndexOf('/') + 1);
|
|
190
|
+
suggestion = new URL(folder + suggestion + '.md', 'https://clay.invalid/').pathname.replace(/^\//, '').replace(/\.md$/i, '');
|
|
191
|
+
}
|
|
192
|
+
drafts[id] = { file: { id: id, name: '', common: false, itemType: 'text' }, content: '', savedContent: '', revision: null, dirty: true, writable: true, newDocument: true, mode: 'edit', name: suggestion, version: 0, error: '' };
|
|
193
|
+
setState(key, Object.assign({}, state, { drafts: drafts, tabs: state.tabs.concat([id]), selected: id, selectionVersion: state.selectionVersion + 1, fullscreen: true, error: '' })); render({ forceDocument: true });
|
|
194
|
+
setTimeout(function () { var input = panel.querySelector('[data-document-name]'); if (input) input.focus(); }, 0);
|
|
195
|
+
}
|
|
196
|
+
function openDocument(id, replace, skipHistory) {
|
|
197
|
+
saveScroll(); var key = workspaceKey(); var state = stateFor(key);
|
|
198
|
+
var back = state.navigationBack || []; var forward = state.navigationForward || [];
|
|
199
|
+
if (!skipHistory && state.selected && state.selected !== id) { back = back.concat([state.selected]).slice(-50); forward = []; }
|
|
200
|
+
if (state.drafts[id] && !replace) { var selectedState = selectKnowledgeDocument(state, id); selectedState.fullscreen = false; selectedState.drafts[id].mode = 'read'; selectedState.navigationBack = back; selectedState.navigationForward = forward; setState(key, selectedState); render({ forceDocument: true }); return; }
|
|
201
|
+
var file = fileForId(state, id); if (!file) return; var ws = readySocket(key); if (!ws) return;
|
|
202
|
+
if (file.itemType === 'db') { var dbDrafts = Object.assign({}, state.drafts); dbDrafts[id] = dbDrafts[id] || { file: file, content: '', writable: file.writable !== false, dirty: false, mode: 'read' }; var dbTabs = state.tabs.indexOf(id) === -1 ? state.tabs.concat([id]) : state.tabs; setState(key, Object.assign({}, state, { drafts: dbDrafts, tabs: dbTabs, selected: id, fullscreen: false, navigationBack: back, navigationForward: forward })); openKnowledgeDatabase(file); render({ forceDocument: true }); return; }
|
|
203
|
+
var requestId = nextRequestId(); var begun = beginKnowledgeRead(state, file, requestId, replace === true);
|
|
204
|
+
begun.state.fullscreen = false; begun.state.navigationBack = back; begun.state.navigationForward = forward;
|
|
205
|
+
sendPrepared(key, 'knowledge_read', { name: file.name, documentId: file.id || null, common: !!file.common, ownMateId: file.ownMateId || null }, begun.pending, begun.state); render();
|
|
206
|
+
}
|
|
207
|
+
function navigateHistory(direction) {
|
|
208
|
+
var key = workspaceKey(); var state = stateFor(key); var back = state.navigationBack || []; var forward = state.navigationForward || [];
|
|
209
|
+
var source = direction === 'back' ? back : forward; if (!source.length) return; var id = source[source.length - 1];
|
|
210
|
+
if (direction === 'back') { back = back.slice(0, -1); if (state.selected) forward = forward.concat([state.selected]); }
|
|
211
|
+
else { forward = forward.slice(0, -1); if (state.selected) back = back.concat([state.selected]); }
|
|
212
|
+
setState(key, Object.assign({}, state, { navigationBack: back, navigationForward: forward })); openDocument(id, false, true);
|
|
213
|
+
}
|
|
214
|
+
function selectTab(id, focusAfter) {
|
|
215
|
+
saveScroll(); var key = workspaceKey(); setState(key, selectKnowledgeDocument(stateFor(key), id)); render();
|
|
216
|
+
if (focusAfter) setTimeout(function () { var target = panel.querySelector('[data-select-tab="' + escapeAttr(id) + '"]'); if (target) target.focus(); }, 0);
|
|
217
|
+
}
|
|
218
|
+
function closeTab(id) {
|
|
219
|
+
var key = workspaceKey(); var state = stateFor(key); var draft = state.drafts[id];
|
|
220
|
+
var restoreFocus = document.activeElement && document.activeElement.closest('[data-close-tab]');
|
|
221
|
+
if (state.selected === id && (!draft || !draft.dirty)) saveScroll(); var next = closeKnowledgeTab(state, id); setState(key, next); render();
|
|
222
|
+
if (restoreFocus) setTimeout(function () { var target = next.selected && panel.querySelector('[data-select-tab="' + escapeAttr(next.selected) + '"]'); if (!target) target = panel.querySelector('[data-open-document], .knowledge-search input'); if (target) target.focus(); }, 0);
|
|
223
|
+
}
|
|
224
|
+
function handleAction(action, control) {
|
|
225
|
+
var key = workspaceKey(); var state = stateFor(key); var draft = currentDraft(state);
|
|
226
|
+
if (action === 'history-exit') { setState(key, Object.assign({}, clearHistoryView(state), { fullscreen: false })); render({ forceDocument: true }); return; }
|
|
227
|
+
if (!draft) return;
|
|
228
|
+
if (action === 'edit') enterEditor(); else if (action === 'read') exitEditor();
|
|
229
|
+
else if (action === 'save') saveDocument(); else if (action === 'rename') renameDocument();
|
|
230
|
+
else if (action === 'delete') openDelete(control); else if (action === 'reload') discardDraft();
|
|
231
|
+
else if (action === 'keep') { patchDraft(key, state.selected, { conflict: false, revision: draft.diskRevision || draft.revision, error: '' }); renderToolbar(); }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function loadHistory() {
|
|
235
|
+
var key = workspaceKey(); var state = stateFor(key); var target = liveHistoryTarget(state.selected, currentDraft(state)); if (!target) return;
|
|
236
|
+
var requestId = nextRequestId(); var tracked = { requestId: requestId, documentId: target.documentKey, historyTarget: target };
|
|
237
|
+
sendPrepared(key, 'knowledge_history', { documentId: target.documentId }, tracked, beginHistoryRequest(state, target, requestId)); renderDetails();
|
|
238
|
+
}
|
|
239
|
+
function loadDeletedHistory(documentId, name) {
|
|
240
|
+
var key = workspaceKey(); var state = stateFor(key); var target = deletedHistoryTarget(documentId, name); if (!target) return;
|
|
241
|
+
var requestId = nextRequestId(); var tracked = { requestId: requestId, documentId: null, historyTarget: target };
|
|
242
|
+
sendPrepared(key, 'knowledge_history', { documentId: target.documentId }, tracked, beginHistoryRequest(state, target, requestId)); renderDetails();
|
|
243
|
+
}
|
|
244
|
+
function previewHistory(version) {
|
|
245
|
+
var key = workspaceKey(); var state = stateFor(key); var target = state.historyView; if (!target) return;
|
|
246
|
+
var requestId = nextRequestId(); var tracked = { requestId: requestId, documentId: target.documentKey, historyTarget: Object.assign({}, target, { preview: null }), historyVersion: version };
|
|
247
|
+
sendPrepared(key, 'knowledge_history_read', { documentId: target.documentId, version: version }, tracked, beginHistoryPreview(state, requestId)); renderDetails();
|
|
248
|
+
}
|
|
249
|
+
function reviewHistoryRestore() {
|
|
250
|
+
var key = workspaceKey(); var state = stateFor(key); if (!state.historyView || !state.historyView.preview) return;
|
|
251
|
+
patchState(key, { fullscreen: true }); render({ forceDocument: true });
|
|
252
|
+
}
|
|
253
|
+
function restoreHistory(version) {
|
|
254
|
+
var key = workspaceKey(); var state = stateFor(key); var target = state.historyView; if (!state.fullscreen || !target || !target.preview || target.preview.version !== version) return;
|
|
255
|
+
var targetDraft = target.documentKey && state.drafts[target.documentKey];
|
|
256
|
+
if (targetDraft && targetDraft.dirty) { patchState(key, { error: 'Save or discard the current draft before restoring its history.' }); renderStatus(); return; }
|
|
257
|
+
requestForKey(key, 'knowledge_restore', { documentId: target.documentId, version: version, name: target.name, expectedRevision: target.revision },
|
|
258
|
+
{ documentId: target.documentKey, historyRestore: true, historyTarget: target });
|
|
259
|
+
}
|
|
260
|
+
function readRestoredDocument(key, msg) {
|
|
261
|
+
var previous = stateFor(key); var state = Object.assign({}, clearHistoryView(previous), { fullscreen: false, error: '', deletedFiles: (previous.deletedFiles || []).filter(function (item) { return item.id !== msg.id; }) });
|
|
262
|
+
var file = { id: msg.id, name: msg.name, common: false, itemType: 'text', revision: msg.revision }; var id = documentIdentity(file); var files = state.files.slice(); var found = false;
|
|
263
|
+
files = files.map(function (item) { if (documentIdentity(item) !== id) return item; found = true; return Object.assign({}, item, file); }); if (!found) files.push(file); state.files = files;
|
|
264
|
+
if (key !== workspaceKey()) { setState(key, state); return; }
|
|
265
|
+
var requestId = nextRequestId(); var begun = beginKnowledgeRead(state, file, requestId, true);
|
|
266
|
+
sendPrepared(key, 'knowledge_read', { name: file.name, documentId: file.id, common: false, ownMateId: null }, begun.pending, begun.state);
|
|
267
|
+
}
|
|
268
|
+
function discardDraft() {
|
|
269
|
+
var key = workspaceKey(); var state = stateFor(key); var draft = currentDraft(state); if (!draft || draft.newDocument) return;
|
|
270
|
+
var requestId = nextRequestId(); sendPrepared(key, 'knowledge_discard_draft', { documentId: draft.file.id, name: draft.file.name,
|
|
271
|
+
expectedRevision: draft.diskRevision || draft.revision, throughVersion: Math.max(draft.version || 0, draft.draftVersion || 0) }, { requestId: requestId, documentId: state.selected, submittedVersion: draft.version || 0 }, null);
|
|
272
|
+
}
|
|
273
|
+
function enterEditor() {
|
|
274
|
+
var key = workspaceKey(); var state = stateFor(key); var draft = currentDraft(state); if (!draft || !draft.writable) return;
|
|
275
|
+
saveScroll(); patchDraft(key, state.selected, { mode: 'edit' }); patchState(key, { fullscreen: true }); render({ forceDocument: true });
|
|
276
|
+
}
|
|
277
|
+
function exitEditor() {
|
|
278
|
+
var key = workspaceKey(); var state = stateFor(key); if (!state.fullscreen) return;
|
|
279
|
+
saveScroll(); patchDraft(key, state.selected, { mode: 'read' }); patchState(key, { fullscreen: false }); destroyKnowledgeEditor(); render({ forceDocument: true });
|
|
280
|
+
}
|
|
281
|
+
function scheduleDraftSave(key, id) {
|
|
282
|
+
var timerKey = key + ':' + id; clearTimeout(draftSaveTimers[timerKey]);
|
|
283
|
+
draftSaveTimers[timerKey] = setTimeout(function () { delete draftSaveTimers[timerKey]; saveDraftNow(key, id); }, 600);
|
|
284
|
+
}
|
|
285
|
+
function saveDraftNow(key, id) {
|
|
286
|
+
var state = stateFor(key); var draft = state.drafts[id]; if (!draft || !draft.dirty || !draft.writable) return false;
|
|
287
|
+
if (key !== workspaceKey() || !getWs() || getWs().readyState !== 1) { patchDraft(key, id, { draftState: 'retry', error: draft.error || 'Draft will retry after reconnect.' }); return false; }
|
|
288
|
+
var requestId = nextRequestId(); var version = draft.version || 0; patchDraft(key, id, { draftState: 'pending' });
|
|
289
|
+
return sendPrepared(key, 'knowledge_draft_save', { documentId: draft.file.id || id, name: draft.newDocument ? draft.name : draft.file.name, content: draft.content,
|
|
290
|
+
baseRevision: draft.conflictRevision !== undefined ? draft.conflictRevision : draft.revision, cursor: draft.cursor || null,
|
|
291
|
+
clientVersion: version, newDocument: draft.newDocument === true }, { requestId: requestId, documentId: id, submittedVersion: version }, null);
|
|
292
|
+
}
|
|
293
|
+
function cancelDraftSaves(key) {
|
|
294
|
+
Object.keys(draftSaveTimers).forEach(function (timerKey) { if (timerKey.indexOf(key + ':') !== 0) return; clearTimeout(draftSaveTimers[timerKey]); delete draftSaveTimers[timerKey]; });
|
|
295
|
+
}
|
|
296
|
+
function saveDocument() {
|
|
297
|
+
var key = workspaceKey(); var state = stateFor(key); var draft = currentDraft(state); if (!draft || !draft.writable || draft.savingRequest || draft.conflict) return;
|
|
298
|
+
var input = panel.querySelector('[data-document-name]'); var name = draft.newDocument ? safeName(input && input.value) : draft.file.name;
|
|
299
|
+
if (!name) { patchDraft(key, state.selected, { error: 'Use a valid Markdown path without traversal or control characters.' }); renderToolbar(); return; }
|
|
300
|
+
var ws = readySocket(key); if (!ws) { setState(key, failKnowledgeOffline(stateFor(key), state.selected, 'Reconnect to save this document.')); renderToolbar(); return; }
|
|
301
|
+
var requestId = nextRequestId(); var begun = beginKnowledgeSave(state, state.selected, requestId, name);
|
|
302
|
+
sendPrepared(key, 'knowledge_save', { documentId: draft.file.id || state.selected, name: name, content: draft.content, expectedRevision: draft.revision,
|
|
303
|
+
draftVersion: begun.pending.draftVersion }, begun.pending, begun.state); renderToolbar();
|
|
304
|
+
}
|
|
305
|
+
function renameDocument() {
|
|
306
|
+
var key = workspaceKey(); var state = stateFor(key); var draft = currentDraft(state); var input = panel.querySelector('[data-document-name]'); var name = safeName(input && input.value);
|
|
307
|
+
if (!draft || !draft.writable || draft.newDocument || draft.savingRequest || draft.dirty || !name || name === draft.file.name) return;
|
|
308
|
+
if (!readySocket(key)) { setState(key, failKnowledgeOffline(stateFor(key), state.selected, 'Reconnect to rename this document.')); renderToolbar(); return; }
|
|
309
|
+
var requestId = nextRequestId(); var begun = beginKnowledgeRename(state, state.selected, requestId, name);
|
|
310
|
+
sendPrepared(key, 'knowledge_rename', { documentId: draft.file.id || null, from: draft.file.name, name: name, expectedRevision: draft.revision }, begun.pending, begun.state); renderToolbar();
|
|
311
|
+
}
|
|
312
|
+
function openDelete(control) {
|
|
313
|
+
var state = stateFor(workspaceKey()); var draft = currentDraft(state); if (!draft || !draft.writable || draft.newDocument) return;
|
|
314
|
+
deleteTarget = { key: workspaceKey(), documentId: state.selected, name: draft.file.name, revision: draft.revision, returnFocus: control };
|
|
315
|
+
panel.querySelector('.knowledge-topbar').inert = true; panel.querySelector('.knowledge-status').inert = true; panel.querySelector('.knowledge-layout').inert = true;
|
|
316
|
+
var dialog = panel.querySelector('.knowledge-delete-dialog'); dialog.classList.remove('hidden'); dialog.querySelector('[data-delete-cancel]').focus();
|
|
317
|
+
}
|
|
318
|
+
function closeDelete() {
|
|
319
|
+
if (!deleteTarget) return; var focus = deleteTarget.returnFocus; deleteTarget = null;
|
|
320
|
+
panel.querySelector('.knowledge-topbar').inert = false; panel.querySelector('.knowledge-status').inert = false; panel.querySelector('.knowledge-layout').inert = false; panel.querySelector('.knowledge-delete-dialog').classList.add('hidden');
|
|
321
|
+
if (focus && focus.isConnected) focus.focus();
|
|
322
|
+
}
|
|
323
|
+
function confirmDelete() {
|
|
324
|
+
if (!deleteTarget) return; var target = deleteTarget; var key = target.key;
|
|
325
|
+
if (!readySocket(key)) { closeDelete(); return; }
|
|
326
|
+
var requestId = nextRequestId(); var begun = beginKnowledgeDelete(stateFor(key), target.documentId, requestId, target.revision);
|
|
327
|
+
closeDelete(); sendPrepared(key, 'knowledge_delete', { name: target.name, expectedRevision: target.revision }, begun.pending, begun.state);
|
|
328
|
+
}
|
|
329
|
+
function retrySync() {
|
|
330
|
+
var key = workspaceKey(); var retry = stateFor(key).syncRetry; if (!retry || !retry.names) return;
|
|
331
|
+
simpleRequest('knowledge_sync', { names: retry.names }, { syncRetry: retry });
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function renderShell() {
|
|
335
|
+
var state = stateFor(workspaceKey()); panel.classList.toggle('knowledge-wide', state.wide); panel.classList.toggle('panel-fullscreen', state.fullscreen);
|
|
336
|
+
panel.querySelector('[data-knowledge-wide]').setAttribute('aria-pressed', String(state.wide)); panel.querySelector('[data-knowledge-full]').setAttribute('aria-pressed', String(state.fullscreen));
|
|
337
|
+
panel.querySelector('[data-knowledge-full]').hidden = !state.fullscreen; panel.querySelector('[data-knowledge-wide]').hidden = state.fullscreen;
|
|
338
|
+
panel.querySelector('[data-knowledge-back]').disabled = !(state.navigationBack || []).length; panel.querySelector('[data-knowledge-forward]').disabled = !(state.navigationForward || []).length;
|
|
339
|
+
}
|
|
340
|
+
function renderStatus() {
|
|
341
|
+
var state = stateFor(workspaceKey()); var draft = currentDraft(state); var message = state.error || draft && (draft.syncWarning || draft.indexWarning) || '';
|
|
342
|
+
var status = panel.querySelector('.knowledge-status'); status.innerHTML = message ? escapeText(message) + (state.syncRetry ? ' <button type="button" data-sync-retry>Retry index sync</button>' : '') : '';
|
|
343
|
+
}
|
|
344
|
+
function renderExplorer() {
|
|
345
|
+
if (!panel) return; var state = stateFor(workspaceKey()); var list = panel.querySelector('.knowledge-explorer-list'); var files = state.query.trim() ? state.searchFiles : state.files; list.innerHTML = '';
|
|
346
|
+
panel.querySelector('[data-knowledge-count]').textContent = state.query.trim() && state.searchTotal > files.length ? files.length + ' of ' + state.searchTotal : String(files.length);
|
|
347
|
+
if ((state.loading && !state.files.length) || state.searching) list.innerHTML = '<p class="knowledge-empty">' + (state.searching ? 'Searching document text…' : 'Reading project documents…') + '</p>';
|
|
348
|
+
else if (!files.length) list.innerHTML = '<p class="knowledge-empty">' + (state.query.trim() ? 'No matching names or document text' : 'No documents yet') + '</p>';
|
|
349
|
+
var lastFolder = null;
|
|
350
|
+
files.forEach(function (file) {
|
|
351
|
+
var parts = file.name.split('/'); var folder = parts.length > 1 ? parts.slice(0, -1).join('/') : '';
|
|
352
|
+
if (!state.query.trim() && folder !== lastFolder) { var folderRow = document.createElement('div'); folderRow.className = 'knowledge-folder-row'; folderRow.innerHTML = iconHtml('folder') + '<span>' + escapeText(folder || 'Top level') + '</span>'; list.appendChild(folderRow); lastFolder = folder; }
|
|
353
|
+
var id = documentIdentity(file); var button = document.createElement('button'); button.type = 'button'; button.className = 'knowledge-file-row'; button.dataset.openDocument = id; button.style.setProperty('--knowledge-depth', String(Math.min(parts.length - 1, 6))); if (id === state.selected) button.setAttribute('aria-current', 'true');
|
|
354
|
+
button.innerHTML = iconHtml(file.itemType === 'db' || file.name.endsWith('.jsonl') ? 'database' : 'file-text') + '<span>' + escapeText(parts[parts.length - 1].replace(/\.(md|jsonl)$/i, '')) + (file.snippet ? '<em>' + escapeText(file.snippet) + '</em>' : '') + '</span>' + (file.common ? '<small>shared</small>' : ''); list.appendChild(button);
|
|
355
|
+
});
|
|
356
|
+
if (state.query.trim() && state.searchFiles.length < state.searchTotal) { var more = document.createElement('button'); more.type = 'button'; more.className = 'knowledge-search-more'; more.dataset.searchMore = ''; more.disabled = state.searching; more.textContent = state.searching ? 'Loading more…' : 'Load more results'; list.appendChild(more); }
|
|
357
|
+
list.scrollTop = state.explorerScroll || 0; refreshIcons(list);
|
|
358
|
+
}
|
|
359
|
+
function renderTabs() {
|
|
360
|
+
var state = stateFor(workspaceKey()); var tabs = panel.querySelector('.knowledge-tabs'); tabs.innerHTML = '';
|
|
361
|
+
state.tabs.forEach(function (id) { var draft = state.drafts[id]; if (!draft) return; var shell = document.createElement('div'); shell.className = 'knowledge-tab-shell';
|
|
362
|
+
var label = draft.newDocument ? 'Untitled' : draft.file.name.replace(/\.md$/i, '');
|
|
363
|
+
shell.innerHTML = '<button type="button" class="knowledge-tab" data-select-tab="' + escapeAttr(id) + '" role="tab" aria-selected="' + String(id === state.selected) + '" tabindex="' + (id === state.selected ? '0' : '-1') + '"><span>' + escapeText(label) + (draft.dirty ? ' •' : '') + '</span></button><button type="button" class="knowledge-tab-close" data-close-tab="' + escapeAttr(id) + '" aria-label="Close ' + escapeAttr(label) + ' tab">×</button>'; tabs.appendChild(shell); });
|
|
364
|
+
}
|
|
365
|
+
function renderToolbar() {
|
|
366
|
+
var state = stateFor(workspaceKey()); var draft = currentDraft(state); var history = state.historyView; var bar = panel.querySelector('.knowledge-document-toolbar'); bar.innerHTML = '';
|
|
367
|
+
if (renderKnowledgeDatabase(panel)) return;
|
|
368
|
+
if (history && history.preview) {
|
|
369
|
+
bar.innerHTML = historyToolbarHtml(history, state.fullscreen); return;
|
|
370
|
+
}
|
|
371
|
+
if (!draft) return;
|
|
372
|
+
var readOnly = !draft.writable; var suffix = draft.file.name && draft.file.name.endsWith('.jsonl') ? '.jsonl' : '.md'; var shownName = draft.name != null ? draft.name : (draft.newDocument ? '' : draft.file.name.replace(/\.(md|jsonl)$/i, ''));
|
|
373
|
+
if (!state.fullscreen) {
|
|
374
|
+
bar.innerHTML = '<strong class="knowledge-preview-name">' + escapeText(draft.file.name || 'Untitled') + '</strong>' +
|
|
375
|
+
(readOnly ? '<span class="knowledge-readonly">Read only</span>' : '<button type="button" data-document-action="edit">' + iconHtml('pencil') + 'Edit fullscreen</button>');
|
|
376
|
+
} else {
|
|
377
|
+
bar.innerHTML = '<label class="knowledge-name"><span class="sr-only">Document path</span><input data-document-name value="' + escapeAttr(shownName) + '" ' + (readOnly ? 'readonly' : '') + (draft.savingRequest ? ' disabled' : '') + '><span>' + suffix + '</span></label><span class="knowledge-save-state">' + (draft.savingRequest ? 'Saving…' : draft.draftState === 'pending' ? 'Saving draft…' : draft.draftState === 'retry' ? 'Draft waiting for reconnect' : draft.draftState === 'saved' ? 'Draft saved on server' : draft.dirty ? 'Unsaved changes' : 'Saved') + '</span>' + (!readOnly ? '<button type="button" data-document-action="rename" ' + (draft.newDocument || draft.savingRequest || draft.dirty ? 'disabled' : '') + '>Move / rename</button><button type="button" data-document-action="save" class="primary" ' + (!draft.dirty || draft.savingRequest || draft.conflict ? 'disabled' : '') + '>' + (draft.savingRequest ? 'Saving…' : 'Save') + '</button><button type="button" data-document-action="delete" class="danger" ' + (draft.newDocument || draft.savingRequest ? 'disabled' : '') + ' aria-label="Delete document">' + iconHtml('trash-2') + '</button><button type="button" data-document-action="read">Exit editor</button>' : '<span class="knowledge-readonly">Read only</span>');
|
|
378
|
+
}
|
|
379
|
+
if (draft.error) bar.insertAdjacentHTML('beforeend', '<span class="knowledge-inline-error">' + escapeText(draft.error) + (draft.conflict ? ' <button type="button" data-document-action="keep">Keep draft</button> <button type="button" data-document-action="reload">Reload disk version</button>' : '') + '</span>'); refreshIcons(bar);
|
|
380
|
+
}
|
|
381
|
+
function renderDocument(forceReplace, preserveFromId) {
|
|
382
|
+
var state = stateFor(workspaceKey()); var draft = currentDraft(state); var history = state.historyView && state.historyView.preview; var body = panel.querySelector('.knowledge-document-body'); var mode = !history && draft && state.fullscreen && draft.writable ? 'edit' : 'read';
|
|
383
|
+
if (renderKnowledgeDatabase(panel)) return;
|
|
384
|
+
if (history) {
|
|
385
|
+
var historyId = 'history:' + state.historyView.documentId + ':' + history.version; if (!forceReplace && body.dataset.knowledgeDocumentId === historyId) return;
|
|
386
|
+
destroyKnowledgeEditor(); body.innerHTML = ''; body.dataset.knowledgeDocumentId = historyId; body.dataset.knowledgeDocumentMode = 'read'; body.dataset.knowledgeDocumentWritable = 'false';
|
|
387
|
+
var historical = document.createElement('article'); historical.className = 'knowledge-preview md-content'; historical.dataset.knowledgePreview = ''; historical.innerHTML = renderMarkdown(history.content || ''); body.appendChild(historical); highlightCodeBlocks(historical); return;
|
|
388
|
+
}
|
|
389
|
+
var sameDocument = draft && (body.dataset.knowledgeDocumentId === state.selected || body.dataset.knowledgeDocumentId === preserveFromId);
|
|
390
|
+
if (!forceReplace && sameDocument && body.dataset.knowledgeDocumentMode === mode && body.dataset.knowledgeDocumentWritable === String(!!draft.writable)) {
|
|
391
|
+
body.dataset.knowledgeDocumentId = state.selected; return;
|
|
392
|
+
}
|
|
393
|
+
destroyKnowledgeEditor(); body.innerHTML = ''; body.dataset.knowledgeDocumentId = draft ? state.selected : ''; body.dataset.knowledgeDocumentMode = draft ? mode : ''; body.dataset.knowledgeDocumentWritable = draft ? String(!!draft.writable) : '';
|
|
394
|
+
if (!draft) { body.innerHTML = '<div class="knowledge-welcome"><i data-lucide="book-open"></i><h2>Knowledge</h2><p>Open a document from the explorer or create a new Markdown note.</p></div>'; refreshIcons(body); return; }
|
|
395
|
+
if (mode === 'read') { var preview = document.createElement('article'); preview.className = 'knowledge-preview md-content'; preview.dataset.knowledgePreview = ''; preview.innerHTML = renderMarkdown(draft.content || ''); body.appendChild(preview); decorateKnowledgePreview(preview, draft.outgoing); highlightCodeBlocks(preview); preview.scrollTop = draft.readScroll || 0; }
|
|
396
|
+
else {
|
|
397
|
+
var split = document.createElement('div'); split.className = 'knowledge-editor-split'; var editorHost = document.createElement('div'); editorHost.className = 'knowledge-editor-host';
|
|
398
|
+
var live = document.createElement('article'); live.className = 'knowledge-live-preview md-content'; live.innerHTML = renderMarkdown(draft.content || ''); split.appendChild(editorHost); split.appendChild(live); body.appendChild(split); if (!draft.dirty) decorateKnowledgePreview(live, draft.outgoing); highlightCodeBlocks(live);
|
|
399
|
+
mountKnowledgeEditor(editorHost, { value: draft.content || '', cursor: draft.cursor, history: draft.editorHistory, files: state.files, currentName: draft.file.name || draft.name, onCursor: function (cursor) { patchDraft(workspaceKey(), state.selected, { cursor: cursor }); scheduleDraftSave(workspaceKey(), state.selected); }, onChange: function (content, cursor) {
|
|
400
|
+
var key = workspaceKey(); var current = stateFor(key); setState(key, editKnowledgeDraft(current, current.selected, { content: content, cursor: cursor, dirty: content !== current.drafts[current.selected].savedContent, error: current.drafts[current.selected].conflict ? current.drafts[current.selected].error : '' }));
|
|
401
|
+
live.innerHTML = renderMarkdown(content || ''); highlightCodeBlocks(live); renderDetails(); updateToolbarState(); scheduleDraftSave(key, current.selected);
|
|
402
|
+
} }); scrollKnowledgeEditor(draft.editScroll || 0);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
function updateToolbarState() {
|
|
406
|
+
var state = stateFor(workspaceKey()); var draft = currentDraft(state); if (!draft || !panel) return;
|
|
407
|
+
var status = panel.querySelector('.knowledge-save-state'); if (status) status.textContent = draft.draftState === 'pending' ? 'Saving draft…' : draft.draftState === 'retry' ? 'Draft waiting for reconnect' : draft.dirty ? 'Unsaved changes' : 'Saved';
|
|
408
|
+
var save = panel.querySelector('[data-document-action="save"]'); if (save) save.disabled = !draft.dirty || !!draft.savingRequest || !!draft.conflict;
|
|
409
|
+
var rename = panel.querySelector('[data-document-action="rename"]'); if (rename) rename.disabled = draft.newDocument || !!draft.savingRequest || !!draft.dirty;
|
|
410
|
+
}
|
|
411
|
+
function renderDetails() {
|
|
412
|
+
var state = stateFor(workspaceKey()); var draft = currentDraft(state); var history = state.historyView; var outline = panel.querySelector('[data-detail-content="outline"]'); var linkBox = panel.querySelector('[data-detail-content="links"]'); var historyBox = panel.querySelector('[data-detail-content="history"]'); outline.innerHTML = ''; linkBox.innerHTML = ''; historyBox.innerHTML = '';
|
|
413
|
+
panel.querySelector('.knowledge-details').hidden = !!(draft && draft.file.itemType === 'db'); if (draft && draft.file.itemType === 'db') return;
|
|
414
|
+
if (state.deletedFiles && state.deletedFiles.length) historyBox.innerHTML += '<small>RECOVERABLE KNOWLEDGE</small>' + state.deletedFiles.map(function (item) { return item.itemType === 'db' ? '<button type="button" class="knowledge-link-item" data-db-recover-database="' + escapeAttr(item.id) + '" data-db-recover-name="' + escapeAttr(item.name) + '" data-db-recover-revision="' + escapeAttr(item.revision) + '">' + iconHtml('database') + escapeText(item.name) + '</button>' : '<button type="button" class="knowledge-link-item" data-deleted-history="' + escapeAttr(item.id) + '" data-deleted-name="' + escapeAttr(item.name) + '">' + escapeText(item.name) + '</button>'; }).join('');
|
|
415
|
+
historyBox.innerHTML += historyVersionsHtml(history);
|
|
416
|
+
if (!draft) { outline.innerHTML = '<p>Open a document to see its outline.</p>'; linkBox.innerHTML = '<p>Document links appear here.</p>';
|
|
417
|
+
if (!historyBox.innerHTML) historyBox.innerHTML = '<p>Load deleted documents from the history button.</p>'; return; }
|
|
418
|
+
var outlineItems = draft.outline && !draft.dirty ? draft.outline : headings(draft.content); outline.innerHTML = outlineItems.length ? outlineItems.map(function (item) { return '<button type="button" class="knowledge-outline-item depth-' + item.depth + '" data-outline-heading="' + escapeAttr(item.slug || item.text) + '">' + escapeText(item.text) + '</button>'; }).join('') : '<p>No headings</p>';
|
|
419
|
+
var outbound = draft.outgoing || []; var inbound = draft.backlinks || []; var parts = [];
|
|
420
|
+
if (outbound.length) parts.push('<small>OUTGOING</small>' + outbound.map(function (item) { var target = item.documentKey || item.documentId && documentIdentity({ id: item.documentId, name: item.name, common: false }); return target ? '<button type="button" class="knowledge-link-item' + (item.heading && !item.headingValid ? ' unresolved' : '') + '" data-open-document="' + escapeAttr(target) + '" data-open-heading="' + escapeAttr(item.heading || '') + '">' + iconHtml('link-2') + '<span>' + escapeText(item.label) + (item.heading ? ' › ' + escapeText(item.heading) + (item.headingValid ? '' : ' (missing heading)') : '') + '</span></button>' : item.external ? '<span class="knowledge-link-item">' + escapeText(item.label) + ' (external)</span>' : '<button type="button" class="knowledge-link-item unresolved" data-create-linked="' + escapeAttr(item.target) + '">' + iconHtml('file-plus-2') + '<span>' + escapeText(item.label || item.target) + ' (create)</span></button>'; }).join(''));
|
|
421
|
+
if (inbound.length) parts.push('<small>BACKLINKS FROM WORKSPACE</small>' + inbound.map(function (item) { var id = item.documentKey || documentIdentity({ id: item.documentId, name: item.name, common: false }); return '<button type="button" class="knowledge-link-item knowledge-backlink" data-open-document="' + escapeAttr(id) + '">' + iconHtml('corner-up-left') + '<span><b>' + escapeText(item.name) + '</b><em>' + escapeText(item.snippet) + '</em></span></button>'; }).join(''));
|
|
422
|
+
if (draft.tags && draft.tags.length) parts.push('<small>TAGS</small><div class="knowledge-tags">' + draft.tags.map(function (tag) { return '<span>#' + escapeText(tag) + '</span>'; }).join('') + '</div>');
|
|
423
|
+
var propertyNames = Object.keys(draft.properties || {}); if (propertyNames.length) parts.push('<small>PROPERTIES</small><dl class="knowledge-properties">' + propertyNames.map(function (name) { return '<dt>' + escapeText(name) + '</dt><dd>' + escapeText(draft.properties[name]) + '</dd>'; }).join('') + '</dl>');
|
|
424
|
+
if (draft.propertyErrors && draft.propertyErrors.length) parts.push('<p class="knowledge-property-error">' + escapeText(draft.propertyErrors.join(' ')) + '</p>');
|
|
425
|
+
linkBox.innerHTML = parts.join('') || '<p>No document links</p>'; refreshIcons(linkBox);
|
|
426
|
+
if (draft && !draft.newDocument) historyBox.innerHTML += '<button type="button" class="knowledge-link-item" data-history-load>Load version history</button>';
|
|
427
|
+
}
|
|
428
|
+
function headingSlug(value) { return String(value || '').trim().toLowerCase().replace(/[^\p{L}\p{N}\s-]/gu, '').replace(/\s+/g, '-').replace(/-+/g, '-'); }
|
|
429
|
+
function scrollPendingHeading() {
|
|
430
|
+
if (!panel || !pendingHeading) return; var wanted = headingSlug(pendingHeading); var candidates = panel.querySelectorAll('.knowledge-preview h1, .knowledge-preview h2, .knowledge-preview h3, .knowledge-preview h4, .knowledge-preview h5, .knowledge-preview h6, .knowledge-live-preview h1, .knowledge-live-preview h2, .knowledge-live-preview h3, .knowledge-live-preview h4, .knowledge-live-preview h5, .knowledge-live-preview h6');
|
|
431
|
+
for (var i = 0; i < candidates.length; i++) if (headingSlug(candidates[i].textContent) === wanted) { candidates[i].scrollIntoView({ block: 'start' }); pendingHeading = null; return; }
|
|
432
|
+
}
|
|
433
|
+
function render(options) { if (!panel || panel.classList.contains('hidden')) return; var state = stateFor(workspaceKey()); var settings = options || {}; renderShell(); renderStatus(); panel.querySelector('.knowledge-search input').value = state.query; renderExplorer(); renderTabs(); renderToolbar(); renderDocument(settings.forceDocument === true, settings.preserveFromId); renderDetails(); }
|
|
434
|
+
function escapeText(value) { var span = document.createElement('span'); span.textContent = String(value || ''); return span.innerHTML; }
|
|
435
|
+
function escapeAttr(value) { return escapeText(value).replace(/"/g, '"'); }
|
|
436
|
+
|
|
437
|
+
export function openKnowledgeWorkbench() {
|
|
438
|
+
ensurePanel(); if (!panel || !isMateWorkspace(store.snap())) return; returnFocus = document.activeElement;
|
|
439
|
+
claimRightWorkbench('mate-knowledge'); panel.classList.remove('hidden'); setKnowledgeNarrowBackground(panel, true); var button = document.getElementById('knowledge-workbench-btn'); if (button) button.classList.add('active');
|
|
440
|
+
var key = workspaceKey(); var state = stateFor(key); var drafts = Object.assign({}, state.drafts); if (state.selected && drafts[state.selected]) drafts[state.selected] = Object.assign({}, drafts[state.selected], { mode: 'read' });
|
|
441
|
+
setState(key, Object.assign({}, state, { drafts: drafts, fullscreen: false, loading: true, error: '' })); simpleRequest('knowledge_list'); if (stateFor(key).query.trim()) { patchState(key, { searching: true }); requestSearch(key, stateFor(key).query, 0, false); } render(); setTimeout(function () { var search = panel.querySelector('.knowledge-search input'); if (search) search.focus(); }, 0);
|
|
442
|
+
}
|
|
443
|
+
export function closeKnowledgeWorkbench(skipSave) {
|
|
444
|
+
if (!panel) return; var active = document.activeElement; var shouldReturn = skipSave !== true && (!active || active === document.body || panel.contains(active));
|
|
445
|
+
var key = workspaceKey(); cancelScheduledSearch(); if (deleteTarget) closeDelete(); if (skipSave !== true) { saveScroll(); var selected = stateFor(key).selected; if (selected) saveDraftNow(key, selected); }
|
|
446
|
+
patchState(key, { fullscreen: false }); destroyKnowledgeEditor(); panel.classList.add('hidden'); setKnowledgeNarrowBackground(panel, false); releaseRightWorkbench('mate-knowledge');
|
|
447
|
+
var button = document.getElementById('knowledge-workbench-btn'); if (button) button.classList.remove('active'); if (shouldReturn && returnFocus && returnFocus.isConnected) returnFocus.focus(); returnFocus = null;
|
|
448
|
+
}
|
|
449
|
+
export function initMateKnowledgeWorkbench() {
|
|
450
|
+
ensurePanel(); configureKnowledgeDbWorkbench({ render: render, openDocument: openDocument, documentIdentity: documentIdentity }); registerRightWorkbench('mate-knowledge', closeKnowledgeWorkbench); var button = ensureButton(); if (button) button.addEventListener('click', function () { if (panel.classList.contains('hidden')) openKnowledgeWorkbench(); else closeKnowledgeWorkbench(); });
|
|
451
|
+
store.subscribe(function (state, previous) { if (state.currentSlug !== previous.currentSlug) { saveScroll(previous.currentSlug); cancelDraftSaves(previous.currentSlug || ''); patchState(previous.currentSlug || '', { fullscreen: false }); closeKnowledgeWorkbench(true); } });
|
|
452
|
+
window.addEventListener('resize', function () { if (panel && !panel.classList.contains('hidden')) setKnowledgeNarrowBackground(panel, true); });
|
|
453
|
+
}
|
|
454
|
+
export function handleKnowledgeWorkbenchMessage(msg) {
|
|
455
|
+
if (handleKnowledgeDbMessage(msg)) return true;
|
|
456
|
+
var tracked = msg.requestId && pending[msg.requestId]; if (!tracked) return false; delete pending[msg.requestId]; var key = tracked.key; var state = stateFor(key); var forceDocument = false;
|
|
457
|
+
if (msg.type === 'knowledge_list') {
|
|
458
|
+
var nextFiles = msg.files || []; var drafts = Object.assign({}, state.drafts); var refresh = [];
|
|
459
|
+
(msg.drafts || []).forEach(function (savedDraft) {
|
|
460
|
+
if (!savedDraft.newDocument || drafts[savedDraft.documentId]) return;
|
|
461
|
+
var file = { id: savedDraft.documentId, clientIdentity: savedDraft.documentId, name: savedDraft.name || 'Recovered draft.md', itemType: 'text', writable: true, draftOnly: true };
|
|
462
|
+
nextFiles.push(file); drafts[savedDraft.documentId] = { file: file, content: savedDraft.content, savedContent: '', revision: null, dirty: true, writable: true,
|
|
463
|
+
newDocument: true, mode: 'read', name: String(savedDraft.name || '').replace(/\.md$/i, ''), version: savedDraft.clientVersion || 0,
|
|
464
|
+
draftVersion: savedDraft.clientVersion || 0, cursor: savedDraft.cursor || null, draftState: 'saved', recovered: true, error: '' };
|
|
465
|
+
});
|
|
466
|
+
Object.keys(drafts).forEach(function (id) {
|
|
467
|
+
var draft = drafts[id]; if (draft.newDocument || draft.file.common) return;
|
|
468
|
+
var current = nextFiles.filter(function (file) { return documentIdentity(file) === id; })[0];
|
|
469
|
+
if (!current) { if (draft.dirty) drafts[id] = Object.assign({}, draft, { conflict: true, error: 'This document was removed outside Clay. Keep the draft and save it under a new path.' }); return; }
|
|
470
|
+
var changed = current.revision !== draft.revision; drafts[id] = Object.assign({}, draft, { file: Object.assign({}, draft.file, current) });
|
|
471
|
+
if (changed && draft.dirty) drafts[id] = Object.assign({}, drafts[id], { conflict: true, diskRevision: current.revision, error: 'The disk version changed while this draft was open. Keep the draft or reload the disk version.' });
|
|
472
|
+
else if (changed) refresh.push(id);
|
|
473
|
+
});
|
|
474
|
+
setState(key, Object.assign({}, state, { files: nextFiles, drafts: drafts, loading: false, error: '' })); if (key === workspaceKey()) {
|
|
475
|
+
render(); if (refresh.indexOf(state.selected) !== -1) openDocument(state.selected, true);
|
|
476
|
+
Object.keys(drafts).forEach(function (id) { if (drafts[id].dirty && drafts[id].draftState === 'retry') scheduleDraftSave(key, id); });
|
|
477
|
+
} return true;
|
|
478
|
+
}
|
|
479
|
+
if (msg.type === 'knowledge_search_results') { if (state.query === tracked.query && (!tracked.append || tracked.offset === state.searchFiles.length)) setState(key, Object.assign({}, state, { searchFiles: tracked.append ? state.searchFiles.concat(msg.files || []) : msg.files || [], searchTotal: msg.total || 0, searching: false,
|
|
480
|
+
error: msg.complete === false ? 'Some workspace documents could not be indexed; search results are incomplete.' : '' })); if (key === workspaceKey()) render(); return true; }
|
|
481
|
+
if (msg.type === 'knowledge_content') { var beforeRead = state.drafts[tracked.documentId]; var readState = applyKnowledgeRead(state, tracked, msg); forceDocument = tracked.replace === true && readState.drafts[tracked.documentId] !== beforeRead; setState(key, readState); }
|
|
482
|
+
else if (msg.type === 'knowledge_saved') { var savedState = applyKnowledgeSave(state, tracked, msg); setState(key, savedState); var savedId = savedState.tabs.filter(function (id) { return savedState.drafts[id] && savedState.drafts[id].file.id === msg.id; })[0]; if (savedId && savedState.drafts[savedId].dirty) scheduleDraftSave(key, savedId); }
|
|
483
|
+
else if (msg.type === 'knowledge_renamed') setState(key, applyKnowledgeRename(state, tracked, msg));
|
|
484
|
+
else if (msg.type === 'knowledge_deleted') setState(key, applyKnowledgeDelete(state, tracked, msg));
|
|
485
|
+
else if (msg.type === 'knowledge_draft_saved') {
|
|
486
|
+
var savedDraft = stateFor(key).drafts[tracked.documentId]; if (savedDraft && (savedDraft.version || 0) === tracked.submittedVersion) patchDraft(key, tracked.documentId, { draftState: 'saved', draftSavedAt: msg.savedAt, draftVersion: msg.clientVersion });
|
|
487
|
+
else if (savedDraft) { patchDraft(key, tracked.documentId, { draftState: 'pending', draftVersion: msg.clientVersion }); scheduleDraftSave(key, tracked.documentId); }
|
|
488
|
+
}
|
|
489
|
+
else if (msg.type === 'knowledge_draft_deleted') { patchDraft(key, tracked.documentId, { draftState: '' }); }
|
|
490
|
+
else if (msg.type === 'knowledge_draft_discarded') { setState(key, applyKnowledgeDiscard(state, tracked, msg)); forceDocument = true; }
|
|
491
|
+
else if (msg.type === 'knowledge_history') setState(key, receiveHistoryVersions(state, tracked, msg.versions || []));
|
|
492
|
+
else if (msg.type === 'knowledge_history_version') { setState(key, receiveHistoryPreview(state, tracked, { version: msg.version, content: msg.content })); forceDocument = true; }
|
|
493
|
+
else if (msg.type === 'knowledge_deleted_list') setState(key, Object.assign({}, state, { deletedFiles: msg.files || [] }));
|
|
494
|
+
else if (msg.type === 'knowledge_restored') { readRestoredDocument(key, msg); return true; }
|
|
495
|
+
else if (msg.type === 'knowledge_synced') { var drafts = Object.assign({}, state.drafts); Object.keys(drafts).forEach(function (id) { if (drafts[id].syncWarning) drafts[id] = Object.assign({}, drafts[id], { syncWarning: '' }); }); setState(key, Object.assign({}, state, { drafts: drafts, syncRetry: null, error: '' })); }
|
|
496
|
+
else if (msg.type === 'knowledge_error') { if (tracked.type === 'knowledge_sync') setState(key, Object.assign({}, state, { error: msg.error || 'Knowledge index synchronization is still pending.' })); else setState(key, failKnowledgeRequest(state, tracked, msg)); }
|
|
497
|
+
else return false;
|
|
498
|
+
if (key === workspaceKey()) { if (['knowledge_saved', 'knowledge_renamed', 'knowledge_deleted'].indexOf(msg.type) !== -1) simpleRequest('knowledge_list'); render({ forceDocument: forceDocument, preserveFromId: ['knowledge_saved', 'knowledge_renamed'].indexOf(msg.type) !== -1 ? tracked.documentId : null }); if (pendingHeading) setTimeout(scrollPendingHeading, 0); }
|
|
499
|
+
return true;
|
|
500
|
+
}
|
|
@@ -41,6 +41,7 @@ var nameGroupEl = null;
|
|
|
41
41
|
var editingFile = null;
|
|
42
42
|
var editingCommon = false;
|
|
43
43
|
var editingOwnMateId = null;
|
|
44
|
+
var editingRevision = null;
|
|
44
45
|
var viewingContent = "";
|
|
45
46
|
var dirty = false;
|
|
46
47
|
var mode = "none"; // "none" | "viewer" | "editor"
|
|
@@ -113,6 +114,7 @@ export function initMateKnowledge(wsGetter) {
|
|
|
113
114
|
// New file goes straight to editor
|
|
114
115
|
editingFile = null;
|
|
115
116
|
editingCommon = false;
|
|
117
|
+
editingRevision = null;
|
|
116
118
|
viewingContent = "";
|
|
117
119
|
openEditor(null, "");
|
|
118
120
|
});
|
|
@@ -125,7 +127,7 @@ export function initMateKnowledge(wsGetter) {
|
|
|
125
127
|
if (editingFile) {
|
|
126
128
|
var ws = getWs ? getWs() : null;
|
|
127
129
|
if (ws && ws.readyState === 1) {
|
|
128
|
-
ws.send(JSON.stringify({ type: "knowledge_delete", name: editingFile }));
|
|
130
|
+
ws.send(JSON.stringify({ type: "knowledge_delete", name: editingFile, expectedRevision: editingRevision }));
|
|
129
131
|
}
|
|
130
132
|
closePanel();
|
|
131
133
|
}
|
|
@@ -200,6 +202,7 @@ function closePanel() {
|
|
|
200
202
|
if (viewerEl) viewerEl.classList.add("hidden");
|
|
201
203
|
if (editorEl) editorEl.classList.add("hidden");
|
|
202
204
|
editingFile = null;
|
|
205
|
+
editingRevision = null;
|
|
203
206
|
mode = "none";
|
|
204
207
|
renderFileList();
|
|
205
208
|
}
|
|
@@ -342,6 +345,7 @@ export function renderKnowledgeList(files) {
|
|
|
342
345
|
export function handleKnowledgeContent(msg) {
|
|
343
346
|
editingCommon = !!msg.common;
|
|
344
347
|
editingOwnMateId = msg.ownMateId || null;
|
|
348
|
+
editingRevision = msg.revision || null;
|
|
345
349
|
editingFile = msg.name || null;
|
|
346
350
|
viewingContent = msg.content || "";
|
|
347
351
|
|
|
@@ -357,6 +361,10 @@ export function handleKnowledgeContent(msg) {
|
|
|
357
361
|
showViewer(msg.name, viewingContent);
|
|
358
362
|
}
|
|
359
363
|
|
|
364
|
+
export function handleKnowledgeMutation(msg) {
|
|
365
|
+
if (msg.type === "knowledge_saved" && msg.name === editingFile) editingRevision = msg.revision || editingRevision;
|
|
366
|
+
}
|
|
367
|
+
|
|
360
368
|
// --- File list rendering ---
|
|
361
369
|
|
|
362
370
|
function renderFileList() {
|
|
@@ -556,7 +564,7 @@ function saveKnowledge() {
|
|
|
556
564
|
}
|
|
557
565
|
var ws = getWs ? getWs() : null;
|
|
558
566
|
if (ws && ws.readyState === 1) {
|
|
559
|
-
ws.send(JSON.stringify({ type: "knowledge_save", name: name, content: content }));
|
|
567
|
+
ws.send(JSON.stringify({ type: "knowledge_save", name: name, content: content, expectedRevision: editingRevision }));
|
|
560
568
|
}
|
|
561
569
|
editingFile = name;
|
|
562
570
|
viewingContent = content;
|
|
@@ -772,7 +780,7 @@ function showFileMenu(file, anchorBtn) {
|
|
|
772
780
|
e.stopPropagation();
|
|
773
781
|
var ws = getWs ? getWs() : null;
|
|
774
782
|
if (ws && ws.readyState === 1) {
|
|
775
|
-
ws.send(JSON.stringify({ type: "knowledge_delete", name: file.name }));
|
|
783
|
+
ws.send(JSON.stringify({ type: "knowledge_delete", name: file.name, expectedRevision: file.revision || null }));
|
|
776
784
|
}
|
|
777
785
|
hideFileMenu();
|
|
778
786
|
if (editingFile === file.name) closePanel();
|