sqlite-hub 0.16.0 → 0.17.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 +106 -19
- package/bin/sqlite-hub.js +1041 -224
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +28 -0
- package/frontend/js/app.js +362 -6
- package/frontend/js/components/modal.js +244 -44
- package/frontend/js/components/pageHeader.js +14 -16
- package/frontend/js/components/queryHistoryPanel.js +126 -131
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +641 -0
- package/frontend/js/utils/markdownDocuments.js +248 -0
- package/frontend/js/views/documents.js +300 -0
- package/frontend/js/views/settings.js +39 -3
- package/frontend/styles/components.css +13 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +422 -0
- package/package.json +2 -1
- package/server/routes/documents.js +163 -0
- package/server/routes/settings.js +22 -6
- package/server/server.js +6 -0
- package/server/services/storage/appStateStore.js +313 -0
- package/tests/cli-args.test.js +100 -0
- package/tests/copy-column-modal.test.js +83 -0
- package/tests/database-documents.test.js +85 -0
- package/tests/markdown-documents.test.js +79 -0
- package/tests/settings-metadata.test.js +16 -0
- package/tests/settings-view.test.js +32 -0
package/frontend/index.html
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
<script src="/vendor/echarts/dist/echarts.min.js"></script>
|
|
24
24
|
<script src="/vendor/elkjs/lib/elk.bundled.js"></script>
|
|
25
25
|
<script src="/vendor/cytoscape-elk/dist/cytoscape-elk.js"></script>
|
|
26
|
+
<script src="/vendor/marked/lib/marked.umd.js"></script>
|
|
26
27
|
<script src="js/app.js" type="module"></script>
|
|
27
28
|
</body>
|
|
28
29
|
</html>
|
package/frontend/js/api.js
CHANGED
|
@@ -490,3 +490,31 @@ export function downloadTableExport(tableName, options = {}) {
|
|
|
490
490
|
export function downloadTableCsv(tableName, options = {}) {
|
|
491
491
|
return downloadTableExport(tableName, { ...options, format: "csv" });
|
|
492
492
|
}
|
|
493
|
+
|
|
494
|
+
export function getDocuments() {
|
|
495
|
+
return request("/api/documents");
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export function createDocument(payload = {}) {
|
|
499
|
+
return request("/api/documents", {
|
|
500
|
+
method: "POST",
|
|
501
|
+
body: payload,
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
export function getDocument(documentId) {
|
|
506
|
+
return request(`/api/documents/${encodeURIComponent(documentId)}`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
export function updateDocument(documentId, payload = {}) {
|
|
510
|
+
return request(`/api/documents/${encodeURIComponent(documentId)}`, {
|
|
511
|
+
method: "PATCH",
|
|
512
|
+
body: payload,
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
export function deleteDocument(documentId) {
|
|
517
|
+
return request(`/api/documents/${encodeURIComponent(documentId)}`, {
|
|
518
|
+
method: "DELETE",
|
|
519
|
+
});
|
|
520
|
+
}
|
package/frontend/js/app.js
CHANGED
|
@@ -24,6 +24,8 @@ import { renderTopNav } from './components/topNav.js';
|
|
|
24
24
|
import { createRouter } from './router.js';
|
|
25
25
|
import {
|
|
26
26
|
createActiveConnectionBackup,
|
|
27
|
+
createDocument,
|
|
28
|
+
createDocumentFromMarkdownExport,
|
|
27
29
|
clearCurrentQuery,
|
|
28
30
|
clearDataRowSelection,
|
|
29
31
|
clearEditorRowSelection,
|
|
@@ -48,12 +50,15 @@ import {
|
|
|
48
50
|
openDeleteQueryHistoryModal,
|
|
49
51
|
openDeleteQueryChartModal,
|
|
50
52
|
openDataExportModal,
|
|
53
|
+
openDeleteDocumentModal,
|
|
51
54
|
openQueryExportModal,
|
|
52
55
|
openDataRowByIdentity,
|
|
53
56
|
openEditConnectionModal,
|
|
54
57
|
openCreateQueryChartModal,
|
|
55
58
|
openCopyColumnModal,
|
|
56
59
|
openEditQueryChartModal,
|
|
60
|
+
openDocumentInsertNoteModal,
|
|
61
|
+
openDocumentInsertTableModal,
|
|
57
62
|
preserveCurrentDataRowSelectionForReload,
|
|
58
63
|
openDataRowUpdatePreview,
|
|
59
64
|
openEditorRowUpdatePreview,
|
|
@@ -66,6 +71,7 @@ import {
|
|
|
66
71
|
runQueryHistoryItem,
|
|
67
72
|
skipCurrentMediaTaggingItem,
|
|
68
73
|
saveCurrentQueryChartDraft,
|
|
74
|
+
saveCurrentDocument,
|
|
69
75
|
saveCurrentMediaTaggingConfig,
|
|
70
76
|
selectDataRow,
|
|
71
77
|
selectEditorRow,
|
|
@@ -90,6 +96,9 @@ import {
|
|
|
90
96
|
setEditorPanelVisibility,
|
|
91
97
|
setEditorTab,
|
|
92
98
|
submitDeleteChartConfirmation,
|
|
99
|
+
submitDeleteDocumentConfirmation,
|
|
100
|
+
submitDocumentInsertNote,
|
|
101
|
+
submitDocumentInsertTable,
|
|
93
102
|
submitCreateMediaTaggingTagTable,
|
|
94
103
|
submitCreateMediaTaggingMappingTable,
|
|
95
104
|
submitDeleteQueryHistoryConfirmation,
|
|
@@ -100,6 +109,7 @@ import {
|
|
|
100
109
|
setQueryHistorySearchInput,
|
|
101
110
|
setQueryHistoryTab,
|
|
102
111
|
setCopyColumnModalError,
|
|
112
|
+
setCopyColumnModalEditedText,
|
|
103
113
|
setCopyColumnModalSubmitting,
|
|
104
114
|
setRoute,
|
|
105
115
|
saveQueryHistoryNotes,
|
|
@@ -107,6 +117,8 @@ import {
|
|
|
107
117
|
saveCurrentTableDesignerDraft,
|
|
108
118
|
toggleChartsResultsPanel,
|
|
109
119
|
toggleChartsSqlPanel,
|
|
120
|
+
toggleCurrentDocumentTodo,
|
|
121
|
+
toggleDocumentsPane,
|
|
110
122
|
setMediaTaggingWorkflowMediaDetailsVisible,
|
|
111
123
|
setMediaTaggingWorkflowMediaRotationDegrees,
|
|
112
124
|
queueTableDesignerCsvImport,
|
|
@@ -124,6 +136,8 @@ import {
|
|
|
124
136
|
updateCurrentMediaTaggingField,
|
|
125
137
|
updateCurrentMediaTaggingTagFormField,
|
|
126
138
|
updateCopyColumnModalFormatField,
|
|
139
|
+
updateCurrentDocumentDraftField,
|
|
140
|
+
updateDocumentInsertQuerySelection,
|
|
127
141
|
updateCurrentQueryChartDraftConfigField,
|
|
128
142
|
updateCurrentQueryChartDraftField,
|
|
129
143
|
updateCurrentTableDesignerColumnField,
|
|
@@ -136,6 +150,7 @@ import {
|
|
|
136
150
|
import { renderChartsDetail, renderChartsView } from './views/charts.js';
|
|
137
151
|
import { renderConnectionsView } from './views/connections.js';
|
|
138
152
|
import { renderDataRowEditorPanel, renderDataView } from './views/data.js';
|
|
153
|
+
import { renderDocumentsView } from './views/documents.js';
|
|
139
154
|
import { renderEditorView } from './views/editor.js';
|
|
140
155
|
import { renderLandingView } from './views/landing.js';
|
|
141
156
|
import { renderMediaTaggingView } from './views/mediaTagging.js';
|
|
@@ -197,6 +212,10 @@ let lastRenderedLockedRoute = false;
|
|
|
197
212
|
let pendingNewTableDesignerAutofocus = false;
|
|
198
213
|
let pendingQueryEditorFocus = false;
|
|
199
214
|
let pendingMediaTaggingTagSearchFocus = false;
|
|
215
|
+
let documentAutosaveTimer = null;
|
|
216
|
+
let pendingDocumentAutosaveId = null;
|
|
217
|
+
|
|
218
|
+
const DOCUMENT_AUTOSAVE_DELAY_MS = 5000;
|
|
200
219
|
|
|
201
220
|
const APP_TITLE = 'SQLite Hub';
|
|
202
221
|
const ROUTE_TITLE_SEGMENTS = {
|
|
@@ -207,6 +226,7 @@ const ROUTE_TITLE_SEGMENTS = {
|
|
|
207
226
|
editor: 'SQL Editor',
|
|
208
227
|
editorResults: 'SQL Editor',
|
|
209
228
|
charts: 'Charts',
|
|
229
|
+
documents: 'Documents',
|
|
210
230
|
tableDesigner: 'Table Designer',
|
|
211
231
|
mediaTaggingSetup: 'Media Tagging',
|
|
212
232
|
mediaTaggingQueue: 'Tagging Queue',
|
|
@@ -797,13 +817,58 @@ function readFileAsText(file) {
|
|
|
797
817
|
resolve(String(reader.result ?? ''));
|
|
798
818
|
};
|
|
799
819
|
reader.onerror = () => {
|
|
800
|
-
reject(reader.error ?? new Error('The selected
|
|
820
|
+
reject(reader.error ?? new Error('The selected file could not be read.'));
|
|
801
821
|
};
|
|
802
822
|
|
|
803
823
|
reader.readAsText(file);
|
|
804
824
|
});
|
|
805
825
|
}
|
|
806
826
|
|
|
827
|
+
function clearDocumentAutosaveTimer() {
|
|
828
|
+
if (documentAutosaveTimer !== null) {
|
|
829
|
+
window.clearTimeout(documentAutosaveTimer);
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
documentAutosaveTimer = null;
|
|
833
|
+
pendingDocumentAutosaveId = null;
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
function scheduleDocumentAutosave(documentId) {
|
|
837
|
+
clearDocumentAutosaveTimer();
|
|
838
|
+
|
|
839
|
+
if (!documentId) {
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
pendingDocumentAutosaveId = String(documentId);
|
|
844
|
+
documentAutosaveTimer = window.setTimeout(async () => {
|
|
845
|
+
const state = getState();
|
|
846
|
+
const scheduledDocumentId = pendingDocumentAutosaveId;
|
|
847
|
+
|
|
848
|
+
documentAutosaveTimer = null;
|
|
849
|
+
pendingDocumentAutosaveId = null;
|
|
850
|
+
|
|
851
|
+
if (
|
|
852
|
+
state.route.name !== 'documents' ||
|
|
853
|
+
String(state.documents.selectedId ?? '') !== scheduledDocumentId ||
|
|
854
|
+
!state.documents.dirty
|
|
855
|
+
) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
if (state.documents.saving) {
|
|
860
|
+
scheduleDocumentAutosave(scheduledDocumentId);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
if (state.documents.deleting) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
await saveCurrentDocument({ toast: false });
|
|
869
|
+
}, DOCUMENT_AUTOSAVE_DELAY_MS);
|
|
870
|
+
}
|
|
871
|
+
|
|
807
872
|
async function buildConnectionLogoUpload(file) {
|
|
808
873
|
if (!(file instanceof File) || !file.size) {
|
|
809
874
|
return null;
|
|
@@ -852,6 +917,8 @@ function resolveView(state) {
|
|
|
852
917
|
return renderOverviewView(state);
|
|
853
918
|
case 'charts':
|
|
854
919
|
return renderChartsView(state);
|
|
920
|
+
case 'documents':
|
|
921
|
+
return renderDocumentsView(state);
|
|
855
922
|
case 'data':
|
|
856
923
|
return renderDataView(state);
|
|
857
924
|
case 'editor':
|
|
@@ -1101,6 +1168,39 @@ async function handleTableDesignerCsvImport(fileInput) {
|
|
|
1101
1168
|
}
|
|
1102
1169
|
}
|
|
1103
1170
|
|
|
1171
|
+
async function handleDocumentMarkdownImport(fileInput) {
|
|
1172
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
const file = fileInput.files?.[0];
|
|
1177
|
+
|
|
1178
|
+
if (!(file instanceof File)) {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
try {
|
|
1183
|
+
const content = await readFileAsText(file);
|
|
1184
|
+
const document = await createDocumentFromMarkdownExport({
|
|
1185
|
+
filename: file.name,
|
|
1186
|
+
content,
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
if (!document?.id) {
|
|
1190
|
+
showToast('Markdown document could not be imported.', 'alert');
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
clearDocumentAutosaveTimer();
|
|
1195
|
+
showToast(`Document "${document.filename}" imported.`, 'success');
|
|
1196
|
+
router.navigate(`/documents/${encodeURIComponent(document.id)}`);
|
|
1197
|
+
} catch (error) {
|
|
1198
|
+
showToast(error?.message || 'Markdown import failed.', 'alert');
|
|
1199
|
+
} finally {
|
|
1200
|
+
fileInput.value = '';
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1104
1204
|
function renderApp(state) {
|
|
1105
1205
|
syncDocumentTitle(state);
|
|
1106
1206
|
|
|
@@ -1118,6 +1218,7 @@ function renderApp(state) {
|
|
|
1118
1218
|
'editorResults',
|
|
1119
1219
|
'data',
|
|
1120
1220
|
'charts',
|
|
1221
|
+
'documents',
|
|
1121
1222
|
'structure',
|
|
1122
1223
|
'tableDesigner',
|
|
1123
1224
|
'mediaTaggingSetup',
|
|
@@ -1520,6 +1621,69 @@ function buildCopyColumnExportFilename(columnName, copyMode) {
|
|
|
1520
1621
|
return `${columnSlug}-${metadata.suffix}.${metadata.extension}`;
|
|
1521
1622
|
}
|
|
1522
1623
|
|
|
1624
|
+
function normalizeMarkdownDownloadFilename(value) {
|
|
1625
|
+
let filename = String(value ?? '')
|
|
1626
|
+
.trim()
|
|
1627
|
+
.replace(/[\u0000-\u001f\u007f]/g, ' ')
|
|
1628
|
+
.replace(/[\\/]+/g, ' ')
|
|
1629
|
+
.replace(/\s+/g, ' ')
|
|
1630
|
+
.replace(/^\.+/, '')
|
|
1631
|
+
.trim();
|
|
1632
|
+
|
|
1633
|
+
if (!filename) {
|
|
1634
|
+
filename = 'document.md';
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
if (!/\.md$/i.test(filename)) {
|
|
1638
|
+
filename = `${filename}.md`;
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
return filename;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function countEditedMarkdownTodoItems(text) {
|
|
1645
|
+
const lines = String(text ?? '')
|
|
1646
|
+
.split(/\r\n|\r|\n/g)
|
|
1647
|
+
.filter(line => line.trim());
|
|
1648
|
+
|
|
1649
|
+
return lines.length;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function buildDocumentTimestampSlug(date = new Date()) {
|
|
1653
|
+
const pad = value => String(value).padStart(2, '0');
|
|
1654
|
+
|
|
1655
|
+
return [
|
|
1656
|
+
date.getFullYear(),
|
|
1657
|
+
pad(date.getMonth() + 1),
|
|
1658
|
+
pad(date.getDate()),
|
|
1659
|
+
pad(date.getHours()),
|
|
1660
|
+
pad(date.getMinutes()),
|
|
1661
|
+
].join('-');
|
|
1662
|
+
}
|
|
1663
|
+
|
|
1664
|
+
function buildCopyColumnDocumentFilename(columnName) {
|
|
1665
|
+
const columnSlug = slugifyExportFilenamePart(columnName);
|
|
1666
|
+
|
|
1667
|
+
return `${columnSlug}-todos-${buildDocumentTimestampSlug()}.md`;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
function buildCopyColumnDocumentContent({ state, columnName, text, valueCount }) {
|
|
1671
|
+
const activeConnection = state.connections.active;
|
|
1672
|
+
const lines = [
|
|
1673
|
+
`# ${columnName || 'Column'} todos`,
|
|
1674
|
+
'',
|
|
1675
|
+
`- Database: ${activeConnection?.label || 'Active database'}`,
|
|
1676
|
+
`- Column: ${columnName || 'N/A'}`,
|
|
1677
|
+
`- Items: ${valueCount}`,
|
|
1678
|
+
`- Generated: ${new Date().toLocaleString()}`,
|
|
1679
|
+
'',
|
|
1680
|
+
];
|
|
1681
|
+
|
|
1682
|
+
lines.push('## Todos', '', String(text ?? '').trim(), '');
|
|
1683
|
+
|
|
1684
|
+
return lines.join('\n');
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1523
1687
|
function downloadTextFile({ text, filename, mimeType }) {
|
|
1524
1688
|
const blob = new Blob([text], { type: mimeType });
|
|
1525
1689
|
const url = URL.createObjectURL(blob);
|
|
@@ -1677,6 +1841,37 @@ function exportRowEditorJson() {
|
|
|
1677
1841
|
showToast(`Row from "${payload.label}" exported as JSON.`, 'success');
|
|
1678
1842
|
}
|
|
1679
1843
|
|
|
1844
|
+
function exportCurrentDocumentMarkdown() {
|
|
1845
|
+
const documents = getState().documents;
|
|
1846
|
+
|
|
1847
|
+
if (!documents.selectedId) {
|
|
1848
|
+
showToast('No document is selected.', 'alert');
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
const filename = normalizeMarkdownDownloadFilename(documents.draftFilename || documents.selected?.filename);
|
|
1853
|
+
|
|
1854
|
+
downloadTextFile({
|
|
1855
|
+
text: documents.draftContent ?? '',
|
|
1856
|
+
filename,
|
|
1857
|
+
mimeType: 'text/markdown;charset=utf-8',
|
|
1858
|
+
});
|
|
1859
|
+
showToast(`Document "${filename}" exported.`, 'success');
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
function getCurrentDocumentEditorInsertionRange() {
|
|
1863
|
+
const textarea = document.querySelector('.documents-editor-input');
|
|
1864
|
+
|
|
1865
|
+
if (!(textarea instanceof HTMLTextAreaElement)) {
|
|
1866
|
+
return null;
|
|
1867
|
+
}
|
|
1868
|
+
|
|
1869
|
+
return {
|
|
1870
|
+
start: textarea.selectionStart,
|
|
1871
|
+
end: textarea.selectionEnd,
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1680
1875
|
function preserveDataRowEditorSelectionForReload() {
|
|
1681
1876
|
preserveCurrentDataRowSelectionForReload();
|
|
1682
1877
|
}
|
|
@@ -1698,6 +1893,9 @@ async function submitCopyColumnModal(formData) {
|
|
|
1698
1893
|
const outputSeparator = lineBreaks ? '\n' : separator;
|
|
1699
1894
|
const intent = String(formData.get('intent') ?? 'copy');
|
|
1700
1895
|
const isExportIntent = intent === 'export';
|
|
1896
|
+
const isDocumentIntent = intent === 'document';
|
|
1897
|
+
const isMarkdownTodo = isMarkdownTodoCopyColumnMode(copyMode);
|
|
1898
|
+
const editedText = formData.has('editedText') ? String(formData.get('editedText') ?? '') : null;
|
|
1701
1899
|
const result = getCopyColumnResult(state, scope);
|
|
1702
1900
|
const hasColumn = (result?.columns ?? []).some(column => String(column) === columnName);
|
|
1703
1901
|
|
|
@@ -1710,20 +1908,57 @@ async function submitCopyColumnModal(formData) {
|
|
|
1710
1908
|
return;
|
|
1711
1909
|
}
|
|
1712
1910
|
|
|
1713
|
-
if (!
|
|
1911
|
+
if (!isMarkdownTodo) {
|
|
1714
1912
|
storeCopyColumnPreferences({ separator, wrapper, lineBreaks });
|
|
1913
|
+
} else if (editedText !== null) {
|
|
1914
|
+
setCopyColumnModalEditedText(editedText);
|
|
1715
1915
|
}
|
|
1716
1916
|
setCopyColumnModalSubmitting(true);
|
|
1717
1917
|
|
|
1718
|
-
const
|
|
1918
|
+
const generatedOutput = buildCopyColumnText({
|
|
1719
1919
|
result,
|
|
1720
1920
|
columnName,
|
|
1721
1921
|
copyMode,
|
|
1722
1922
|
separator: outputSeparator,
|
|
1723
1923
|
wrapper,
|
|
1724
1924
|
});
|
|
1925
|
+
const text = isMarkdownTodo && editedText !== null ? editedText : generatedOutput.text;
|
|
1926
|
+
const valueCount = isMarkdownTodo && editedText !== null
|
|
1927
|
+
? countEditedMarkdownTodoItems(editedText)
|
|
1928
|
+
: generatedOutput.valueCount;
|
|
1725
1929
|
|
|
1726
1930
|
try {
|
|
1931
|
+
if (isDocumentIntent) {
|
|
1932
|
+
if (!isMarkdownTodo) {
|
|
1933
|
+
throw new Error('Document export is only available for Markdown Todo columns.');
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
const document = await createDocumentFromMarkdownExport({
|
|
1937
|
+
filename: buildCopyColumnDocumentFilename(columnName),
|
|
1938
|
+
title: `${columnName || 'Column'} todos`,
|
|
1939
|
+
content: buildCopyColumnDocumentContent({
|
|
1940
|
+
state,
|
|
1941
|
+
columnName,
|
|
1942
|
+
text,
|
|
1943
|
+
valueCount,
|
|
1944
|
+
}),
|
|
1945
|
+
});
|
|
1946
|
+
|
|
1947
|
+
if (!document) {
|
|
1948
|
+
throw new Error('Document could not be created.');
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
closeModal();
|
|
1952
|
+
showToast(
|
|
1953
|
+
`Document "${document.filename}" created · ${formatNumber(valueCount)} ${
|
|
1954
|
+
valueCount === 1 ? 'item' : 'items'
|
|
1955
|
+
}`,
|
|
1956
|
+
'success',
|
|
1957
|
+
);
|
|
1958
|
+
router.navigate(`/documents/${encodeURIComponent(document.id)}`);
|
|
1959
|
+
return;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1727
1962
|
if (isExportIntent) {
|
|
1728
1963
|
const metadata = getCopyColumnExportMetadata(copyMode);
|
|
1729
1964
|
|
|
@@ -1754,10 +1989,23 @@ async function submitCopyColumnModal(formData) {
|
|
|
1754
1989
|
);
|
|
1755
1990
|
} catch (error) {
|
|
1756
1991
|
setCopyColumnModalError({
|
|
1757
|
-
code:
|
|
1758
|
-
|
|
1992
|
+
code: isDocumentIntent
|
|
1993
|
+
? 'COPY_COLUMN_DOCUMENT_EXPORT_FAILED'
|
|
1994
|
+
: isExportIntent
|
|
1995
|
+
? 'COPY_COLUMN_EXPORT_FAILED'
|
|
1996
|
+
: 'CLIPBOARD_ACCESS_FAILED',
|
|
1997
|
+
message:
|
|
1998
|
+
error?.message ||
|
|
1999
|
+
(isDocumentIntent
|
|
2000
|
+
? 'Document export failed.'
|
|
2001
|
+
: isExportIntent
|
|
2002
|
+
? 'Column export failed.'
|
|
2003
|
+
: 'Clipboard access failed.'),
|
|
1759
2004
|
});
|
|
1760
|
-
showToast(
|
|
2005
|
+
showToast(
|
|
2006
|
+
isDocumentIntent ? 'Document export failed.' : isExportIntent ? 'Column export failed.' : 'Clipboard access failed.',
|
|
2007
|
+
'alert',
|
|
2008
|
+
);
|
|
1761
2009
|
}
|
|
1762
2010
|
}
|
|
1763
2011
|
|
|
@@ -1794,6 +2042,58 @@ async function handleAction(actionNode) {
|
|
|
1794
2042
|
mode: actionNode.dataset.copyMode,
|
|
1795
2043
|
});
|
|
1796
2044
|
return;
|
|
2045
|
+
case 'create-document': {
|
|
2046
|
+
const document = await createDocument();
|
|
2047
|
+
|
|
2048
|
+
if (document?.id) {
|
|
2049
|
+
router.navigate(`/documents/${encodeURIComponent(document.id)}`);
|
|
2050
|
+
}
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
case 'select-document': {
|
|
2054
|
+
const documentId = String(actionNode.dataset.documentId ?? '').trim();
|
|
2055
|
+
|
|
2056
|
+
if (documentId) {
|
|
2057
|
+
clearDocumentAutosaveTimer();
|
|
2058
|
+
router.navigate(`/documents/${encodeURIComponent(documentId)}`);
|
|
2059
|
+
}
|
|
2060
|
+
return;
|
|
2061
|
+
}
|
|
2062
|
+
case 'save-document':
|
|
2063
|
+
clearDocumentAutosaveTimer();
|
|
2064
|
+
await saveCurrentDocument();
|
|
2065
|
+
return;
|
|
2066
|
+
case 'export-document-markdown':
|
|
2067
|
+
exportCurrentDocumentMarkdown();
|
|
2068
|
+
return;
|
|
2069
|
+
case 'open-document-insert-table-modal':
|
|
2070
|
+
await openDocumentInsertTableModal(getCurrentDocumentEditorInsertionRange());
|
|
2071
|
+
return;
|
|
2072
|
+
case 'open-document-insert-note-modal':
|
|
2073
|
+
await openDocumentInsertNoteModal(getCurrentDocumentEditorInsertionRange());
|
|
2074
|
+
return;
|
|
2075
|
+
case 'import-document-markdown': {
|
|
2076
|
+
const fileInput = document.querySelector('[data-bind="document-import-file"]');
|
|
2077
|
+
|
|
2078
|
+
if (!(fileInput instanceof HTMLInputElement)) {
|
|
2079
|
+
return;
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
fileInput.value = '';
|
|
2083
|
+
fileInput.click();
|
|
2084
|
+
return;
|
|
2085
|
+
}
|
|
2086
|
+
case 'delete-document':
|
|
2087
|
+
clearDocumentAutosaveTimer();
|
|
2088
|
+
openDeleteDocumentModal();
|
|
2089
|
+
return;
|
|
2090
|
+
case 'toggle-document-pane':
|
|
2091
|
+
toggleDocumentsPane(actionNode.dataset.pane);
|
|
2092
|
+
return;
|
|
2093
|
+
case 'toggle-document-todo':
|
|
2094
|
+
clearDocumentAutosaveTimer();
|
|
2095
|
+
await toggleCurrentDocumentTodo(actionNode.dataset.lineIndex);
|
|
2096
|
+
return;
|
|
1797
2097
|
case 'open-create-query-chart-modal':
|
|
1798
2098
|
openCreateQueryChartModal();
|
|
1799
2099
|
return;
|
|
@@ -2284,6 +2584,20 @@ document.addEventListener('keydown', event => {
|
|
|
2284
2584
|
const target = event.target;
|
|
2285
2585
|
const state = getState();
|
|
2286
2586
|
|
|
2587
|
+
if (
|
|
2588
|
+
(event.key === 's' || event.key === 'S') &&
|
|
2589
|
+
(event.ctrlKey || event.metaKey) &&
|
|
2590
|
+
!event.altKey &&
|
|
2591
|
+
!event.shiftKey &&
|
|
2592
|
+
!event.defaultPrevented &&
|
|
2593
|
+
state.route.name === 'documents'
|
|
2594
|
+
) {
|
|
2595
|
+
event.preventDefault();
|
|
2596
|
+
clearDocumentAutosaveTimer();
|
|
2597
|
+
void saveCurrentDocument();
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2287
2601
|
// Handle Enter key in tag form fields to trigger create tag
|
|
2288
2602
|
if (
|
|
2289
2603
|
event.key === 'Enter' &&
|
|
@@ -2409,6 +2723,12 @@ document.addEventListener('input', event => {
|
|
|
2409
2723
|
return;
|
|
2410
2724
|
}
|
|
2411
2725
|
|
|
2726
|
+
if (bindNode.dataset.bind === 'document-field') {
|
|
2727
|
+
updateCurrentDocumentDraftField(bindNode.dataset.field, bindNode.value);
|
|
2728
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
2729
|
+
return;
|
|
2730
|
+
}
|
|
2731
|
+
|
|
2412
2732
|
if (bindNode.dataset.bind === 'data-search-query') {
|
|
2413
2733
|
void setDataSearchQuery(bindNode.value);
|
|
2414
2734
|
return;
|
|
@@ -2579,6 +2899,16 @@ document.addEventListener('change', event => {
|
|
|
2579
2899
|
return;
|
|
2580
2900
|
}
|
|
2581
2901
|
|
|
2902
|
+
if (bindNode.dataset.bind === 'document-import-file') {
|
|
2903
|
+
void handleDocumentMarkdownImport(bindNode);
|
|
2904
|
+
return;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2907
|
+
if (bindNode.dataset.bind === 'document-insert-query-select') {
|
|
2908
|
+
updateDocumentInsertQuerySelection(bindNode.value);
|
|
2909
|
+
return;
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2582
2912
|
if (bindNode.dataset.bind === 'table-designer-field') {
|
|
2583
2913
|
if (bindNode instanceof HTMLInputElement && bindNode.type === 'checkbox') {
|
|
2584
2914
|
updateCurrentTableDesignerField(bindNode.dataset.field, bindNode.checked);
|
|
@@ -2757,6 +3087,32 @@ document.addEventListener('submit', async event => {
|
|
|
2757
3087
|
case 'delete-query-history-confirm':
|
|
2758
3088
|
await submitDeleteQueryHistoryConfirmation();
|
|
2759
3089
|
return;
|
|
3090
|
+
case 'delete-document-confirm': {
|
|
3091
|
+
const result = await submitDeleteDocumentConfirmation();
|
|
3092
|
+
|
|
3093
|
+
if (result.deleted) {
|
|
3094
|
+
router.navigate(
|
|
3095
|
+
result.nextDocumentId ? `/documents/${encodeURIComponent(result.nextDocumentId)}` : '/documents',
|
|
3096
|
+
);
|
|
3097
|
+
}
|
|
3098
|
+
return;
|
|
3099
|
+
}
|
|
3100
|
+
case 'document-insert-table': {
|
|
3101
|
+
const inserted = await submitDocumentInsertTable();
|
|
3102
|
+
|
|
3103
|
+
if (inserted) {
|
|
3104
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
3105
|
+
}
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
case 'document-insert-note': {
|
|
3109
|
+
const inserted = submitDocumentInsertNote();
|
|
3110
|
+
|
|
3111
|
+
if (inserted) {
|
|
3112
|
+
scheduleDocumentAutosave(getState().documents.selectedId);
|
|
3113
|
+
}
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
2760
3116
|
case 'apply-row-update-preview':
|
|
2761
3117
|
await submitRowUpdatePreviewConfirmation();
|
|
2762
3118
|
return;
|