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.
- package/README.md +21 -9
- package/docs/MCP.md +8 -0
- package/docs/changelog.md +16 -0
- package/docs/guidelines/DESIGN.md +64 -0
- package/docs/todo.md +2 -0
- package/frontend/assets/mockups/backups_1_1920.webp +0 -0
- package/frontend/assets/mockups/backups_2_compare_drawer_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_6_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_1_1920.webp +0 -0
- package/frontend/assets/mockups/charts_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/charts_3_create_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_4_edit_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_5_delete_query_chart_modal_1920.webp +0 -0
- package/frontend/assets/mockups/charts_6_copy_column_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_1_1920.webp +0 -0
- package/frontend/assets/mockups/connections_2_create_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/connections_3_open_connection_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_1_1920.webp +0 -0
- package/frontend/assets/mockups/data_2_roweditor_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_generate_data_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_4_data_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_1_1920.webp +0 -0
- package/frontend/assets/mockups/documents_2_document_insert_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/documents_3_document_insert_note_modal_1920.webp +0 -0
- package/frontend/assets/mockups/logs_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_queue_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_1_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_2_create_media_tagging_tag_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/media_tagging_setup_3_create_media_tagging_mapping_table_modal_1920.webp +0 -0
- package/frontend/assets/mockups/overview_1_1920.webp +0 -0
- package/frontend/assets/mockups/settings_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_detail_1920.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_query_export_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_1_1920.webp +0 -0
- package/frontend/assets/mockups/structure_2_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/structure_3_generate_types_modal_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1920.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_table_designer_constraints_modal_1920.webp +0 -0
- package/frontend/js/api.js +18 -0
- package/frontend/js/app.js +567 -15
- package/frontend/js/components/badges.js +16 -0
- package/frontend/js/components/connectionCard.js +35 -0
- package/frontend/js/components/modal.js +268 -247
- package/frontend/js/components/queryHistoryDetail.js +25 -45
- package/frontend/js/components/rowEditorPanel.js +7 -60
- package/frontend/js/components/structureGraph.js +292 -13
- package/frontend/js/components/tableDesignerConstraintsDrawer.js +400 -0
- package/frontend/js/components/tableDesignerEditor.js +51 -16
- package/frontend/js/components/tableDesignerSidebar.js +9 -5
- package/frontend/js/store.js +1847 -190
- package/frontend/js/utils/connectionRegistry.js +76 -0
- package/frontend/js/utils/markdownDocuments.js +21 -4
- package/frontend/js/utils/tableDesigner.js +431 -62
- package/frontend/js/views/backups.js +2 -5
- package/frontend/js/views/charts.js +20 -10
- package/frontend/js/views/connections.js +101 -6
- package/frontend/js/views/data.js +52 -9
- package/frontend/js/views/documents.js +153 -59
- package/frontend/js/views/structure.js +28 -5
- package/frontend/js/views/tableAdvisor.js +14 -6
- package/frontend/js/views/tableDesigner.js +44 -9
- package/frontend/styles/base.css +9 -0
- package/frontend/styles/components.css +676 -79
- package/frontend/styles/structure-graph.css +49 -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/connections.js +12 -0
- package/server/routes/data.js +30 -2
- package/server/routes/documents.js +61 -5
- package/server/routes/settings.js +32 -0
- package/server/routes/tableDesigner.js +35 -2
- package/server/server.js +3 -3
- package/server/services/mcpToolService.js +30 -0
- package/server/services/sqlite/connectionManager.js +25 -2
- package/server/services/sqlite/dataBrowserService.js +21 -0
- package/server/services/sqlite/introspection.js +39 -1
- package/server/services/sqlite/overviewService.js +1 -0
- package/server/services/sqlite/structureService.js +66 -11
- package/server/services/sqlite/tableAdvisor.js +6 -0
- package/server/services/sqlite/tableDesigner/schemaMapping.js +7 -1
- package/server/services/sqlite/tableDesigner/sql.js +31 -2
- package/server/services/sqlite/tableDesigner/validation.js +69 -9
- package/server/services/sqlite/tableDesignerService.js +110 -5
- package/server/services/storage/appStateStore.js +590 -10
- package/server/utils/fileValidation.js +5 -0
- package/server/utils/userActionLog.js +49 -0
- package/frontend/assets/mockups/backups_2_create_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_3_edit_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_4_restore_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/backups_5_delete_backup_modal_1920.webp +0 -0
- package/frontend/assets/mockups/data_3_data_export_modal_1920.webp +0 -0
package/frontend/js/store.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import * as api from './api.js';
|
|
2
|
-
import { formatCellValue, inferStatusTone, truncateMiddle } from './utils/format.js';
|
|
2
|
+
import { formatBytes, formatCellValue, formatNumber, inferStatusTone, truncateMiddle } from './utils/format.js';
|
|
3
3
|
import {
|
|
4
4
|
addTableDesignerColumn,
|
|
5
|
-
|
|
5
|
+
addTableDesignerCheckConstraint,
|
|
6
|
+
createTableDesignerDraftFromImport,
|
|
6
7
|
createNewTableDesignerDraft,
|
|
7
8
|
hydrateTableDesignerDraft,
|
|
9
|
+
recalculateTableDesignerDraft,
|
|
10
|
+
removeTableDesignerCheckConstraint,
|
|
8
11
|
removeTableDesignerColumn,
|
|
9
12
|
updateTableDesignerColumnField,
|
|
10
13
|
updateTableDesignerConstraintField,
|
|
@@ -28,6 +31,10 @@ import {
|
|
|
28
31
|
getDefaultSyntheticOptions,
|
|
29
32
|
normalizeSyntheticGeneratorType,
|
|
30
33
|
} from './utils/syntheticData.js';
|
|
34
|
+
import {
|
|
35
|
+
normalizeConnectionTagKey,
|
|
36
|
+
normalizeConnectionTagName,
|
|
37
|
+
} from './utils/connectionRegistry.js';
|
|
31
38
|
|
|
32
39
|
const listeners = new Set();
|
|
33
40
|
const DEFAULT_SETTINGS = {
|
|
@@ -90,6 +97,16 @@ const MISSING_DATABASE_ERROR = {
|
|
|
90
97
|
message: 'No active SQLite database selected.',
|
|
91
98
|
};
|
|
92
99
|
|
|
100
|
+
function createClosedTableDesignerConstraintsDrawer() {
|
|
101
|
+
return {
|
|
102
|
+
visible: false,
|
|
103
|
+
columnId: '',
|
|
104
|
+
columnName: '',
|
|
105
|
+
editingConstraintId: '',
|
|
106
|
+
editor: null,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
let routeLoadVersion = 0;
|
|
94
111
|
let queryHistoryLoadVersion = 0;
|
|
95
112
|
let queryHistoryDetailLoadVersion = 0;
|
|
@@ -296,6 +313,9 @@ const state = {
|
|
|
296
313
|
connections: {
|
|
297
314
|
recent: [],
|
|
298
315
|
active: null,
|
|
316
|
+
tags: [],
|
|
317
|
+
searchQuery: '',
|
|
318
|
+
selectedTagIds: [],
|
|
299
319
|
loading: false,
|
|
300
320
|
backupLoading: false,
|
|
301
321
|
error: null,
|
|
@@ -442,6 +462,7 @@ const state = {
|
|
|
442
462
|
},
|
|
443
463
|
documents: {
|
|
444
464
|
items: [],
|
|
465
|
+
folders: [],
|
|
445
466
|
selectedId: null,
|
|
446
467
|
selected: null,
|
|
447
468
|
searchQuery: '',
|
|
@@ -465,6 +486,7 @@ const state = {
|
|
|
465
486
|
tablesVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.tableDesignerTablesVisible, true),
|
|
466
487
|
sqlPreviewVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.tableDesignerSqlPreviewVisible, true),
|
|
467
488
|
pendingImportedDraft: null,
|
|
489
|
+
constraintsDrawer: createClosedTableDesignerConstraintsDrawer(),
|
|
468
490
|
loading: false,
|
|
469
491
|
detailLoading: false,
|
|
470
492
|
saving: false,
|
|
@@ -1155,6 +1177,7 @@ function resetChartsState() {
|
|
|
1155
1177
|
function resetDocumentsState() {
|
|
1156
1178
|
documentsLoadVersion += 1;
|
|
1157
1179
|
state.documents.items = [];
|
|
1180
|
+
state.documents.folders = [];
|
|
1158
1181
|
state.documents.selectedId = null;
|
|
1159
1182
|
state.documents.selected = null;
|
|
1160
1183
|
state.documents.searchQuery = '';
|
|
@@ -1190,6 +1213,7 @@ function upsertDocumentSummary(document) {
|
|
|
1190
1213
|
const summary = {
|
|
1191
1214
|
id: document.id,
|
|
1192
1215
|
databaseKey: document.databaseKey,
|
|
1216
|
+
folderId: document.folderId ?? null,
|
|
1193
1217
|
title: document.title,
|
|
1194
1218
|
filename: document.filename,
|
|
1195
1219
|
contentLength: document.contentLength,
|
|
@@ -1531,13 +1555,17 @@ async function refreshConnectionsState() {
|
|
|
1531
1555
|
emitChange();
|
|
1532
1556
|
|
|
1533
1557
|
try {
|
|
1534
|
-
const [recentResponse, activeResponse] = await Promise.all([
|
|
1558
|
+
const [recentResponse, activeResponse, tagsResponse] = await Promise.all([
|
|
1535
1559
|
api.getRecentConnections(),
|
|
1536
1560
|
api.getActiveConnection(),
|
|
1561
|
+
api.getConnectionTags(),
|
|
1537
1562
|
]);
|
|
1538
1563
|
|
|
1539
1564
|
state.connections.recent = recentResponse.data ?? [];
|
|
1540
1565
|
state.connections.active = activeResponse.data ?? null;
|
|
1566
|
+
state.connections.tags = tagsResponse.data ?? [];
|
|
1567
|
+
const availableTagIds = new Set(state.connections.tags.map(tag => String(tag.id)));
|
|
1568
|
+
state.connections.selectedTagIds = state.connections.selectedTagIds.filter(tagId => availableTagIds.has(String(tagId)));
|
|
1541
1569
|
state.connections.error = null;
|
|
1542
1570
|
} catch (error) {
|
|
1543
1571
|
state.connections.error = normalizeError(error);
|
|
@@ -2145,7 +2173,12 @@ async function loadDocumentDetail(version, documentId) {
|
|
|
2145
2173
|
return;
|
|
2146
2174
|
}
|
|
2147
2175
|
|
|
2148
|
-
const document = response.data ?? null;
|
|
2176
|
+
const document = await updateDocumentMagicSnippetsOnOpen(version, response.data ?? null);
|
|
2177
|
+
|
|
2178
|
+
if (version !== routeLoadVersion) {
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2149
2182
|
applyCurrentDocument(document);
|
|
2150
2183
|
upsertDocumentSummary(document);
|
|
2151
2184
|
} catch (error) {
|
|
@@ -2178,6 +2211,7 @@ async function loadDocuments(version, route) {
|
|
|
2178
2211
|
}
|
|
2179
2212
|
|
|
2180
2213
|
state.documents.items = response.data?.items ?? [];
|
|
2214
|
+
state.documents.folders = response.data?.folders ?? [];
|
|
2181
2215
|
state.documents.error = null;
|
|
2182
2216
|
|
|
2183
2217
|
await loadDocumentDetail(version, resolveDocumentSelection(route));
|
|
@@ -2187,6 +2221,7 @@ async function loadDocuments(version, route) {
|
|
|
2187
2221
|
}
|
|
2188
2222
|
|
|
2189
2223
|
state.documents.items = [];
|
|
2224
|
+
state.documents.folders = [];
|
|
2190
2225
|
applyCurrentDocument(null);
|
|
2191
2226
|
state.documents.error = normalizeError(error);
|
|
2192
2227
|
} finally {
|
|
@@ -2474,7 +2509,7 @@ async function loadTableAdvisor(version, route) {
|
|
|
2474
2509
|
return;
|
|
2475
2510
|
}
|
|
2476
2511
|
|
|
2477
|
-
const tables = response.data?.tables ?? [];
|
|
2512
|
+
const tables = (response.data?.tables ?? []).filter(table => !table?.isShadow);
|
|
2478
2513
|
const requestedTableName = route.params?.tableName ?? null;
|
|
2479
2514
|
const selectedTableName =
|
|
2480
2515
|
requestedTableName && tables.some(table => table.name === requestedTableName)
|
|
@@ -2613,6 +2648,7 @@ async function loadTableDesignerDetail(version, tableName) {
|
|
|
2613
2648
|
}
|
|
2614
2649
|
|
|
2615
2650
|
state.tableDesigner.selectedTableName = tableName;
|
|
2651
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
2616
2652
|
state.tableDesigner.draft = decorateTableDesignerDraft(response.data?.draft ?? null);
|
|
2617
2653
|
} catch (error) {
|
|
2618
2654
|
if (version !== routeLoadVersion) {
|
|
@@ -2649,6 +2685,7 @@ async function loadTableDesigner(version, route) {
|
|
|
2649
2685
|
state.tableDesigner.selectedTableName = null;
|
|
2650
2686
|
state.tableDesigner.detailLoading = false;
|
|
2651
2687
|
state.tableDesigner.pendingImportedDraft = null;
|
|
2688
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
2652
2689
|
state.tableDesigner.draft = decorateTableDesignerDraft(importedDraft ?? createNewTableDesignerDraft());
|
|
2653
2690
|
return;
|
|
2654
2691
|
}
|
|
@@ -2665,6 +2702,7 @@ async function loadTableDesigner(version, route) {
|
|
|
2665
2702
|
if (!tableName) {
|
|
2666
2703
|
state.tableDesigner.selectedTableName = null;
|
|
2667
2704
|
state.tableDesigner.detailLoading = false;
|
|
2705
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
2668
2706
|
state.tableDesigner.draft = null;
|
|
2669
2707
|
return;
|
|
2670
2708
|
}
|
|
@@ -3221,6 +3259,11 @@ export function openGenerateDataModal() {
|
|
|
3221
3259
|
return;
|
|
3222
3260
|
}
|
|
3223
3261
|
|
|
3262
|
+
if (table.isShadow) {
|
|
3263
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
3264
|
+
return;
|
|
3265
|
+
}
|
|
3266
|
+
|
|
3224
3267
|
const columns = (table.columnMeta ?? []).filter(column => column.visible && !column.generated);
|
|
3225
3268
|
|
|
3226
3269
|
state.modal = {
|
|
@@ -3519,8 +3562,8 @@ export function toggleDocumentsPane(pane) {
|
|
|
3519
3562
|
}
|
|
3520
3563
|
}
|
|
3521
3564
|
|
|
3522
|
-
function normalizeDocumentInsertionRange(range = null) {
|
|
3523
|
-
const contentLength = String(
|
|
3565
|
+
function normalizeDocumentInsertionRange(range = null, currentContent = state.documents.draftContent) {
|
|
3566
|
+
const contentLength = String(currentContent ?? '').length;
|
|
3524
3567
|
const start = Number(range?.start);
|
|
3525
3568
|
const end = Number(range?.end);
|
|
3526
3569
|
const normalizedStart = Number.isInteger(start) ? Math.max(0, Math.min(contentLength, start)) : contentLength;
|
|
@@ -3542,7 +3585,7 @@ function buildDocumentMarkdownInsertion(currentContent, insertion, range = null)
|
|
|
3542
3585
|
return content;
|
|
3543
3586
|
}
|
|
3544
3587
|
|
|
3545
|
-
const normalizedRange = normalizeDocumentInsertionRange(range);
|
|
3588
|
+
const normalizedRange = normalizeDocumentInsertionRange(range, content);
|
|
3546
3589
|
const before = content.slice(0, normalizedRange.start);
|
|
3547
3590
|
const after = content.slice(normalizedRange.end);
|
|
3548
3591
|
const prefix = before && !before.endsWith('\n\n') ? (before.endsWith('\n') ? '\n' : '\n\n') : '';
|
|
@@ -3551,254 +3594,1351 @@ function buildDocumentMarkdownInsertion(currentContent, insertion, range = null)
|
|
|
3551
3594
|
return `${before}${prefix}${text}${suffix}${after}`;
|
|
3552
3595
|
}
|
|
3553
3596
|
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3597
|
+
const DOCUMENT_METADATA_MAGIC_SNIPPET_ID = 'document-metadata';
|
|
3598
|
+
const DATABASE_INFO_MAGIC_SNIPPET_ID = 'database-info';
|
|
3599
|
+
const TABLE_DEFINITION_MAGIC_SNIPPET_ID = 'table-definition';
|
|
3600
|
+
const DOCUMENT_METADATA_MAGIC_SNIPPET_PATTERN =
|
|
3601
|
+
/(^|\n)-?[^\S\r\n]*created at:[^\r\n]*(?:\r?\n)-?[^\S\r\n]*last modified:[^\r\n]*/i;
|
|
3602
|
+
const DATABASE_INFO_MAGIC_SNIPPET_START = '<!-- sqlite-hub:magic database-info -->';
|
|
3603
|
+
const DATABASE_INFO_MAGIC_SNIPPET_END = '<!-- /sqlite-hub:magic database-info -->';
|
|
3604
|
+
const DATABASE_INFO_MAGIC_SNIPPET_MARKED_PATTERN =
|
|
3605
|
+
/(^|\n)<!-- sqlite-hub:magic database-info -->[\s\S]*?<!-- \/sqlite-hub:magic database-info -->/i;
|
|
3606
|
+
const DATABASE_INFO_MAGIC_SNIPPET_LEGACY_PATTERN =
|
|
3607
|
+
/(^|\n)## Database Info[^\S\r\n]*(?:\r?\n){2}- Database Size:[^\r\n]*(?:\r?\n)- Estimated pages:[^\r\n]*(?:\r?\n)- Tables:[^\r\n]*(?:\r?\n)- Journal Mode:[^\r\n]*/i;
|
|
3608
|
+
const DATABASE_INFO_MAGIC_SNIPPET_PATTERN =
|
|
3609
|
+
/(^|\n)(?:<!-- sqlite-hub:magic database-info -->[\s\S]*?<!-- \/sqlite-hub:magic database-info -->|## Database Info[^\S\r\n]*(?:\r?\n){2}- Database Size:[^\r\n]*(?:\r?\n)- Estimated pages:[^\r\n]*(?:\r?\n)- Tables:[^\r\n]*(?:\r?\n)- Journal Mode:[^\r\n]*)/i;
|
|
3610
|
+
const TABLE_DEFINITION_MAGIC_SNIPPET_PATTERN =
|
|
3611
|
+
/(^|\n)<!-- sqlite-hub:magic table-definition ([^\r\n]+) -->[\s\S]*?<!-- \/sqlite-hub:magic table-definition -->/i;
|
|
3612
|
+
const TABLE_DEFINITION_MAGIC_SNIPPET_BLOCK_PATTERN =
|
|
3613
|
+
/(^|\n)<!-- sqlite-hub:magic table-definition ([^\r\n]+) -->[\s\S]*?<!-- \/sqlite-hub:magic table-definition -->/gi;
|
|
3614
|
+
const TABLE_DEFINITION_SAMPLE_ROW_COUNTS = new Set([3, 5, 10]);
|
|
3615
|
+
const TABLE_DEFINITION_DEFAULT_SAMPLE_ROW_COUNT = 5;
|
|
3616
|
+
const TABLE_DEFINITION_LONG_TEXT_LIMIT = 120;
|
|
3617
|
+
|
|
3618
|
+
function buildDocumentMetadataMagicSnippet(context = {}) {
|
|
3619
|
+
const document = context.document ?? {};
|
|
3620
|
+
const createdAt = String(context.databaseCreatedAt ?? context.createdAt ?? document.createdAt ?? '').trim();
|
|
3621
|
+
const lastModifiedAt = String(
|
|
3622
|
+
context.lastModifiedAt ?? context.latestActivityLogTimestamp ?? document.updatedAt ?? '',
|
|
3623
|
+
).trim();
|
|
3624
|
+
|
|
3625
|
+
if (!createdAt || !lastModifiedAt) {
|
|
3626
|
+
return '';
|
|
3557
3627
|
}
|
|
3558
3628
|
|
|
3559
|
-
|
|
3629
|
+
return [
|
|
3630
|
+
`- created at: ${createdAt}`,
|
|
3631
|
+
`- last modified: ${lastModifiedAt}`,
|
|
3632
|
+
].join('\n');
|
|
3633
|
+
}
|
|
3560
3634
|
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3635
|
+
function unwrapDatabaseInfoMagicSnippet(markdown = '') {
|
|
3636
|
+
const value = String(markdown ?? '').trim();
|
|
3637
|
+
const match = value.match(
|
|
3638
|
+
/^<!-- sqlite-hub:magic database-info -->\s*([\s\S]*?)\s*<!-- \/sqlite-hub:magic database-info -->$/i,
|
|
3639
|
+
);
|
|
3564
3640
|
|
|
3565
|
-
|
|
3566
|
-
state.documents.dirty = true;
|
|
3567
|
-
state.documents.saveError = null;
|
|
3568
|
-
emitChange();
|
|
3569
|
-
return true;
|
|
3641
|
+
return String(match?.[1] ?? value).trim();
|
|
3570
3642
|
}
|
|
3571
3643
|
|
|
3572
|
-
|
|
3573
|
-
|
|
3574
|
-
const inserted = insertMarkdownIntoCurrentDocumentDraft(markdown);
|
|
3644
|
+
function wrapDatabaseInfoMagicSnippet(body = '') {
|
|
3645
|
+
const normalizedBody = String(body ?? '').trim();
|
|
3575
3646
|
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
inserted,
|
|
3579
|
-
saved: false,
|
|
3580
|
-
};
|
|
3647
|
+
if (!normalizedBody) {
|
|
3648
|
+
return '';
|
|
3581
3649
|
}
|
|
3582
3650
|
|
|
3583
|
-
|
|
3651
|
+
return [
|
|
3652
|
+
DATABASE_INFO_MAGIC_SNIPPET_START,
|
|
3653
|
+
normalizedBody,
|
|
3654
|
+
DATABASE_INFO_MAGIC_SNIPPET_END,
|
|
3655
|
+
].join('\n');
|
|
3656
|
+
}
|
|
3584
3657
|
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
};
|
|
3658
|
+
function buildDatabaseInfoMagicSnippetBody(context = {}) {
|
|
3659
|
+
const providedMarkdown = unwrapDatabaseInfoMagicSnippet(context.databaseInfoMarkdown);
|
|
3660
|
+
|
|
3661
|
+
if (providedMarkdown) {
|
|
3662
|
+
return providedMarkdown;
|
|
3591
3663
|
}
|
|
3592
3664
|
|
|
3593
|
-
const
|
|
3594
|
-
const document = response.data ?? null;
|
|
3665
|
+
const overview = context.overview;
|
|
3595
3666
|
|
|
3596
|
-
if (!
|
|
3597
|
-
return
|
|
3598
|
-
documentId: null,
|
|
3599
|
-
inserted: false,
|
|
3600
|
-
saved: false,
|
|
3601
|
-
};
|
|
3667
|
+
if (!overview) {
|
|
3668
|
+
return '';
|
|
3602
3669
|
}
|
|
3603
3670
|
|
|
3604
|
-
const
|
|
3671
|
+
const estimatedSizeBytes = overview.estimatedSizeBytes || overview.file?.sizeBytes || 0;
|
|
3605
3672
|
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
}
|
|
3612
|
-
|
|
3673
|
+
return [
|
|
3674
|
+
'## Database Info',
|
|
3675
|
+
'',
|
|
3676
|
+
`- Database Size: ${formatBytes(overview.file?.sizeBytes ?? estimatedSizeBytes)}`,
|
|
3677
|
+
`- Estimated pages: ${formatNumber(overview.sqlite?.pageCount ?? 0)}`,
|
|
3678
|
+
`- Tables: ${formatNumber(overview.counts?.tables ?? 0)}`,
|
|
3679
|
+
`- Journal Mode: ${String(overview.sqlite?.journalMode ?? 'n/a').toUpperCase()}`,
|
|
3680
|
+
].join('\n');
|
|
3681
|
+
}
|
|
3613
3682
|
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
const updatedDocument = updateResponse.data ?? document;
|
|
3683
|
+
function buildDatabaseInfoMagicSnippet(context = {}) {
|
|
3684
|
+
return wrapDatabaseInfoMagicSnippet(buildDatabaseInfoMagicSnippetBody(context));
|
|
3685
|
+
}
|
|
3618
3686
|
|
|
3619
|
-
|
|
3620
|
-
|
|
3687
|
+
function buildDatabaseInfoMarkdown(overview = {}) {
|
|
3688
|
+
return buildDatabaseInfoMagicSnippetBody({ overview });
|
|
3689
|
+
}
|
|
3690
|
+
|
|
3691
|
+
function normalizeTableDefinitionSampleRowCount(value) {
|
|
3692
|
+
const numericValue = Number(value);
|
|
3693
|
+
|
|
3694
|
+
return TABLE_DEFINITION_SAMPLE_ROW_COUNTS.has(numericValue)
|
|
3695
|
+
? numericValue
|
|
3696
|
+
: TABLE_DEFINITION_DEFAULT_SAMPLE_ROW_COUNT;
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3699
|
+
function normalizeTableDefinitionOptions(options = {}) {
|
|
3700
|
+
const tableName = String(options.tableName ?? options.table ?? '').trim();
|
|
3701
|
+
const markdownTable = Boolean(options.markdownTable ?? options.includeMarkdownTable ?? false);
|
|
3702
|
+
const sqlDefinition = Boolean(options.sqlDefinition ?? options.includeSqlDefinition ?? false);
|
|
3703
|
+
const sampleData = Boolean(options.sampleData ?? options.includeSampleData ?? false);
|
|
3621
3704
|
|
|
3622
3705
|
return {
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3706
|
+
tableName,
|
|
3707
|
+
markdownTable,
|
|
3708
|
+
sqlDefinition,
|
|
3709
|
+
sampleData,
|
|
3710
|
+
sampleRowCount: normalizeTableDefinitionSampleRowCount(options.sampleRowCount),
|
|
3626
3711
|
};
|
|
3627
3712
|
}
|
|
3628
3713
|
|
|
3629
|
-
function
|
|
3630
|
-
return
|
|
3714
|
+
function normalizeTableDefinitionTableKey(tableName) {
|
|
3715
|
+
return String(tableName ?? '').trim().toLowerCase();
|
|
3631
3716
|
}
|
|
3632
3717
|
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3718
|
+
function getTableDefinitionSnippetKey(options = {}) {
|
|
3719
|
+
const normalized = normalizeTableDefinitionOptions(options);
|
|
3720
|
+
|
|
3721
|
+
return JSON.stringify({
|
|
3722
|
+
tableName: normalizeTableDefinitionTableKey(normalized.tableName),
|
|
3723
|
+
markdownTable: normalized.markdownTable,
|
|
3724
|
+
sqlDefinition: normalized.sqlDefinition,
|
|
3725
|
+
sampleData: normalized.sampleData,
|
|
3726
|
+
sampleRowCount: normalized.sampleRowCount,
|
|
3727
|
+
});
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3730
|
+
function encodeDocumentMagicSnippetMetadata(metadata = {}) {
|
|
3731
|
+
return encodeURIComponent(JSON.stringify(metadata));
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
function decodeDocumentMagicSnippetMetadata(encodedMetadata = '') {
|
|
3735
|
+
try {
|
|
3736
|
+
return JSON.parse(decodeURIComponent(String(encodedMetadata ?? '').trim()));
|
|
3737
|
+
} catch {
|
|
3738
|
+
return null;
|
|
3637
3739
|
}
|
|
3740
|
+
}
|
|
3638
3741
|
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3742
|
+
function buildTableDefinitionMagicSnippetMetadata(options = {}) {
|
|
3743
|
+
const normalized = normalizeTableDefinitionOptions(options);
|
|
3744
|
+
|
|
3745
|
+
return {
|
|
3746
|
+
type: TABLE_DEFINITION_MAGIC_SNIPPET_ID,
|
|
3747
|
+
version: 1,
|
|
3748
|
+
tableName: normalized.tableName,
|
|
3749
|
+
markdownTable: normalized.markdownTable,
|
|
3750
|
+
sqlDefinition: normalized.sqlDefinition,
|
|
3751
|
+
sampleData: normalized.sampleData,
|
|
3752
|
+
sampleRowCount: normalized.sampleRowCount,
|
|
3648
3753
|
};
|
|
3649
|
-
|
|
3754
|
+
}
|
|
3650
3755
|
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
onlySaved: true,
|
|
3655
|
-
limit: 100,
|
|
3656
|
-
});
|
|
3657
|
-
const loadedQueries = response.data?.items ?? [];
|
|
3658
|
-
const queries =
|
|
3659
|
-
kind === 'document-insert-note'
|
|
3660
|
-
? loadedQueries.filter(query => String(query.notes ?? '').trim())
|
|
3661
|
-
: loadedQueries;
|
|
3756
|
+
function normalizeMarkdownInlineText(value = '') {
|
|
3757
|
+
return String(value ?? '').replace(/\s+/g, ' ').trim();
|
|
3758
|
+
}
|
|
3662
3759
|
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3760
|
+
function escapeMarkdownTableCell(value = '') {
|
|
3761
|
+
return String(value ?? '')
|
|
3762
|
+
.replace(/\r?\n/g, ' ')
|
|
3763
|
+
.replace(/\|/g, '\\|')
|
|
3764
|
+
.trim();
|
|
3765
|
+
}
|
|
3666
3766
|
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
state.modal.error = null;
|
|
3671
|
-
emitChange();
|
|
3672
|
-
} catch (error) {
|
|
3673
|
-
if (state.modal?.kind !== kind) {
|
|
3674
|
-
return;
|
|
3675
|
-
}
|
|
3767
|
+
function escapeRegExpText(value = '') {
|
|
3768
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3769
|
+
}
|
|
3676
3770
|
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3771
|
+
function expressionReferencesColumn(expression = '', columnName = '') {
|
|
3772
|
+
const normalizedColumn = String(columnName ?? '').trim();
|
|
3773
|
+
|
|
3774
|
+
if (!normalizedColumn) {
|
|
3775
|
+
return false;
|
|
3680
3776
|
}
|
|
3777
|
+
|
|
3778
|
+
return new RegExp(`(^|[^A-Za-z0-9_$])${escapeRegExpText(normalizedColumn)}([^A-Za-z0-9_$]|$)`, 'i').test(
|
|
3779
|
+
String(expression ?? ''),
|
|
3780
|
+
);
|
|
3681
3781
|
}
|
|
3682
3782
|
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3783
|
+
function tableColumnHasUniqueConstraint(table = {}, columnName = '') {
|
|
3784
|
+
return (table.indexes ?? []).some(index => {
|
|
3785
|
+
if (!index?.unique) {
|
|
3786
|
+
return false;
|
|
3787
|
+
}
|
|
3687
3788
|
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3789
|
+
const indexColumns = (index.columns ?? [])
|
|
3790
|
+
.map(column => String(column.name ?? '').trim())
|
|
3791
|
+
.filter(Boolean);
|
|
3792
|
+
|
|
3793
|
+
return indexColumns.length === 1 && indexColumns[0] === columnName;
|
|
3794
|
+
});
|
|
3691
3795
|
}
|
|
3692
3796
|
|
|
3693
|
-
function
|
|
3694
|
-
|
|
3797
|
+
function tableColumnHasAutoincrement(table = {}, column = {}) {
|
|
3798
|
+
return Boolean(column.primaryKeyPosition) && /\bAUTOINCREMENT\b/i.test(String(table.ddl ?? ''));
|
|
3799
|
+
}
|
|
3695
3800
|
|
|
3696
|
-
|
|
3801
|
+
function getColumnForeignKeyRestrictions(table = {}, columnName = '') {
|
|
3802
|
+
return (table.foreignKeys ?? [])
|
|
3803
|
+
.flatMap(foreignKey =>
|
|
3804
|
+
(foreignKey.mappings ?? [])
|
|
3805
|
+
.filter(mapping => mapping.from === columnName)
|
|
3806
|
+
.map(mapping => {
|
|
3807
|
+
const referencedColumn = String(mapping.to ?? '').trim();
|
|
3808
|
+
const columnSuffix = referencedColumn ? `(${referencedColumn})` : '';
|
|
3809
|
+
|
|
3810
|
+
return `REFERENCES ${foreignKey.referencedTable}${columnSuffix}`;
|
|
3811
|
+
}),
|
|
3812
|
+
)
|
|
3813
|
+
.filter(Boolean);
|
|
3697
3814
|
}
|
|
3698
3815
|
|
|
3699
|
-
function
|
|
3700
|
-
|
|
3701
|
-
|
|
3816
|
+
function buildColumnRestrictionText(table = {}, column = {}) {
|
|
3817
|
+
const restrictions = [];
|
|
3818
|
+
|
|
3819
|
+
if (Number(column.primaryKeyPosition ?? 0) > 0) {
|
|
3820
|
+
restrictions.push('PRIMARY KEY');
|
|
3702
3821
|
}
|
|
3703
3822
|
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
}
|
|
3823
|
+
if (tableColumnHasAutoincrement(table, column)) {
|
|
3824
|
+
restrictions.push('AUTOINCREMENT');
|
|
3825
|
+
}
|
|
3708
3826
|
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
return false;
|
|
3827
|
+
if (column.notNull) {
|
|
3828
|
+
restrictions.push('NOT NULL');
|
|
3712
3829
|
}
|
|
3713
3830
|
|
|
3714
|
-
if (
|
|
3715
|
-
|
|
3716
|
-
return false;
|
|
3831
|
+
if (tableColumnHasUniqueConstraint(table, column.name)) {
|
|
3832
|
+
restrictions.push('UNIQUE');
|
|
3717
3833
|
}
|
|
3718
3834
|
|
|
3719
|
-
|
|
3835
|
+
if (column.defaultValue !== null && column.defaultValue !== undefined && String(column.defaultValue).trim()) {
|
|
3836
|
+
restrictions.push(`DEFAULT ${String(column.defaultValue).trim()}`);
|
|
3837
|
+
}
|
|
3838
|
+
|
|
3839
|
+
(table.checkConstraints ?? [])
|
|
3840
|
+
.filter(constraint => expressionReferencesColumn(constraint.expression, column.name))
|
|
3841
|
+
.forEach(constraint => restrictions.push(`CHECK (${String(constraint.expression ?? '').trim()})`));
|
|
3842
|
+
|
|
3843
|
+
restrictions.push(...getColumnForeignKeyRestrictions(table, column.name));
|
|
3844
|
+
|
|
3845
|
+
return restrictions.join(', ');
|
|
3720
3846
|
}
|
|
3721
3847
|
|
|
3722
|
-
|
|
3723
|
-
|
|
3848
|
+
function getVisibleTableDefinitionColumns(table = {}) {
|
|
3849
|
+
return (table.columns ?? []).filter(column => column.visible !== false && !column.generated);
|
|
3724
3850
|
}
|
|
3725
3851
|
|
|
3726
|
-
|
|
3727
|
-
|
|
3852
|
+
function buildTableDefinitionMarkdownTable(table = {}) {
|
|
3853
|
+
const rows = getVisibleTableDefinitionColumns(table).map(column => [
|
|
3854
|
+
escapeMarkdownTableCell(column.name),
|
|
3855
|
+
escapeMarkdownTableCell(column.declaredType || column.affinity || ''),
|
|
3856
|
+
escapeMarkdownTableCell(buildColumnRestrictionText(table, column)),
|
|
3857
|
+
]);
|
|
3858
|
+
|
|
3859
|
+
return [
|
|
3860
|
+
'| Column | Type | Restrictions |',
|
|
3861
|
+
'|---|---|---|',
|
|
3862
|
+
...rows.map(row => `| ${row.join(' | ')} |`),
|
|
3863
|
+
].join('\n');
|
|
3728
3864
|
}
|
|
3729
3865
|
|
|
3730
|
-
|
|
3731
|
-
const
|
|
3866
|
+
function normalizeSqlDefinition(sql = '') {
|
|
3867
|
+
const statement = String(sql ?? '').trim();
|
|
3732
3868
|
|
|
3733
|
-
if (!
|
|
3734
|
-
return
|
|
3869
|
+
if (!statement) {
|
|
3870
|
+
return '';
|
|
3735
3871
|
}
|
|
3736
3872
|
|
|
3737
|
-
|
|
3873
|
+
return /;\s*$/.test(statement) ? statement : `${statement};`;
|
|
3874
|
+
}
|
|
3738
3875
|
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
return false;
|
|
3742
|
-
}
|
|
3876
|
+
function buildTableDefinitionSqlBlock(table = {}) {
|
|
3877
|
+
const statement = normalizeSqlDefinition(table.ddl);
|
|
3743
3878
|
|
|
3744
|
-
|
|
3879
|
+
if (!statement) {
|
|
3880
|
+
return '';
|
|
3881
|
+
}
|
|
3745
3882
|
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
const markdownTable = String(response.data?.content ?? '').trim();
|
|
3883
|
+
return ['```sql', statement, '```'].join('\n');
|
|
3884
|
+
}
|
|
3749
3885
|
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
}
|
|
3886
|
+
function formatTableDefinitionSampleValue(value) {
|
|
3887
|
+
if (value && typeof value === 'object' && value.__type === 'blob') {
|
|
3888
|
+
return `[BLOB - ${formatBytes(value.sizeBytes ?? 0)}]`;
|
|
3889
|
+
}
|
|
3753
3890
|
|
|
3754
|
-
|
|
3891
|
+
if (value === null) {
|
|
3892
|
+
return 'NULL';
|
|
3893
|
+
}
|
|
3755
3894
|
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
pushToast(`Inserted table from "${getDocumentInsertQueryTitle(query)}".`, 'success');
|
|
3759
|
-
}
|
|
3760
|
-
return inserted;
|
|
3761
|
-
} catch (error) {
|
|
3762
|
-
withModalError(error);
|
|
3763
|
-
return false;
|
|
3895
|
+
if (value === undefined) {
|
|
3896
|
+
return '';
|
|
3764
3897
|
}
|
|
3898
|
+
|
|
3899
|
+
const rawText =
|
|
3900
|
+
typeof value === 'object'
|
|
3901
|
+
? JSON.stringify(value)
|
|
3902
|
+
: String(value);
|
|
3903
|
+
const normalizedText = rawText.replace(/\s+/g, ' ').trim();
|
|
3904
|
+
|
|
3905
|
+
return normalizedText.length > TABLE_DEFINITION_LONG_TEXT_LIMIT
|
|
3906
|
+
? `${normalizedText.slice(0, TABLE_DEFINITION_LONG_TEXT_LIMIT - 3)}...`
|
|
3907
|
+
: normalizedText;
|
|
3765
3908
|
}
|
|
3766
3909
|
|
|
3767
|
-
|
|
3768
|
-
const
|
|
3910
|
+
function isNumericTableDefinitionColumn(table = {}, columnName = '') {
|
|
3911
|
+
const column = (table.columns ?? []).find(candidate => candidate.name === columnName);
|
|
3912
|
+
const affinity = String(column?.affinity ?? column?.declaredType ?? '').toUpperCase();
|
|
3769
3913
|
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
}
|
|
3914
|
+
return ['INTEGER', 'REAL', 'NUMERIC'].includes(affinity);
|
|
3915
|
+
}
|
|
3773
3916
|
|
|
3774
|
-
|
|
3775
|
-
const
|
|
3917
|
+
function buildTableDefinitionSampleDataMarkdown(table = {}, sampleData = {}) {
|
|
3918
|
+
const columns = (sampleData.columns ?? getVisibleTableDefinitionColumns(table).map(column => column.name))
|
|
3919
|
+
.filter(columnName => columnName !== '__identity__');
|
|
3920
|
+
const rows = (sampleData.rows ?? []).slice(0, normalizeTableDefinitionSampleRowCount(sampleData.limit));
|
|
3776
3921
|
|
|
3777
|
-
if (!
|
|
3778
|
-
|
|
3779
|
-
return false;
|
|
3922
|
+
if (!rows.length) {
|
|
3923
|
+
return ['### Sample Data', '', '_No rows available._'].join('\n');
|
|
3780
3924
|
}
|
|
3781
3925
|
|
|
3782
|
-
|
|
3783
|
-
const
|
|
3926
|
+
const header = `| ${columns.map(escapeMarkdownTableCell).join(' | ')} |`;
|
|
3927
|
+
const alignment = `| ${columns
|
|
3928
|
+
.map(columnName => (isNumericTableDefinitionColumn(table, columnName) ? '---:' : '---'))
|
|
3929
|
+
.join(' | ')} |`;
|
|
3930
|
+
const rowLines = rows.map(row => {
|
|
3931
|
+
const cells = columns.map(columnName => escapeMarkdownTableCell(formatTableDefinitionSampleValue(row[columnName])));
|
|
3784
3932
|
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
return inserted;
|
|
3933
|
+
return `| ${cells.join(' | ')} |`;
|
|
3934
|
+
});
|
|
3935
|
+
|
|
3936
|
+
return ['### Sample Data', '', header, alignment, ...rowLines].join('\n');
|
|
3790
3937
|
}
|
|
3791
3938
|
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
emitChange();
|
|
3939
|
+
function buildMissingTableDefinitionSnippetBody(tableName, message = 'Table no longer exists.') {
|
|
3940
|
+
return [`## Definition: ${normalizeMarkdownInlineText(tableName || 'Table')}`, '', message].join('\n');
|
|
3941
|
+
}
|
|
3796
3942
|
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3943
|
+
function buildTableDefinitionSnippetBody({ table = null, sampleData = null, options = {}, errorMessage = '' } = {}) {
|
|
3944
|
+
const normalizedOptions = normalizeTableDefinitionOptions(options);
|
|
3945
|
+
|
|
3946
|
+
if (!table || errorMessage) {
|
|
3947
|
+
return buildMissingTableDefinitionSnippetBody(normalizedOptions.tableName, errorMessage || 'Table no longer exists.');
|
|
3948
|
+
}
|
|
3949
|
+
|
|
3950
|
+
if (table.isShadow) {
|
|
3951
|
+
return buildMissingTableDefinitionSnippetBody(
|
|
3952
|
+
normalizedOptions.tableName,
|
|
3953
|
+
'Shadow tables are not available for table definitions.',
|
|
3954
|
+
);
|
|
3955
|
+
}
|
|
3956
|
+
|
|
3957
|
+
const sections = [`## Definition: ${normalizeMarkdownInlineText(table.name ?? normalizedOptions.tableName)}`];
|
|
3958
|
+
|
|
3959
|
+
if (normalizedOptions.markdownTable) {
|
|
3960
|
+
sections.push('', buildTableDefinitionMarkdownTable(table));
|
|
3961
|
+
}
|
|
3962
|
+
|
|
3963
|
+
if (normalizedOptions.sqlDefinition) {
|
|
3964
|
+
const sqlBlock = buildTableDefinitionSqlBlock(table);
|
|
3965
|
+
|
|
3966
|
+
if (sqlBlock) {
|
|
3967
|
+
sections.push('', sqlBlock);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
|
|
3971
|
+
if (normalizedOptions.sampleData) {
|
|
3972
|
+
sections.push('', buildTableDefinitionSampleDataMarkdown(table, sampleData ?? {}));
|
|
3973
|
+
}
|
|
3974
|
+
|
|
3975
|
+
if (sections.length === 1) {
|
|
3976
|
+
sections.push('', '_No table definition options are enabled._');
|
|
3977
|
+
}
|
|
3978
|
+
|
|
3979
|
+
return sections.join('\n');
|
|
3980
|
+
}
|
|
3981
|
+
|
|
3982
|
+
export function buildTableDefinitionMagicSnippet(context = {}) {
|
|
3983
|
+
const options = normalizeTableDefinitionOptions(context.options ?? context);
|
|
3984
|
+
const metadata = buildTableDefinitionMagicSnippetMetadata(options);
|
|
3985
|
+
const body = buildTableDefinitionSnippetBody({
|
|
3986
|
+
table: context.table ?? null,
|
|
3987
|
+
sampleData: context.sampleData ?? null,
|
|
3988
|
+
options,
|
|
3989
|
+
errorMessage: context.errorMessage ?? '',
|
|
3990
|
+
});
|
|
3991
|
+
|
|
3992
|
+
if (!options.tableName) {
|
|
3993
|
+
return '';
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
return [
|
|
3997
|
+
`<!-- sqlite-hub:magic table-definition ${encodeDocumentMagicSnippetMetadata(metadata)} -->`,
|
|
3998
|
+
body,
|
|
3999
|
+
'<!-- /sqlite-hub:magic table-definition -->',
|
|
4000
|
+
].join('\n');
|
|
4001
|
+
}
|
|
4002
|
+
|
|
4003
|
+
export function getDocumentTableDefinitionMagicSnippets(content = '') {
|
|
4004
|
+
TABLE_DEFINITION_MAGIC_SNIPPET_BLOCK_PATTERN.lastIndex = 0;
|
|
4005
|
+
|
|
4006
|
+
return Array.from(String(content ?? '').matchAll(TABLE_DEFINITION_MAGIC_SNIPPET_BLOCK_PATTERN))
|
|
4007
|
+
.map(match => {
|
|
4008
|
+
const metadata = decodeDocumentMagicSnippetMetadata(match[2]);
|
|
4009
|
+
const options = normalizeTableDefinitionOptions(metadata ?? {});
|
|
4010
|
+
|
|
4011
|
+
return metadata && options.tableName
|
|
4012
|
+
? {
|
|
4013
|
+
metadata,
|
|
4014
|
+
options,
|
|
4015
|
+
key: getTableDefinitionSnippetKey(options),
|
|
4016
|
+
tableKey: normalizeTableDefinitionTableKey(options.tableName),
|
|
4017
|
+
}
|
|
4018
|
+
: null;
|
|
4019
|
+
})
|
|
4020
|
+
.filter(Boolean);
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
export function hasDocumentTableDefinitionMagicSnippet(content = '', tableName = '') {
|
|
4024
|
+
const tableKey = normalizeTableDefinitionTableKey(tableName);
|
|
4025
|
+
|
|
4026
|
+
if (!tableKey) {
|
|
4027
|
+
return false;
|
|
4028
|
+
}
|
|
4029
|
+
|
|
4030
|
+
return getDocumentTableDefinitionMagicSnippets(content).some(snippet => snippet.tableKey === tableKey);
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
async function buildTableDefinitionMagicSnippetFromOptions(options = {}) {
|
|
4034
|
+
const normalizedOptions = normalizeTableDefinitionOptions(options);
|
|
4035
|
+
|
|
4036
|
+
if (!normalizedOptions.tableName) {
|
|
4037
|
+
return '';
|
|
4038
|
+
}
|
|
4039
|
+
|
|
4040
|
+
try {
|
|
4041
|
+
const structureResponse = await api.getStructureDetail(normalizedOptions.tableName);
|
|
4042
|
+
const table = structureResponse.data ?? null;
|
|
4043
|
+
|
|
4044
|
+
if (!table || table.isShadow) {
|
|
4045
|
+
return buildTableDefinitionMagicSnippet({
|
|
4046
|
+
...normalizedOptions,
|
|
4047
|
+
errorMessage: table?.isShadow
|
|
4048
|
+
? 'Shadow tables are not available for table definitions.'
|
|
4049
|
+
: 'Table no longer exists.',
|
|
4050
|
+
});
|
|
4051
|
+
}
|
|
4052
|
+
|
|
4053
|
+
let sampleData = null;
|
|
4054
|
+
|
|
4055
|
+
if (normalizedOptions.sampleData) {
|
|
4056
|
+
const sampleResponse = await api.getDataTable(normalizedOptions.tableName, {
|
|
4057
|
+
limit: normalizedOptions.sampleRowCount,
|
|
4058
|
+
offset: 0,
|
|
4059
|
+
});
|
|
4060
|
+
|
|
4061
|
+
sampleData = sampleResponse.data ?? {};
|
|
4062
|
+
}
|
|
4063
|
+
|
|
4064
|
+
return buildTableDefinitionMagicSnippet({
|
|
4065
|
+
...normalizedOptions,
|
|
4066
|
+
table,
|
|
4067
|
+
sampleData,
|
|
4068
|
+
});
|
|
4069
|
+
} catch {
|
|
4070
|
+
return buildTableDefinitionMagicSnippet({
|
|
4071
|
+
...normalizedOptions,
|
|
4072
|
+
errorMessage: 'Table no longer exists.',
|
|
4073
|
+
});
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
|
|
4077
|
+
async function hydrateDocumentMetadataMagicSnippetContext(context = {}) {
|
|
4078
|
+
return {
|
|
4079
|
+
...context,
|
|
4080
|
+
latestActivityLogTimestamp: await getLatestActivityLogTimestamp(context.document?.updatedAt ?? null),
|
|
4081
|
+
databaseCreatedAt: await getDatabaseCreatedAt(context.databaseCreatedAt ?? null),
|
|
4082
|
+
};
|
|
4083
|
+
}
|
|
4084
|
+
|
|
4085
|
+
async function hydrateDatabaseInfoMagicSnippetContext(context = {}) {
|
|
4086
|
+
try {
|
|
4087
|
+
const response = await api.getOverview();
|
|
4088
|
+
|
|
4089
|
+
return {
|
|
4090
|
+
...context,
|
|
4091
|
+
overview: response.data ?? {},
|
|
4092
|
+
};
|
|
4093
|
+
} catch {
|
|
4094
|
+
return context;
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
|
|
4098
|
+
async function hydrateTableDefinitionMagicSnippetContext(context = {}) {
|
|
4099
|
+
const content = String(context.content ?? context.document?.content ?? '');
|
|
4100
|
+
const snippets = getDocumentTableDefinitionMagicSnippets(content);
|
|
4101
|
+
|
|
4102
|
+
if (!snippets.length) {
|
|
4103
|
+
return context;
|
|
4104
|
+
}
|
|
4105
|
+
|
|
4106
|
+
const tableDefinitionSnippets = new Map(context.tableDefinitionSnippets ?? []);
|
|
4107
|
+
|
|
4108
|
+
for (const snippet of snippets) {
|
|
4109
|
+
if (tableDefinitionSnippets.has(snippet.key)) {
|
|
4110
|
+
continue;
|
|
4111
|
+
}
|
|
4112
|
+
|
|
4113
|
+
tableDefinitionSnippets.set(snippet.key, {
|
|
4114
|
+
markdown: await buildTableDefinitionMagicSnippetFromOptions(snippet.options),
|
|
4115
|
+
});
|
|
4116
|
+
}
|
|
4117
|
+
|
|
4118
|
+
return {
|
|
4119
|
+
...context,
|
|
4120
|
+
tableDefinitionSnippets,
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4123
|
+
|
|
4124
|
+
const DOCUMENT_MAGIC_SNIPPETS = [
|
|
4125
|
+
{
|
|
4126
|
+
id: DOCUMENT_METADATA_MAGIC_SNIPPET_ID,
|
|
4127
|
+
pattern: DOCUMENT_METADATA_MAGIC_SNIPPET_PATTERN,
|
|
4128
|
+
build: buildDocumentMetadataMagicSnippet,
|
|
4129
|
+
hydrateContext: hydrateDocumentMetadataMagicSnippetContext,
|
|
4130
|
+
},
|
|
4131
|
+
{
|
|
4132
|
+
id: DATABASE_INFO_MAGIC_SNIPPET_ID,
|
|
4133
|
+
pattern: DATABASE_INFO_MAGIC_SNIPPET_PATTERN,
|
|
4134
|
+
build: buildDatabaseInfoMagicSnippet,
|
|
4135
|
+
hydrateContext: hydrateDatabaseInfoMagicSnippetContext,
|
|
4136
|
+
},
|
|
4137
|
+
{
|
|
4138
|
+
id: TABLE_DEFINITION_MAGIC_SNIPPET_ID,
|
|
4139
|
+
pattern: TABLE_DEFINITION_MAGIC_SNIPPET_PATTERN,
|
|
4140
|
+
hydrateContext: hydrateTableDefinitionMagicSnippetContext,
|
|
4141
|
+
replace(content, context = {}) {
|
|
4142
|
+
if (!context.tableDefinitionSnippets) {
|
|
4143
|
+
return String(content ?? '');
|
|
4144
|
+
}
|
|
4145
|
+
|
|
4146
|
+
TABLE_DEFINITION_MAGIC_SNIPPET_BLOCK_PATTERN.lastIndex = 0;
|
|
4147
|
+
|
|
4148
|
+
return String(content ?? '').replace(
|
|
4149
|
+
TABLE_DEFINITION_MAGIC_SNIPPET_BLOCK_PATTERN,
|
|
4150
|
+
(match, prefix = '', encodedMetadata = '') => {
|
|
4151
|
+
const metadata = decodeDocumentMagicSnippetMetadata(encodedMetadata);
|
|
4152
|
+
const options = normalizeTableDefinitionOptions(metadata ?? {});
|
|
4153
|
+
const key = getTableDefinitionSnippetKey(options);
|
|
4154
|
+
const replacement = context.tableDefinitionSnippets.get(key)?.markdown;
|
|
4155
|
+
|
|
4156
|
+
return replacement ? `${prefix}${replacement}` : match;
|
|
4157
|
+
},
|
|
4158
|
+
);
|
|
4159
|
+
},
|
|
4160
|
+
},
|
|
4161
|
+
];
|
|
4162
|
+
|
|
4163
|
+
function getDocumentMagicSnippet(snippetId) {
|
|
4164
|
+
return DOCUMENT_MAGIC_SNIPPETS.find(snippet => snippet.id === snippetId) ?? null;
|
|
4165
|
+
}
|
|
4166
|
+
|
|
4167
|
+
function hasDocumentMagicSnippet(content, snippet) {
|
|
4168
|
+
return snippet.pattern.test(String(content ?? ''));
|
|
4169
|
+
}
|
|
4170
|
+
|
|
4171
|
+
function replaceDocumentMagicSnippet(content, snippet, context = {}) {
|
|
4172
|
+
const block = String(snippet.build(context) ?? '').trim();
|
|
4173
|
+
|
|
4174
|
+
if (!block || !hasDocumentMagicSnippet(content, snippet)) {
|
|
4175
|
+
return String(content ?? '');
|
|
4176
|
+
}
|
|
4177
|
+
|
|
4178
|
+
return String(content ?? '').replace(snippet.pattern, (match, prefix = '') => `${prefix}${block}`);
|
|
4179
|
+
}
|
|
4180
|
+
|
|
4181
|
+
async function buildDocumentMagicSnippetContext(content, document) {
|
|
4182
|
+
let context = { document, content, databaseCreatedAt: state.connections.active?.createdAt ?? null };
|
|
4183
|
+
|
|
4184
|
+
for (const snippet of DOCUMENT_MAGIC_SNIPPETS) {
|
|
4185
|
+
if (!hasDocumentMagicSnippet(content, snippet) || !snippet.hydrateContext) {
|
|
4186
|
+
continue;
|
|
4187
|
+
}
|
|
4188
|
+
|
|
4189
|
+
context = await snippet.hydrateContext(context);
|
|
4190
|
+
}
|
|
4191
|
+
|
|
4192
|
+
return context;
|
|
4193
|
+
}
|
|
4194
|
+
|
|
4195
|
+
export function updateDocumentMagicSnippets(content, context = {}) {
|
|
4196
|
+
return DOCUMENT_MAGIC_SNIPPETS.reduce(
|
|
4197
|
+
(nextContent, snippet) =>
|
|
4198
|
+
typeof snippet.replace === 'function'
|
|
4199
|
+
? snippet.replace(nextContent, context)
|
|
4200
|
+
: replaceDocumentMagicSnippet(nextContent, snippet, context),
|
|
4201
|
+
String(content ?? ''),
|
|
4202
|
+
);
|
|
4203
|
+
}
|
|
4204
|
+
|
|
4205
|
+
export function insertDocumentMagicSnippet(currentContent, snippetId, context = {}, range = null) {
|
|
4206
|
+
const content = String(currentContent ?? '');
|
|
4207
|
+
const snippet = getDocumentMagicSnippet(snippetId);
|
|
4208
|
+
|
|
4209
|
+
if (!snippet) {
|
|
4210
|
+
return {
|
|
4211
|
+
alreadyInserted: false,
|
|
4212
|
+
content,
|
|
4213
|
+
inserted: false,
|
|
4214
|
+
};
|
|
4215
|
+
}
|
|
4216
|
+
|
|
4217
|
+
if (hasDocumentMagicSnippet(content, snippet)) {
|
|
4218
|
+
return {
|
|
4219
|
+
alreadyInserted: true,
|
|
4220
|
+
content,
|
|
4221
|
+
inserted: false,
|
|
4222
|
+
};
|
|
4223
|
+
}
|
|
4224
|
+
|
|
4225
|
+
const block = String(snippet.build(context) ?? '').trim();
|
|
4226
|
+
|
|
4227
|
+
if (!block) {
|
|
4228
|
+
return {
|
|
4229
|
+
alreadyInserted: false,
|
|
4230
|
+
content,
|
|
4231
|
+
inserted: false,
|
|
4232
|
+
};
|
|
4233
|
+
}
|
|
4234
|
+
|
|
4235
|
+
return {
|
|
4236
|
+
alreadyInserted: false,
|
|
4237
|
+
content: buildDocumentMarkdownInsertion(content, block, range),
|
|
4238
|
+
inserted: true,
|
|
4239
|
+
};
|
|
4240
|
+
}
|
|
4241
|
+
|
|
4242
|
+
export function upsertDocumentMagicSnippet(currentContent, snippetId, context = {}, range = null) {
|
|
4243
|
+
const content = String(currentContent ?? '');
|
|
4244
|
+
const snippet = getDocumentMagicSnippet(snippetId);
|
|
4245
|
+
const block = String(snippet?.build(context) ?? '').trim();
|
|
4246
|
+
|
|
4247
|
+
if (!snippet || !block) {
|
|
4248
|
+
return content;
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
if (hasDocumentMagicSnippet(content, snippet)) {
|
|
4252
|
+
return replaceDocumentMagicSnippet(content, snippet, context);
|
|
4253
|
+
}
|
|
4254
|
+
|
|
4255
|
+
return buildDocumentMarkdownInsertion(content, block, range);
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4258
|
+
export function upsertDatabaseInfoMarkdown(currentContent, databaseInfoMarkdown, range = null) {
|
|
4259
|
+
return upsertDocumentMagicSnippet(
|
|
4260
|
+
currentContent,
|
|
4261
|
+
DATABASE_INFO_MAGIC_SNIPPET_ID,
|
|
4262
|
+
{ overview: null, databaseInfoMarkdown },
|
|
4263
|
+
range,
|
|
4264
|
+
);
|
|
4265
|
+
}
|
|
4266
|
+
|
|
4267
|
+
export function getLatestActivityLogTimestampFromResponse(response = {}) {
|
|
4268
|
+
return String(response?.data?.items?.[0]?.occurredAt ?? '').trim() || null;
|
|
4269
|
+
}
|
|
4270
|
+
|
|
4271
|
+
async function getLatestActivityLogTimestamp(fallback = null) {
|
|
4272
|
+
try {
|
|
4273
|
+
const response = await api.getLogs({
|
|
4274
|
+
range: 'all',
|
|
4275
|
+
limit: 1,
|
|
4276
|
+
});
|
|
4277
|
+
|
|
4278
|
+
return getLatestActivityLogTimestampFromResponse(response) ?? fallback;
|
|
4279
|
+
} catch {
|
|
4280
|
+
return fallback;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
|
|
4284
|
+
async function getDatabaseCreatedAt(fallback = null) {
|
|
4285
|
+
try {
|
|
4286
|
+
const response = await api.getOverview();
|
|
4287
|
+
|
|
4288
|
+
return String(response.data?.file?.createdAt ?? response.data?.connection?.createdAt ?? '').trim() || fallback;
|
|
4289
|
+
} catch {
|
|
4290
|
+
return fallback;
|
|
4291
|
+
}
|
|
4292
|
+
}
|
|
4293
|
+
|
|
4294
|
+
async function updateDocumentMagicSnippetsOnOpen(version, document) {
|
|
4295
|
+
if (!document?.id) {
|
|
4296
|
+
return document;
|
|
4297
|
+
}
|
|
4298
|
+
|
|
4299
|
+
const context = await buildDocumentMagicSnippetContext(document.content, document);
|
|
4300
|
+
const nextContent = updateDocumentMagicSnippets(document.content, context);
|
|
4301
|
+
|
|
4302
|
+
if (nextContent === String(document.content ?? '')) {
|
|
4303
|
+
return document;
|
|
4304
|
+
}
|
|
4305
|
+
|
|
4306
|
+
try {
|
|
4307
|
+
const response = await api.updateDocument(document.id, {
|
|
4308
|
+
content: nextContent,
|
|
4309
|
+
});
|
|
4310
|
+
|
|
4311
|
+
if (version !== routeLoadVersion) {
|
|
4312
|
+
return null;
|
|
4313
|
+
}
|
|
4314
|
+
|
|
4315
|
+
return response.data ?? { ...document, content: nextContent };
|
|
4316
|
+
} catch {
|
|
4317
|
+
return document;
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
|
|
4321
|
+
export function insertMarkdownIntoCurrentDocumentDraft(markdown, range = null) {
|
|
4322
|
+
if (!state.documents.selectedId) {
|
|
4323
|
+
return false;
|
|
4324
|
+
}
|
|
4325
|
+
|
|
4326
|
+
const nextContent = buildDocumentMarkdownInsertion(state.documents.draftContent, markdown, range);
|
|
4327
|
+
|
|
4328
|
+
if (nextContent === state.documents.draftContent) {
|
|
4329
|
+
return false;
|
|
4330
|
+
}
|
|
4331
|
+
|
|
4332
|
+
state.documents.draftContent = nextContent;
|
|
4333
|
+
state.documents.dirty = true;
|
|
4334
|
+
state.documents.saveError = null;
|
|
4335
|
+
emitChange();
|
|
4336
|
+
return true;
|
|
4337
|
+
}
|
|
4338
|
+
|
|
4339
|
+
export async function insertMarkdownIntoLastOpenDocument(markdown) {
|
|
4340
|
+
if (state.route.name === 'documents' && state.documents.selectedId) {
|
|
4341
|
+
const inserted = insertMarkdownIntoCurrentDocumentDraft(markdown);
|
|
4342
|
+
|
|
4343
|
+
return {
|
|
4344
|
+
documentId: state.documents.selectedId,
|
|
4345
|
+
inserted,
|
|
4346
|
+
saved: false,
|
|
4347
|
+
};
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
const documentId = lastOpenDocumentId ?? state.documents.selectedId;
|
|
4351
|
+
|
|
4352
|
+
if (!documentId) {
|
|
4353
|
+
return {
|
|
4354
|
+
documentId: null,
|
|
4355
|
+
inserted: false,
|
|
4356
|
+
saved: false,
|
|
4357
|
+
};
|
|
4358
|
+
}
|
|
4359
|
+
|
|
4360
|
+
const response = await api.getDocument(documentId);
|
|
4361
|
+
const document = response.data ?? null;
|
|
4362
|
+
|
|
4363
|
+
if (!document?.id) {
|
|
4364
|
+
return {
|
|
4365
|
+
documentId: null,
|
|
4366
|
+
inserted: false,
|
|
4367
|
+
saved: false,
|
|
4368
|
+
};
|
|
4369
|
+
}
|
|
4370
|
+
|
|
4371
|
+
const nextContent = buildDocumentMarkdownInsertion(document.content, markdown);
|
|
4372
|
+
|
|
4373
|
+
if (nextContent === document.content) {
|
|
4374
|
+
return {
|
|
4375
|
+
documentId: document.id,
|
|
4376
|
+
inserted: false,
|
|
4377
|
+
saved: false,
|
|
4378
|
+
};
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4381
|
+
const updateResponse = await api.updateDocument(document.id, {
|
|
4382
|
+
content: nextContent,
|
|
4383
|
+
});
|
|
4384
|
+
const updatedDocument = updateResponse.data ?? document;
|
|
4385
|
+
|
|
4386
|
+
lastOpenDocumentId = updatedDocument.id;
|
|
4387
|
+
upsertDocumentSummary(updatedDocument);
|
|
4388
|
+
|
|
4389
|
+
return {
|
|
4390
|
+
documentId: updatedDocument.id,
|
|
4391
|
+
inserted: true,
|
|
4392
|
+
saved: true,
|
|
4393
|
+
};
|
|
4394
|
+
}
|
|
4395
|
+
|
|
4396
|
+
function getDocumentInsertQueryTitle(query) {
|
|
4397
|
+
return query?.displayTitle || query?.title || query?.previewSql || query?.rawSql || 'Saved query';
|
|
4398
|
+
}
|
|
4399
|
+
|
|
4400
|
+
export function buildSavedQueriesMarkdown(queries = []) {
|
|
4401
|
+
const queryTitles = (Array.isArray(queries) ? queries : [])
|
|
4402
|
+
.map(query => String(getDocumentInsertQueryTitle(query)).replace(/\s+/g, ' ').trim())
|
|
4403
|
+
.filter(Boolean);
|
|
4404
|
+
|
|
4405
|
+
if (!queryTitles.length) {
|
|
4406
|
+
return '';
|
|
4407
|
+
}
|
|
4408
|
+
|
|
4409
|
+
return [
|
|
4410
|
+
'## Saved Queries',
|
|
4411
|
+
'',
|
|
4412
|
+
...queryTitles.map(title => `- ${title}`),
|
|
4413
|
+
].join('\n');
|
|
4414
|
+
}
|
|
4415
|
+
|
|
4416
|
+
async function openDocumentInsertModal(kind, insertionRange = null) {
|
|
4417
|
+
if (!state.documents.selectedId) {
|
|
4418
|
+
pushToast('Select a document before inserting Markdown.', 'alert');
|
|
4419
|
+
return;
|
|
4420
|
+
}
|
|
4421
|
+
|
|
4422
|
+
state.modal = {
|
|
4423
|
+
kind,
|
|
4424
|
+
documentId: state.documents.selectedId,
|
|
4425
|
+
insertionRange: normalizeDocumentInsertionRange(insertionRange),
|
|
4426
|
+
queries: [],
|
|
4427
|
+
selectedHistoryId: '',
|
|
4428
|
+
loading: true,
|
|
4429
|
+
error: null,
|
|
4430
|
+
submitting: false,
|
|
4431
|
+
};
|
|
4432
|
+
emitChange();
|
|
4433
|
+
|
|
4434
|
+
try {
|
|
4435
|
+
const response = await api.getQueryHistory({
|
|
4436
|
+
tab: 'saved',
|
|
4437
|
+
onlySaved: true,
|
|
4438
|
+
limit: 100,
|
|
4439
|
+
});
|
|
4440
|
+
const loadedQueries = response.data?.items ?? [];
|
|
4441
|
+
const queries =
|
|
4442
|
+
kind === 'document-insert-note'
|
|
4443
|
+
? loadedQueries.filter(query => String(query.notes ?? '').trim())
|
|
4444
|
+
: loadedQueries;
|
|
4445
|
+
|
|
4446
|
+
if (state.modal?.kind !== kind) {
|
|
4447
|
+
return;
|
|
4448
|
+
}
|
|
4449
|
+
|
|
4450
|
+
state.modal.queries = queries;
|
|
4451
|
+
state.modal.selectedHistoryId = String(queries[0]?.id ?? '');
|
|
4452
|
+
state.modal.loading = false;
|
|
4453
|
+
state.modal.error = null;
|
|
4454
|
+
emitChange();
|
|
4455
|
+
} catch (error) {
|
|
4456
|
+
if (state.modal?.kind !== kind) {
|
|
4457
|
+
return;
|
|
4458
|
+
}
|
|
4459
|
+
|
|
4460
|
+
state.modal.loading = false;
|
|
4461
|
+
state.modal.error = normalizeError(error);
|
|
4462
|
+
emitChange();
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
|
|
4466
|
+
export function updateDocumentInsertQuerySelection(historyId) {
|
|
4467
|
+
if (state.modal?.kind !== 'document-insert-table' && state.modal?.kind !== 'document-insert-note') {
|
|
4468
|
+
return;
|
|
4469
|
+
}
|
|
4470
|
+
|
|
4471
|
+
state.modal.selectedHistoryId = String(historyId ?? '');
|
|
4472
|
+
state.modal.error = null;
|
|
4473
|
+
emitChange();
|
|
4474
|
+
}
|
|
4475
|
+
|
|
4476
|
+
function getSelectedDocumentInsertQuery(modal) {
|
|
4477
|
+
const selectedHistoryId = String(modal?.selectedHistoryId ?? '');
|
|
4478
|
+
|
|
4479
|
+
return (modal?.queries ?? []).find(query => String(query.id) === selectedHistoryId) ?? null;
|
|
4480
|
+
}
|
|
4481
|
+
|
|
4482
|
+
function setDocumentInsertModalError(message, code = 'DOCUMENT_INSERT_UNAVAILABLE') {
|
|
4483
|
+
if (!state.modal) {
|
|
4484
|
+
return;
|
|
4485
|
+
}
|
|
4486
|
+
|
|
4487
|
+
state.modal.error = { code, message };
|
|
4488
|
+
state.modal.submitting = false;
|
|
4489
|
+
emitChange();
|
|
4490
|
+
}
|
|
4491
|
+
|
|
4492
|
+
function canSubmitDocumentInsertModal(modal, kind) {
|
|
4493
|
+
if (modal?.kind !== kind) {
|
|
4494
|
+
return false;
|
|
4495
|
+
}
|
|
4496
|
+
|
|
4497
|
+
if (String(modal.documentId ?? '') !== String(state.documents.selectedId ?? '')) {
|
|
4498
|
+
setDocumentInsertModalError('The selected document changed while the dialog was open.');
|
|
4499
|
+
return false;
|
|
4500
|
+
}
|
|
4501
|
+
|
|
4502
|
+
return true;
|
|
4503
|
+
}
|
|
4504
|
+
|
|
4505
|
+
export async function openDocumentInsertTableModal(insertionRange = null) {
|
|
4506
|
+
await openDocumentInsertModal('document-insert-table', insertionRange);
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
export async function openDocumentInsertNoteModal(insertionRange = null) {
|
|
4510
|
+
await openDocumentInsertModal('document-insert-note', insertionRange);
|
|
4511
|
+
}
|
|
4512
|
+
|
|
4513
|
+
function getDocumentTableDefinitionModalOptions(modal = state.modal) {
|
|
4514
|
+
return normalizeTableDefinitionOptions({
|
|
4515
|
+
tableName: modal?.selectedTableName,
|
|
4516
|
+
markdownTable: modal?.markdownTable,
|
|
4517
|
+
sqlDefinition: modal?.sqlDefinition,
|
|
4518
|
+
sampleData: modal?.sampleData,
|
|
4519
|
+
sampleRowCount: modal?.sampleRowCount,
|
|
4520
|
+
});
|
|
4521
|
+
}
|
|
4522
|
+
|
|
4523
|
+
function setDocumentTableDefinitionModalError(message, code = 'TABLE_DEFINITION_INSERT_UNAVAILABLE') {
|
|
4524
|
+
setDocumentInsertModalError(message, code);
|
|
4525
|
+
}
|
|
4526
|
+
|
|
4527
|
+
export async function openDocumentInsertTableDefinitionModal(insertionRange = null) {
|
|
4528
|
+
if (!state.documents.selectedId) {
|
|
4529
|
+
pushToast('Select a document before inserting a table definition.', 'alert');
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
|
|
4533
|
+
state.modal = {
|
|
4534
|
+
kind: 'document-insert-table-definition',
|
|
4535
|
+
documentId: state.documents.selectedId,
|
|
4536
|
+
insertionRange: normalizeDocumentInsertionRange(insertionRange),
|
|
4537
|
+
tables: [],
|
|
4538
|
+
selectedTableName: '',
|
|
4539
|
+
markdownTable: true,
|
|
4540
|
+
sqlDefinition: false,
|
|
4541
|
+
sampleData: false,
|
|
4542
|
+
sampleRowCount: TABLE_DEFINITION_DEFAULT_SAMPLE_ROW_COUNT,
|
|
4543
|
+
loading: true,
|
|
4544
|
+
error: null,
|
|
4545
|
+
submitting: false,
|
|
4546
|
+
};
|
|
4547
|
+
emitChange();
|
|
4548
|
+
|
|
4549
|
+
try {
|
|
4550
|
+
const response = await api.getDataTables();
|
|
4551
|
+
const tables = (response.data?.tables ?? []).filter(table => !table.isShadow && table.tableKind !== 'shadow');
|
|
4552
|
+
|
|
4553
|
+
if (state.modal?.kind !== 'document-insert-table-definition') {
|
|
4554
|
+
return;
|
|
4555
|
+
}
|
|
4556
|
+
|
|
4557
|
+
state.modal.tables = tables;
|
|
4558
|
+
state.modal.selectedTableName = String(tables[0]?.name ?? '');
|
|
4559
|
+
state.modal.loading = false;
|
|
4560
|
+
state.modal.error = null;
|
|
4561
|
+
emitChange();
|
|
4562
|
+
} catch (error) {
|
|
4563
|
+
if (state.modal?.kind !== 'document-insert-table-definition') {
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
|
|
4567
|
+
state.modal.loading = false;
|
|
4568
|
+
state.modal.error = normalizeError(error);
|
|
4569
|
+
emitChange();
|
|
4570
|
+
}
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
export function updateDocumentTableDefinitionSelection(tableName) {
|
|
4574
|
+
if (state.modal?.kind !== 'document-insert-table-definition') {
|
|
4575
|
+
return;
|
|
4576
|
+
}
|
|
4577
|
+
|
|
4578
|
+
const selectedTable = (state.modal.tables ?? []).find(table => table.name === tableName);
|
|
4579
|
+
|
|
4580
|
+
state.modal.selectedTableName = String(selectedTable?.name ?? '');
|
|
4581
|
+
state.modal.error = null;
|
|
4582
|
+
emitChange();
|
|
4583
|
+
}
|
|
4584
|
+
|
|
4585
|
+
export function updateDocumentTableDefinitionOption(option, value) {
|
|
4586
|
+
if (state.modal?.kind !== 'document-insert-table-definition') {
|
|
4587
|
+
return;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
const normalizedOption = String(option ?? '');
|
|
4591
|
+
|
|
4592
|
+
if (!['markdownTable', 'sqlDefinition', 'sampleData'].includes(normalizedOption)) {
|
|
4593
|
+
return;
|
|
4594
|
+
}
|
|
4595
|
+
|
|
4596
|
+
state.modal[normalizedOption] = Boolean(value);
|
|
4597
|
+
state.modal.error = null;
|
|
4598
|
+
emitChange();
|
|
4599
|
+
}
|
|
4600
|
+
|
|
4601
|
+
export function updateDocumentTableDefinitionSampleRowCount(value) {
|
|
4602
|
+
if (state.modal?.kind !== 'document-insert-table-definition') {
|
|
4603
|
+
return;
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4606
|
+
state.modal.sampleRowCount = normalizeTableDefinitionSampleRowCount(value);
|
|
4607
|
+
state.modal.error = null;
|
|
4608
|
+
emitChange();
|
|
4609
|
+
}
|
|
4610
|
+
|
|
4611
|
+
export async function submitDocumentInsertTableDefinition() {
|
|
4612
|
+
const modal = state.modal;
|
|
4613
|
+
|
|
4614
|
+
if (!canSubmitDocumentInsertModal(modal, 'document-insert-table-definition')) {
|
|
4615
|
+
return false;
|
|
4616
|
+
}
|
|
4617
|
+
|
|
4618
|
+
const options = getDocumentTableDefinitionModalOptions(modal);
|
|
4619
|
+
|
|
4620
|
+
if (!options.tableName) {
|
|
4621
|
+
setDocumentTableDefinitionModalError('Select a table before inserting a definition.');
|
|
4622
|
+
return false;
|
|
4623
|
+
}
|
|
4624
|
+
|
|
4625
|
+
if (!options.markdownTable && !options.sqlDefinition && !options.sampleData) {
|
|
4626
|
+
setDocumentTableDefinitionModalError('Select at least one table definition option.');
|
|
4627
|
+
return false;
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
if (hasDocumentTableDefinitionMagicSnippet(state.documents.draftContent, options.tableName)) {
|
|
4631
|
+
pushToast('Magic Snippet already inserted.');
|
|
4632
|
+
return false;
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
startModalSubmission();
|
|
4636
|
+
|
|
4637
|
+
try {
|
|
4638
|
+
const markdown = await buildTableDefinitionMagicSnippetFromOptions(options);
|
|
4639
|
+
const inserted = insertMarkdownIntoCurrentDocumentDraft(markdown, modal.insertionRange);
|
|
4640
|
+
|
|
4641
|
+
closeModalInternal();
|
|
4642
|
+
if (inserted) {
|
|
4643
|
+
pushToast(`Inserted definition for "${options.tableName}".`, 'success');
|
|
4644
|
+
}
|
|
4645
|
+
return inserted;
|
|
4646
|
+
} catch (error) {
|
|
4647
|
+
withModalError(error);
|
|
4648
|
+
return false;
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
export async function submitDocumentInsertTable() {
|
|
4653
|
+
const modal = state.modal;
|
|
4654
|
+
|
|
4655
|
+
if (!canSubmitDocumentInsertModal(modal, 'document-insert-table')) {
|
|
4656
|
+
return false;
|
|
4657
|
+
}
|
|
4658
|
+
|
|
4659
|
+
const query = getSelectedDocumentInsertQuery(modal);
|
|
4660
|
+
|
|
4661
|
+
if (!query) {
|
|
4662
|
+
setDocumentInsertModalError('Select a saved query before inserting a table.');
|
|
4663
|
+
return false;
|
|
4664
|
+
}
|
|
4665
|
+
|
|
4666
|
+
startModalSubmission();
|
|
4667
|
+
|
|
4668
|
+
try {
|
|
4669
|
+
const response = await api.getQueryExport(query.rawSql, 'md');
|
|
4670
|
+
const markdownTable = String(response.data?.content ?? '').trim();
|
|
4671
|
+
|
|
4672
|
+
if (!markdownTable) {
|
|
4673
|
+
throw new Error('The selected query returned no Markdown table content.');
|
|
4674
|
+
}
|
|
4675
|
+
|
|
4676
|
+
const inserted = insertMarkdownIntoCurrentDocumentDraft(markdownTable, modal.insertionRange);
|
|
4677
|
+
|
|
4678
|
+
closeModalInternal();
|
|
4679
|
+
if (inserted) {
|
|
4680
|
+
pushToast(`Inserted table from "${getDocumentInsertQueryTitle(query)}".`, 'success');
|
|
4681
|
+
}
|
|
4682
|
+
return inserted;
|
|
4683
|
+
} catch (error) {
|
|
4684
|
+
withModalError(error);
|
|
4685
|
+
return false;
|
|
4686
|
+
}
|
|
4687
|
+
}
|
|
4688
|
+
|
|
4689
|
+
export async function insertCurrentDocumentDatabaseInfo(insertionRange = null) {
|
|
4690
|
+
if (!state.documents.selectedId) {
|
|
4691
|
+
pushToast('Select a document before inserting database info.', 'alert');
|
|
4692
|
+
return false;
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
const snippet = getDocumentMagicSnippet(DATABASE_INFO_MAGIC_SNIPPET_ID);
|
|
4696
|
+
|
|
4697
|
+
if (snippet && hasDocumentMagicSnippet(state.documents.draftContent, snippet)) {
|
|
4698
|
+
pushToast('Magic Snippet already inserted.');
|
|
4699
|
+
return false;
|
|
4700
|
+
}
|
|
4701
|
+
|
|
4702
|
+
try {
|
|
4703
|
+
const response = await api.getOverview();
|
|
4704
|
+
const result = insertDocumentMagicSnippet(
|
|
4705
|
+
state.documents.draftContent,
|
|
4706
|
+
DATABASE_INFO_MAGIC_SNIPPET_ID,
|
|
4707
|
+
{ databaseInfoMarkdown: buildDatabaseInfoMarkdown(response.data ?? {}) },
|
|
4708
|
+
insertionRange,
|
|
4709
|
+
);
|
|
4710
|
+
|
|
4711
|
+
if (result.alreadyInserted) {
|
|
4712
|
+
pushToast('Magic Snippet already inserted.');
|
|
4713
|
+
return false;
|
|
4714
|
+
}
|
|
4715
|
+
|
|
4716
|
+
if (!result.inserted || result.content === state.documents.draftContent) {
|
|
4717
|
+
return false;
|
|
4718
|
+
}
|
|
4719
|
+
|
|
4720
|
+
state.documents.draftContent = result.content;
|
|
4721
|
+
state.documents.dirty = true;
|
|
4722
|
+
state.documents.saveError = null;
|
|
4723
|
+
emitChange();
|
|
4724
|
+
pushToast('Database info inserted.', 'success');
|
|
4725
|
+
return true;
|
|
4726
|
+
} catch (error) {
|
|
4727
|
+
pushToast(normalizeError(error).message || 'Database info could not be inserted.', 'alert');
|
|
4728
|
+
return false;
|
|
4729
|
+
}
|
|
4730
|
+
}
|
|
4731
|
+
|
|
4732
|
+
export async function insertCurrentDocumentTimeMetadata(insertionRange = null) {
|
|
4733
|
+
if (!state.documents.selectedId) {
|
|
4734
|
+
pushToast('Select a document before inserting time metadata.', 'alert');
|
|
4735
|
+
return false;
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
const snippet = getDocumentMagicSnippet(DOCUMENT_METADATA_MAGIC_SNIPPET_ID);
|
|
4739
|
+
|
|
4740
|
+
if (snippet && hasDocumentMagicSnippet(state.documents.draftContent, snippet)) {
|
|
4741
|
+
pushToast('Magic Snippet already inserted.');
|
|
4742
|
+
return false;
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
const latestActivityLogTimestamp = await getLatestActivityLogTimestamp(state.documents.selected?.updatedAt ?? null);
|
|
4746
|
+
const databaseCreatedAt = await getDatabaseCreatedAt(state.connections.active?.createdAt ?? state.documents.selected?.createdAt ?? null);
|
|
4747
|
+
const result = insertDocumentMagicSnippet(
|
|
4748
|
+
state.documents.draftContent,
|
|
4749
|
+
DOCUMENT_METADATA_MAGIC_SNIPPET_ID,
|
|
4750
|
+
{
|
|
4751
|
+
document: state.documents.selected,
|
|
4752
|
+
databaseCreatedAt,
|
|
4753
|
+
latestActivityLogTimestamp,
|
|
4754
|
+
},
|
|
4755
|
+
insertionRange,
|
|
4756
|
+
);
|
|
4757
|
+
|
|
4758
|
+
if (result.alreadyInserted) {
|
|
4759
|
+
pushToast('Magic Snippet already inserted.');
|
|
4760
|
+
return false;
|
|
4761
|
+
}
|
|
4762
|
+
|
|
4763
|
+
if (!result.inserted || result.content === state.documents.draftContent) {
|
|
4764
|
+
return false;
|
|
4765
|
+
}
|
|
4766
|
+
|
|
4767
|
+
state.documents.draftContent = result.content;
|
|
4768
|
+
state.documents.dirty = true;
|
|
4769
|
+
state.documents.saveError = null;
|
|
4770
|
+
emitChange();
|
|
4771
|
+
pushToast('Time metadata inserted.', 'success');
|
|
4772
|
+
return true;
|
|
4773
|
+
}
|
|
4774
|
+
|
|
4775
|
+
export async function insertCurrentDocumentSavedQueries(insertionRange = null) {
|
|
4776
|
+
if (!state.documents.selectedId) {
|
|
4777
|
+
pushToast('Select a document before inserting saved queries.', 'alert');
|
|
4778
|
+
return false;
|
|
4779
|
+
}
|
|
4780
|
+
|
|
4781
|
+
try {
|
|
4782
|
+
const response = await api.getQueryHistory({
|
|
4783
|
+
tab: 'saved',
|
|
4784
|
+
onlySaved: true,
|
|
4785
|
+
limit: 100,
|
|
4786
|
+
});
|
|
4787
|
+
const savedQueriesMarkdown = buildSavedQueriesMarkdown(response.data?.items ?? []);
|
|
4788
|
+
|
|
4789
|
+
if (!savedQueriesMarkdown) {
|
|
4790
|
+
pushToast('No saved queries are available for this database.', 'alert');
|
|
4791
|
+
return false;
|
|
4792
|
+
}
|
|
4793
|
+
|
|
4794
|
+
const inserted = insertMarkdownIntoCurrentDocumentDraft(savedQueriesMarkdown, insertionRange);
|
|
4795
|
+
|
|
4796
|
+
if (inserted) {
|
|
4797
|
+
pushToast('Saved queries inserted.', 'success');
|
|
4798
|
+
}
|
|
4799
|
+
|
|
4800
|
+
return inserted;
|
|
4801
|
+
} catch (error) {
|
|
4802
|
+
pushToast(normalizeError(error).message || 'Saved queries could not be inserted.', 'alert');
|
|
4803
|
+
return false;
|
|
4804
|
+
}
|
|
4805
|
+
}
|
|
4806
|
+
|
|
4807
|
+
export function submitDocumentInsertNote() {
|
|
4808
|
+
const modal = state.modal;
|
|
4809
|
+
|
|
4810
|
+
if (!canSubmitDocumentInsertModal(modal, 'document-insert-note')) {
|
|
4811
|
+
return false;
|
|
4812
|
+
}
|
|
4813
|
+
|
|
4814
|
+
const query = getSelectedDocumentInsertQuery(modal);
|
|
4815
|
+
const note = String(query?.notes ?? '').trim();
|
|
4816
|
+
|
|
4817
|
+
if (!query || !note) {
|
|
4818
|
+
setDocumentInsertModalError('Select a saved query with notes before inserting.');
|
|
4819
|
+
return false;
|
|
4820
|
+
}
|
|
4821
|
+
|
|
4822
|
+
startModalSubmission();
|
|
4823
|
+
const inserted = insertMarkdownIntoCurrentDocumentDraft(note, modal.insertionRange);
|
|
4824
|
+
|
|
4825
|
+
closeModalInternal();
|
|
4826
|
+
if (inserted) {
|
|
4827
|
+
pushToast(`Inserted note from "${getDocumentInsertQueryTitle(query)}".`, 'success');
|
|
4828
|
+
}
|
|
4829
|
+
return inserted;
|
|
4830
|
+
}
|
|
4831
|
+
|
|
4832
|
+
export function openCreateDocumentFolderModal() {
|
|
4833
|
+
state.modal = {
|
|
4834
|
+
kind: 'create-document-folder',
|
|
4835
|
+
name: '',
|
|
4836
|
+
error: null,
|
|
4837
|
+
submitting: false,
|
|
4838
|
+
};
|
|
4839
|
+
emitChange();
|
|
4840
|
+
}
|
|
4841
|
+
|
|
4842
|
+
export async function submitCreateDocumentFolder(name) {
|
|
4843
|
+
const folderName = String(name ?? '').trim();
|
|
4844
|
+
|
|
4845
|
+
if (!folderName) {
|
|
4846
|
+
withModalError(new Error('Folder name is required.'));
|
|
4847
|
+
return null;
|
|
4848
|
+
}
|
|
4849
|
+
|
|
4850
|
+
startModalSubmission();
|
|
4851
|
+
|
|
4852
|
+
try {
|
|
4853
|
+
const response = await api.createDocumentFolder({ name: folderName });
|
|
4854
|
+
const folder = response.data ?? null;
|
|
4855
|
+
|
|
4856
|
+
state.documents.folders = response.metadata?.folders ?? (
|
|
4857
|
+
folder ? [...state.documents.folders.filter(candidate => candidate.id !== folder.id), folder] : state.documents.folders
|
|
4858
|
+
);
|
|
4859
|
+
state.documents.folders.sort((left, right) =>
|
|
4860
|
+
String(left.name ?? '').localeCompare(String(right.name ?? ''), undefined, { sensitivity: 'base' }) ||
|
|
4861
|
+
String(left.id).localeCompare(String(right.id)),
|
|
4862
|
+
);
|
|
4863
|
+
closeModalInternal();
|
|
4864
|
+
|
|
4865
|
+
if (folder) {
|
|
4866
|
+
pushToast(`Folder "${folder.name}" created.`, 'success');
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
return folder;
|
|
4870
|
+
} catch (error) {
|
|
4871
|
+
withModalError(error);
|
|
4872
|
+
return null;
|
|
4873
|
+
}
|
|
4874
|
+
}
|
|
4875
|
+
|
|
4876
|
+
export async function moveCurrentDocumentToFolder(folderId = null) {
|
|
4877
|
+
const documentId = state.documents.selectedId;
|
|
4878
|
+
|
|
4879
|
+
if (!documentId) {
|
|
4880
|
+
pushToast('Select a document before moving it.', 'alert');
|
|
4881
|
+
return null;
|
|
4882
|
+
}
|
|
4883
|
+
|
|
4884
|
+
const normalizedFolderId = String(folderId ?? '').trim() || null;
|
|
4885
|
+
const currentFolderId = state.documents.selected?.folderId ?? null;
|
|
4886
|
+
|
|
4887
|
+
if (String(currentFolderId ?? '') === String(normalizedFolderId ?? '')) {
|
|
4888
|
+
return state.documents.selected;
|
|
4889
|
+
}
|
|
4890
|
+
|
|
4891
|
+
state.documents.saving = true;
|
|
4892
|
+
state.documents.saveError = null;
|
|
4893
|
+
emitChange();
|
|
4894
|
+
|
|
4895
|
+
try {
|
|
4896
|
+
const response = await api.updateDocument(documentId, {
|
|
4897
|
+
folderId: normalizedFolderId,
|
|
4898
|
+
});
|
|
4899
|
+
const document = response.data ?? null;
|
|
4900
|
+
|
|
4901
|
+
if (document) {
|
|
4902
|
+
upsertDocumentSummary(document);
|
|
4903
|
+
if (String(state.documents.selectedId ?? '') === String(documentId)) {
|
|
4904
|
+
if (state.documents.dirty) {
|
|
4905
|
+
state.documents.selected = {
|
|
4906
|
+
...state.documents.selected,
|
|
4907
|
+
folderId: document.folderId ?? null,
|
|
4908
|
+
updatedAt: document.updatedAt,
|
|
4909
|
+
};
|
|
4910
|
+
} else {
|
|
4911
|
+
applyCurrentDocument(document);
|
|
4912
|
+
}
|
|
4913
|
+
}
|
|
4914
|
+
}
|
|
4915
|
+
|
|
4916
|
+
const folder = normalizedFolderId
|
|
4917
|
+
? state.documents.folders.find(candidate => String(candidate.id) === String(normalizedFolderId))
|
|
4918
|
+
: null;
|
|
4919
|
+
pushToast(`Document moved to ${folder ? `"${folder.name}"` : 'No Folder'}.`, 'success');
|
|
4920
|
+
return document;
|
|
4921
|
+
} catch (error) {
|
|
4922
|
+
state.documents.saveError = normalizeError(error);
|
|
4923
|
+
pushToast(state.documents.saveError.message || 'Document could not be moved.', 'alert');
|
|
4924
|
+
return null;
|
|
4925
|
+
} finally {
|
|
4926
|
+
state.documents.saving = false;
|
|
4927
|
+
emitChange();
|
|
4928
|
+
}
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
export async function createDocument(options = {}) {
|
|
4932
|
+
state.documents.saving = true;
|
|
4933
|
+
state.documents.saveError = null;
|
|
4934
|
+
emitChange();
|
|
4935
|
+
|
|
4936
|
+
try {
|
|
4937
|
+
const response = await api.createDocument({
|
|
4938
|
+
title: options.title,
|
|
3800
4939
|
filename: options.filename,
|
|
3801
4940
|
content: options.content,
|
|
4941
|
+
folderId: options.folderId,
|
|
3802
4942
|
});
|
|
3803
4943
|
const document = response.data ?? null;
|
|
3804
4944
|
|
|
@@ -3832,13 +4972,19 @@ export async function saveCurrentDocument(options = {}) {
|
|
|
3832
4972
|
}
|
|
3833
4973
|
|
|
3834
4974
|
const submittedFilename = state.documents.draftFilename;
|
|
3835
|
-
const
|
|
4975
|
+
const submittedDraftContent = state.documents.draftContent;
|
|
3836
4976
|
|
|
3837
4977
|
state.documents.saving = true;
|
|
3838
4978
|
state.documents.saveError = null;
|
|
3839
|
-
emitChange();
|
|
3840
4979
|
|
|
3841
4980
|
try {
|
|
4981
|
+
const latestActivityLogTimestamp = await getLatestActivityLogTimestamp(state.documents.selected?.updatedAt ?? null);
|
|
4982
|
+
const databaseCreatedAt = await getDatabaseCreatedAt(state.connections.active?.createdAt ?? state.documents.selected?.createdAt ?? null);
|
|
4983
|
+
const submittedContent = updateDocumentMagicSnippets(submittedDraftContent, {
|
|
4984
|
+
document: state.documents.selected,
|
|
4985
|
+
databaseCreatedAt,
|
|
4986
|
+
latestActivityLogTimestamp,
|
|
4987
|
+
});
|
|
3842
4988
|
const response = await api.updateDocument(documentId, {
|
|
3843
4989
|
filename: submittedFilename,
|
|
3844
4990
|
content: submittedContent,
|
|
@@ -3849,7 +4995,7 @@ export async function saveCurrentDocument(options = {}) {
|
|
|
3849
4995
|
const draftUnchanged =
|
|
3850
4996
|
String(state.documents.selectedId ?? '') === String(documentId) &&
|
|
3851
4997
|
state.documents.draftFilename === submittedFilename &&
|
|
3852
|
-
state.documents.draftContent ===
|
|
4998
|
+
state.documents.draftContent === submittedDraftContent;
|
|
3853
4999
|
|
|
3854
5000
|
upsertDocumentSummary(document);
|
|
3855
5001
|
if (draftUnchanged) {
|
|
@@ -3913,15 +5059,15 @@ export async function deleteCurrentDocument(options = {}) {
|
|
|
3913
5059
|
}
|
|
3914
5060
|
}
|
|
3915
5061
|
|
|
3916
|
-
export
|
|
5062
|
+
export function toggleCurrentDocumentTodo(lineIndex) {
|
|
3917
5063
|
if (!state.documents.selectedId) {
|
|
3918
|
-
return
|
|
5064
|
+
return false;
|
|
3919
5065
|
}
|
|
3920
5066
|
|
|
3921
5067
|
const nextContent = toggleMarkdownTodoLine(state.documents.draftContent, lineIndex);
|
|
3922
5068
|
|
|
3923
5069
|
if (nextContent === state.documents.draftContent) {
|
|
3924
|
-
return
|
|
5070
|
+
return false;
|
|
3925
5071
|
}
|
|
3926
5072
|
|
|
3927
5073
|
state.documents.draftContent = nextContent;
|
|
@@ -3929,7 +5075,7 @@ export async function toggleCurrentDocumentTodo(lineIndex) {
|
|
|
3929
5075
|
state.documents.saveError = null;
|
|
3930
5076
|
emitChange();
|
|
3931
5077
|
|
|
3932
|
-
return
|
|
5078
|
+
return true;
|
|
3933
5079
|
}
|
|
3934
5080
|
|
|
3935
5081
|
export async function createDocumentFromMarkdownExport({ filename, content, title } = {}) {
|
|
@@ -3961,6 +5107,136 @@ export async function submitDeleteDocumentConfirmation() {
|
|
|
3961
5107
|
return result;
|
|
3962
5108
|
}
|
|
3963
5109
|
|
|
5110
|
+
function normalizeConnectionTagDrafts(tags = []) {
|
|
5111
|
+
const draftsByKey = new Map();
|
|
5112
|
+
|
|
5113
|
+
for (const tag of tags) {
|
|
5114
|
+
const name = normalizeConnectionTagName(tag?.name ?? tag);
|
|
5115
|
+
|
|
5116
|
+
if (!name) {
|
|
5117
|
+
continue;
|
|
5118
|
+
}
|
|
5119
|
+
|
|
5120
|
+
const key = normalizeConnectionTagKey(name);
|
|
5121
|
+
|
|
5122
|
+
if (!draftsByKey.has(key)) {
|
|
5123
|
+
draftsByKey.set(key, {
|
|
5124
|
+
id: tag?.id ?? null,
|
|
5125
|
+
name,
|
|
5126
|
+
});
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
|
|
5130
|
+
return [...draftsByKey.values()];
|
|
5131
|
+
}
|
|
5132
|
+
|
|
5133
|
+
function findKnownConnectionTagByName(name) {
|
|
5134
|
+
const key = normalizeConnectionTagKey(name);
|
|
5135
|
+
|
|
5136
|
+
return state.connections.tags.find(tag => normalizeConnectionTagKey(tag.name) === key) ?? null;
|
|
5137
|
+
}
|
|
5138
|
+
|
|
5139
|
+
export function setConnectionSearchQuery(query) {
|
|
5140
|
+
const nextQuery = String(query ?? '');
|
|
5141
|
+
|
|
5142
|
+
if (state.connections.searchQuery === nextQuery) {
|
|
5143
|
+
return;
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
state.connections.searchQuery = nextQuery;
|
|
5147
|
+
emitChange();
|
|
5148
|
+
}
|
|
5149
|
+
|
|
5150
|
+
export function toggleConnectionTagFilter(tagId, selected) {
|
|
5151
|
+
const normalizedTagId = String(tagId ?? '').trim();
|
|
5152
|
+
|
|
5153
|
+
if (!normalizedTagId) {
|
|
5154
|
+
return;
|
|
5155
|
+
}
|
|
5156
|
+
|
|
5157
|
+
const currentIds = new Set(state.connections.selectedTagIds.map(id => String(id)));
|
|
5158
|
+
|
|
5159
|
+
if (selected) {
|
|
5160
|
+
currentIds.add(normalizedTagId);
|
|
5161
|
+
} else {
|
|
5162
|
+
currentIds.delete(normalizedTagId);
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
const nextTagIds = [...currentIds];
|
|
5166
|
+
|
|
5167
|
+
if (JSON.stringify(nextTagIds) === JSON.stringify(state.connections.selectedTagIds)) {
|
|
5168
|
+
return;
|
|
5169
|
+
}
|
|
5170
|
+
|
|
5171
|
+
state.connections.selectedTagIds = nextTagIds;
|
|
5172
|
+
emitChange();
|
|
5173
|
+
}
|
|
5174
|
+
|
|
5175
|
+
export function clearConnectionTagFilters() {
|
|
5176
|
+
if (!state.connections.selectedTagIds.length) {
|
|
5177
|
+
return;
|
|
5178
|
+
}
|
|
5179
|
+
|
|
5180
|
+
state.connections.selectedTagIds = [];
|
|
5181
|
+
emitChange();
|
|
5182
|
+
}
|
|
5183
|
+
|
|
5184
|
+
export function updateEditConnectionTagQuery(query) {
|
|
5185
|
+
if (state.modal?.kind !== 'edit-connection') {
|
|
5186
|
+
return;
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
state.modal.tagQuery = String(query ?? '');
|
|
5190
|
+
state.modal.tagError = null;
|
|
5191
|
+
emitChange();
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5194
|
+
export function addEditConnectionTag(tagName) {
|
|
5195
|
+
if (state.modal?.kind !== 'edit-connection') {
|
|
5196
|
+
return;
|
|
5197
|
+
}
|
|
5198
|
+
|
|
5199
|
+
const name = normalizeConnectionTagName(tagName);
|
|
5200
|
+
|
|
5201
|
+
if (!name) {
|
|
5202
|
+
state.modal.tagError = 'Tag names must be 1-40 characters.';
|
|
5203
|
+
emitChange();
|
|
5204
|
+
return;
|
|
5205
|
+
}
|
|
5206
|
+
|
|
5207
|
+
const currentTags = normalizeConnectionTagDrafts(state.modal.assignedTags);
|
|
5208
|
+
const key = normalizeConnectionTagKey(name);
|
|
5209
|
+
|
|
5210
|
+
if (currentTags.some(tag => normalizeConnectionTagKey(tag.name) === key)) {
|
|
5211
|
+
state.modal.tagQuery = '';
|
|
5212
|
+
state.modal.tagError = null;
|
|
5213
|
+
emitChange();
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
const knownTag = findKnownConnectionTagByName(name);
|
|
5218
|
+
state.modal.assignedTags = [
|
|
5219
|
+
...currentTags,
|
|
5220
|
+
knownTag ? { id: knownTag.id, name: knownTag.name } : { id: null, name },
|
|
5221
|
+
];
|
|
5222
|
+
state.modal.tagQuery = '';
|
|
5223
|
+
state.modal.tagError = null;
|
|
5224
|
+
emitChange();
|
|
5225
|
+
}
|
|
5226
|
+
|
|
5227
|
+
export function removeEditConnectionTag(tagName) {
|
|
5228
|
+
if (state.modal?.kind !== 'edit-connection') {
|
|
5229
|
+
return;
|
|
5230
|
+
}
|
|
5231
|
+
|
|
5232
|
+
const key = normalizeConnectionTagKey(tagName);
|
|
5233
|
+
state.modal.assignedTags = normalizeConnectionTagDrafts(state.modal.assignedTags).filter(
|
|
5234
|
+
tag => normalizeConnectionTagKey(tag.name) !== key,
|
|
5235
|
+
);
|
|
5236
|
+
state.modal.tagError = null;
|
|
5237
|
+
emitChange();
|
|
5238
|
+
}
|
|
5239
|
+
|
|
3964
5240
|
export function openEditConnectionModal(id) {
|
|
3965
5241
|
const connection = state.connections.recent.find(entry => entry.id === id);
|
|
3966
5242
|
|
|
@@ -3973,6 +5249,9 @@ export function openEditConnectionModal(id) {
|
|
|
3973
5249
|
kind: 'edit-connection',
|
|
3974
5250
|
connectionId: connection.id,
|
|
3975
5251
|
connection,
|
|
5252
|
+
assignedTags: normalizeConnectionTagDrafts(connection.tags),
|
|
5253
|
+
tagQuery: '',
|
|
5254
|
+
tagError: null,
|
|
3976
5255
|
error: null,
|
|
3977
5256
|
submitting: false,
|
|
3978
5257
|
};
|
|
@@ -4158,6 +5437,11 @@ export async function openDataRowUpdatePreview(rowIndex, values, identity = null
|
|
|
4158
5437
|
return null;
|
|
4159
5438
|
}
|
|
4160
5439
|
|
|
5440
|
+
if (state.dataBrowser.table?.isShadow) {
|
|
5441
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
5442
|
+
return null;
|
|
5443
|
+
}
|
|
5444
|
+
|
|
4161
5445
|
state.dataBrowser.saving = true;
|
|
4162
5446
|
state.dataBrowser.saveError = null;
|
|
4163
5447
|
emitChange();
|
|
@@ -5192,6 +6476,19 @@ export async function selectStructureEntry(name) {
|
|
|
5192
6476
|
await loadStructureDetail(++routeLoadVersion);
|
|
5193
6477
|
}
|
|
5194
6478
|
|
|
6479
|
+
export function clearStructureSelection() {
|
|
6480
|
+
const hadSelection = Boolean(state.structure.selectedName || state.structure.detail || state.structure.detailLoading);
|
|
6481
|
+
|
|
6482
|
+
routeLoadVersion += 1;
|
|
6483
|
+
state.structure.selectedName = null;
|
|
6484
|
+
state.structure.detail = null;
|
|
6485
|
+
state.structure.detailLoading = false;
|
|
6486
|
+
|
|
6487
|
+
if (hadSelection) {
|
|
6488
|
+
emitChange();
|
|
6489
|
+
}
|
|
6490
|
+
}
|
|
6491
|
+
|
|
5195
6492
|
function getDefaultTypeGenerationOptions(target = 'typescript') {
|
|
5196
6493
|
const propertyNaming = target === 'rust' ? 'snake' : 'camel';
|
|
5197
6494
|
|
|
@@ -5480,6 +6777,14 @@ export function updateCurrentTableDesignerColumnField(columnId, field, value, op
|
|
|
5480
6777
|
value,
|
|
5481
6778
|
getTableDesignerContext(),
|
|
5482
6779
|
);
|
|
6780
|
+
|
|
6781
|
+
if (field === 'name') {
|
|
6782
|
+
state.tableDesigner.draft = refreshTableDesignerPresetChecksForColumnRename(
|
|
6783
|
+
state.tableDesigner.draft,
|
|
6784
|
+
columnId,
|
|
6785
|
+
);
|
|
6786
|
+
}
|
|
6787
|
+
|
|
5483
6788
|
state.tableDesigner.saveError = null;
|
|
5484
6789
|
|
|
5485
6790
|
if (options.notify !== false) {
|
|
@@ -5507,6 +6812,342 @@ export function updateCurrentTableDesignerConstraintField(constraintKind, constr
|
|
|
5507
6812
|
}
|
|
5508
6813
|
}
|
|
5509
6814
|
|
|
6815
|
+
function quoteSqlIdentifier(identifier) {
|
|
6816
|
+
return `"${String(identifier ?? '').replaceAll('"', '""')}"`;
|
|
6817
|
+
}
|
|
6818
|
+
|
|
6819
|
+
function quoteSqlStringLiteral(value) {
|
|
6820
|
+
return `'${String(value ?? '').replaceAll("'", "''")}'`;
|
|
6821
|
+
}
|
|
6822
|
+
|
|
6823
|
+
function getTableDesignerDrawerColumn() {
|
|
6824
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
6825
|
+
return (state.tableDesigner.draft?.columns ?? []).find(column => column.id === drawer.columnId) ?? null;
|
|
6826
|
+
}
|
|
6827
|
+
|
|
6828
|
+
function getTableDesignerCheckPresetFields(seed = {}) {
|
|
6829
|
+
return {
|
|
6830
|
+
minLength: String(seed.minLength ?? '3'),
|
|
6831
|
+
maxLength: String(seed.maxLength ?? '255'),
|
|
6832
|
+
minValue: String(seed.minValue ?? '0'),
|
|
6833
|
+
maxValue: String(seed.maxValue ?? '100'),
|
|
6834
|
+
allowedValues: String(seed.allowedValues ?? 'draft\npublished\narchived'),
|
|
6835
|
+
};
|
|
6836
|
+
}
|
|
6837
|
+
|
|
6838
|
+
export function buildTableDesignerCheckPresetExpression(presetId, column, fields = {}) {
|
|
6839
|
+
const columnSql = quoteSqlIdentifier(column?.name || 'column_name');
|
|
6840
|
+
const presetFields = getTableDesignerCheckPresetFields(fields);
|
|
6841
|
+
const allowedValues = presetFields.allowedValues
|
|
6842
|
+
.split(/\r?\n|,/)
|
|
6843
|
+
.map(value => value.trim())
|
|
6844
|
+
.filter(Boolean);
|
|
6845
|
+
|
|
6846
|
+
switch (presetId) {
|
|
6847
|
+
case 'text-non-empty':
|
|
6848
|
+
return `length(trim(${columnSql})) > 0`;
|
|
6849
|
+
case 'text-min-length':
|
|
6850
|
+
return `length(${columnSql}) >= ${Number(presetFields.minLength) || 0}`;
|
|
6851
|
+
case 'text-max-length':
|
|
6852
|
+
return `length(${columnSql}) <= ${Number(presetFields.maxLength) || 0}`;
|
|
6853
|
+
case 'text-length-range':
|
|
6854
|
+
return `length(${columnSql}) BETWEEN ${Number(presetFields.minLength) || 0} AND ${Number(presetFields.maxLength) || 0}`;
|
|
6855
|
+
case 'text-allowed-values':
|
|
6856
|
+
return `${columnSql} IN (${allowedValues.map(quoteSqlStringLiteral).join(', ')})`;
|
|
6857
|
+
case 'numeric-positive':
|
|
6858
|
+
return `${columnSql} > 0`;
|
|
6859
|
+
case 'numeric-non-negative':
|
|
6860
|
+
return `${columnSql} >= 0`;
|
|
6861
|
+
case 'numeric-min-value':
|
|
6862
|
+
return `${columnSql} >= ${Number(presetFields.minValue) || 0}`;
|
|
6863
|
+
case 'numeric-max-value':
|
|
6864
|
+
return `${columnSql} <= ${Number(presetFields.maxValue) || 0}`;
|
|
6865
|
+
case 'numeric-range':
|
|
6866
|
+
return `${columnSql} BETWEEN ${Number(presetFields.minValue) || 0} AND ${Number(presetFields.maxValue) || 0}`;
|
|
6867
|
+
case 'numeric-non-zero':
|
|
6868
|
+
return `${columnSql} <> 0`;
|
|
6869
|
+
case 'boolean-integer':
|
|
6870
|
+
return `${columnSql} IN (0, 1)`;
|
|
6871
|
+
default:
|
|
6872
|
+
return '';
|
|
6873
|
+
}
|
|
6874
|
+
}
|
|
6875
|
+
|
|
6876
|
+
function refreshTableDesignerPresetChecksForColumnRename(draft, columnId) {
|
|
6877
|
+
const column = (draft.columns ?? []).find(candidate => candidate.id === columnId);
|
|
6878
|
+
|
|
6879
|
+
if (!column) {
|
|
6880
|
+
return draft;
|
|
6881
|
+
}
|
|
6882
|
+
|
|
6883
|
+
let changed = false;
|
|
6884
|
+
const checkConstraints = (draft.checkConstraints ?? []).map(constraint => {
|
|
6885
|
+
if (
|
|
6886
|
+
constraint.deleted ||
|
|
6887
|
+
constraint.columnId !== columnId ||
|
|
6888
|
+
!constraint.presetId ||
|
|
6889
|
+
constraint.source !== 'preset'
|
|
6890
|
+
) {
|
|
6891
|
+
return constraint;
|
|
6892
|
+
}
|
|
6893
|
+
|
|
6894
|
+
const expression = buildTableDesignerCheckPresetExpression(
|
|
6895
|
+
constraint.presetId,
|
|
6896
|
+
column,
|
|
6897
|
+
constraint.presetFields,
|
|
6898
|
+
);
|
|
6899
|
+
|
|
6900
|
+
if (!expression || expression === constraint.expression) {
|
|
6901
|
+
return constraint;
|
|
6902
|
+
}
|
|
6903
|
+
|
|
6904
|
+
changed = true;
|
|
6905
|
+
return {
|
|
6906
|
+
...constraint,
|
|
6907
|
+
columns: [{ name: column.name, allowedValues: constraint.columns?.[0]?.allowedValues ?? [] }],
|
|
6908
|
+
expression,
|
|
6909
|
+
};
|
|
6910
|
+
});
|
|
6911
|
+
|
|
6912
|
+
return changed
|
|
6913
|
+
? recalculateTableDesignerDraft(
|
|
6914
|
+
{
|
|
6915
|
+
...draft,
|
|
6916
|
+
checkConstraints,
|
|
6917
|
+
},
|
|
6918
|
+
getTableDesignerContext(),
|
|
6919
|
+
)
|
|
6920
|
+
: draft;
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
function createTableDesignerCheckEditor(seed = {}) {
|
|
6924
|
+
return {
|
|
6925
|
+
expression: String(seed.expression ?? ''),
|
|
6926
|
+
presetId: String(seed.presetId ?? ''),
|
|
6927
|
+
presetFields: getTableDesignerCheckPresetFields(seed.presetFields),
|
|
6928
|
+
error: null,
|
|
6929
|
+
validating: false,
|
|
6930
|
+
};
|
|
6931
|
+
}
|
|
6932
|
+
|
|
6933
|
+
export function openTableDesignerConstraintsDrawer(columnId = '', columnName = '') {
|
|
6934
|
+
const column = (state.tableDesigner.draft?.columns ?? []).find(candidate => candidate.id === columnId);
|
|
6935
|
+
|
|
6936
|
+
state.tableDesigner.constraintsDrawer = {
|
|
6937
|
+
visible: true,
|
|
6938
|
+
columnId: String(columnId ?? ''),
|
|
6939
|
+
columnName: String(columnName || column?.name || ''),
|
|
6940
|
+
editingConstraintId: '',
|
|
6941
|
+
editor: null,
|
|
6942
|
+
};
|
|
6943
|
+
emitChange();
|
|
6944
|
+
}
|
|
6945
|
+
|
|
6946
|
+
export function closeTableDesignerConstraintsDrawer() {
|
|
6947
|
+
state.tableDesigner.constraintsDrawer = createClosedTableDesignerConstraintsDrawer();
|
|
6948
|
+
emitChange();
|
|
6949
|
+
}
|
|
6950
|
+
|
|
6951
|
+
export function startTableDesignerCheckEditor(constraintId = '') {
|
|
6952
|
+
const constraint = (state.tableDesigner.draft?.checkConstraints ?? []).find(item => item.id === constraintId);
|
|
6953
|
+
|
|
6954
|
+
state.tableDesigner.constraintsDrawer = {
|
|
6955
|
+
...(state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer()),
|
|
6956
|
+
visible: true,
|
|
6957
|
+
editingConstraintId: String(constraintId ?? ''),
|
|
6958
|
+
editor: createTableDesignerCheckEditor({
|
|
6959
|
+
expression: constraint?.expression ?? '',
|
|
6960
|
+
presetId: constraint?.presetId ?? '',
|
|
6961
|
+
presetFields: constraint?.presetFields ?? {},
|
|
6962
|
+
}),
|
|
6963
|
+
};
|
|
6964
|
+
emitChange();
|
|
6965
|
+
}
|
|
6966
|
+
|
|
6967
|
+
export function cancelTableDesignerCheckEditor() {
|
|
6968
|
+
state.tableDesigner.constraintsDrawer = {
|
|
6969
|
+
...(state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer()),
|
|
6970
|
+
editingConstraintId: '',
|
|
6971
|
+
editor: null,
|
|
6972
|
+
};
|
|
6973
|
+
emitChange();
|
|
6974
|
+
}
|
|
6975
|
+
|
|
6976
|
+
export function applyTableDesignerCheckPreset(presetId) {
|
|
6977
|
+
const column = getTableDesignerDrawerColumn();
|
|
6978
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
6979
|
+
const editor = drawer.editor ?? createTableDesignerCheckEditor();
|
|
6980
|
+
const presetFields = getTableDesignerCheckPresetFields(editor.presetFields);
|
|
6981
|
+
const expression = buildTableDesignerCheckPresetExpression(presetId, column, presetFields);
|
|
6982
|
+
|
|
6983
|
+
state.tableDesigner.constraintsDrawer = {
|
|
6984
|
+
...drawer,
|
|
6985
|
+
editor: {
|
|
6986
|
+
...editor,
|
|
6987
|
+
presetId,
|
|
6988
|
+
presetFields,
|
|
6989
|
+
expression,
|
|
6990
|
+
error: null,
|
|
6991
|
+
},
|
|
6992
|
+
};
|
|
6993
|
+
emitChange();
|
|
6994
|
+
}
|
|
6995
|
+
|
|
6996
|
+
export function updateTableDesignerCheckEditorField(field, value, options = {}) {
|
|
6997
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
6998
|
+
const editor = drawer.editor ?? createTableDesignerCheckEditor();
|
|
6999
|
+
const nextEditor = { ...editor, error: null };
|
|
7000
|
+
|
|
7001
|
+
if (field in getTableDesignerCheckPresetFields()) {
|
|
7002
|
+
nextEditor.presetFields = {
|
|
7003
|
+
...getTableDesignerCheckPresetFields(editor.presetFields),
|
|
7004
|
+
[field]: String(value ?? ''),
|
|
7005
|
+
};
|
|
7006
|
+
|
|
7007
|
+
if (editor.presetId) {
|
|
7008
|
+
nextEditor.expression = buildTableDesignerCheckPresetExpression(
|
|
7009
|
+
editor.presetId,
|
|
7010
|
+
getTableDesignerDrawerColumn(),
|
|
7011
|
+
nextEditor.presetFields,
|
|
7012
|
+
);
|
|
7013
|
+
}
|
|
7014
|
+
} else {
|
|
7015
|
+
nextEditor[field] = String(value ?? '');
|
|
7016
|
+
|
|
7017
|
+
if (field === 'expression') {
|
|
7018
|
+
nextEditor.presetId = '';
|
|
7019
|
+
}
|
|
7020
|
+
}
|
|
7021
|
+
|
|
7022
|
+
state.tableDesigner.constraintsDrawer = {
|
|
7023
|
+
...drawer,
|
|
7024
|
+
editor: nextEditor,
|
|
7025
|
+
};
|
|
7026
|
+
|
|
7027
|
+
if (options.notify !== false) {
|
|
7028
|
+
emitChange();
|
|
7029
|
+
}
|
|
7030
|
+
}
|
|
7031
|
+
|
|
7032
|
+
export async function saveTableDesignerCheckEditor() {
|
|
7033
|
+
const draft = state.tableDesigner.draft;
|
|
7034
|
+
const drawer = state.tableDesigner.constraintsDrawer ?? createClosedTableDesignerConstraintsDrawer();
|
|
7035
|
+
const editor = drawer.editor ?? createTableDesignerCheckEditor();
|
|
7036
|
+
const expression = String(editor.expression ?? '').trim();
|
|
7037
|
+
const column = getTableDesignerDrawerColumn();
|
|
7038
|
+
|
|
7039
|
+
if (!draft) {
|
|
7040
|
+
return false;
|
|
7041
|
+
}
|
|
7042
|
+
|
|
7043
|
+
if (!expression) {
|
|
7044
|
+
state.tableDesigner.constraintsDrawer = {
|
|
7045
|
+
...drawer,
|
|
7046
|
+
editor: {
|
|
7047
|
+
...editor,
|
|
7048
|
+
error: { code: 'CHECK_EXPRESSION_REQUIRED', message: 'CHECK expression is required.' },
|
|
7049
|
+
},
|
|
7050
|
+
};
|
|
7051
|
+
emitChange();
|
|
7052
|
+
return false;
|
|
7053
|
+
}
|
|
7054
|
+
|
|
7055
|
+
state.tableDesigner.constraintsDrawer = {
|
|
7056
|
+
...drawer,
|
|
7057
|
+
editor: {
|
|
7058
|
+
...editor,
|
|
7059
|
+
validating: true,
|
|
7060
|
+
error: null,
|
|
7061
|
+
},
|
|
7062
|
+
};
|
|
7063
|
+
emitChange();
|
|
7064
|
+
|
|
7065
|
+
try {
|
|
7066
|
+
await api.validateTableDesignerCheck({ draft, expression });
|
|
7067
|
+
|
|
7068
|
+
const seed = {
|
|
7069
|
+
expression,
|
|
7070
|
+
columnId: drawer.columnId,
|
|
7071
|
+
columns: column ? [{ name: column.name, allowedValues: [] }] : [],
|
|
7072
|
+
source: editor.presetId ? 'preset' : 'user',
|
|
7073
|
+
presetId: editor.presetId,
|
|
7074
|
+
presetFields: editor.presetFields,
|
|
7075
|
+
};
|
|
7076
|
+
|
|
7077
|
+
state.tableDesigner.draft = drawer.editingConstraintId
|
|
7078
|
+
? updateTableDesignerConstraintField(
|
|
7079
|
+
draft,
|
|
7080
|
+
'check',
|
|
7081
|
+
drawer.editingConstraintId,
|
|
7082
|
+
'expression',
|
|
7083
|
+
expression,
|
|
7084
|
+
getTableDesignerContext(),
|
|
7085
|
+
)
|
|
7086
|
+
: addTableDesignerCheckConstraint(draft, seed, getTableDesignerContext());
|
|
7087
|
+
|
|
7088
|
+
if (drawer.editingConstraintId) {
|
|
7089
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
7090
|
+
state.tableDesigner.draft,
|
|
7091
|
+
'check',
|
|
7092
|
+
drawer.editingConstraintId,
|
|
7093
|
+
'presetId',
|
|
7094
|
+
editor.presetId,
|
|
7095
|
+
getTableDesignerContext(),
|
|
7096
|
+
);
|
|
7097
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
7098
|
+
state.tableDesigner.draft,
|
|
7099
|
+
'check',
|
|
7100
|
+
drawer.editingConstraintId,
|
|
7101
|
+
'presetFields',
|
|
7102
|
+
editor.presetFields,
|
|
7103
|
+
getTableDesignerContext(),
|
|
7104
|
+
);
|
|
7105
|
+
state.tableDesigner.draft = updateTableDesignerConstraintField(
|
|
7106
|
+
state.tableDesigner.draft,
|
|
7107
|
+
'check',
|
|
7108
|
+
drawer.editingConstraintId,
|
|
7109
|
+
'source',
|
|
7110
|
+
editor.presetId ? 'preset' : 'user',
|
|
7111
|
+
getTableDesignerContext(),
|
|
7112
|
+
);
|
|
7113
|
+
}
|
|
7114
|
+
|
|
7115
|
+
state.tableDesigner.saveError = null;
|
|
7116
|
+
state.tableDesigner.constraintsDrawer = {
|
|
7117
|
+
...drawer,
|
|
7118
|
+
editingConstraintId: '',
|
|
7119
|
+
editor: null,
|
|
7120
|
+
};
|
|
7121
|
+
emitChange();
|
|
7122
|
+
return true;
|
|
7123
|
+
} catch (error) {
|
|
7124
|
+
state.tableDesigner.constraintsDrawer = {
|
|
7125
|
+
...drawer,
|
|
7126
|
+
editor: {
|
|
7127
|
+
...editor,
|
|
7128
|
+
validating: false,
|
|
7129
|
+
error: normalizeError(error),
|
|
7130
|
+
},
|
|
7131
|
+
};
|
|
7132
|
+
emitChange();
|
|
7133
|
+
return false;
|
|
7134
|
+
}
|
|
7135
|
+
}
|
|
7136
|
+
|
|
7137
|
+
export function removeCurrentTableDesignerCheckConstraint(constraintId) {
|
|
7138
|
+
if (!state.tableDesigner.draft) {
|
|
7139
|
+
return;
|
|
7140
|
+
}
|
|
7141
|
+
|
|
7142
|
+
state.tableDesigner.draft = removeTableDesignerCheckConstraint(
|
|
7143
|
+
state.tableDesigner.draft,
|
|
7144
|
+
constraintId,
|
|
7145
|
+
getTableDesignerContext(),
|
|
7146
|
+
);
|
|
7147
|
+
state.tableDesigner.saveError = null;
|
|
7148
|
+
emitChange();
|
|
7149
|
+
}
|
|
7150
|
+
|
|
5510
7151
|
export function addCurrentTableDesignerColumn() {
|
|
5511
7152
|
if (!state.tableDesigner.draft) {
|
|
5512
7153
|
return null;
|
|
@@ -5522,10 +7163,12 @@ export function addCurrentTableDesignerColumn() {
|
|
|
5522
7163
|
return nextColumn?.id ?? null;
|
|
5523
7164
|
}
|
|
5524
7165
|
|
|
5525
|
-
export function
|
|
7166
|
+
export function queueTableDesignerImport(fileName, text, options = {}) {
|
|
7167
|
+
const format = String(options.format ?? '').trim().toLowerCase();
|
|
7168
|
+
|
|
5526
7169
|
try {
|
|
5527
|
-
const imported =
|
|
5528
|
-
{ fileName,
|
|
7170
|
+
const imported = createTableDesignerDraftFromImport(
|
|
7171
|
+
{ fileName, text, format },
|
|
5529
7172
|
{
|
|
5530
7173
|
...getTableDesignerContext(),
|
|
5531
7174
|
...(options.context ?? {}),
|
|
@@ -5543,11 +7186,15 @@ export function queueTableDesignerCsvImport(fileName, csvText, options = {}) {
|
|
|
5543
7186
|
throw error;
|
|
5544
7187
|
}
|
|
5545
7188
|
|
|
5546
|
-
pushToast(error?.message || '
|
|
7189
|
+
pushToast(error?.message || 'Data import failed.', 'alert');
|
|
5547
7190
|
return null;
|
|
5548
7191
|
}
|
|
5549
7192
|
}
|
|
5550
7193
|
|
|
7194
|
+
export function queueTableDesignerCsvImport(fileName, csvText, options = {}) {
|
|
7195
|
+
return queueTableDesignerImport(fileName, csvText, { ...options, format: 'csv' });
|
|
7196
|
+
}
|
|
7197
|
+
|
|
5551
7198
|
export function removeCurrentTableDesignerColumn(columnId) {
|
|
5552
7199
|
if (!state.tableDesigner.draft) {
|
|
5553
7200
|
return;
|
|
@@ -6190,6 +7837,11 @@ export async function submitDataRowUpdate(rowIndex, values, identity = null, opt
|
|
|
6190
7837
|
return null;
|
|
6191
7838
|
}
|
|
6192
7839
|
|
|
7840
|
+
if (state.dataBrowser.table?.isShadow) {
|
|
7841
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
7842
|
+
return null;
|
|
7843
|
+
}
|
|
7844
|
+
|
|
6193
7845
|
state.dataBrowser.saving = true;
|
|
6194
7846
|
state.dataBrowser.saveError = null;
|
|
6195
7847
|
emitChange();
|
|
@@ -6228,6 +7880,11 @@ export async function submitDataRowDelete(rowIndex, options = {}) {
|
|
|
6228
7880
|
return null;
|
|
6229
7881
|
}
|
|
6230
7882
|
|
|
7883
|
+
if (state.dataBrowser.table?.isShadow) {
|
|
7884
|
+
pushToast('Shadow tables are read-only in Data.', 'alert');
|
|
7885
|
+
return null;
|
|
7886
|
+
}
|
|
7887
|
+
|
|
6231
7888
|
const shouldStepBackPage =
|
|
6232
7889
|
selected.rowIndex !== null && (state.dataBrowser.table?.rows?.length ?? 0) <= 1 && state.dataBrowser.page > 1;
|
|
6233
7890
|
|