sqlite-hub 2.0.1 → 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.
Files changed (98) hide show
  1. package/README.md +21 -9
  2. package/docs/MCP.md +8 -0
  3. package/docs/changelog.md +16 -0
  4. package/docs/guidelines/DESIGN.md +64 -0
  5. package/docs/todo.md +2 -0
  6. package/frontend/assets/mockups/backups_1_1920.webp +0 -0
  7. package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
  8. package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
  9. package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
  10. package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
  11. package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
  12. package/frontend/assets/mockups/charts_1_1920.webp +0 -0
  13. package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
  14. package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
  15. package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
  16. package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
  17. package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
  18. package/frontend/assets/mockups/connections_1_1920.webp +0 -0
  19. package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
  20. package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
  21. package/frontend/assets/mockups/data_1_1920.webp +0 -0
  22. package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
  23. package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
  24. package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
  25. package/frontend/assets/mockups/documents_1_1920.webp +0 -0
  26. package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
  27. package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
  28. package/frontend/assets/mockups/logs_1_1920.webp +0 -0
  29. package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
  30. package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
  31. package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
  32. package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
  33. package/frontend/assets/mockups/overview_1_1920.webp +0 -0
  34. package/frontend/assets/mockups/settings_1_1920.webp +0 -0
  35. package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
  36. package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
  37. package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
  38. package/frontend/assets/mockups/structure_1_1920.webp +0 -0
  39. package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
  40. package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
  41. package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
  42. package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
  43. package/frontend/js/api.js +18 -0
  44. package/frontend/js/app.js +567 -15
  45. package/frontend/js/components/badges.js +16 -0
  46. package/frontend/js/components/connectionCard.js +35 -0
  47. package/frontend/js/components/modal.js +268 -247
  48. package/frontend/js/components/queryHistoryDetail.js +25 -45
  49. package/frontend/js/components/rowEditorPanel.js +7 -60
  50. package/frontend/js/components/structureGraph.js +292 -13
  51. package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
  52. package/frontend/js/components/tableDesignerEditor.js +51 -16
  53. package/frontend/js/components/tableDesignerSidebar.js +9 -5
  54. package/frontend/js/store.js +1847 -190
  55. package/frontend/js/utils/connectionRegistry.js +76 -0
  56. package/frontend/js/utils/markdownDocuments.js +21 -4
  57. package/frontend/js/utils/tableDesigner.js +431 -62
  58. package/frontend/js/views/backups.js +2 -5
  59. package/frontend/js/views/charts.js +20 -10
  60. package/frontend/js/views/connections.js +101 -6
  61. package/frontend/js/views/data.js +52 -9
  62. package/frontend/js/views/documents.js +153 -59
  63. package/frontend/js/views/structure.js +28 -5
  64. package/frontend/js/views/tableAdvisor.js +14 -6
  65. package/frontend/js/views/tableDesigner.js +44 -9
  66. package/frontend/styles/base.css +9 -0
  67. package/frontend/styles/components.css +676 -79
  68. package/frontend/styles/structure-graph.css +49 -0
  69. package/frontend/styles/tailwind.generated.css +1 -1
  70. package/frontend/styles/views.css +12 -0
  71. package/package.json +1 -1
  72. package/server/routes/backups.js +37 -1
  73. package/server/routes/charts.js +28 -0
  74. package/server/routes/connections.js +12 -0
  75. package/server/routes/data.js +30 -2
  76. package/server/routes/documents.js +61 -5
  77. package/server/routes/settings.js +32 -0
  78. package/server/routes/tableDesigner.js +35 -2
  79. package/server/server.js +3 -3
  80. package/server/services/mcpToolService.js +30 -0
  81. package/server/services/sqlite/connectionManager.js +25 -2
  82. package/server/services/sqlite/dataBrowserService.js +21 -0
  83. package/server/services/sqlite/introspection.js +39 -1
  84. package/server/services/sqlite/overviewService.js +1 -0
  85. package/server/services/sqlite/structureService.js +66 -11
  86. package/server/services/sqlite/tableAdvisor.js +6 -0
  87. package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
  88. package/server/services/sqlite/tableDesigner/sql.js +31 -2
  89. package/server/services/sqlite/tableDesigner/validation.js +69 -9
  90. package/server/services/sqlite/tableDesignerService.js +110 -5
  91. package/server/services/storage/appStateStore.js +590 -10
  92. package/server/utils/fileValidation.js +5 -0
  93. package/server/utils/userActionLog.js +49 -0
  94. package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
  95. package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
  96. package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
  97. package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
  98. package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
