sqlite-hub 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/docs/MCP.md +2 -2
- package/docs/changelog.md +6 -0
- package/docs/todo.md +2 -0
- package/frontend/js/api.js +7 -0
- package/frontend/js/app.js +329 -7
- package/frontend/js/components/modal.js +152 -2
- package/frontend/js/store.js +1332 -219
- package/frontend/js/utils/markdownDocuments.js +21 -4
- package/frontend/js/views/connections.js +3 -8
- package/frontend/js/views/data.js +34 -5
- package/frontend/js/views/documents.js +153 -59
- package/frontend/styles/base.css +9 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +12 -0
- package/package.json +1 -1
- package/server/routes/backups.js +37 -1
- package/server/routes/charts.js +28 -0
- package/server/routes/data.js +29 -1
- package/server/routes/documents.js +61 -5
- package/server/routes/settings.js +32 -0
- package/server/routes/tableDesigner.js +20 -1
- package/server/server.js +3 -3
- package/server/services/mcpToolService.js +4 -2
- package/server/services/sqlite/connectionManager.js +2 -0
- package/server/services/sqlite/overviewService.js +1 -0
- package/server/services/storage/appStateStore.js +287 -6
- package/server/utils/fileValidation.js +5 -0
- package/server/utils/userActionLog.js +49 -0
package/README.md
CHANGED
|
@@ -154,6 +154,13 @@ The preview supports regular Markdown, ordered and unordered lists, tables, code
|
|
|
154
154
|
- Insert Note opens saved queries that have notes and inserts the selected note directly into the document.
|
|
155
155
|
- Markdown Todo column exports from query results can create a new document without embedding the original SQL query.
|
|
156
156
|
|
|
157
|
+
Documents also support Magic Snippets from the Insert menu:
|
|
158
|
+
|
|
159
|
+
- Insert Time Metadata adds `created at` and `last modified` lines for the document.
|
|
160
|
+
- Insert Database Info adds current database size, estimated pages, table count, and journal mode.
|
|
161
|
+
|
|
162
|
+
Magic Snippets are managed blocks. When a document is opened, SQLite Hub refreshes existing Time Metadata and Database Info snippets with the latest document/database values.
|
|
163
|
+
|
|
157
164
|
<p>
|
|
158
165
|
<a href="./frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp"><img src="./frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp" alt="SQLite Hub insert saved query table into a document" width="49%"></a>
|
|
159
166
|
<a href="./frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp"><img src="./frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp" alt="SQLite Hub insert saved query note into a document" width="49%"></a>
|
package/docs/MCP.md
CHANGED
|
@@ -58,7 +58,7 @@ Read-only and safe tools:
|
|
|
58
58
|
- `get_indexes`: return all indexes or indexes for one table.
|
|
59
59
|
- `get_foreign_keys`: return all foreign keys or foreign keys for one table.
|
|
60
60
|
- `run_readonly_query`: execute a read-only `SELECT`, `PRAGMA`, or `EXPLAIN` query.
|
|
61
|
-
- `
|
|
61
|
+
- `get_saved_queries`: list saved SQL Editor queries for a database, equivalent to `sqlite-hub --database:name --queries`.
|
|
62
62
|
- `explain_query_plan`: run `EXPLAIN QUERY PLAN` and return structured plan rows plus index hints when a table scan appears.
|
|
63
63
|
- `read_documents`: read database-scoped Markdown documents.
|
|
64
64
|
|
|
@@ -113,5 +113,5 @@ Use SQLite Hub MCP to explain the query plan for this SQL query.
|
|
|
113
113
|
```
|
|
114
114
|
|
|
115
115
|
```text
|
|
116
|
-
Use SQLite Hub MCP to list my
|
|
116
|
+
Use SQLite Hub MCP to list my saved queries and execute the one named "Company List".
|
|
117
117
|
```
|
package/docs/changelog.md
CHANGED
package/docs/todo.md
CHANGED
package/frontend/js/api.js
CHANGED
|
@@ -671,6 +671,13 @@ export function createDocument(payload = {}) {
|
|
|
671
671
|
});
|
|
672
672
|
}
|
|
673
673
|
|
|
674
|
+
export function createDocumentFolder(payload = {}) {
|
|
675
|
+
return request("/api/documents/folders", {
|
|
676
|
+
method: "POST",
|
|
677
|
+
body: payload,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
|
|
674
681
|
export function getDocument(documentId) {
|
|
675
682
|
return request(`/api/documents/${encodeURIComponent(documentId)}`);
|
|
676
683
|
}
|
package/frontend/js/app.js
CHANGED
|
@@ -47,6 +47,9 @@ import {
|
|
|
47
47
|
exportCurrentQueryFormat,
|
|
48
48
|
getState,
|
|
49
49
|
initializeApp,
|
|
50
|
+
insertCurrentDocumentDatabaseInfo,
|
|
51
|
+
insertCurrentDocumentSavedQueries,
|
|
52
|
+
insertCurrentDocumentTimeMetadata,
|
|
50
53
|
insertMarkdownIntoLastOpenDocument,
|
|
51
54
|
loadMoreQueryHistory,
|
|
52
55
|
loadMoreLogs,
|
|
@@ -67,6 +70,7 @@ import {
|
|
|
67
70
|
openDeleteQueryChartModal,
|
|
68
71
|
openDataExportModal,
|
|
69
72
|
openGenerateDataModal,
|
|
73
|
+
openCreateDocumentFolderModal,
|
|
70
74
|
openDeleteDocumentModal,
|
|
71
75
|
openQueryExportModal,
|
|
72
76
|
openDataRowByIdentity,
|
|
@@ -76,6 +80,7 @@ import {
|
|
|
76
80
|
openEditQueryChartModal,
|
|
77
81
|
openDocumentInsertNoteModal,
|
|
78
82
|
openDocumentInsertTableModal,
|
|
83
|
+
openDocumentInsertTableDefinitionModal,
|
|
79
84
|
preserveCurrentDataRowSelectionForReload,
|
|
80
85
|
openDataRowUpdatePreview,
|
|
81
86
|
openEditorRowUpdatePreview,
|
|
@@ -135,6 +140,8 @@ import {
|
|
|
135
140
|
submitDeleteDocumentConfirmation,
|
|
136
141
|
submitDocumentInsertNote,
|
|
137
142
|
submitDocumentInsertTable,
|
|
143
|
+
submitDocumentInsertTableDefinition,
|
|
144
|
+
submitCreateDocumentFolder,
|
|
138
145
|
submitCreateMediaTaggingTagTable,
|
|
139
146
|
submitCreateMediaTaggingMappingTable,
|
|
140
147
|
submitDeleteQueryHistoryConfirmation,
|
|
@@ -171,6 +178,7 @@ import {
|
|
|
171
178
|
storeCopyColumnPreferences,
|
|
172
179
|
submitCreateConnection,
|
|
173
180
|
createCurrentMediaTag,
|
|
181
|
+
moveCurrentDocumentToFolder,
|
|
174
182
|
submitDeleteRowConfirmation,
|
|
175
183
|
submitEditConnection,
|
|
176
184
|
submitImportSql,
|
|
@@ -184,6 +192,9 @@ import {
|
|
|
184
192
|
updateCurrentMediaTaggingTagFormField,
|
|
185
193
|
updateCopyColumnModalFormatField,
|
|
186
194
|
updateCurrentDocumentDraftField,
|
|
195
|
+
updateDocumentTableDefinitionOption,
|
|
196
|
+
updateDocumentTableDefinitionSampleRowCount,
|
|
197
|
+
updateDocumentTableDefinitionSelection,
|
|
187
198
|
updateDocumentInsertQuerySelection,
|
|
188
199
|
updateCurrentQueryChartDraftConfigField,
|
|
189
200
|
updateCurrentQueryChartDraftField,
|
|
@@ -202,7 +213,7 @@ import {
|
|
|
202
213
|
import { renderChartsDetail, renderChartsView } from './views/charts.js';
|
|
203
214
|
import { renderBackupsView } from './views/backups.js';
|
|
204
215
|
import { renderConnectionsView } from './views/connections.js';
|
|
205
|
-
import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
216
|
+
import { buildDataSidebarSignature, renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
206
217
|
import { renderDocumentsView } from './views/documents.js';
|
|
207
218
|
import { renderEditorView } from './views/editor.js';
|
|
208
219
|
import { renderLandingView } from './views/landing.js';
|
|
@@ -274,6 +285,7 @@ let lastRenderedToastMarkup = '';
|
|
|
274
285
|
let lastRenderedChartsHistorySignature = '';
|
|
275
286
|
let lastRenderedChartsDetailSignature = '';
|
|
276
287
|
let lastRenderedChartsCardSignature = '';
|
|
288
|
+
let lastRenderedDataSidebarSignature = '';
|
|
277
289
|
let lastRenderedPanelOpen = false;
|
|
278
290
|
let lastRenderedLockedRoute = false;
|
|
279
291
|
let pendingNewTableDesignerAutofocus = false;
|
|
@@ -281,8 +293,9 @@ let pendingQueryEditorFocus = false;
|
|
|
281
293
|
let pendingMediaTaggingTagSearchFocus = false;
|
|
282
294
|
let documentAutosaveTimer = null;
|
|
283
295
|
let pendingDocumentAutosaveId = null;
|
|
296
|
+
let lastDocumentEditorInsertionRange = null;
|
|
284
297
|
|
|
285
|
-
const DOCUMENT_AUTOSAVE_DELAY_MS =
|
|
298
|
+
const DOCUMENT_AUTOSAVE_DELAY_MS = 2000;
|
|
286
299
|
|
|
287
300
|
const APP_TITLE = 'SQLite Hub';
|
|
288
301
|
const TABLE_DESIGNER_IMPORT_FORMATS = {
|
|
@@ -650,6 +663,81 @@ function syncDataRowSelectionUi(selectedRowIndex = null) {
|
|
|
650
663
|
return true;
|
|
651
664
|
}
|
|
652
665
|
|
|
666
|
+
function syncDataSidebarActiveTable(state) {
|
|
667
|
+
if (state.route.name !== 'data') {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
const sidebarNode = shellRefs.view.querySelector('[data-data-sidebar]');
|
|
672
|
+
|
|
673
|
+
if (!(sidebarNode instanceof HTMLElement)) {
|
|
674
|
+
return state.dataBrowser.tablesVisible === false;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const selectedTableName = String(state.dataBrowser.selectedTable ?? '');
|
|
678
|
+
|
|
679
|
+
for (const itemNode of sidebarNode.querySelectorAll('[data-data-table-item]')) {
|
|
680
|
+
if (!(itemNode instanceof HTMLElement)) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
const isActive = itemNode.dataset.dataTableName === selectedTableName;
|
|
685
|
+
|
|
686
|
+
itemNode.classList.toggle('is-active', isActive);
|
|
687
|
+
itemNode.classList.toggle('border-primary-container/30', isActive);
|
|
688
|
+
itemNode.classList.toggle('bg-surface-container-high', isActive);
|
|
689
|
+
itemNode.classList.toggle('border-outline-variant/10', !isActive);
|
|
690
|
+
itemNode.classList.toggle('bg-surface-container-lowest', !isActive);
|
|
691
|
+
itemNode.classList.toggle('hover:bg-surface-container-high', !isActive);
|
|
692
|
+
|
|
693
|
+
const labelNode = itemNode.querySelector('[data-data-table-name-label]');
|
|
694
|
+
|
|
695
|
+
if (labelNode instanceof HTMLElement) {
|
|
696
|
+
labelNode.classList.toggle('is-active', isActive);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return true;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
function renderDataMainIntoScratch(state) {
|
|
704
|
+
const scratch = document.createElement('div');
|
|
705
|
+
|
|
706
|
+
replaceChildrenFromRenderedMarkup(scratch, renderDataView(state).main);
|
|
707
|
+
return scratch;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function patchDataMainUi(state, { tableHorizontalScrollState = null } = {}) {
|
|
711
|
+
const dataView = shellRefs.view.querySelector('[data-data-view]');
|
|
712
|
+
|
|
713
|
+
if (!(dataView instanceof HTMLElement)) {
|
|
714
|
+
return false;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const currentWorkspace = dataView.querySelector('[data-data-workspace]');
|
|
718
|
+
const currentSidebar = dataView.querySelector('[data-data-sidebar]');
|
|
719
|
+
const scratch = renderDataMainIntoScratch(state);
|
|
720
|
+
const nextWorkspace = scratch.querySelector('[data-data-workspace]');
|
|
721
|
+
const nextSidebar = scratch.querySelector('[data-data-sidebar]');
|
|
722
|
+
|
|
723
|
+
if (
|
|
724
|
+
!(currentWorkspace instanceof HTMLElement) ||
|
|
725
|
+
!(nextWorkspace instanceof HTMLElement) ||
|
|
726
|
+
Boolean(currentSidebar) !== Boolean(nextSidebar)
|
|
727
|
+
) {
|
|
728
|
+
return false;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
currentWorkspace.replaceWith(nextWorkspace);
|
|
732
|
+
restoreTableHorizontalScrollState({
|
|
733
|
+
snapshot: tableHorizontalScrollState,
|
|
734
|
+
routeName: state.route.name,
|
|
735
|
+
scrollNodes: nextWorkspace.querySelectorAll('[data-table-horizontal-scroll]'),
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
return syncDataSidebarActiveTable(state);
|
|
739
|
+
}
|
|
740
|
+
|
|
653
741
|
function syncQueryHistoryUi(historyId) {
|
|
654
742
|
const state = getState();
|
|
655
743
|
const numericId = Number(historyId);
|
|
@@ -1124,6 +1212,71 @@ function patchLogsTableUi(state) {
|
|
|
1124
1212
|
return true;
|
|
1125
1213
|
}
|
|
1126
1214
|
|
|
1215
|
+
function normalizeDocumentsTextPatchMarkup(markup) {
|
|
1216
|
+
return String(markup ?? '')
|
|
1217
|
+
.replace(
|
|
1218
|
+
/(<span\b[^>]*data-document-save-state=")(?:saved|unsaved)("[^>]*>)[\s\S]*?(<\/span>)/g,
|
|
1219
|
+
'$1__document_save_state__$2__document_save_state__$3',
|
|
1220
|
+
)
|
|
1221
|
+
.replace(
|
|
1222
|
+
/(<span\b[^>]*data-document-list-meta\b[^>]*>)[\s\S]*?(<\/span>)/g,
|
|
1223
|
+
'$1__document_list_meta__$2',
|
|
1224
|
+
);
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function renderDocumentsMainIntoScratch(state) {
|
|
1228
|
+
const scratch = document.createElement('div');
|
|
1229
|
+
|
|
1230
|
+
replaceChildrenFromRenderedMarkup(scratch, renderDocumentsView(state).main);
|
|
1231
|
+
return scratch;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
function patchDocumentsTextNodes(selector, scratch) {
|
|
1235
|
+
const currentNodes = shellRefs.view.querySelectorAll(selector);
|
|
1236
|
+
const nextNodes = scratch.querySelectorAll(selector);
|
|
1237
|
+
|
|
1238
|
+
if (currentNodes.length !== nextNodes.length) {
|
|
1239
|
+
return false;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
for (let index = 0; index < currentNodes.length; index += 1) {
|
|
1243
|
+
const currentNode = currentNodes[index];
|
|
1244
|
+
const nextNode = nextNodes[index];
|
|
1245
|
+
|
|
1246
|
+
if (!(currentNode instanceof HTMLElement) || !(nextNode instanceof HTMLElement)) {
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
currentNode.textContent = nextNode.textContent;
|
|
1251
|
+
|
|
1252
|
+
if (selector === '[data-document-save-state]') {
|
|
1253
|
+
currentNode.dataset.documentSaveState = nextNode.dataset.documentSaveState ?? '';
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
return true;
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
function patchDocumentsTextOnlyUi(state, nextMainMarkup) {
|
|
1261
|
+
if (lastRenderedRouteName !== 'documents' || state.route.name !== 'documents') {
|
|
1262
|
+
return false;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
const previousComparable = normalizeDocumentsTextPatchMarkup(lastRenderedMainMarkup);
|
|
1266
|
+
const nextComparable = normalizeDocumentsTextPatchMarkup(nextMainMarkup);
|
|
1267
|
+
|
|
1268
|
+
if (previousComparable !== nextComparable) {
|
|
1269
|
+
return false;
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
const scratch = renderDocumentsMainIntoScratch(state);
|
|
1273
|
+
|
|
1274
|
+
return (
|
|
1275
|
+
patchDocumentsTextNodes('[data-document-save-state]', scratch) &&
|
|
1276
|
+
patchDocumentsTextNodes('[data-document-list-meta]', scratch)
|
|
1277
|
+
);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1127
1280
|
function readFileAsBase64(file) {
|
|
1128
1281
|
return new Promise((resolve, reject) => {
|
|
1129
1282
|
const reader = new FileReader();
|
|
@@ -1433,6 +1586,29 @@ function focusMediaTaggingTagSearchInput() {
|
|
|
1433
1586
|
return true;
|
|
1434
1587
|
}
|
|
1435
1588
|
|
|
1589
|
+
function focusModalAutofocusElement() {
|
|
1590
|
+
const element = shellRefs.modal.querySelector('[autofocus]');
|
|
1591
|
+
|
|
1592
|
+
if (
|
|
1593
|
+
!(
|
|
1594
|
+
element instanceof HTMLInputElement ||
|
|
1595
|
+
element instanceof HTMLTextAreaElement ||
|
|
1596
|
+
element instanceof HTMLSelectElement ||
|
|
1597
|
+
element instanceof HTMLButtonElement
|
|
1598
|
+
)
|
|
1599
|
+
) {
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
element.focus({ preventScroll: true });
|
|
1604
|
+
|
|
1605
|
+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
|
|
1606
|
+
element.setSelectionRange(element.value.length, element.value.length);
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
return true;
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1436
1612
|
function syncSidebarActiveRoute(routeName) {
|
|
1437
1613
|
if (!isMediaTaggingRouteName(routeName)) {
|
|
1438
1614
|
return false;
|
|
@@ -1560,6 +1736,7 @@ function renderApp(state) {
|
|
|
1560
1736
|
const chartsHistorySignature = buildChartsHistorySignature(state);
|
|
1561
1737
|
const chartsDetailSignature = buildChartsDetailSignature(state);
|
|
1562
1738
|
const chartsCardSignature = buildChartsCardSignature(state);
|
|
1739
|
+
const dataSidebarSignature = buildDataSidebarSignature(state);
|
|
1563
1740
|
const isLockedRoute = [
|
|
1564
1741
|
'editor',
|
|
1565
1742
|
'editorResults',
|
|
@@ -1583,6 +1760,7 @@ function renderApp(state) {
|
|
|
1583
1760
|
const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
|
|
1584
1761
|
const chartsDetailChanged = chartsDetailSignature !== lastRenderedChartsDetailSignature;
|
|
1585
1762
|
const chartsCardsChanged = chartsCardSignature !== lastRenderedChartsCardSignature;
|
|
1763
|
+
const dataSidebarChanged = dataSidebarSignature !== lastRenderedDataSidebarSignature;
|
|
1586
1764
|
const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
|
|
1587
1765
|
const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
|
|
1588
1766
|
const shellMarkupUnchanged =
|
|
@@ -1623,9 +1801,16 @@ function renderApp(state) {
|
|
|
1623
1801
|
|
|
1624
1802
|
const canPatchChartsMain = mainChanged && previousRouteName === 'charts' && state.route.name === 'charts';
|
|
1625
1803
|
const canPatchLogsMain = mainChanged && previousRouteName === 'logs' && state.route.name === 'logs';
|
|
1804
|
+
const canPatchDocumentsMain = mainChanged && previousRouteName === 'documents' && state.route.name === 'documents';
|
|
1805
|
+
const canPatchDataMain =
|
|
1806
|
+
mainChanged && previousRouteName === 'data' && state.route.name === 'data' && !dataSidebarChanged;
|
|
1626
1807
|
let mainPatched = false;
|
|
1627
1808
|
let preservedChartsDom = false;
|
|
1628
1809
|
|
|
1810
|
+
if (canPatchDataMain) {
|
|
1811
|
+
mainPatched = patchDataMainUi(state, { tableHorizontalScrollState });
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1629
1814
|
if (canPatchChartsMain) {
|
|
1630
1815
|
const historyPatched = !chartsHistoryChanged || patchChartsHistoryUi(state);
|
|
1631
1816
|
|
|
@@ -1652,6 +1837,10 @@ function renderApp(state) {
|
|
|
1652
1837
|
mainPatched = patchLogsTableUi(state);
|
|
1653
1838
|
}
|
|
1654
1839
|
|
|
1840
|
+
if (!mainPatched && canPatchDocumentsMain) {
|
|
1841
|
+
mainPatched = patchDocumentsTextOnlyUi(state, main);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1655
1844
|
if (mainChanged) {
|
|
1656
1845
|
if (!mainPatched) {
|
|
1657
1846
|
teardownStructureGraph();
|
|
@@ -1731,6 +1920,8 @@ function renderApp(state) {
|
|
|
1731
1920
|
if (focusNewTableDesignerNameField()) {
|
|
1732
1921
|
pendingNewTableDesignerAutofocus = false;
|
|
1733
1922
|
}
|
|
1923
|
+
} else if (modalChanged && state.modal && focusModalAutofocusElement()) {
|
|
1924
|
+
// Modal content is inserted dynamically, so native autofocus is not reliable.
|
|
1734
1925
|
} else {
|
|
1735
1926
|
restoreFocusedInputState(focusedInput);
|
|
1736
1927
|
}
|
|
@@ -1747,6 +1938,7 @@ function renderApp(state) {
|
|
|
1747
1938
|
lastRenderedChartsHistorySignature = chartsHistorySignature;
|
|
1748
1939
|
lastRenderedChartsDetailSignature = chartsDetailSignature;
|
|
1749
1940
|
lastRenderedChartsCardSignature = chartsCardSignature;
|
|
1941
|
+
lastRenderedDataSidebarSignature = dataSidebarSignature;
|
|
1750
1942
|
lastRenderedPanelOpen = panelOpen;
|
|
1751
1943
|
lastRenderedLockedRoute = isLockedRoute;
|
|
1752
1944
|
|
|
@@ -2436,16 +2628,60 @@ function exportCurrentDocumentMarkdown() {
|
|
|
2436
2628
|
showToast(`Document "${filename}" exported.`, 'success');
|
|
2437
2629
|
}
|
|
2438
2630
|
|
|
2631
|
+
function isDocumentEditorInput(node) {
|
|
2632
|
+
return (
|
|
2633
|
+
node instanceof HTMLTextAreaElement &&
|
|
2634
|
+
node.classList.contains('documents-editor-input') &&
|
|
2635
|
+
node.dataset.bind === 'document-field' &&
|
|
2636
|
+
node.dataset.field === 'content'
|
|
2637
|
+
);
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
function rememberDocumentEditorInsertionRange(textarea) {
|
|
2641
|
+
if (!isDocumentEditorInput(textarea)) {
|
|
2642
|
+
return null;
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
const documents = getState().documents;
|
|
2646
|
+
const range = {
|
|
2647
|
+
documentId: String(documents.selectedId ?? ''),
|
|
2648
|
+
start: textarea.selectionStart,
|
|
2649
|
+
end: textarea.selectionEnd,
|
|
2650
|
+
};
|
|
2651
|
+
|
|
2652
|
+
lastDocumentEditorInsertionRange = range;
|
|
2653
|
+
return range;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
function rememberDocumentEditorInsertionRangeFromTarget(target) {
|
|
2657
|
+
if (isDocumentEditorInput(target)) {
|
|
2658
|
+
rememberDocumentEditorInsertionRange(target);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2439
2662
|
function getCurrentDocumentEditorInsertionRange() {
|
|
2440
2663
|
const textarea = document.querySelector('.documents-editor-input');
|
|
2441
2664
|
|
|
2442
|
-
if (
|
|
2665
|
+
if (isDocumentEditorInput(textarea)) {
|
|
2666
|
+
const range = rememberDocumentEditorInsertionRange(textarea);
|
|
2667
|
+
|
|
2668
|
+
return range
|
|
2669
|
+
? {
|
|
2670
|
+
start: range.start,
|
|
2671
|
+
end: range.end,
|
|
2672
|
+
}
|
|
2673
|
+
: null;
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
const documents = getState().documents;
|
|
2677
|
+
|
|
2678
|
+
if (String(lastDocumentEditorInsertionRange?.documentId ?? '') !== String(documents.selectedId ?? '')) {
|
|
2443
2679
|
return null;
|
|
2444
2680
|
}
|
|
2445
2681
|
|
|
2446
2682
|
return {
|
|
2447
|
-
start:
|
|
2448
|
-
end:
|
|
2683
|
+
start: lastDocumentEditorInsertionRange.start,
|
|
2684
|
+
end: lastDocumentEditorInsertionRange.end,
|
|
2449
2685
|
};
|
|
2450
2686
|
}
|
|
2451
2687
|
|
|
@@ -2678,6 +2914,12 @@ async function handleAction(actionNode) {
|
|
|
2678
2914
|
}
|
|
2679
2915
|
return;
|
|
2680
2916
|
}
|
|
2917
|
+
case 'open-create-document-folder-modal':
|
|
2918
|
+
openCreateDocumentFolderModal();
|
|
2919
|
+
return;
|
|
2920
|
+
case 'move-document-to-folder':
|
|
2921
|
+
await moveCurrentDocumentToFolder(actionNode.dataset.folderId || null);
|
|
2922
|
+
return;
|
|
2681
2923
|
case 'select-document': {
|
|
2682
2924
|
const documentId = String(actionNode.dataset.documentId ?? '').trim();
|
|
2683
2925
|
|
|
@@ -2700,6 +2942,33 @@ async function handleAction(actionNode) {
|
|
|
2700
2942
|
case 'open-document-insert-note-modal':
|
|
2701
2943
|
await openDocumentInsertNoteModal(getCurrentDocumentEditorInsertionRange());
|
|
2702
2944
|
return;
|
|
2945
|
+
case 'open-document-insert-table-definition-modal':
|
|
2946
|
+
await openDocumentInsertTableDefinitionModal(getCurrentDocumentEditorInsertionRange());
|
|
2947
|
+
return;
|
|
2948
|
+
case 'insert-document-saved-queries': {
|
|
2949
|
+
const inserted = await insertCurrentDocumentSavedQueries(getCurrentDocumentEditorInsertionRange());
|
|
2950
|
+
|
|
2951
|
+
if (inserted) {
|
|
2952
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
2953
|
+
}
|
|
2954
|
+
return;
|
|
2955
|
+
}
|
|
2956
|
+
case 'insert-document-time-metadata': {
|
|
2957
|
+
const inserted = await insertCurrentDocumentTimeMetadata(getCurrentDocumentEditorInsertionRange());
|
|
2958
|
+
|
|
2959
|
+
if (inserted) {
|
|
2960
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
2961
|
+
}
|
|
2962
|
+
return;
|
|
2963
|
+
}
|
|
2964
|
+
case 'insert-document-database-info': {
|
|
2965
|
+
const inserted = await insertCurrentDocumentDatabaseInfo(getCurrentDocumentEditorInsertionRange());
|
|
2966
|
+
|
|
2967
|
+
if (inserted) {
|
|
2968
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
2969
|
+
}
|
|
2970
|
+
return;
|
|
2971
|
+
}
|
|
2703
2972
|
case 'import-document-markdown': {
|
|
2704
2973
|
const fileInput = document.querySelector('[data-bind="document-import-file"]');
|
|
2705
2974
|
|
|
@@ -2719,8 +2988,9 @@ async function handleAction(actionNode) {
|
|
|
2719
2988
|
toggleDocumentsPane(actionNode.dataset.pane);
|
|
2720
2989
|
return;
|
|
2721
2990
|
case 'toggle-document-todo':
|
|
2722
|
-
|
|
2723
|
-
|
|
2991
|
+
if (toggleCurrentDocumentTodo(actionNode.dataset.lineIndex)) {
|
|
2992
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
2993
|
+
}
|
|
2724
2994
|
return;
|
|
2725
2995
|
case 'open-create-query-chart-modal':
|
|
2726
2996
|
openCreateQueryChartModal();
|
|
@@ -3448,6 +3718,30 @@ document.addEventListener('contextmenu', event => {
|
|
|
3448
3718
|
openCopyColumnMenu(headerNode);
|
|
3449
3719
|
});
|
|
3450
3720
|
|
|
3721
|
+
document.addEventListener('focusin', event => {
|
|
3722
|
+
rememberDocumentEditorInsertionRangeFromTarget(event.target);
|
|
3723
|
+
});
|
|
3724
|
+
|
|
3725
|
+
document.addEventListener('keyup', event => {
|
|
3726
|
+
rememberDocumentEditorInsertionRangeFromTarget(event.target);
|
|
3727
|
+
});
|
|
3728
|
+
|
|
3729
|
+
document.addEventListener('pointerup', event => {
|
|
3730
|
+
rememberDocumentEditorInsertionRangeFromTarget(event.target);
|
|
3731
|
+
});
|
|
3732
|
+
|
|
3733
|
+
document.addEventListener(
|
|
3734
|
+
'select',
|
|
3735
|
+
event => {
|
|
3736
|
+
rememberDocumentEditorInsertionRangeFromTarget(event.target);
|
|
3737
|
+
},
|
|
3738
|
+
true,
|
|
3739
|
+
);
|
|
3740
|
+
|
|
3741
|
+
document.addEventListener('selectionchange', () => {
|
|
3742
|
+
rememberDocumentEditorInsertionRangeFromTarget(document.activeElement);
|
|
3743
|
+
});
|
|
3744
|
+
|
|
3451
3745
|
document.addEventListener('keydown', event => {
|
|
3452
3746
|
const target = event.target;
|
|
3453
3747
|
const state = getState();
|
|
@@ -3714,6 +4008,7 @@ document.addEventListener('input', event => {
|
|
|
3714
4008
|
}
|
|
3715
4009
|
|
|
3716
4010
|
if (bindNode.dataset.bind === 'document-field') {
|
|
4011
|
+
rememberDocumentEditorInsertionRangeFromTarget(bindNode);
|
|
3717
4012
|
updateCurrentDocumentDraftField(bindNode.dataset.field, bindNode.value);
|
|
3718
4013
|
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
3719
4014
|
return;
|
|
@@ -3974,6 +4269,21 @@ document.addEventListener('change', event => {
|
|
|
3974
4269
|
return;
|
|
3975
4270
|
}
|
|
3976
4271
|
|
|
4272
|
+
if (bindNode.dataset.bind === 'document-table-definition-select') {
|
|
4273
|
+
updateDocumentTableDefinitionSelection(bindNode.value);
|
|
4274
|
+
return;
|
|
4275
|
+
}
|
|
4276
|
+
|
|
4277
|
+
if (bindNode.dataset.bind === 'document-table-definition-option') {
|
|
4278
|
+
updateDocumentTableDefinitionOption(bindNode.dataset.option, bindNode.checked);
|
|
4279
|
+
return;
|
|
4280
|
+
}
|
|
4281
|
+
|
|
4282
|
+
if (bindNode.dataset.bind === 'document-table-definition-row-count') {
|
|
4283
|
+
updateDocumentTableDefinitionSampleRowCount(bindNode.value);
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4286
|
+
|
|
3977
4287
|
if (bindNode.dataset.bind === 'table-designer-field') {
|
|
3978
4288
|
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
3979
4289
|
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.checked);
|
|
@@ -4188,6 +4498,10 @@ document.addEventListener('submit', async event => {
|
|
|
4188
4498
|
case 'delete-api-token-confirm':
|
|
4189
4499
|
await submitDeleteSettingsApiTokenConfirmation();
|
|
4190
4500
|
return;
|
|
4501
|
+
case 'create-document-folder': {
|
|
4502
|
+
await submitCreateDocumentFolder(String(formData.get('name') ?? ''));
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4191
4505
|
case 'document-insert-table': {
|
|
4192
4506
|
const inserted = await submitDocumentInsertTable();
|
|
4193
4507
|
|
|
@@ -4196,6 +4510,14 @@ document.addEventListener('submit', async event => {
|
|
|
4196
4510
|
}
|
|
4197
4511
|
return;
|
|
4198
4512
|
}
|
|
4513
|
+
case 'document-insert-table-definition': {
|
|
4514
|
+
const inserted = await submitDocumentInsertTableDefinition();
|
|
4515
|
+
|
|
4516
|
+
if (inserted) {
|
|
4517
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
4518
|
+
}
|
|
4519
|
+
return;
|
|
4520
|
+
}
|
|
4199
4521
|
case 'document-insert-note': {
|
|
4200
4522
|
const inserted = submitDocumentInsertNote();
|
|
4201
4523
|
|