clay-server 5.1.0-beta.1 → 5.1.0-beta.2
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/lib/daemon-project-access.js +7 -1
- package/lib/daemon.js +2 -0
- 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/mate-knowledge-mcp-server.js +29 -6
- package/lib/mate-knowledge-service.js +23 -2
- package/lib/mate-knowledge-sync.js +36 -21
- package/lib/mate-workspace-color.js +4 -0
- package/lib/mates.js +18 -0
- package/lib/project-connection.js +36 -2
- package/lib/project-debate-proposal.js +6 -0
- package/lib/project-debate-workspace.js +43 -0
- package/lib/project-debate.js +1 -0
- package/lib/project-knowledge.js +315 -136
- package/lib/project-mate-creation-proposal.js +1 -0
- package/lib/project-sessions.js +39 -2
- package/lib/project-user-knowledge.js +60 -0
- package/lib/project.js +59 -18
- package/lib/public/app.js +73 -34
- 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/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/messages.css +1 -114
- package/lib/public/css/mobile-nav.css +16 -16
- package/lib/public/css/overlays.css +8 -0
- package/lib/public/css/project-logs.css +7 -0
- package/lib/public/css/project-mate-creation.css +105 -0
- package/lib/public/css/right-workbench.css +3 -3
- package/lib/public/css/session-folders.css +80 -21
- package/lib/public/css/sidebar.css +183 -107
- package/lib/public/css/title-bar.css +151 -0
- 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 +33 -101
- package/lib/public/modules/account-island.js +230 -0
- package/lib/public/modules/app-cursors.js +19 -43
- package/lib/public/modules/app-debate-ui.js +4 -3
- 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 +34 -9
- package/lib/public/modules/app-notifications.js +4 -7
- package/lib/public/modules/app-projects.js +5 -38
- package/lib/public/modules/app-rendering.js +12 -13
- package/lib/public/modules/avatar.js +25 -9
- package/lib/public/modules/chat-render-runtime.js +16 -4
- package/lib/public/modules/command-palette.js +2 -15
- 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 +81 -65
- 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/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/profile.js +51 -18
- 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-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/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-list-visibility.js +41 -0
- package/lib/public/modules/session-presence-ui.js +4 -1
- package/lib/public/modules/session-title-marquee.js +51 -0
- 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/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/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 +16 -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/server-admin.js +11 -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-mate-instructions.js +44 -0
- package/lib/server-mates.js +12 -3
- package/lib/server-palette.js +1 -1
- package/lib/server-settings.js +65 -1
- package/lib/server-user-knowledge.js +119 -0
- package/lib/server.js +21 -1
- package/lib/session-visibility.js +7 -2
- package/lib/sessions.js +32 -6
- package/lib/user-knowledge-service.js +167 -0
- package/lib/users-permissions.js +7 -0
- package/lib/users-project-mate-filter-preferences.js +39 -0
- package/lib/users.js +6 -0
- package/lib/workspace-query-access.js +4 -1
- package/lib/ws-schema.js +50 -0
- package/package.json +10 -1
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { store } from './store.js';
|
|
2
|
+
import { getWs } from './ws-ref.js';
|
|
3
|
+
import { iconHtml, refreshIcons } from './icons.js';
|
|
4
|
+
import { isRecordEdit, buildRecordAttempt, buildCreateAttempt, isCurrentAttempt, applyRecordSave, updateViewPresentation } from './knowledge-db-edit-state.js';
|
|
5
|
+
|
|
6
|
+
var requestCounter = 0;
|
|
7
|
+
var pending = {};
|
|
8
|
+
var renderOwner = null;
|
|
9
|
+
var openOwnerDocument = null;
|
|
10
|
+
var identityOwner = null;
|
|
11
|
+
|
|
12
|
+
function setDialogBackground(panel, inert) {
|
|
13
|
+
Array.prototype.forEach.call(panel.children, function (child) {
|
|
14
|
+
if (child.classList.contains('knowledge-db-dialog')) return;
|
|
15
|
+
if (inert) { child.dataset.knowledgeDialogWasInert = child.inert ? 'true' : 'false'; child.dataset.knowledgeDialogAriaHidden = child.getAttribute('aria-hidden') == null ? '' : child.getAttribute('aria-hidden'); child.inert = true; child.setAttribute('aria-hidden', 'true'); }
|
|
16
|
+
else if (child.dataset.knowledgeDialogWasInert !== undefined) { child.inert = child.dataset.knowledgeDialogWasInert === 'true'; if (child.dataset.knowledgeDialogAriaHidden === '') child.removeAttribute('aria-hidden'); else child.setAttribute('aria-hidden', child.dataset.knowledgeDialogAriaHidden); delete child.dataset.knowledgeDialogWasInert; delete child.dataset.knowledgeDialogAriaHidden; }
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function workspaceKey() { return store.get('currentSlug') || ''; }
|
|
21
|
+
function allStates() { return store.get('mateKnowledgeDatabaseWorkspaces') || {}; }
|
|
22
|
+
function stateFor(key) { return allStates()[key] || { databases: {}, createDraft: null, dialog: null, error: '' }; }
|
|
23
|
+
function setState(key, value) { var states = Object.assign({}, allStates()); states[key] = value; store.set({ mateKnowledgeDatabaseWorkspaces: states }); }
|
|
24
|
+
function patchState(key, patch) { setState(key, Object.assign({}, stateFor(key), patch)); }
|
|
25
|
+
function databaseState(key, id) { return stateFor(key).databases[id] || { database: null, records: [], nextCursor: null, selectedRecord: null, recordDetail: null, edit: null, loading: true, error: '' }; }
|
|
26
|
+
function setDatabaseState(key, id, value) { var outer = stateFor(key); var databases = Object.assign({}, outer.databases); databases[id] = value; setState(key, Object.assign({}, outer, { databases: databases })); }
|
|
27
|
+
function patchDatabase(key, id, patch) { setDatabaseState(key, id, Object.assign({}, databaseState(key, id), patch)); }
|
|
28
|
+
function mainState(key) { var all = store.get('mateKnowledgeWorkspaces') || {}; return all[key] || null; }
|
|
29
|
+
function patchMain(key, patch) { var all = Object.assign({}, store.get('mateKnowledgeWorkspaces') || {}); if (!all[key]) return; all[key] = Object.assign({}, all[key], patch); store.set({ mateKnowledgeWorkspaces: all }); }
|
|
30
|
+
function ownerToken(key) { var main = mainState(key) || {}; return { ownerSelection: main.selected || null, ownerSelectionVersion: main.selectionVersion || 0 }; }
|
|
31
|
+
function stillOwns(key, tracked) { var main = mainState(key); return !!(main && main.selected === tracked.ownerSelection && (main.selectionVersion || 0) === tracked.ownerSelectionVersion); }
|
|
32
|
+
function operationId(prefix) { requestCounter++; return prefix + '_' + Date.now() + '_' + requestCounter; }
|
|
33
|
+
function send(key, type, details, track) {
|
|
34
|
+
var ws = getWs(); if (key !== workspaceKey() || !ws || ws.readyState !== 1) return null;
|
|
35
|
+
var requestId = 'knowledge-db-' + Date.now() + '-' + (++requestCounter); pending[requestId] = Object.assign({ key: key, type: type, socket: ws }, track || {});
|
|
36
|
+
try { ws.send(JSON.stringify(Object.assign({ type: type, requestId: requestId }, details || {}))); return requestId; }
|
|
37
|
+
catch (cause) { delete pending[requestId]; return null; }
|
|
38
|
+
}
|
|
39
|
+
function escapeText(value) { var span = document.createElement('span'); span.textContent = String(value == null ? '' : value); return span.innerHTML.replace(/"/g, '"').replace(/'/g, '''); }
|
|
40
|
+
function escapeAttr(value) { return escapeText(value).replace(/"/g, '"').replace(/'/g, '''); }
|
|
41
|
+
function selectedFile(key) { var state = mainState(key); return state && state.selected && state.drafts[state.selected] ? state.drafts[state.selected].file : null; }
|
|
42
|
+
function active(key) { var file = selectedFile(key); return file && file.itemType === 'db' ? databaseState(key, file.id) : null; }
|
|
43
|
+
function titleField(database) { return database && database.schema.filter(function (field) { return field.type === 'title' && !field.archived; })[0]; }
|
|
44
|
+
function fieldById(database, id) { return database.schema.filter(function (field) { return field.id === id; })[0] || null; }
|
|
45
|
+
function optionName(field, id) { var option = (field.options || []).filter(function (item) { return item.id === id; })[0]; return option ? option.name : 'Missing option'; }
|
|
46
|
+
function displayValue(field, value, references) {
|
|
47
|
+
if (value === null || value === undefined || value === '') return '<span class="knowledge-db-empty">—</span>';
|
|
48
|
+
if (field.type === 'boolean') return value ? 'Yes' : 'No';
|
|
49
|
+
if (field.type === 'select') return escapeText(optionName(field, value));
|
|
50
|
+
if (field.type === 'multi-select') return value.map(function (id) { return '<span class="knowledge-db-pill">' + escapeText(optionName(field, id)) + '</span>'; }).join(' ');
|
|
51
|
+
if (field.type === 'reference') { var reference = (references || []).filter(function (item) { return item.fieldId === field.id; })[0] || { status: 'missing' }; var label = reference.label || value.id; if (reference.status !== 'available') return '<span class="knowledge-db-reference is-' + escapeText(reference.status) + '">' + iconHtml('circle-alert') + escapeText(label) + ' (' + escapeText(reference.status) + ')</span>'; return '<button type="button" class="knowledge-db-reference" data-db-reference="' + escapeText(value.id) + '" data-db-reference-type="' + escapeText(value.type) + '" data-db-reference-database="' + escapeText(value.databaseId || '') + '">' + iconHtml(value.type === 'text' ? 'file-text' : 'link') + escapeText(label) + '</button>'; }
|
|
52
|
+
return escapeText(value);
|
|
53
|
+
}
|
|
54
|
+
function query(key, databaseId, append, cursorValue) { var state = databaseState(key, databaseId); var viewId = state.selectedViewId || state.database && state.database.views[0] && state.database.views[0].id || null; var epoch = (state.queryEpoch || 0) + 1; patchDatabase(key, databaseId, { loading: true, queryEpoch: epoch, error: state.syncPending ? state.error : '' }); var sent = send(key, 'knowledge_db_query', { databaseId: databaseId, viewId: viewId, cursor: cursorValue || null, limit: 50, archived: state.showArchived === true }, { databaseId: databaseId, append: append === true, queryEpoch: epoch, viewId: viewId, archived: state.showArchived === true }); if (!sent) patchDatabase(key, databaseId, { loading: false, error: 'Reconnect to query this database.' }); }
|
|
55
|
+
|
|
56
|
+
export function configureKnowledgeDbWorkbench(callback) { renderOwner = typeof callback === 'function' ? callback : callback.render; openOwnerDocument = callback.openDocument || null; identityOwner = callback.documentIdentity || null; }
|
|
57
|
+
export function openKnowledgeDatabase(file) {
|
|
58
|
+
var key = workspaceKey(); patchDatabase(key, file.id, { loading: true, error: '' });
|
|
59
|
+
if (!send(key, 'knowledge_db_read', { databaseId: file.id }, { databaseId: file.id })) patchDatabase(key, file.id, { loading: false, error: 'Reconnect to open this database.' });
|
|
60
|
+
}
|
|
61
|
+
export function showKnowledgeCreateMenu(panel) {
|
|
62
|
+
var existing = panel.querySelector('.knowledge-create-menu'); if (existing) { existing.remove(); return; }
|
|
63
|
+
var menu = document.createElement('div'); menu.className = 'knowledge-create-menu'; menu.setAttribute('role', 'menu'); menu.innerHTML = '<button type="button" role="menuitem" data-db-create-document>' + iconHtml('file-plus-2') + '<span>New document<small>Markdown text</small></span></button><button type="button" role="menuitem" data-db-create-database>' + iconHtml('database') + '<span>New database<small>Typed table</small></span></button>';
|
|
64
|
+
panel.querySelector('.knowledge-topbar').appendChild(menu); refreshIcons(menu); menu.querySelector('button').focus();
|
|
65
|
+
}
|
|
66
|
+
export function beginKnowledgeDatabaseCreate(panel) {
|
|
67
|
+
var key = workspaceKey(); var owner = ownerToken(key); var existing = stateFor(key).createDraft; patchState(key, { createDraft: Object.assign({}, existing || { name: '', saving: false, error: '', operationId: operationId('create') }, owner), error: '' }); patchMain(key, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true });
|
|
68
|
+
setTimeout(function () { var input = panel.querySelector('[data-db-new-name]'); if (input) input.focus(); }, 0);
|
|
69
|
+
}
|
|
70
|
+
export function beginKnowledgeDatabaseRecovery(panel, item) {
|
|
71
|
+
var key = workspaceKey(); var owner = ownerToken(key); patchState(key, { recoveryDraft: { item: item, operationId: operationId('recover-database'), saving: false, error: '', ownerSelection: owner.ownerSelection, ownerSelectionVersion: owner.ownerSelectionVersion } }); patchMain(key, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true });
|
|
72
|
+
}
|
|
73
|
+
function submitCreate(panel) {
|
|
74
|
+
var key = workspaceKey(); var draft = stateFor(key).createDraft; var input = panel.querySelector('[data-db-new-name]'); var name = input && input.value.trim(); if (!name) { patchState(key, { createDraft: Object.assign({}, draft, { name: '', saving: false, error: 'Enter a database name.' }) }); if (renderOwner) renderOwner({ forceDocument: true }); return; }
|
|
75
|
+
var attempt = buildCreateAttempt(draft, name); draft = Object.assign({}, draft, { name: draft.attempt ? draft.name : name, saving: true, retryPending: false, attempt: attempt, error: '' }); patchState(key, { createDraft: draft }); var sent = send(key, 'knowledge_db_create', { name: attempt.name, operationId: attempt.operationId }, Object.assign({ create: true, createOperationId: attempt.operationId }, ownerToken(key))); if (!sent) { patchState(key, { createDraft: Object.assign({}, draft, { saving: false, retryPending: true, error: 'Reconnect to create this database.' }) }); if (renderOwner) renderOwner({ forceDocument: true }); return; } input.disabled = true; var button = panel.querySelector('[data-db-create-submit]'); if (button) button.textContent = 'Retry create';
|
|
76
|
+
}
|
|
77
|
+
function selectedRecordState(key, databaseId) { var dbState = databaseState(key, databaseId); return dbState.recordDetail && dbState.recordDetail.record && dbState.recordDetail.record.id === dbState.selectedRecord ? dbState.recordDetail : null; }
|
|
78
|
+
function openRecord(databaseId, recordId, keyValue) { var key = keyValue || workspaceKey(); var state = databaseState(key, databaseId); var generation = (state.recordGeneration || 0) + 1; var drafts = Object.assign({}, state.recordDrafts || {}); if (isRecordEdit(state.edit) && state.edit.recordId) drafts[state.edit.recordId] = state.edit; patchDatabase(key, databaseId, { selectedRecord: recordId, recordDetail: null, recordGeneration: generation, recordDrafts: drafts, edit: drafts[recordId] || null }); if (!send(key, 'knowledge_db_record_read', { databaseId: databaseId, recordId: recordId }, { databaseId: databaseId, recordId: recordId, recordGeneration: generation })) patchDatabase(key, databaseId, { error: 'Reconnect to open this record.' }); }
|
|
79
|
+
function beginRecordEdit(databaseId, creating) {
|
|
80
|
+
var key = workspaceKey(); var state = databaseState(key, databaseId); var detail = selectedRecordState(key, databaseId); var recordId = creating ? null : detail.record.id; var saved = !creating && (state.recordDrafts || {})[recordId]; var generation = (state.editGeneration || 0) + 1; var edit = saved || { id: generation, creating: creating === true, recordId: recordId, values: creating ? {} : Object.assign({}, detail.record.values), baseVersion: detail && detail.record.revision || null, version: 0, saving: false, retryPending: false, attempt: null, operationId: operationId('record'), error: '' }; patchDatabase(key, databaseId, { editGeneration: generation, edit: Object.assign({}, edit, { id: generation }) }); patchMain(key, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true });
|
|
81
|
+
}
|
|
82
|
+
function inputValue(input, field) {
|
|
83
|
+
if (field.type === 'boolean') return input.checked;
|
|
84
|
+
if (field.type === 'number') return input.value === '' ? null : Number(input.value);
|
|
85
|
+
if (field.type === 'multi-select') return Array.prototype.filter.call(input.options, function (option) { return option.selected; }).map(function (option) { return option.value; });
|
|
86
|
+
if (field.type === 'reference') { if (!input.value.trim()) return null; var parts = input.value.trim().split(':'); return field.target === 'text' ? { type: 'text', id: input.value.trim() } : { type: 'record', databaseId: parts[0], id: parts.slice(1).join(':') }; }
|
|
87
|
+
return input.value === '' ? null : input.value;
|
|
88
|
+
}
|
|
89
|
+
function submitRecord(panel, databaseId) {
|
|
90
|
+
var key = workspaceKey(); var state = databaseState(key, databaseId); var values = {}; panel.querySelectorAll('[data-db-field]').forEach(function (input) { var field = fieldById(state.database, input.dataset.dbField); values[field.id] = inputValue(input, field); });
|
|
91
|
+
var attempt = buildRecordAttempt(state.edit, databaseId, state.database.revision, values); var savingEdit = Object.assign({}, state.edit, { values: values, saving: true, retryPending: false, attempt: attempt, error: '' }); patchDatabase(key, databaseId, { edit: savingEdit }); var tracking = Object.assign({ databaseId: databaseId, recordId: attempt.recordId, saveRecord: true, creating: attempt.creating, submittedVersion: attempt.submittedVersion, editId: state.edit.id, attempt: attempt }, ownerToken(key)); if (!send(key, attempt.type, attempt.payload, tracking)) { patchDatabase(key, databaseId, { edit: Object.assign({}, savingEdit, { saving: false, retryPending: true, error: 'Reconnect to save this record.' }) }); if (renderOwner) renderOwner({ forceDocument: true }); return; } var saveButton = panel.querySelector('[data-db-save-record]'); if (saveButton) saveButton.textContent = 'Retry save';
|
|
92
|
+
}
|
|
93
|
+
export function handleKnowledgeDbInput(event) {
|
|
94
|
+
var viewSelect = event.target.closest('[data-db-view-select]'); if (viewSelect) { var viewKey = workspaceKey(); patchDatabase(viewKey, viewSelect.dataset.dbViewSelect, { selectedViewId: viewSelect.value, records: [], nextCursor: null }); query(viewKey, viewSelect.dataset.dbViewSelect, false, null); return true; }
|
|
95
|
+
var newName = event.target.closest('[data-db-new-name]'); if (newName) { var createKey = workspaceKey(); var createDraft = stateFor(createKey).createDraft; if (createDraft && !createDraft.attempt) patchState(createKey, { createDraft: Object.assign({}, createDraft, { name: newName.value, error: '' }) }); return true; }
|
|
96
|
+
var settingsName = event.target.closest('[data-db-settings-name]'); if (settingsName) { var settingsKey = workspaceKey(); var settingsFile = selectedFile(settingsKey); var settingsState = databaseState(settingsKey, settingsFile.id); patchDatabase(settingsKey, settingsFile.id, { edit: Object.assign({}, settingsState.edit, { name: settingsName.value, error: '' }) }); return true; }
|
|
97
|
+
var schemaControl = event.target.closest('[data-db-schema-field] input, [data-db-schema-field] select'); if (schemaControl) { var schemaKey = workspaceKey(); var schemaFile = selectedFile(schemaKey); var schemaState = databaseState(schemaKey, schemaFile.id); var row = schemaControl.closest('[data-db-schema-field]'); var schemaFields = (schemaState.edit.schemaFields || schemaState.database.schema).map(function (field) { if (field.id !== row.dataset.dbSchemaField) return field; var next = Object.assign({}, field); if (schemaControl.matches('[data-schema-name]')) next.name = schemaControl.value; else if (schemaControl.matches('[data-schema-required]')) next.required = schemaControl.checked; else if (schemaControl.matches('[data-schema-archived]')) next.archived = schemaControl.checked; else if (schemaControl.matches('[data-schema-type]')) { next.type = schemaControl.value; if (next.type === 'select' || next.type === 'multi-select') next.options = next.options || []; if (next.type === 'reference') next.target = next.target || 'text'; } else if (schemaControl.matches('[data-schema-option-name]')) next.options = (field.options || []).map(function (option) { return option.id === schemaControl.dataset.optionId ? Object.assign({}, option, { name: schemaControl.value }) : option; }); else if (schemaControl.matches('[data-schema-option-archived]')) next.options = (field.options || []).map(function (option) { return option.id === schemaControl.dataset.optionId ? Object.assign({}, option, { archived: schemaControl.checked }) : option; }); else if (schemaControl.matches('[data-schema-reference-target]')) next.target = schemaControl.value; else if (schemaControl.matches('[data-schema-reference-db]')) { if (schemaControl.value.trim()) next.targetDatabaseId = schemaControl.value.trim(); else delete next.targetDatabaseId; } return next; }); patchDatabase(schemaKey, schemaFile.id, { edit: Object.assign({}, schemaState.edit, { schemaFields: schemaFields, error: '' }) }); if (schemaControl.matches('[data-schema-type]') && renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
98
|
+
var viewControl = event.target.closest('[data-view-name], [data-view-column], [data-view-filter-field], [data-view-filter-op], [data-view-filter-value], [data-view-sort-field], [data-view-sort-direction]'); if (viewControl) { var viewKeyValue = workspaceKey(); var viewFile = selectedFile(viewKeyValue); var viewDbState = databaseState(viewKeyValue, viewFile.id); var form = viewControl.closest('form'); var columns = Array.prototype.filter.call(form.querySelectorAll('[data-view-column]'), function (control) { return control.checked; }).map(function (control) { return control.value; }); var filterField = form.querySelector('[data-view-filter-field]').value; var filterOp = form.querySelector('[data-view-filter-op]').value; var filterInput = form.querySelector('[data-view-filter-value]'); var filterSchema = fieldById(viewDbState.database, filterField); var changingFilterShape = viewControl.matches('[data-view-filter-field]') || viewControl.matches('[data-view-filter-op]'); var filterValue = changingFilterShape ? null : typedFilterValue(filterSchema, filterInput, filterOp); var sortField = form.querySelector('[data-view-sort-field]').value; var draftView = updateViewPresentation(viewDbState.edit.view, { name: form.querySelector('[data-view-name]').value, columns: columns, filters: filterField ? [{ fieldId: filterField, op: filterOp, value: filterValue }] : [], sorts: sortField ? [{ fieldId: sortField, direction: form.querySelector('[data-view-sort-direction]').value }] : [] }); patchDatabase(viewKeyValue, viewFile.id, { edit: Object.assign({}, viewDbState.edit, { view: draftView, error: '' }) }); if (changingFilterShape && renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
99
|
+
var input = event.target.closest('[data-db-field]'); if (!input) return false; var key = workspaceKey(); var file = selectedFile(key); if (!file || file.itemType !== 'db') return false; var state = databaseState(key, file.id); if (!state.edit) return false; var field = fieldById(state.database, input.dataset.dbField); var values = Object.assign({}, state.edit.values); values[field.id] = inputValue(input, field); patchDatabase(key, file.id, { edit: Object.assign({}, state.edit, { values: values, version: (state.edit.version || 0) + 1, error: '' }) }); return true;
|
|
100
|
+
}
|
|
101
|
+
function submitSchema(panel, databaseId) {
|
|
102
|
+
var key = workspaceKey(); var state = databaseState(key, databaseId); var fields = JSON.parse(JSON.stringify(state.edit.schemaFields || state.database.schema));
|
|
103
|
+
var edit = Object.assign({}, state.edit, { schemaFields: fields, saving: true, error: '' }); patchDatabase(key, databaseId, { edit: edit }); if (!send(key, 'knowledge_db_schema_save', { databaseId: databaseId, schema: fields, expectedRevision: state.database.revision, operationId: edit.operationId }, Object.assign({ databaseId: databaseId, schemaSave: true, editId: edit.id }, ownerToken(key)))) patchDatabase(key, databaseId, { edit: Object.assign({}, edit, { saving: false, error: 'Reconnect to save the schema.' }) });
|
|
104
|
+
}
|
|
105
|
+
function typedFilterValue(field, input, op) {
|
|
106
|
+
if (op === 'empty' || op === 'not-empty') return null;
|
|
107
|
+
if (field.type === 'number') return input.value === '' ? null : Number(input.value);
|
|
108
|
+
if (field.type === 'boolean') return input.value === 'true';
|
|
109
|
+
if (field.type === 'multi-select' && op !== 'contains') return Array.prototype.filter.call(input.options, function (option) { return option.selected; }).map(function (option) { return option.value; });
|
|
110
|
+
if (field.type === 'reference') { var parts = input.value.split(':'); return field.target === 'text' ? { type: 'text', id: input.value } : { type: 'record', databaseId: parts[0], id: parts.slice(1).join(':') }; }
|
|
111
|
+
return input.value;
|
|
112
|
+
}
|
|
113
|
+
function submitView(panel, databaseId) {
|
|
114
|
+
var key = workspaceKey(); var state = databaseState(key, databaseId); var edit = state.edit;
|
|
115
|
+
var view = JSON.parse(JSON.stringify(edit.view)); view.name = view.name.trim(); var saving = Object.assign({}, edit, { view: view, saving: true, error: '' }); patchDatabase(key, databaseId, { edit: saving }); if (!send(key, 'knowledge_db_view_save', { databaseId: databaseId, view: view, expectedRevision: state.database.revision, operationId: edit.operationId }, Object.assign({ databaseId: databaseId, viewSave: true, viewId: view.id, editId: edit.id }, ownerToken(key)))) patchDatabase(key, databaseId, { edit: Object.assign({}, saving, { saving: false, error: 'Reconnect to save the view.' }) });
|
|
116
|
+
}
|
|
117
|
+
function archiveRecord(databaseId) { var key = workspaceKey(); var detail = selectedRecordState(key, databaseId); if (!detail) return; patchState(key, { dialog: Object.assign({ databaseId: databaseId, recordId: detail.record.id, revision: detail.record.revision, title: detail.title, operationId: operationId('archive-record') }, ownerToken(key)) }); if (renderOwner) renderOwner({ forceDocument: true }); }
|
|
118
|
+
function confirmArchive() { var key = workspaceKey(); var dialog = stateFor(key).dialog; if (!dialog) return; var tracking = Object.assign({ databaseId: dialog.databaseId }, { ownerSelection: dialog.ownerSelection, ownerSelectionVersion: dialog.ownerSelectionVersion }); var sent = dialog.kind === 'database' ? send(key, 'knowledge_db_archive', { databaseId: dialog.databaseId, expectedRevision: dialog.revision, operationId: dialog.operationId }, Object.assign({}, tracking, { archiveDatabase: true })) : send(key, 'knowledge_db_record_archive', { databaseId: dialog.databaseId, recordId: dialog.recordId, expectedRevision: dialog.revision, operationId: dialog.operationId }, Object.assign({}, tracking, { recordId: dialog.recordId, archive: true })); if (!sent) { patchState(key, { dialog: Object.assign({}, dialog, { error: 'Reconnect to archive this item. Retry will use the same operation id.' }) }); if (renderOwner) renderOwner({ forceDocument: true }); return; } patchState(key, { dialog: null }); if (renderOwner) renderOwner({ forceDocument: true }); }
|
|
119
|
+
function closeArchiveDialog(panel) { var dialog = stateFor(workspaceKey()).dialog; patchState(workspaceKey(), { dialog: null }); if (renderOwner) renderOwner({ forceDocument: true }); setTimeout(function () { var selector = dialog && dialog.kind === 'database' ? '[data-db-archive-database]' : '[data-db-archive-record]'; var button = panel.querySelector(selector); if (button) button.focus(); }, 0); }
|
|
120
|
+
|
|
121
|
+
export function handleKnowledgeDbClick(event, panel) {
|
|
122
|
+
var createDocument = event.target.closest('[data-db-create-document]'); if (createDocument) { panel.querySelector('.knowledge-create-menu').remove(); return 'document'; }
|
|
123
|
+
if (event.target.closest('[data-db-create-database]')) { panel.querySelector('.knowledge-create-menu').remove(); beginKnowledgeDatabaseCreate(panel); return true; }
|
|
124
|
+
if (event.target.closest('[data-db-resume-create]')) { var resumeKey = workspaceKey(); var resumed = Object.assign({}, stateFor(resumeKey).createDraft, ownerToken(resumeKey)); patchState(resumeKey, { createDraft: resumed }); patchMain(resumeKey, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
125
|
+
if (event.target.closest('[data-db-create-submit]')) { submitCreate(panel); return true; }
|
|
126
|
+
if (event.target.closest('[data-db-create-cancel]')) { patchState(workspaceKey(), { createDraft: null }); patchMain(workspaceKey(), { fullscreen: false }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
127
|
+
var reference = event.target.closest('[data-db-reference]'); if (reference) { var referenceKey = workspaceKey(); if (reference.dataset.dbReferenceType === 'text') { if (openOwnerDocument) openOwnerDocument('local:' + reference.dataset.dbReference); } else { var databaseIdentity = identityOwner ? identityOwner({ id: reference.dataset.dbReferenceDatabase, itemType: 'db', common: false }) : 'local:' + reference.dataset.dbReferenceDatabase; if (openOwnerDocument) openOwnerDocument(databaseIdentity); openRecord(reference.dataset.dbReferenceDatabase, reference.dataset.dbReference, referenceKey); } return true; }
|
|
128
|
+
var row = event.target.closest('[data-db-record]'); if (row) { openRecord(row.dataset.dbDatabase, row.dataset.dbRecord); return true; }
|
|
129
|
+
var edit = event.target.closest('[data-db-edit-record]'); if (edit) { beginRecordEdit(edit.dataset.dbEditRecord, false); return true; }
|
|
130
|
+
var add = event.target.closest('[data-db-add-record]'); if (add) { beginRecordEdit(add.dataset.dbAddRecord, true); return true; }
|
|
131
|
+
var save = event.target.closest('[data-db-save-record]'); if (save) { submitRecord(panel, save.dataset.dbSaveRecord); return true; }
|
|
132
|
+
if (event.target.closest('[data-db-cancel-edit]')) { var cancelKey = workspaceKey(); var file = selectedFile(cancelKey); var cancelState = databaseState(cancelKey, file.id); var cancelDrafts = Object.assign({}, cancelState.recordDrafts || {}); if (isRecordEdit(cancelState.edit) && cancelState.edit.recordId) delete cancelDrafts[cancelState.edit.recordId]; patchDatabase(cancelKey, file.id, { edit: null, recordDrafts: cancelDrafts }); patchMain(cancelKey, { fullscreen: false }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
133
|
+
var schema = event.target.closest('[data-db-edit-schema]'); if (schema) { var schemaKey = workspaceKey(); var schemaState = databaseState(schemaKey, schema.dataset.dbEditSchema); var schemaGeneration = (schemaState.editGeneration || 0) + 1; patchDatabase(schemaKey, schema.dataset.dbEditSchema, { editGeneration: schemaGeneration, edit: { id: schemaGeneration, schema: true, schemaFields: JSON.parse(JSON.stringify(schemaState.database.schema)), operationId: operationId('schema'), saving: false, error: '' } }); patchMain(schemaKey, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
134
|
+
var settings = event.target.closest('[data-db-settings]'); if (settings) { var settingsKey = workspaceKey(); var settingsState = databaseState(settingsKey, settings.dataset.dbSettings); var settingsGeneration = (settingsState.editGeneration || 0) + 1; patchDatabase(settingsKey, settings.dataset.dbSettings, { editGeneration: settingsGeneration, edit: { id: settingsGeneration, settings: true, name: settingsState.database.name, operationId: operationId('rename-database'), saving: false, error: '' } }); patchMain(settingsKey, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
135
|
+
var saveSettings = event.target.closest('[data-db-save-settings]'); if (saveSettings) { var saveSettingsKey = workspaceKey(); var saveSettingsState = databaseState(saveSettingsKey, saveSettings.dataset.dbSaveSettings); var databaseName = panel.querySelector('[data-db-settings-name]').value.trim(); var settingsEdit = Object.assign({}, saveSettingsState.edit, { name: databaseName, saving: true, error: '' }); patchDatabase(saveSettingsKey, saveSettings.dataset.dbSaveSettings, { edit: settingsEdit }); if (!send(saveSettingsKey, 'knowledge_db_rename', { databaseId: saveSettings.dataset.dbSaveSettings, name: databaseName, expectedRevision: saveSettingsState.database.revision, operationId: settingsEdit.operationId }, Object.assign({ databaseId: saveSettings.dataset.dbSaveSettings, editId: settingsEdit.id, renameDatabase: true }, ownerToken(saveSettingsKey)))) patchDatabase(saveSettingsKey, saveSettings.dataset.dbSaveSettings, { edit: Object.assign({}, settingsEdit, { saving: false, error: 'Reconnect to rename this database.' }) }); return true; }
|
|
136
|
+
var archiveDatabase = event.target.closest('[data-db-archive-database]'); if (archiveDatabase) { var archiveKey = workspaceKey(); var archiveState = databaseState(archiveKey, archiveDatabase.dataset.dbArchiveDatabase); patchState(archiveKey, { dialog: Object.assign({ kind: 'database', databaseId: archiveState.database.id, revision: archiveState.database.revision, title: archiveState.database.name, operationId: operationId('archive-database') }, ownerToken(archiveKey)) }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
137
|
+
var archivedRecords = event.target.closest('[data-db-archived-records]'); if (archivedRecords) { var archivedKey = workspaceKey(); var archivedId = archivedRecords.dataset.dbArchivedRecords; var archivedState = databaseState(archivedKey, archivedId); patchDatabase(archivedKey, archivedId, { showArchived: !archivedState.showArchived, records: [], selectedRecord: null, recordDetail: null }); query(archivedKey, archivedId, false, null); return true; }
|
|
138
|
+
var beginRecover = event.target.closest('[data-db-begin-recover-record]'); if (beginRecover) { var recoverKey = workspaceKey(); var recoverState = databaseState(recoverKey, beginRecover.dataset.dbBeginRecoverRecord); var recoverDetail = selectedRecordState(recoverKey, recoverState.database.id); var recoverGeneration = (recoverState.editGeneration || 0) + 1; patchDatabase(recoverKey, recoverState.database.id, { editGeneration: recoverGeneration, edit: { id: recoverGeneration, recover: true, recordId: recoverDetail.record.id, baseVersion: recoverDetail.record.revision, operationId: operationId('recover-record'), saving: false, error: '' } }); patchMain(recoverKey, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
139
|
+
var confirmRecover = event.target.closest('[data-db-confirm-recover-record]'); if (confirmRecover) { var confirmKey = workspaceKey(); var confirmState = databaseState(confirmKey, confirmRecover.dataset.dbConfirmRecoverRecord); var recoverEdit = Object.assign({}, confirmState.edit, { saving: true }); patchDatabase(confirmKey, confirmState.database.id, { edit: recoverEdit }); if (!send(confirmKey, 'knowledge_db_record_recover', { databaseId: confirmState.database.id, recordId: recoverEdit.recordId, expectedRevision: recoverEdit.baseVersion, operationId: recoverEdit.operationId }, Object.assign({ databaseId: confirmState.database.id, recordId: recoverEdit.recordId, editId: recoverEdit.id, recoverRecord: true }, ownerToken(confirmKey)))) patchDatabase(confirmKey, confirmState.database.id, { edit: Object.assign({}, recoverEdit, { saving: false, error: 'Reconnect to recover this record.' }) }); return true; }
|
|
140
|
+
var addField = event.target.closest('[data-db-add-field]'); if (addField) { var addState = databaseState(workspaceKey(), addField.dataset.dbAddField); var fields = (addState.edit.schemaFields || addState.database.schema).concat([{ id: 'fld_ui_' + Date.now(), name: 'New field', type: 'text', required: false, archived: false }]); patchDatabase(workspaceKey(), addState.database.id, { edit: Object.assign({}, addState.edit, { schema: true, schemaFields: fields }) }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
141
|
+
var addOption = event.target.closest('[data-db-add-option]'); if (addOption) { var optionKey = workspaceKey(); var optionFile = selectedFile(optionKey); var optionState = databaseState(optionKey, optionFile.id); var optionFields = (optionState.edit.schemaFields || optionState.database.schema).map(function (field) { if (field.id !== addOption.dataset.dbAddOption) return field; return Object.assign({}, field, { options: (field.options || []).concat([{ id: 'opt_ui_' + Date.now() + '_' + (++requestCounter), name: 'New option', archived: false }]) }); }); patchDatabase(optionKey, optionFile.id, { edit: Object.assign({}, optionState.edit, { schemaFields: optionFields }) }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
142
|
+
var schemaSave = event.target.closest('[data-db-save-schema]'); if (schemaSave) { submitSchema(panel, schemaSave.dataset.dbSaveSchema); return true; }
|
|
143
|
+
var views = event.target.closest('[data-db-edit-views]'); if (views) { var viewKey = workspaceKey(); var viewState = databaseState(viewKey, views.dataset.dbEditViews); var selected = viewState.database.views.filter(function (view) { return view.id === (viewState.selectedViewId || viewState.database.views[0].id); })[0] || viewState.database.views[0]; var viewGeneration = (viewState.editGeneration || 0) + 1; patchDatabase(viewKey, viewState.database.id, { editGeneration: viewGeneration, edit: { id: viewGeneration, view: JSON.parse(JSON.stringify(selected)), operationId: operationId('view'), saving: false, error: '' } }); patchMain(viewKey, { fullscreen: true }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
144
|
+
var newView = event.target.closest('[data-db-new-view]'); if (newView) { var newViewKey = workspaceKey(); var newViewState = databaseState(newViewKey, newView.dataset.dbNewView); var newGeneration = (newViewState.editGeneration || 0) + 1; patchDatabase(newViewKey, newViewState.database.id, { editGeneration: newGeneration, edit: { id: newGeneration, operationId: operationId('view'), saving: false, error: '', view: { id: 'view_ui_' + Date.now(), name: 'New view', columns: newViewState.database.schema.filter(function (field) { return !field.archived; }).map(function (field) { return field.id; }), filters: [], sorts: [] } } }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
145
|
+
var saveView = event.target.closest('[data-db-save-view]'); if (saveView) { submitView(panel, saveView.dataset.dbSaveView); return true; }
|
|
146
|
+
var archive = event.target.closest('[data-db-archive-record]'); if (archive) { archiveRecord(archive.dataset.dbArchiveRecord); return true; }
|
|
147
|
+
if (event.target.closest('[data-db-dialog-cancel]')) { closeArchiveDialog(panel); return true; }
|
|
148
|
+
if (event.target.closest('[data-db-dialog-confirm]')) { confirmArchive(); return true; }
|
|
149
|
+
var sync = event.target.closest('[data-db-sync]'); if (sync) { var syncId = sync.dataset.dbSync; patchDatabase(workspaceKey(), syncId, { error: '', syncPending: true }); send(workspaceKey(), 'knowledge_db_sync', { databaseId: syncId }, { databaseId: syncId, sync: true }); return true; }
|
|
150
|
+
var more = event.target.closest('[data-db-more]'); if (more) { var moreKey = workspaceKey(); var state = databaseState(moreKey, more.dataset.dbMore); query(moreKey, more.dataset.dbMore, true, state.nextCursor); return true; }
|
|
151
|
+
var back = event.target.closest('[data-db-record-back]'); if (back) { patchDatabase(workspaceKey(), back.dataset.dbRecordBack, { selectedRecord: null, recordDetail: null, edit: null }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
152
|
+
if (event.target.closest('[data-db-cancel-recover-database]')) { patchState(workspaceKey(), { recoveryDraft: null }); patchMain(workspaceKey(), { fullscreen: false }); if (renderOwner) renderOwner({ forceDocument: true }); return true; }
|
|
153
|
+
if (event.target.closest('[data-db-confirm-recover-database]')) { var recoveryKey = workspaceKey(); var recovery = stateFor(recoveryKey).recoveryDraft; if (!recovery) return true; recovery = Object.assign({}, recovery, { saving: true, error: '' }); patchState(recoveryKey, { recoveryDraft: recovery }); var recoveryTrack = Object.assign({ databaseId: recovery.item.id, recoverDatabase: true, recoveryOperationId: recovery.operationId }, ownerToken(recoveryKey)); if (!send(recoveryKey, 'knowledge_db_recover', { databaseId: recovery.item.id, expectedRevision: recovery.item.revision, operationId: recovery.operationId }, recoveryTrack)) { patchState(recoveryKey, { recoveryDraft: Object.assign({}, recovery, { saving: false, error: 'Reconnect to recover this database.' }) }); if (renderOwner) renderOwner({ forceDocument: true }); } return true; }
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function handleKnowledgeDbKeydown(event, panel) {
|
|
158
|
+
var keyboardRow = event.target.closest && event.target.closest('[data-db-record]'); if (keyboardRow && (event.key === 'Enter' || event.key === ' ')) { event.preventDefault(); openRecord(keyboardRow.dataset.dbDatabase, keyboardRow.dataset.dbRecord); return true; }
|
|
159
|
+
var dialog = panel && panel.querySelector('.knowledge-db-dialog'); if (!dialog) return false;
|
|
160
|
+
if (event.key === 'Escape') { event.preventDefault(); closeArchiveDialog(panel); return true; }
|
|
161
|
+
if (event.key !== 'Tab') return false; var controls = dialog.querySelectorAll('button:not(:disabled)'); if (!controls.length) return false; var first = controls[0]; var last = controls[controls.length - 1];
|
|
162
|
+
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); return true; } if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); return true; } return false;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function fieldInput(field, value) {
|
|
166
|
+
var attrs = ' data-db-field="' + escapeText(field.id) + '"' + (field.required ? ' required' : '');
|
|
167
|
+
if (field.type === 'boolean') return '<input type="checkbox"' + attrs + (value === true ? ' checked' : '') + '>';
|
|
168
|
+
if (field.type === 'select' || field.type === 'multi-select') return '<select' + attrs + (field.type === 'multi-select' ? ' multiple' : '') + '><option value="">—</option>' + (field.options || []).filter(function (option) { return !option.archived; }).map(function (option) { var selected = field.type === 'multi-select' ? Array.isArray(value) && value.indexOf(option.id) !== -1 : value === option.id; return '<option value="' + escapeText(option.id) + '"' + (selected ? ' selected' : '') + '>' + escapeText(option.name) + '</option>'; }).join('') + '</select>';
|
|
169
|
+
var type = field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'; var shown = field.type === 'reference' && value ? (value.databaseId ? value.databaseId + ':' : '') + value.id : value == null ? '' : value;
|
|
170
|
+
return '<input type="' + type + '"' + attrs + ' value="' + escapeText(shown) + '"' + (field.type === 'number' ? ' step="any"' : '') + '>';
|
|
171
|
+
}
|
|
172
|
+
function renderCreate(body, toolbar, state) { var pendingCreate = !!state.createDraft.attempt; toolbar.innerHTML = '<span class="knowledge-preview-name">New database</span><button type="button" data-db-create-cancel>Cancel</button><button type="button" class="primary" data-db-create-submit>' + (pendingCreate ? 'Retry create' : 'Create') + '</button>'; body.innerHTML = '<form class="knowledge-db-form" onsubmit="return false"><h2>Create database</h2><label>Name<input data-db-new-name value="' + escapeText(state.createDraft.name) + '" maxlength="120" autocomplete="off"' + (pendingCreate ? ' disabled' : '') + '></label>' + (state.createDraft.error ? '<p class="knowledge-inline-error">' + escapeText(state.createDraft.error) + '</p>' : '') + '<p>' + (pendingCreate ? 'This name is fixed for the pending operation. Retry uses the same request.' : 'A required title field and Table view will be created. You can refine the schema after creation.') + '</p></form>'; }
|
|
173
|
+
function renderCreatePaused(body, toolbar, state) { toolbar.innerHTML = '<span class="knowledge-preview-name">Database draft</span><button type="button" class="primary" data-db-resume-create>Resume fullscreen</button>'; body.innerHTML = '<div class="knowledge-db-form"><h2>Database draft saved</h2><p>' + (state.createDraft.name ? escapeText(state.createDraft.name) : 'Untitled database') + ' remains available. Resume fullscreen to create it.</p></div>'; }
|
|
174
|
+
function renderDatabaseRecovery(body, toolbar, draft) { toolbar.innerHTML = '<span class="knowledge-preview-name">Recover database</span><button type="button" data-db-cancel-recover-database>Cancel</button><button type="button" class="primary" data-db-confirm-recover-database>' + (draft.saving ? 'Retry recovery' : 'Recover') + '</button>'; body.innerHTML = '<div class="knowledge-db-form"><h2>Recover ' + escapeText(draft.item.name) + '?</h2><p>Review this mutation in fullscreen. Its schema, records, views, and history will return to this Mate’s Knowledge.</p>' + (draft.error ? '<p class="knowledge-inline-error">' + escapeText(draft.error) + '</p>' : '') + '</div>'; }
|
|
175
|
+
function renderTable(body, dbState) {
|
|
176
|
+
var database = dbState.database; var view = database.views.filter(function (item) { return item.id === dbState.selectedViewId; })[0] || database.views[0] || { columns: database.schema.filter(function (field) { return !field.archived; }).map(function (field) { return field.id; }) }; var fields = view.columns.map(function (id) { return fieldById(database, id); }).filter(Boolean);
|
|
177
|
+
var html = '<div class="knowledge-db-table-wrap"><table class="knowledge-db-table"><thead><tr>' + fields.map(function (field) { return '<th scope="col">' + escapeText(field.name) + '</th>'; }).join('') + '</tr></thead><tbody>';
|
|
178
|
+
html += dbState.records.map(function (record) { return '<tr tabindex="0" data-db-record="' + escapeText(record.id) + '" data-db-database="' + escapeText(database.id) + '">' + fields.map(function (field) { return '<td>' + displayValue(field, record.values[field.id], record.references) + '</td>'; }).join('') + '</tr>'; }).join('');
|
|
179
|
+
html += '</tbody></table>' + (!dbState.records.length && !dbState.loading ? '<p class="knowledge-empty">No records in this view.</p>' : '') + (dbState.nextCursor ? '<button type="button" class="knowledge-search-more" data-db-more="' + escapeText(database.id) + '">Load more</button>' : '') + '</div>'; body.innerHTML = html;
|
|
180
|
+
}
|
|
181
|
+
function renderRecord(body, dbState) { var detail = selectedRecordState(workspaceKey(), dbState.database.id); if (!detail) { body.innerHTML = '<p class="knowledge-empty">Loading record…</p>'; return; } var history = (detail.history || []).map(function (entry) { return '<li><b>' + escapeText(entry.action || 'update') + '</b><span>Revision ' + escapeText(entry.revision) + ' · ' + new Date(entry.at).toLocaleString() + '</span></li>'; }).join(''); body.innerHTML = '<article class="knowledge-db-record"><button type="button" data-db-record-back="' + escapeText(dbState.database.id) + '">' + iconHtml('arrow-left') + 'Table</button><h2>' + escapeText(detail.title) + '</h2><dl>' + dbState.database.schema.filter(function (field) { return !field.archived; }).map(function (field) { return '<dt>' + escapeText(field.name) + '</dt><dd>' + displayValue(field, detail.record.values[field.id], detail.record.references) + '</dd>'; }).join('') + '</dl><p class="knowledge-db-attribution">Revision ' + detail.record.revision + ' · updated ' + new Date(detail.record.updatedAt).toLocaleString() + '</p>' + (history ? '<h3>Recent history</h3><ol class="knowledge-db-history">' + history + '</ol>' : '') + '</article>'; }
|
|
182
|
+
function renderRecordForm(body, toolbar, dbState) { var edit = dbState.edit; var values = edit.values || {}; toolbar.innerHTML = '<span class="knowledge-preview-name">' + (edit.creating ? 'New record' : 'Edit record') + '</span>' + (!edit.creating ? '<button type="button" class="danger" data-db-archive-record="' + escapeText(dbState.database.id) + '">Archive</button>' : '') + '<button type="button" data-db-cancel-edit>Cancel</button><button type="button" class="primary" data-db-save-record="' + escapeText(dbState.database.id) + '">' + (edit.saving ? 'Retry save' : 'Save') + '</button>'; body.innerHTML = '<form class="knowledge-db-form" onsubmit="return false"><h2>' + (edit.creating ? 'New record' : 'Edit record') + '</h2>' + dbState.database.schema.filter(function (field) { return !field.archived; }).map(function (field) { return '<label>' + escapeText(field.name) + fieldInput(field, values[field.id]) + '</label>'; }).join('') + (edit.error ? '<p class="knowledge-inline-error">' + escapeText(edit.error) + '</p>' : '') + '</form>'; }
|
|
183
|
+
function renderSettings(body, toolbar, dbState) { var edit = dbState.edit; toolbar.innerHTML = '<span class="knowledge-preview-name">Database settings</span><button type="button" data-db-cancel-edit>Cancel</button><button type="button" class="primary" data-db-save-settings="' + escapeText(dbState.database.id) + '">' + (edit.saving ? 'Retry rename' : 'Save name') + '</button>'; body.innerHTML = '<form class="knowledge-db-form" onsubmit="return false"><h2>Database settings</h2><label>Name<input data-db-settings-name value="' + escapeText(edit.name) + '" maxlength="120"></label>' + (edit.error ? '<p class="knowledge-inline-error">' + escapeText(edit.error) + '</p>' : '') + '<section class="knowledge-danger-zone"><h3>Archive database</h3><p>Records, schema, views, and history remain recoverable.</p><button type="button" class="danger" data-db-archive-database="' + escapeText(dbState.database.id) + '">Archive database</button></section></form>'; }
|
|
184
|
+
function renderRecover(body, toolbar, dbState) { var edit = dbState.edit; toolbar.innerHTML = '<span class="knowledge-preview-name">Recover record</span><button type="button" data-db-cancel-edit>Cancel</button><button type="button" class="primary" data-db-confirm-recover-record="' + escapeText(dbState.database.id) + '">' + (edit.saving ? 'Retry recovery' : 'Recover') + '</button>'; body.innerHTML = '<div class="knowledge-db-form"><h2>Recover archived record?</h2><p>This restores the exact record at revision ' + escapeText(edit.baseVersion) + '.</p>' + (edit.error ? '<p class="knowledge-inline-error">' + escapeText(edit.error) + '</p>' : '') + '</div>'; }
|
|
185
|
+
function optionEditor(field) { return '<fieldset class="knowledge-db-options"><legend>Options</legend>' + (field.options || []).map(function (option) { return '<div class="knowledge-db-option"><input aria-label="Option name" data-schema-option-name data-option-id="' + escapeText(option.id) + '" value="' + escapeText(option.name) + '"><label class="knowledge-db-check"><input type="checkbox" data-schema-option-archived data-option-id="' + escapeText(option.id) + '"' + (option.archived ? ' checked' : '') + '>Archived</label></div>'; }).join('') + '<button type="button" data-db-add-option="' + escapeText(field.id) + '">' + iconHtml('plus') + 'Add option</button></fieldset>'; }
|
|
186
|
+
function renderSchema(body, toolbar, dbState) { var fields = dbState.edit.schemaFields || dbState.database.schema; toolbar.innerHTML = '<span class="knowledge-preview-name">Edit schema</span><button type="button" data-db-add-field="' + escapeText(dbState.database.id) + '">' + iconHtml('plus') + 'Add field</button><button type="button" data-db-cancel-edit>Cancel</button><button type="button" class="primary" data-db-save-schema="' + escapeText(dbState.database.id) + '">Save schema</button>'; body.innerHTML = '<form class="knowledge-db-form knowledge-db-schema" onsubmit="return false"><h2>Schema</h2><p>Field and option ids stay stable. Archive populated fields and options instead of removing them.</p>' + fields.map(function (field) { var existing = !!fieldById(dbState.database, field.id); return '<fieldset data-db-schema-field="' + escapeText(field.id) + '"><label>Name<input data-schema-name value="' + escapeText(field.name) + '"></label><label>Type<select data-schema-type' + (existing ? ' disabled' : '') + '>' + ['title','text','number','boolean','date','select','multi-select','reference'].map(function (type) { return '<option' + (field.type === type ? ' selected' : '') + '>' + type + '</option>'; }).join('') + '</select></label>' + (field.type === 'select' || field.type === 'multi-select' ? optionEditor(field) : '') + (field.type === 'reference' ? '<label>Target<select data-schema-reference-target><option value="text"' + (field.target === 'text' ? ' selected' : '') + '>Text item</option><option value="record"' + (field.target === 'record' ? ' selected' : '') + '>Database record</option></select></label><label>Target database id<input data-schema-reference-db value="' + escapeText(field.targetDatabaseId || '') + '"></label>' : '') + '<label class="knowledge-db-check"><input type="checkbox" data-schema-required' + (field.required ? ' checked' : '') + '>Required</label><label class="knowledge-db-check"><input type="checkbox" data-schema-archived' + (field.archived ? ' checked' : '') + (field.type === 'title' ? ' disabled' : '') + '>Archived</label></fieldset>'; }).join('') + '</form>'; }
|
|
187
|
+
function filterControl(field, filter) { var value = filter.value; if (!field) return '<input data-view-filter-value value="">'; if (field.type === 'boolean') return '<select data-view-filter-value><option value="true"' + (value === true ? ' selected' : '') + '>True</option><option value="false"' + (value === false ? ' selected' : '') + '>False</option></select>'; if (field.type === 'select' || field.type === 'multi-select') { var multiple = field.type === 'multi-select' && filter.op !== 'contains'; return '<select data-view-filter-value' + (multiple ? ' multiple' : '') + '>' + (field.options || []).filter(function (option) { return !option.archived; }).map(function (option) { var selected = multiple ? Array.isArray(value) && value.indexOf(option.id) !== -1 : value === option.id; return '<option value="' + escapeText(option.id) + '"' + (selected ? ' selected' : '') + '>' + escapeText(option.name) + '</option>'; }).join('') + '</select>'; } var type = field.type === 'number' ? 'number' : field.type === 'date' ? 'date' : 'text'; var shown = field.type === 'reference' && value ? (value.databaseId ? value.databaseId + ':' : '') + value.id : value == null ? '' : value; return '<input type="' + type + '" data-view-filter-value value="' + escapeText(shown) + '"' + (type === 'number' ? ' step="any"' : '') + '>'; }
|
|
188
|
+
function renderView(body, toolbar, dbState) {
|
|
189
|
+
var view = dbState.edit.view; var filter = view.filters[0] || {}; var sort = view.sorts[0] || {}; var fields = dbState.database.schema.filter(function (field) { return !field.archived; }); var filterField = fieldById(dbState.database, filter.fieldId); var complex = view.filters.length > 1 || view.sorts.length > 1; var disabled = complex ? ' disabled' : '';
|
|
190
|
+
toolbar.innerHTML = '<span class="knowledge-preview-name">Edit Table view</span><button type="button" data-db-new-view="' + escapeText(dbState.database.id) + '">New view</button><button type="button" data-db-cancel-edit>Cancel</button><button type="button" class="primary" data-db-save-view="' + escapeText(dbState.database.id) + '">Save view</button>';
|
|
191
|
+
body.innerHTML = '<form class="knowledge-db-form" onsubmit="return false"><h2>Table view</h2>' + (complex ? '<p>All existing filters and sorts will be preserved in order. Their controls are locked in this compact editor; the name and columns remain editable.</p>' : '') + (dbState.edit.error ? '<p class="knowledge-inline-error">' + escapeText(dbState.edit.error) + '</p>' : '') + '<label>Name<input data-view-name value="' + escapeText(view.name) + '"></label><fieldset><legend>Columns</legend>' + fields.map(function (field) { return '<label class="knowledge-db-check"><input type="checkbox" data-view-column value="' + escapeText(field.id) + '"' + (view.columns.indexOf(field.id) !== -1 ? ' checked' : '') + '>' + escapeText(field.name) + '</label>'; }).join('') + '</fieldset><label>Filter field<select data-view-filter-field' + disabled + '><option value="">No filter</option>' + fields.map(function (field) { return '<option value="' + escapeText(field.id) + '"' + (filter.fieldId === field.id ? ' selected' : '') + '>' + escapeText(field.name) + '</option>'; }).join('') + '</select></label><label>Filter operator<select data-view-filter-op' + disabled + '>' + ['eq','neq','contains','gt','gte','lt','lte','empty','not-empty'].map(function (op) { return '<option' + (filter.op === op ? ' selected' : '') + '>' + op + '</option>'; }).join('') + '</select></label><label>Filter value<span class="knowledge-db-filter-control">' + filterControl(filterField, filter).replace(/^(<[^ >]+)/, '$1' + disabled) + '</span></label><label>Sort field<select data-view-sort-field' + disabled + '><option value="">No sort</option>' + fields.map(function (field) { return '<option value="' + escapeText(field.id) + '"' + (sort.fieldId === field.id ? ' selected' : '') + '>' + escapeText(field.name) + '</option>'; }).join('') + '</select></label><label>Direction<select data-view-sort-direction' + disabled + '><option value="asc"' + (sort.direction !== 'desc' ? ' selected' : '') + '>Ascending</option><option value="desc"' + (sort.direction === 'desc' ? ' selected' : '') + '>Descending</option></select></label></form>';
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function renderKnowledgeDatabase(panel) {
|
|
195
|
+
var staleDialog = panel.querySelector('.knowledge-db-dialog'); if (staleDialog) { setDialogBackground(panel, false); staleDialog.remove(); }
|
|
196
|
+
var key = workspaceKey(); var outer = stateFor(key); var body = panel.querySelector('.knowledge-document-body'); var toolbar = panel.querySelector('.knowledge-document-toolbar'); var main = mainState(key); if (outer.createDraft) { var createContext = main && main.selected === outer.createDraft.ownerSelection && (main.selectionVersion || 0) === outer.createDraft.ownerSelectionVersion; if (createContext) { if (main.fullscreen) renderCreate(body, toolbar, outer); else renderCreatePaused(body, toolbar, outer); return true; } } if (outer.recoveryDraft) { var recoveryOwns = main && main.fullscreen && main.selected === outer.recoveryDraft.ownerSelection && (main.selectionVersion || 0) === outer.recoveryDraft.ownerSelectionVersion; if (recoveryOwns) { renderDatabaseRecovery(body, toolbar, outer.recoveryDraft); return true; } }
|
|
197
|
+
var file = selectedFile(key); if (!file || file.itemType !== 'db') return false; var dbState = databaseState(key, file.id); var database = dbState.database; var mayEdit = !!(main && main.fullscreen && file.writable !== false);
|
|
198
|
+
panel.querySelector('.knowledge-details').hidden = true; if (!database) { toolbar.innerHTML = '<span class="knowledge-preview-name">' + escapeText(file.name) + '</span>'; body.innerHTML = '<p class="knowledge-empty">Loading database…</p>'; return true; }
|
|
199
|
+
if (mayEdit && dbState.edit && dbState.edit.schema) renderSchema(body, toolbar, dbState); else if (mayEdit && dbState.edit && dbState.edit.view) renderView(body, toolbar, dbState); else if (mayEdit && dbState.edit && dbState.edit.settings) renderSettings(body, toolbar, dbState); else if (mayEdit && dbState.edit && dbState.edit.recover) renderRecover(body, toolbar, dbState); else if (mayEdit && dbState.edit) renderRecordForm(body, toolbar, dbState); else {
|
|
200
|
+
var mutationTools = file.writable === false ? '' : '<button type="button" data-db-edit-views="' + escapeText(database.id) + '">Views</button><button type="button" data-db-add-record="' + escapeText(database.id) + '">' + iconHtml('plus') + 'New record</button><button type="button" data-db-edit-schema="' + escapeText(database.id) + '">' + iconHtml('settings-2') + 'Schema</button><button type="button" data-db-settings="' + escapeText(database.id) + '">Settings</button>';
|
|
201
|
+
toolbar.innerHTML = '<span class="knowledge-preview-name">' + escapeText(database.name) + '</span><select class="knowledge-db-view-select" data-db-view-select="' + escapeText(database.id) + '" aria-label="Table view">' + database.views.map(function (view) { return '<option value="' + escapeText(view.id) + '"' + ((dbState.selectedViewId || database.views[0].id) === view.id ? ' selected' : '') + '>' + escapeText(view.name) + '</option>'; }).join('') + '</select><span class="knowledge-readonly">' + (dbState.totals ? dbState.totals.count : database.recordCount) + ' records</span>' + mutationTools;
|
|
202
|
+
toolbar.innerHTML += '<button type="button" data-db-archived-records="' + escapeText(database.id) + '">' + (dbState.showArchived ? 'Active records' : 'Archived records') + '</button>';
|
|
203
|
+
if (dbState.selectedRecord && selectedRecordState(key, database.id)) { if (file.writable !== false) toolbar.innerHTML += dbState.recordDetail && dbState.recordDetail.record.archivedAt ? '<button type="button" data-db-begin-recover-record="' + escapeText(database.id) + '">Recover fullscreen</button>' : '<button type="button" data-db-edit-record="' + escapeText(database.id) + '">' + iconHtml('pencil') + 'Edit fullscreen</button>'; renderRecord(body, dbState); } else renderTable(body, dbState);
|
|
204
|
+
}
|
|
205
|
+
if (dbState.error) body.insertAdjacentHTML('afterbegin', '<p class="knowledge-inline-error">' + escapeText(dbState.error) + (dbState.syncPending ? ' <button type="button" data-db-sync="' + escapeText(database.id) + '">Retry sync</button>' : '') + '</p>');
|
|
206
|
+
if (outer.dialog) { var dialog = document.createElement('div'); dialog.className = 'knowledge-db-dialog'; dialog.setAttribute('role', 'dialog'); dialog.setAttribute('aria-modal', 'true'); dialog.innerHTML = '<div><h2>Archive this ' + (outer.dialog.kind === 'database' ? 'database' : 'record') + '?</h2><p>' + escapeText(outer.dialog.title || 'This item') + ' can be recovered later.</p>' + (outer.dialog.error ? '<p class="knowledge-inline-error">' + escapeText(outer.dialog.error) + '</p>' : '') + '<div><button type="button" data-db-dialog-cancel>Cancel</button><button type="button" class="danger" data-db-dialog-confirm>Archive</button></div></div>'; panel.appendChild(dialog); setDialogBackground(panel, true); dialog.querySelector('[data-db-dialog-cancel]').focus(); }
|
|
207
|
+
refreshIcons(panel); return true;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function handleKnowledgeDbMessage(msg) {
|
|
211
|
+
var tracked = msg.requestId && pending[msg.requestId]; if (!tracked) return false; delete pending[msg.requestId]; var key = tracked.key; if (tracked.socket !== getWs()) return true; var dbId = tracked.databaseId; var state = dbId ? databaseState(key, dbId) : null; var currentWorkspace = key === workspaceKey(); var preserveActiveForm = false; var forceDocument = true;
|
|
212
|
+
if (msg.type === 'knowledge_db_created') {
|
|
213
|
+
var database = msg.database; var currentCreate = stateFor(key).createDraft; var knownDatabase = !!(stateFor(key).databases[database.id] && stateFor(key).databases[database.id].database); var matchesCreation = !!(currentCreate && currentCreate.attempt && currentCreate.attempt.operationId === tracked.createOperationId); var ownsCreation = matchesCreation && stillOwns(key, tracked); forceDocument = ownsCreation; preserveActiveForm = !matchesCreation;
|
|
214
|
+
if (matchesCreation) patchState(key, { createDraft: null, error: '' });
|
|
215
|
+
var main = mainState(key); if (main) { var file = Object.assign({}, database, { itemType: 'db', writable: true }); var identity = identityOwner ? identityOwner(file) : 'local:' + file.id; var drafts = Object.assign({}, main.drafts); if (!drafts[identity]) drafts[identity] = { file: file, content: '', writable: true, dirty: false, mode: 'read' }; var files = main.files.some(function (item) { return item.id === file.id; }) ? main.files : main.files.concat([file]); var tabs = main.tabs.indexOf(identity) === -1 ? main.tabs.concat([identity]) : main.tabs; var createPatch = { files: files, drafts: drafts, tabs: tabs }; if (ownsCreation) { createPatch.selected = identity; createPatch.fullscreen = false; } patchMain(key, createPatch); }
|
|
216
|
+
if (!knownDatabase) { setDatabaseState(key, database.id, { database: database, records: [], nextCursor: null, selectedRecord: null, recordDetail: null, edit: null, loading: false, error: msg.projectionPending ? msg.projectionError : '', syncPending: msg.projectionPending === true }); if (currentWorkspace) query(key, database.id, false, null); }
|
|
217
|
+
}
|
|
218
|
+
else if (msg.type === 'knowledge_db_content') { patchDatabase(key, dbId, { database: msg.database, loading: false, error: '' }); if (currentWorkspace) query(key, dbId, false, null); }
|
|
219
|
+
else if (msg.type === 'knowledge_db_query_results') { if (tracked.queryEpoch !== state.queryEpoch || tracked.viewId !== (state.selectedViewId || state.database && state.database.views[0] && state.database.views[0].id || null)) return true; patchDatabase(key, dbId, { records: tracked.append ? state.records.concat(msg.records || []) : msg.records || [], nextCursor: msg.nextCursor || null, loading: false, totals: msg.totals, error: state.syncPending ? state.error : '' }); }
|
|
220
|
+
else if (msg.type === 'knowledge_db_record_content') { if (state.selectedRecord !== tracked.recordId || state.recordGeneration !== tracked.recordGeneration) return true; patchDatabase(key, dbId, { recordDetail: msg, error: '' }); }
|
|
221
|
+
else if (msg.type === 'knowledge_db_record_created' || msg.type === 'knowledge_db_record_updated') { var ownsEdit = state.edit && state.edit.id === tracked.editId; var applied = ownsEdit ? applyRecordSave(state.edit, msg.record, operationId('record'), tracked.attempt) : { edit: state.edit, later: false, stale: true }; if (applied.stale) return true; var selectedRecord = ownsEdit && tracked.creating ? msg.record.id : state.selectedRecord; var recordDetail = selectedRecord === msg.record.id ? { database: state.database, record: msg.record, title: msg.record.values[titleField(state.database).id], history: state.recordDetail && state.recordDetail.history || [] } : state.recordDetail; var recordDrafts = Object.assign({}, state.recordDrafts || {}); if (tracked.recordId) delete recordDrafts[tracked.recordId]; if (applied.later) recordDrafts[msg.record.id] = applied.edit; patchDatabase(key, dbId, { edit: applied.edit, recordDrafts: recordDrafts, selectedRecord: selectedRecord, recordDetail: recordDetail, database: Object.assign({}, state.database, { revision: msg.databaseRevision }), error: msg.projectionPending ? msg.projectionError : '', syncPending: msg.projectionPending === true }); preserveActiveForm = applied.later; if (ownsEdit && !applied.later && stillOwns(key, tracked) && currentWorkspace && selectedFile(key) && selectedFile(key).id === dbId) patchMain(key, { fullscreen: false }); if (currentWorkspace) query(key, dbId, false, null); }
|
|
222
|
+
else if (msg.type === 'knowledge_db_schema_saved' || msg.type === 'knowledge_db_view_saved') { var ownsSchemaEdit = state.edit && state.edit.id === tracked.editId; patchDatabase(key, dbId, { database: msg.database, selectedViewId: tracked.viewId || state.selectedViewId, edit: ownsSchemaEdit ? null : state.edit, error: msg.projectionPending ? msg.projectionError : '', syncPending: msg.projectionPending === true }); if (ownsSchemaEdit && stillOwns(key, tracked) && currentWorkspace && selectedFile(key) && selectedFile(key).id === dbId) patchMain(key, { fullscreen: false }); if (currentWorkspace) query(key, dbId, false, null); }
|
|
223
|
+
else if (msg.type === 'knowledge_db_record_archived' || msg.type === 'knowledge_db_record_recovered') { if (state.selectedRecord === tracked.recordId) patchDatabase(key, dbId, { selectedRecord: null, recordDetail: null, edit: null, database: Object.assign({}, state.database, { revision: msg.databaseRevision }), error: msg.projectionPending ? msg.projectionError : '', syncPending: msg.projectionPending === true }); if (currentWorkspace) query(key, dbId, false, null); }
|
|
224
|
+
else if (msg.type === 'knowledge_db_renamed') { var renamedMain = mainState(key); var renamedIdentity = renamedMain && renamedMain.tabs.filter(function (item) { return renamedMain.drafts[item] && renamedMain.drafts[item].file.id === dbId; })[0]; if (renamedMain && renamedIdentity) { var renamedDrafts = Object.assign({}, renamedMain.drafts); renamedDrafts[renamedIdentity] = Object.assign({}, renamedDrafts[renamedIdentity], { file: Object.assign({}, renamedDrafts[renamedIdentity].file, { name: msg.database.name, revision: msg.database.revision }) }); var renamePatch = { drafts: renamedDrafts, files: renamedMain.files.map(function (file) { return file.id === dbId ? Object.assign({}, file, { name: msg.database.name, revision: msg.database.revision }) : file; }) }; if (stillOwns(key, tracked)) renamePatch.fullscreen = false; patchMain(key, renamePatch); } patchDatabase(key, dbId, { database: msg.database, edit: null, error: msg.projectionPending ? msg.projectionError : '', syncPending: msg.projectionPending === true }); }
|
|
225
|
+
else if (msg.type === 'knowledge_db_archived') { var archivedMain = mainState(key); if (archivedMain) { var archivedIdentity = archivedMain.tabs.filter(function (item) { return archivedMain.drafts[item] && archivedMain.drafts[item].file.id === dbId; })[0]; var archivedTabs = archivedMain.tabs.filter(function (item) { return item !== archivedIdentity; }); var archivedDrafts = Object.assign({}, archivedMain.drafts); delete archivedDrafts[archivedIdentity]; var archivePatch = { files: archivedMain.files.filter(function (file) { return file.id !== dbId; }), tabs: archivedTabs, drafts: archivedDrafts }; if (stillOwns(key, tracked)) { archivePatch.selected = archivedTabs.length ? archivedTabs[archivedTabs.length - 1] : null; archivePatch.fullscreen = false; } patchMain(key, archivePatch); } patchState(key, { dialog: null }); }
|
|
226
|
+
else if (msg.type === 'knowledge_db_recovered') { var recovered = msg.database; var ownsRecovery = stillOwns(key, tracked) && stateFor(key).recoveryDraft && stateFor(key).recoveryDraft.operationId === tracked.recoveryOperationId; var recoveredMain = mainState(key); if (recoveredMain) { var recoveredFile = Object.assign({}, recovered, { itemType: 'db', writable: true }); var recoveredIdentity = identityOwner ? identityOwner(recoveredFile) : 'local:' + recovered.id; var recoveredDrafts = Object.assign({}, recoveredMain.drafts); recoveredDrafts[recoveredIdentity] = { file: recoveredFile, content: '', writable: true, dirty: false, mode: 'read' }; var recoveryPatch = { files: recoveredMain.files.filter(function (file) { return file.id !== recovered.id; }).concat([recoveredFile]), drafts: recoveredDrafts, tabs: recoveredMain.tabs.indexOf(recoveredIdentity) === -1 ? recoveredMain.tabs.concat([recoveredIdentity]) : recoveredMain.tabs, deletedFiles: (recoveredMain.deletedFiles || []).filter(function (file) { return file.id !== recovered.id; }) }; if (ownsRecovery) { recoveryPatch.selected = recoveredIdentity; recoveryPatch.fullscreen = false; } patchMain(key, recoveryPatch); } patchState(key, { recoveryDraft: null }); patchDatabase(key, recovered.id, { database: recovered, records: [], loading: false, error: '', edit: null }); if (currentWorkspace) query(key, recovered.id, false, null); }
|
|
227
|
+
else if (msg.type === 'knowledge_db_synced') patchDatabase(key, dbId, { database: msg.database, error: msg.projectionPending ? msg.projectionError : '', syncPending: msg.projectionPending === true });
|
|
228
|
+
else if (msg.type === 'knowledge_error') { if (tracked.create) { var failedCreate = stateFor(key).createDraft; if (failedCreate && failedCreate.attempt && failedCreate.attempt.operationId === tracked.createOperationId) patchState(key, { createDraft: Object.assign({}, failedCreate, { saving: false, retryPending: false, attempt: null, operationId: operationId('create'), error: msg.error }) }); } else if (tracked.recoverDatabase && stateFor(key).recoveryDraft) patchState(key, { recoveryDraft: Object.assign({}, stateFor(key).recoveryDraft, { saving: false, error: msg.error }) }); else if (state && state.edit && (!tracked.editId || state.edit.id === tracked.editId) && (!tracked.saveRecord || isCurrentAttempt(state.edit, tracked.attempt))) patchDatabase(key, dbId, { edit: Object.assign({}, state.edit, { saving: false, retryPending: false, attempt: null, operationId: tracked.saveRecord ? operationId('record') : state.edit.operationId, error: msg.error }), loading: false, error: '' }); else if (dbId && !tracked.saveRecord) patchDatabase(key, dbId, { loading: false, error: msg.error }); else if (!dbId) patchState(key, { error: msg.error }); }
|
|
229
|
+
else return false;
|
|
230
|
+
var visibleFile = currentWorkspace ? selectedFile(key) : null; var affectsVisibleDatabase = !dbId || visibleFile && visibleFile.itemType === 'db' && visibleFile.id === dbId; var activeForm = document.activeElement && document.activeElement.closest && document.activeElement.closest('.knowledge-db-form'); var backgroundOnly = msg.type === 'knowledge_db_query_results' || msg.type === 'knowledge_db_synced';
|
|
231
|
+
if (currentWorkspace && affectsVisibleDatabase && !preserveActiveForm && !(activeForm && backgroundOnly) && renderOwner) renderOwner({ forceDocument: forceDocument });
|
|
232
|
+
else if (currentWorkspace && !affectsVisibleDatabase && ['knowledge_db_created', 'knowledge_db_renamed', 'knowledge_db_archived', 'knowledge_db_recovered'].indexOf(msg.type) !== -1 && renderOwner) renderOwner({ forceDocument: false });
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function liveHistoryTarget(documentKey, draft) {
|
|
2
|
+
if (!draft || draft.newDocument || !draft.file || !draft.file.id) return null;
|
|
3
|
+
return { documentId: draft.file.id, documentKey: documentKey, name: draft.file.name, revision: draft.revision || null, deleted: false };
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function deletedHistoryTarget(documentId, name) {
|
|
7
|
+
if (!documentId || !name) return null;
|
|
8
|
+
return { documentId: documentId, documentKey: null, name: name, revision: null, deleted: true };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function beginHistoryRequest(state, target, requestId) {
|
|
12
|
+
return Object.assign({}, state, { historyRequestId: requestId,
|
|
13
|
+
historyView: Object.assign({}, target, { versions: [], preview: null, loading: true }) });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function receiveHistoryVersions(state, tracked, versions) {
|
|
17
|
+
if (!tracked || state.historyRequestId !== tracked.requestId) return state;
|
|
18
|
+
return Object.assign({}, state, { historyRequestId: null,
|
|
19
|
+
historyView: Object.assign({}, tracked.historyTarget, { versions: versions || [], preview: null, loading: false }) });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function beginHistoryPreview(state, requestId) {
|
|
23
|
+
if (!state.historyView) return state;
|
|
24
|
+
return Object.assign({}, state, { historyPreviewRequestId: requestId,
|
|
25
|
+
historyView: Object.assign({}, state.historyView, { preview: null, loading: true }) });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function receiveHistoryPreview(state, tracked, selected) {
|
|
29
|
+
if (!tracked || state.historyPreviewRequestId !== tracked.requestId) return state;
|
|
30
|
+
return Object.assign({}, state, { historyPreviewRequestId: null,
|
|
31
|
+
historyView: Object.assign({}, tracked.historyTarget, { versions: state.historyView && state.historyView.versions || [], preview: selected, loading: false }) });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function clearHistoryView(state) {
|
|
35
|
+
return Object.assign({}, state, { historyRequestId: null, historyPreviewRequestId: null, historyView: null });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function escapeText(value) {
|
|
39
|
+
var span = document.createElement('span'); span.textContent = String(value || ''); return span.innerHTML;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function historyToolbarHtml(history, fullscreen) {
|
|
43
|
+
return '<strong class="knowledge-preview-name">' + escapeText(history.name) + '</strong><span class="knowledge-readonly">History preview · read only</span>' +
|
|
44
|
+
(fullscreen ? '<button type="button" data-history-restore="' + escapeText(history.preview.version) + '">Restore this version</button>' : '<button type="button" data-history-review>Review restore fullscreen</button>') +
|
|
45
|
+
'<button type="button" data-document-action="history-exit">Exit preview</button>';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function historyVersionsHtml(history) {
|
|
49
|
+
if (!history) return '';
|
|
50
|
+
if (history.loading) return '<p>Loading history…</p>';
|
|
51
|
+
if (!history.versions || !history.versions.length) return '';
|
|
52
|
+
return '<small>VERSIONS OF ' + escapeText(history.name) + '</small>' + history.versions.map(function (item) {
|
|
53
|
+
return '<button type="button" class="knowledge-link-item" data-history-preview="' + escapeText(item.version) + '">' + escapeText(new Date(Number(item.version.split('-')[0])).toLocaleString()) + '</button>';
|
|
54
|
+
}).join('');
|
|
55
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
var activeEditor = null;
|
|
2
|
+
|
|
3
|
+
function relativeName(from, to) {
|
|
4
|
+
var source = String(from || '').split('/'); source.pop(); var target = String(to || '').replace(/\.md$/i, '').split('/');
|
|
5
|
+
while (source.length && target.length && source[0] === target[0]) { source.shift(); target.shift(); }
|
|
6
|
+
return new Array(source.length + 1).join('../') + target.join('/');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function wikiHint(editor, files, currentName) {
|
|
10
|
+
var cursor = editor.getCursor(); var line = editor.getLine(cursor.line); var before = line.slice(0, cursor.ch); var open = before.lastIndexOf('[[', cursor.ch);
|
|
11
|
+
if (open === -1 || before.slice(open + 2).indexOf(']]') !== -1) return null;
|
|
12
|
+
var typed = before.slice(open + 2).split('|')[0].split('#')[0].toLowerCase();
|
|
13
|
+
var list = (files || []).filter(function (file) { return file.itemType !== 'db' && (!typed || file.name.toLowerCase().indexOf(typed) !== -1); }).slice(0, 30).map(function (file) {
|
|
14
|
+
return { text: relativeName(currentName, file.name) + ']]', displayText: file.name };
|
|
15
|
+
});
|
|
16
|
+
return { list: list, from: window.CodeMirror.Pos(cursor.line, open + 2), to: cursor };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function maybeComplete(editor, change, files, currentName) {
|
|
20
|
+
if (!change || change.origin === 'setValue' || change.origin === 'complete') return;
|
|
21
|
+
var cursor = editor.getCursor(); var line = editor.getLine(cursor.line).slice(0, cursor.ch);
|
|
22
|
+
if (line.lastIndexOf('[[') === -1 || line.lastIndexOf(']]') > line.lastIndexOf('[[')) return;
|
|
23
|
+
editor.showHint({ completeSingle: false, hint: function (cm) { return wikiHint(cm, files, currentName); } });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function mountKnowledgeEditor(host, options) {
|
|
27
|
+
destroyKnowledgeEditor();
|
|
28
|
+
var textarea = document.createElement('textarea'); textarea.setAttribute('aria-label', 'Markdown source'); host.appendChild(textarea);
|
|
29
|
+
if (!window.CodeMirror) { textarea.value = options.value || ''; textarea.className = 'knowledge-editor-fallback'; textarea.addEventListener('input', function () { options.onChange(textarea.value, null); }); activeEditor = { fallback: textarea }; return activeEditor; }
|
|
30
|
+
var editor = window.CodeMirror.fromTextArea(textarea, {
|
|
31
|
+
value: options.value || '', mode: { name: 'markdown', highlightFormatting: true }, lineWrapping: true, lineNumbers: false,
|
|
32
|
+
indentUnit: 2, tabSize: 2, indentWithTabs: false, inputStyle: 'contenteditable', spellcheck: true,
|
|
33
|
+
extraKeys: { 'Ctrl-Space': function (cm) { cm.showHint({ completeSingle: false, hint: function (item) { return wikiHint(item, options.files, options.currentName); } }); },
|
|
34
|
+
'Tab': function (cm) { cm.execCommand('indentMore'); }, 'Shift-Tab': function (cm) { cm.execCommand('indentLess'); },
|
|
35
|
+
'Enter': 'newlineAndIndentContinueMarkdownList' },
|
|
36
|
+
});
|
|
37
|
+
editor.setValue(options.value || ''); editor.clearHistory();
|
|
38
|
+
if (options.history) { try { editor.setHistory(options.history); } catch (error) {} }
|
|
39
|
+
if (options.cursor) { try { editor.setCursor(options.cursor); } catch (error) {} }
|
|
40
|
+
editor.on('change', function (cm, change) { options.onChange(cm.getValue(), cm.getCursor()); maybeComplete(cm, change, options.files, options.currentName); });
|
|
41
|
+
editor.on('cursorActivity', function (cm) { if (options.onCursor) options.onCursor(cm.getCursor()); });
|
|
42
|
+
activeEditor = editor; setTimeout(function () { editor.refresh(); editor.focus(); }, 0); return editor;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function captureKnowledgeEditor() {
|
|
46
|
+
if (!activeEditor) return null;
|
|
47
|
+
if (activeEditor.fallback) return { content: activeEditor.fallback.value, cursor: null, history: null, scroll: activeEditor.fallback.scrollTop || 0 };
|
|
48
|
+
return { content: activeEditor.getValue(), cursor: activeEditor.getCursor(), history: activeEditor.getHistory(), scroll: activeEditor.getScrollInfo().top };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function scrollKnowledgeEditor(top) { if (activeEditor && !activeEditor.fallback) activeEditor.scrollTo(null, top || 0); }
|
|
52
|
+
export function destroyKnowledgeEditor() {
|
|
53
|
+
if (!activeEditor) return;
|
|
54
|
+
if (!activeEditor.fallback) { try { activeEditor.toTextArea(); } catch (error) {} }
|
|
55
|
+
else if (activeEditor.fallback.parentNode) activeEditor.fallback.parentNode.removeChild(activeEditor.fallback);
|
|
56
|
+
activeEditor = null;
|
|
57
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
function applyTarget(element, link) {
|
|
2
|
+
if (link.documentKey) {
|
|
3
|
+
element.dataset.openDocument = link.documentKey;
|
|
4
|
+
if (link.heading) element.dataset.openHeading = link.heading;
|
|
5
|
+
} else if (link.self && link.heading) element.dataset.outlineHeading = link.heading;
|
|
6
|
+
else if (link.unresolved && link.target) element.dataset.createLinked = link.target;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function decode(value) {
|
|
10
|
+
try { return decodeURIComponent(value); } catch (error) { return value; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function normalizedTarget(value) {
|
|
14
|
+
return decode(String(value || '').trim().replace(/^<|>$/g, '')).replace(/\\/g, '/').replace(/^\.\//, '').toLowerCase();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function renderedTarget(element) {
|
|
18
|
+
if (element.matches('button.clay-file-link')) return normalizedTarget(element.dataset.filePath);
|
|
19
|
+
return normalizedTarget(element.getAttribute('href'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function matchingLink(element, links, used) {
|
|
23
|
+
var actual = renderedTarget(element);
|
|
24
|
+
if (element.matches('a') && actual.charAt(0) !== '#') return null;
|
|
25
|
+
for (var i = 0; i < links.length; i++) {
|
|
26
|
+
if (used[i] || normalizedTarget(links[i].target + (links[i].heading ? '#' + links[i].heading : '')) !== actual && normalizedTarget(links[i].target) !== actual) continue;
|
|
27
|
+
used[i] = true; return links[i];
|
|
28
|
+
}
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function decorateAnchors(root, outgoing) {
|
|
33
|
+
var links = outgoing.filter(function (item) { return item.kind === 'markdown' && !item.external; }); var anchors = root.querySelectorAll('a[href], button.clay-file-link'); var used = [];
|
|
34
|
+
for (var i = 0; i < anchors.length; i++) {
|
|
35
|
+
var link = matchingLink(anchors[i], links, used); if (!link) continue;
|
|
36
|
+
anchors[i].classList.remove('clay-file-link'); delete anchors[i].dataset.filePath; delete anchors[i].dataset.fileProjectSlug; delete anchors[i].dataset.fileSessionId;
|
|
37
|
+
anchors[i].removeAttribute('title'); anchors[i].setAttribute('aria-label', 'Open Knowledge document ' + (link.label || link.target || 'link'));
|
|
38
|
+
applyTarget(anchors[i], link); anchors[i].classList.add('knowledge-inline-link');
|
|
39
|
+
if (link.documentKey || link.self || link.unresolved) anchors[i].removeAttribute('href');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function safeExternalTarget(value) {
|
|
44
|
+
var target = String(value || '').trim();
|
|
45
|
+
return /^(?:https?:|mailto:|tel:)/i.test(target) ? target : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function decorateWiki(root, outgoing) {
|
|
49
|
+
var links = outgoing.filter(function (item) { return item.kind === 'wiki' || item.kind === 'embed'; }); if (!links.length) return;
|
|
50
|
+
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT); var nodes = []; var node;
|
|
51
|
+
while ((node = walker.nextNode())) if (node.parentElement && !node.parentElement.closest('code, pre, a, button')) nodes.push(node);
|
|
52
|
+
var linkAt = 0;
|
|
53
|
+
for (var n = 0; n < nodes.length && linkAt < links.length; n++) {
|
|
54
|
+
var text = nodes[n].nodeValue || ''; var fragment = document.createDocumentFragment(); var offset = 0; var changed = false;
|
|
55
|
+
while (linkAt < links.length) {
|
|
56
|
+
var link = links[linkAt]; var at = text.indexOf(link.raw, offset); if (at === -1) break;
|
|
57
|
+
fragment.appendChild(document.createTextNode(text.slice(offset, at))); var button;
|
|
58
|
+
if (link.external && safeExternalTarget(link.target)) { button = document.createElement('a'); button.href = safeExternalTarget(link.target); button.target = '_blank'; button.rel = 'noopener noreferrer'; }
|
|
59
|
+
else if (link.external) { button = document.createElement('span'); }
|
|
60
|
+
else { button = document.createElement('button'); button.type = 'button'; applyTarget(button, link); }
|
|
61
|
+
button.className = 'knowledge-inline-link'; button.textContent = link.label || link.target || link.heading; fragment.appendChild(button);
|
|
62
|
+
offset = at + link.raw.length; linkAt++; changed = true;
|
|
63
|
+
}
|
|
64
|
+
if (changed) { fragment.appendChild(document.createTextNode(text.slice(offset))); nodes[n].parentNode.replaceChild(fragment, nodes[n]); }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function decorateKnowledgePreview(root, outgoing) {
|
|
69
|
+
if (!root) return; decorateAnchors(root, outgoing || []); decorateWiki(root, outgoing || []);
|
|
70
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
var inertEntries = [];
|
|
2
|
+
|
|
3
|
+
export function setKnowledgeNarrowBackground(panel, active) {
|
|
4
|
+
if (!panel) return;
|
|
5
|
+
var narrow = active && window.matchMedia('(max-width: 1023px)').matches;
|
|
6
|
+
if (narrow && !inertEntries.length) {
|
|
7
|
+
var branch = panel;
|
|
8
|
+
while (branch.parentElement && branch.parentElement !== document.body) {
|
|
9
|
+
Array.prototype.forEach.call(branch.parentElement.children, function (child) {
|
|
10
|
+
if (child === branch) return;
|
|
11
|
+
inertEntries.push({ element: child, inert: child.inert, ariaHidden: child.getAttribute('aria-hidden') });
|
|
12
|
+
child.inert = true; child.setAttribute('aria-hidden', 'true');
|
|
13
|
+
});
|
|
14
|
+
branch = branch.parentElement;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
if (!narrow && inertEntries.length) {
|
|
18
|
+
inertEntries.forEach(function (item) { item.element.inert = item.inert; if (item.ariaHidden == null) item.element.removeAttribute('aria-hidden'); else item.element.setAttribute('aria-hidden', item.ariaHidden); });
|
|
19
|
+
inertEntries = [];
|
|
20
|
+
}
|
|
21
|
+
}
|