@@ -35,6 +35,7 @@ import {
35
35
  clearEditorRowSelection,
36
36
  clearEditorResults,
37
37
  clearQueryHistorySelection,
38
+ clearConnectionTagFilters,
38
39
  closeModal,
39
40
  checkSettingsAppVersion,
40
41
  dismissMediaTaggingIssue,
@@ -46,6 +47,9 @@ import {
46
47
  exportCurrentQueryFormat,
47
48
  getState,
48
49
  initializeApp,
50
+ insertCurrentDocumentDatabaseInfo,
51
+ insertCurrentDocumentSavedQueries,
52
+ insertCurrentDocumentTimeMetadata,
49
53
  insertMarkdownIntoLastOpenDocument,
50
54
  loadMoreQueryHistory,
51
55
  loadMoreLogs,
@@ -66,6 +70,7 @@ import {
66
70
  openDeleteQueryChartModal,
67
71
  openDataExportModal,
68
72
  openGenerateDataModal,
73
+ openCreateDocumentFolderModal,
69
74
  openDeleteDocumentModal,
70
75
  openQueryExportModal,
71
76
  openDataRowByIdentity,
@@ -75,6 +80,7 @@ import {
75
80
  openEditQueryChartModal,
76
81
  openDocumentInsertNoteModal,
77
82
  openDocumentInsertTableModal,
83
+ openDocumentInsertTableDefinitionModal,
78
84
  preserveCurrentDataRowSelectionForReload,
79
85
  openDataRowUpdatePreview,
80
86
  openEditorRowUpdatePreview,
@@ -97,12 +103,21 @@ import {
97
103
  selectQueryHistoryItem,
98
104
  selectStructureEntry,
99
105
  setDocumentsSearchQuery,
106
+ setConnectionSearchQuery,
100
107
  setLogFilter,
101
108
  setLogSearchInput,
102
109
  applyLogSearch,
103
110
  setTableDesignerSearchQuery,
104
111
  setTableDesignerSqlPreviewVisibility,
105
112
  toggleTableDesignerTablesPanel,
113
+ openTableDesignerConstraintsDrawer,
114
+ closeTableDesignerConstraintsDrawer,
115
+ startTableDesignerCheckEditor,
116
+ cancelTableDesignerCheckEditor,
117
+ applyTableDesignerCheckPreset,
118
+ updateTableDesignerCheckEditorField,
119
+ saveTableDesignerCheckEditor,
120
+ removeCurrentTableDesignerCheckConstraint,
106
121
  setDataTableSearchQuery,
107
122
  setStructureTableSearchQuery,
108
123
  toggleStructureTablesPanel,
@@ -125,6 +140,8 @@ import {
125
140
  submitDeleteDocumentConfirmation,
126
141
  submitDocumentInsertNote,
127
142
  submitDocumentInsertTable,
143
+ submitDocumentInsertTableDefinition,
144
+ submitCreateDocumentFolder,
128
145
  submitCreateMediaTaggingTagTable,
129
146
  submitCreateMediaTaggingMappingTable,
130
147
  submitDeleteQueryHistoryConfirmation,
@@ -156,22 +173,28 @@ import {
156
173
  toggleDocumentsPane,
157
174
  setMediaTaggingWorkflowMediaDetailsVisible,
158
175
  setMediaTaggingWorkflowMediaRotationDegrees,
159
- queueTableDesignerCsvImport,
176
+ queueTableDesignerImport,
160
177
  showToast,
161
178
  storeCopyColumnPreferences,
162
179
  submitCreateConnection,
163
180
  createCurrentMediaTag,
181
+ moveCurrentDocumentToFolder,
164
182
  submitDeleteRowConfirmation,
165
183
  submitEditConnection,
166
184
  submitImportSql,
167
185
  submitOpenConnection,
168
186
  subscribe,
169
187
  toggleCurrentMediaTagSelection,
188
+ toggleConnectionTagFilter,
170
189
  toggleQueryHistorySavedState,
190
+ addEditConnectionTag,
171
191
  updateCurrentMediaTaggingField,
172
192
  updateCurrentMediaTaggingTagFormField,
173
193
  updateCopyColumnModalFormatField,
174
194
  updateCurrentDocumentDraftField,
195
+ updateDocumentTableDefinitionOption,
196
+ updateDocumentTableDefinitionSampleRowCount,
197
+ updateDocumentTableDefinitionSelection,
175
198
  updateDocumentInsertQuerySelection,
176
199
  updateCurrentQueryChartDraftConfigField,
177
200
  updateCurrentQueryChartDraftField,
@@ -181,6 +204,8 @@ import {
181
204
  updateGenerateDataMapping,
182
205
  updateGenerateDataModal,
183
206
  updateGenerateTypesModal,
207
+ updateEditConnectionTagQuery,
208
+ removeEditConnectionTag,
184
209
  addCurrentTableDesignerColumn,
185
210
  applyCurrentMediaTaggingSelection,
186
211
  removeCurrentTableDesignerColumn,
@@ -188,7 +213,7 @@ import {
188
213
  import { renderChartsDetail, renderChartsView } from './views/charts.js';
189
214
  import { renderBackupsView } from './views/backups.js';
190
215
  import { renderConnectionsView } from './views/connections.js';
191
- import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
216
+ import { buildDataSidebarSignature, renderDataRowEditorPanel, renderDataView } from './views/data.js';
192
217
  import { renderDocumentsView } from './views/documents.js';
193
218
  import { renderEditorView } from './views/editor.js';
194
219
  import { renderLandingView } from './views/landing.js';
@@ -260,6 +285,7 @@ let lastRenderedToastMarkup = '';
260
285
  let lastRenderedChartsHistorySignature = '';
261
286
  let lastRenderedChartsDetailSignature = '';
262
287
  let lastRenderedChartsCardSignature = '';
288
+ let lastRenderedDataSidebarSignature = '';
263
289
  let lastRenderedPanelOpen = false;
264
290
  let lastRenderedLockedRoute = false;
265
291
  let pendingNewTableDesignerAutofocus = false;
@@ -267,10 +293,25 @@ let pendingQueryEditorFocus = false;
267
293
  let pendingMediaTaggingTagSearchFocus = false;
268
294
  let documentAutosaveTimer = null;
269
295
  let pendingDocumentAutosaveId = null;
296
+ let lastDocumentEditorInsertionRange = null;
270
297
 
271
- const DOCUMENT_AUTOSAVE_DELAY_MS = 5000;
298
+ const DOCUMENT_AUTOSAVE_DELAY_MS = 2000;
272
299
 
273
300
  const APP_TITLE = 'SQLite Hub';
301
+ const TABLE_DESIGNER_IMPORT_FORMATS = {
302
+ csv: {
303
+ accept: '.csv,text/csv',
304
+ label: 'CSV',
305
+ },
306
+ tsv: {
307
+ accept: '.tsv,text/tab-separated-values',
308
+ label: 'TSV',
309
+ },
310
+ json: {
311
+ accept: '.json,application/json',
312
+ label: 'JSON',
313
+ },
314
+ };
274
315
  const ROUTE_TITLE_SEGMENTS = {
275
316
  connections: 'Connections',
276
317
  backups: 'Backups',
@@ -575,6 +616,28 @@ function syncTableDesignerDraftUi(sourceNode) {
575
616
  return synced;
576
617
  }
577
618
 
619
+ function normalizeCheckExpressionPreview(expression) {
620
+ const normalized = String(expression ?? '').trim();
621
+
622
+ if (!normalized) {
623
+ return 'CHECK (...)';
624
+ }
625
+
626
+ return /^CHECK\s*\(/i.test(normalized) ? normalized : `CHECK (${normalized})`;
627
+ }
628
+
629
+ function syncTableDesignerCheckEditorPreview(sourceNode) {
630
+ const drawerNode = sourceNode.closest('.table-designer-check-drawer');
631
+ const previewNode = drawerNode?.querySelector('[data-table-designer-check-preview]');
632
+
633
+ if (!(previewNode instanceof HTMLElement)) {
634
+ return false;
635
+ }
636
+
637
+ replaceChildrenFromRenderedMarkup(previewNode, highlightSql(normalizeCheckExpressionPreview(sourceNode.value)));
638
+ return true;
639
+ }
640
+
578
641
  function syncDataRowSelectionUi(selectedRowIndex = null) {
579
642
  if (getState().route.name !== 'data') {
580
643
  return false;
@@ -600,6 +663,81 @@ function syncDataRowSelectionUi(selectedRowIndex = null) {
600
663
  return true;
601
664
  }
602
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
+
603
741
  function syncQueryHistoryUi(historyId) {
604
742
  const state = getState();
605
743
  const numericId = Number(historyId);
@@ -1074,6 +1212,71 @@ function patchLogsTableUi(state) {
1074
1212
  return true;
1075
1213
  }
1076
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
+
1077
1280
  function readFileAsBase64(file) {
1078
1281
  return new Promise((resolve, reject) => {
1079
1282
  const reader = new FileReader();
@@ -1383,6 +1586,29 @@ function focusMediaTaggingTagSearchInput() {
1383
1586
  return true;
1384
1587
  }
1385
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
+
1386
1612
  function syncSidebarActiveRoute(routeName) {
1387
1613
  if (!isMediaTaggingRouteName(routeName)) {
1388
1614
  return false;
@@ -1417,11 +1643,18 @@ async function applyMediaTaggingAndFocusSearch() {
1417
1643
  return result;
1418
1644
  }
1419
1645
 
1420
- async function handleTableDesignerCsvImport(fileInput) {
1646
+ function normalizeTableDesignerImportFormat(value) {
1647
+ const normalized = String(value ?? '').trim().toLowerCase();
1648
+ return Object.prototype.hasOwnProperty.call(TABLE_DESIGNER_IMPORT_FORMATS, normalized) ? normalized : 'csv';
1649
+ }
1650
+
1651
+ async function handleTableDesignerImport(fileInput) {
1421
1652
  if (!(fileInput instanceof HTMLInputElement)) {
1422
1653
  return;
1423
1654
  }
1424
1655
 
1656
+ const format = normalizeTableDesignerImportFormat(fileInput.dataset.importFormat);
1657
+ const formatLabel = TABLE_DESIGNER_IMPORT_FORMATS[format].label;
1425
1658
  const file = fileInput.files?.[0];
1426
1659
 
1427
1660
  if (!(file instanceof File)) {
@@ -1429,14 +1662,14 @@ async function handleTableDesignerCsvImport(fileInput) {
1429
1662
  }
1430
1663
 
1431
1664
  if (!file.size) {
1432
- showToast('The selected CSV file is empty.', 'alert');
1665
+ showToast(`The selected ${formatLabel} file is empty.`, 'alert');
1433
1666
  fileInput.value = '';
1434
1667
  return;
1435
1668
  }
1436
1669
 
1437
1670
  try {
1438
- const csvText = await readFileAsText(file);
1439
- const imported = queueTableDesignerCsvImport(file.name, csvText);
1671
+ const text = await readFileAsText(file);
1672
+ const imported = queueTableDesignerImport(file.name, text, { format });
1440
1673
 
1441
1674
  if (!imported) {
1442
1675
  return;
@@ -1450,7 +1683,7 @@ async function handleTableDesignerCsvImport(fileInput) {
1450
1683
  'success',
1451
1684
  );
1452
1685
  } catch (error) {
1453
- showToast(error?.message || 'CSV import failed.', 'alert');
1686
+ showToast(error?.message || `${formatLabel} import failed.`, 'alert');
1454
1687
  } finally {
1455
1688
  fileInput.value = '';
1456
1689
  }
@@ -1503,6 +1736,7 @@ function renderApp(state) {
1503
1736
  const chartsHistorySignature = buildChartsHistorySignature(state);
1504
1737
  const chartsDetailSignature = buildChartsDetailSignature(state);
1505
1738
  const chartsCardSignature = buildChartsCardSignature(state);
1739
+ const dataSidebarSignature = buildDataSidebarSignature(state);
1506
1740
  const isLockedRoute = [
1507
1741
  'editor',
1508
1742
  'editorResults',
@@ -1526,6 +1760,7 @@ function renderApp(state) {
1526
1760
  const chartsHistoryChanged = chartsHistorySignature !== lastRenderedChartsHistorySignature;
1527
1761
  const chartsDetailChanged = chartsDetailSignature !== lastRenderedChartsDetailSignature;
1528
1762
  const chartsCardsChanged = chartsCardSignature !== lastRenderedChartsCardSignature;
1763
+ const dataSidebarChanged = dataSidebarSignature !== lastRenderedDataSidebarSignature;
1529
1764
  const panelOpenChanged = panelOpen !== lastRenderedPanelOpen;
1530
1765
  const lockedRouteChanged = isLockedRoute !== lastRenderedLockedRoute;
1531
1766
  const shellMarkupUnchanged =
@@ -1566,9 +1801,16 @@ function renderApp(state) {
1566
1801
 
1567
1802
  const canPatchChartsMain = mainChanged && previousRouteName === 'charts' && state.route.name === 'charts';
1568
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;
1569
1807
  let mainPatched = false;
1570
1808
  let preservedChartsDom = false;
1571
1809
 
1810
+ if (canPatchDataMain) {
1811
+ mainPatched = patchDataMainUi(state, { tableHorizontalScrollState });
1812
+ }
1813
+
1572
1814
  if (canPatchChartsMain) {
1573
1815
  const historyPatched = !chartsHistoryChanged || patchChartsHistoryUi(state);
1574
1816
 
@@ -1595,6 +1837,10 @@ function renderApp(state) {
1595
1837
  mainPatched = patchLogsTableUi(state);
1596
1838
  }
1597
1839
 
1840
+ if (!mainPatched && canPatchDocumentsMain) {
1841
+ mainPatched = patchDocumentsTextOnlyUi(state, main);
1842
+ }
1843
+
1598
1844
  if (mainChanged) {
1599
1845
  if (!mainPatched) {
1600
1846
  teardownStructureGraph();
@@ -1674,6 +1920,8 @@ function renderApp(state) {
1674
1920
  if (focusNewTableDesignerNameField()) {
1675
1921
  pendingNewTableDesignerAutofocus = false;
1676
1922
  }
1923
+ } else if (modalChanged && state.modal && focusModalAutofocusElement()) {
1924
+ // Modal content is inserted dynamically, so native autofocus is not reliable.
1677
1925
  } else {
1678
1926
  restoreFocusedInputState(focusedInput);
1679
1927
  }
@@ -1690,6 +1938,7 @@ function renderApp(state) {
1690
1938
  lastRenderedChartsHistorySignature = chartsHistorySignature;
1691
1939
  lastRenderedChartsDetailSignature = chartsDetailSignature;
1692
1940
  lastRenderedChartsCardSignature = chartsCardSignature;
1941
+ lastRenderedDataSidebarSignature = dataSidebarSignature;
1693
1942
  lastRenderedPanelOpen = panelOpen;
1694
1943
  lastRenderedLockedRoute = isLockedRoute;
1695
1944
 
@@ -2379,16 +2628,60 @@ function exportCurrentDocumentMarkdown() {
2379
2628
  showToast(`Document "${filename}" exported.`, 'success');
2380
2629
  }
2381
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
+
2382
2662
  function getCurrentDocumentEditorInsertionRange() {
2383
2663
  const textarea = document.querySelector('.documents-editor-input');
2384
2664
 
2385
- if (!(textarea instanceof HTMLTextAreaElement)) {
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 ?? '')) {
2386
2679
  return null;
2387
2680
  }
2388
2681
 
2389
2682
  return {
2390
- start: textarea.selectionStart,
2391
- end: textarea.selectionEnd,
2683
+ start: lastDocumentEditorInsertionRange.start,
2684
+ end: lastDocumentEditorInsertionRange.end,
2392
2685
  };
2393
2686
  }
2394
2687
 
@@ -2621,6 +2914,12 @@ async function handleAction(actionNode) {
2621
2914
  }
2622
2915
  return;
2623
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;
2624
2923
  case 'select-document': {
2625
2924
  const documentId = String(actionNode.dataset.documentId ?? '').trim();
2626
2925
 
@@ -2643,6 +2942,33 @@ async function handleAction(actionNode) {
2643
2942
  case 'open-document-insert-note-modal':
2644
2943
  await openDocumentInsertNoteModal(getCurrentDocumentEditorInsertionRange());
2645
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
+ }
2646
2972
  case 'import-document-markdown': {
2647
2973
  const fileInput = document.querySelector('[data-bind="document-import-file"]');
2648
2974
 
@@ -2662,8 +2988,9 @@ async function handleAction(actionNode) {
2662
2988
  toggleDocumentsPane(actionNode.dataset.pane);
2663
2989
  return;
2664
2990
  case 'toggle-document-todo':
2665
- clearDocumentAutosaveTimer();
2666
- await toggleCurrentDocumentTodo(actionNode.dataset.lineIndex);
2991
+ if (toggleCurrentDocumentTodo(actionNode.dataset.lineIndex)) {
2992
+ scheduleDocumentAutosave(getState().documents.selectedId);
2993
+ }
2667
2994
  return;
2668
2995
  case 'open-create-query-chart-modal':
2669
2996
  openCreateQueryChartModal();
@@ -2680,6 +3007,16 @@ async function handleAction(actionNode) {
2680
3007
  case 'edit-connection':
2681
3008
  openEditConnectionModal(actionNode.dataset.connectionId);
2682
3009
  return;
3010
+ case 'clear-connection-tag-filters':
3011
+ clearConnectionTagFilters();
3012
+ return;
3013
+ case 'add-edit-connection-tag':
3014
+ case 'create-edit-connection-tag':
3015
+ addEditConnectionTag(actionNode.dataset.tagName);
3016
+ return;
3017
+ case 'remove-edit-connection-tag':
3018
+ removeEditConnectionTag(actionNode.dataset.tagName);
3019
+ return;
2683
3020
  case 'choose-open-database-path': {
2684
3021
  const labelNode = actionNode.querySelector('[data-open-database-path-button-label]');
2685
3022
 
@@ -3063,6 +3400,30 @@ async function handleAction(actionNode) {
3063
3400
  removeCurrentTableDesignerColumn(actionNode.dataset.columnId);
3064
3401
  }
3065
3402
  return;
3403
+ case 'open-table-designer-constraints':
3404
+ openTableDesignerConstraintsDrawer(actionNode.dataset.columnId, actionNode.dataset.columnName);
3405
+ return;
3406
+ case 'close-table-designer-constraints':
3407
+ closeTableDesignerConstraintsDrawer();
3408
+ return;
3409
+ case 'start-table-designer-check-editor':
3410
+ startTableDesignerCheckEditor();
3411
+ return;
3412
+ case 'edit-table-designer-check':
3413
+ startTableDesignerCheckEditor(actionNode.dataset.constraintId);
3414
+ return;
3415
+ case 'cancel-table-designer-check-editor':
3416
+ cancelTableDesignerCheckEditor();
3417
+ return;
3418
+ case 'apply-table-designer-check-preset':
3419
+ applyTableDesignerCheckPreset(actionNode.dataset.presetId);
3420
+ return;
3421
+ case 'save-table-designer-check':
3422
+ await saveTableDesignerCheckEditor();
3423
+ return;
3424
+ case 'remove-table-designer-check':
3425
+ removeCurrentTableDesignerCheckConstraint(actionNode.dataset.constraintId);
3426
+ return;
3066
3427
  case 'save-table-designer': {
3067
3428
  const savedTableName = await saveCurrentTableDesignerDraft();
3068
3429
  if (savedTableName) {
@@ -3186,14 +3547,20 @@ async function handleAction(actionNode) {
3186
3547
  router.navigate(`/structure/${encodeURIComponent(mediaTableName)}`);
3187
3548
  return;
3188
3549
  }
3189
- case 'import-table-designer-csv': {
3550
+ case 'import-table-designer-data': {
3190
3551
  const fileInput = document.querySelector('[data-bind="table-designer-import-file"]');
3191
3552
 
3192
3553
  if (!(fileInput instanceof HTMLInputElement)) {
3193
3554
  return;
3194
3555
  }
3195
3556
 
3557
+ const format = normalizeTableDesignerImportFormat(actionNode.dataset.importFormat);
3558
+ const formatOptions = TABLE_DESIGNER_IMPORT_FORMATS[format];
3559
+
3560
+ fileInput.dataset.importFormat = format;
3561
+ fileInput.accept = actionNode.dataset.importAccept || formatOptions.accept;
3196
3562
  fileInput.value = '';
3563
+ actionNode.closest('[data-dropdown-button]')?.removeAttribute('open');
3197
3564
  fileInput.click();
3198
3565
  return;
3199
3566
  }
@@ -3256,6 +3623,35 @@ function canApplyMediaTaggingShortcut(state) {
3256
3623
  );
3257
3624
  }
3258
3625
 
3626
+ function isEditableShortcutTarget(target) {
3627
+ if (!(target instanceof HTMLElement)) {
3628
+ return false;
3629
+ }
3630
+
3631
+ if (
3632
+ target instanceof HTMLInputElement ||
3633
+ target instanceof HTMLTextAreaElement ||
3634
+ target instanceof HTMLSelectElement
3635
+ ) {
3636
+ return true;
3637
+ }
3638
+
3639
+ if (target.isContentEditable) {
3640
+ return true;
3641
+ }
3642
+
3643
+ if (
3644
+ target.closest(
3645
+ '[contenteditable="true"], [data-bind="current-query"], [data-sql-highlight="true"], .query-editor-input',
3646
+ )
3647
+ ) {
3648
+ return true;
3649
+ }
3650
+
3651
+ const role = String(target.getAttribute('role') ?? '').trim().toLowerCase();
3652
+ return role === 'textbox' || role === 'searchbox' || role === 'combobox';
3653
+ }
3654
+
3259
3655
  document.addEventListener('click', event => {
3260
3656
  const target = event.target instanceof Element ? event.target : null;
3261
3657
 
@@ -3322,6 +3718,30 @@ document.addEventListener('contextmenu', event => {
3322
3718
  openCopyColumnMenu(headerNode);
3323
3719
  });
3324
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
+
3325
3745
  document.addEventListener('keydown', event => {
3326
3746
  const target = event.target;
3327
3747
  const state = getState();
@@ -3340,6 +3760,27 @@ document.addEventListener('keydown', event => {
3340
3760
  return;
3341
3761
  }
3342
3762
 
3763
+ if (
3764
+ event.key === '/' &&
3765
+ !event.shiftKey &&
3766
+ !event.altKey &&
3767
+ !event.ctrlKey &&
3768
+ !event.metaKey &&
3769
+ !event.defaultPrevented &&
3770
+ state.route.name === 'connections' &&
3771
+ !isEditableShortcutTarget(target)
3772
+ ) {
3773
+ const searchInput = document.querySelector('[data-connections-search-input]');
3774
+
3775
+ if (searchInput instanceof HTMLInputElement) {
3776
+ event.preventDefault();
3777
+ searchInput.focus({ preventScroll: true });
3778
+ searchInput.select();
3779
+ }
3780
+
3781
+ return;
3782
+ }
3783
+
3343
3784
  // Handle Enter key in tag form fields to trigger create tag
3344
3785
  if (
3345
3786
  event.key === 'Enter' &&
@@ -3359,6 +3800,34 @@ document.addEventListener('keydown', event => {
3359
3800
  return;
3360
3801
  }
3361
3802
 
3803
+ if (
3804
+ event.key === 'Enter' &&
3805
+ !event.shiftKey &&
3806
+ !event.altKey &&
3807
+ !event.ctrlKey &&
3808
+ !event.metaKey &&
3809
+ !event.defaultPrevented &&
3810
+ target instanceof HTMLElement &&
3811
+ target.dataset.bind === 'edit-connection-tag-query'
3812
+ ) {
3813
+ event.preventDefault();
3814
+ const tagQuery = target instanceof HTMLInputElement ? target.value.trim() : '';
3815
+
3816
+ if (!tagQuery) {
3817
+ return;
3818
+ }
3819
+
3820
+ const primaryTagAction =
3821
+ document.querySelector('[data-edit-connection-tag-primary]') ??
3822
+ document.querySelector('[data-action="add-edit-connection-tag"]');
3823
+
3824
+ if (primaryTagAction instanceof HTMLElement) {
3825
+ primaryTagAction.click();
3826
+ }
3827
+
3828
+ return;
3829
+ }
3830
+
3362
3831
  if (
3363
3832
  (event.key === 'Enter' || event.code === 'Enter' || event.code === 'NumpadEnter') &&
3364
3833
  event.shiftKey &&
@@ -3396,6 +3865,16 @@ document.addEventListener('keydown', event => {
3396
3865
  return;
3397
3866
  }
3398
3867
 
3868
+ if (target instanceof HTMLInputElement && target.dataset.bind === 'connections-search') {
3869
+ event.preventDefault();
3870
+
3871
+ if (!clearInputForEscape(target)) {
3872
+ target.blur();
3873
+ }
3874
+
3875
+ return;
3876
+ }
3877
+
3399
3878
  if (target instanceof HTMLInputElement && clearInputForEscape(target)) {
3400
3879
  event.preventDefault();
3401
3880
  return;
@@ -3425,6 +3904,12 @@ document.addEventListener('keydown', event => {
3425
3904
  return;
3426
3905
  }
3427
3906
 
3907
+ if (state.route.name === 'tableDesigner' && state.tableDesigner.constraintsDrawer?.visible) {
3908
+ event.preventDefault();
3909
+ closeTableDesignerConstraintsDrawer();
3910
+ return;
3911
+ }
3912
+
3428
3913
  if (state.route.name === 'charts' && state.charts.detailPanelVisible) {
3429
3914
  event.preventDefault();
3430
3915
  setChartsDetailPanelVisibility(false);
@@ -3523,6 +4008,7 @@ document.addEventListener('input', event => {
3523
4008
  }
3524
4009
 
3525
4010
  if (bindNode.dataset.bind === 'document-field') {
4011
+ rememberDocumentEditorInsertionRangeFromTarget(bindNode);
3526
4012
  updateCurrentDocumentDraftField(bindNode.dataset.field, bindNode.value);
3527
4013
  scheduleDocumentAutosave(getState().documents.selectedId);
3528
4014
  return;
@@ -3538,6 +4024,16 @@ document.addEventListener('input', event => {
3538
4024
  return;
3539
4025
  }
3540
4026
 
4027
+ if (bindNode.dataset.bind === 'connections-search') {
4028
+ setConnectionSearchQuery(bindNode.value);
4029
+ return;
4030
+ }
4031
+
4032
+ if (bindNode.dataset.bind === 'edit-connection-tag-query') {
4033
+ updateEditConnectionTagQuery(bindNode.value);
4034
+ return;
4035
+ }
4036
+
3541
4037
  if (bindNode.dataset.bind === 'data-search-query') {
3542
4038
  void setDataSearchQuery(bindNode.value);
3543
4039
  return;
@@ -3563,6 +4059,20 @@ document.addEventListener('input', event => {
3563
4059
  return;
3564
4060
  }
3565
4061
 
4062
+ if (bindNode.dataset.bind === 'table-designer-check-editor-field') {
4063
+ const field = bindNode.dataset.field;
4064
+ const value = bindNode instanceof HTMLInputElement || bindNode instanceof HTMLTextAreaElement ? bindNode.value : '';
4065
+ const isCustomExpression = field === 'expression';
4066
+
4067
+ updateTableDesignerCheckEditorField(field, value, { notify: !isCustomExpression });
4068
+
4069
+ if (isCustomExpression) {
4070
+ syncTableDesignerCheckEditorPreview(bindNode);
4071
+ }
4072
+
4073
+ return;
4074
+ }
4075
+
3566
4076
  if (bindNode.dataset.bind === 'table-designer-field') {
3567
4077
  if (
3568
4078
  (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') ||
@@ -3709,6 +4219,12 @@ document.addEventListener('change', event => {
3709
4219
  return;
3710
4220
  }
3711
4221
 
4222
+ if (bindNode.dataset.bind === 'connection-tag-filter') {
4223
+ const nextValue = bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox' ? bindNode.checked : false;
4224
+ toggleConnectionTagFilter(bindNode.dataset.tagId, nextValue);
4225
+ return;
4226
+ }
4227
+
3712
4228
  if (bindNode.dataset.bind === 'media-tagging-field') {
3713
4229
  updateCurrentMediaTaggingField(bindNode.dataset.field, bindNode.value);
3714
4230
  void refreshMediaTaggingPreview();
@@ -3731,7 +4247,15 @@ document.addEventListener('change', event => {
3731
4247
  }
3732
4248
 
3733
4249
  if (bindNode.dataset.bind === 'table-designer-import-file') {
3734
- void handleTableDesignerCsvImport(bindNode);
4250
+ void handleTableDesignerImport(bindNode);
4251
+ return;
4252
+ }
4253
+
4254
+ if (bindNode.dataset.bind === 'table-designer-check-editor-field') {
4255
+ const value = bindNode instanceof HTMLInputElement || bindNode instanceof HTMLTextAreaElement ? bindNode.value : '';
4256
+
4257
+ updateTableDesignerCheckEditorField(bindNode.dataset.field, value, { notify: false });
4258
+ syncTableDesignerCheckEditorPreview(bindNode);
3735
4259
  return;
3736
4260
  }
3737
4261
 
@@ -3745,6 +4269,21 @@ document.addEventListener('change', event => {
3745
4269
  return;
3746
4270
  }
3747
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
+
3748
4287
  if (bindNode.dataset.bind === 'table-designer-field') {
3749
4288
  if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
3750
4289
  updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.checked);
@@ -3913,6 +4452,7 @@ document.addEventListener('submit', async event => {
3913
4452
  readOnly: formData.get('readOnly') === 'on',
3914
4453
  clearLogo: formData.get('clearLogo') === 'on' && !logoUpload,
3915
4454
  logoUpload,
4455
+ tags: formData.getAll('tags').map(value => String(value ?? '')),
3916
4456
  });
3917
4457
 
3918
4458
  return;
@@ -3958,6 +4498,10 @@ document.addEventListener('submit', async event => {
3958
4498
  case 'delete-api-token-confirm':
3959
4499
  await submitDeleteSettingsApiTokenConfirmation();
3960
4500
  return;
4501
+ case 'create-document-folder': {
4502
+ await submitCreateDocumentFolder(String(formData.get('name') ?? ''));
4503
+ return;
4504
+ }
3961
4505
  case 'document-insert-table': {
3962
4506
  const inserted = await submitDocumentInsertTable();
3963
4507
 
@@ -3966,6 +4510,14 @@ document.addEventListener('submit', async event => {
3966
4510
  }
3967
4511
  return;
3968
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
+ }
3969
4521
  case 'document-insert-note': {
3970
4522
  const inserted = submitDocumentInsertNote();
3971
4523