sqlite-hub 0.16.0 → 0.17.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -19
- package/bin/sqlite-hub.js +1041 -224
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +34 -0
- package/frontend/js/app.js +481 -32
- package/frontend/js/components/modal.js +270 -50
- package/frontend/js/components/pageHeader.js +14 -16
- package/frontend/js/components/queryHistoryPanel.js +126 -131
- package/frontend/js/components/queryResults.js +18 -1
- package/frontend/js/components/rowEditorPanel.js +42 -34
- package/frontend/js/components/sidebar.js +1 -0
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +655 -2
- package/frontend/js/utils/jsonPreview.js +31 -0
- package/frontend/js/utils/markdownDocuments.js +248 -0
- package/frontend/js/utils/rowEditorValues.js +41 -0
- package/frontend/js/utils/tableScrollState.js +39 -0
- package/frontend/js/views/charts.js +8 -0
- package/frontend/js/views/data.js +4 -1
- package/frontend/js/views/documents.js +300 -0
- package/frontend/js/views/editor.js +2 -0
- package/frontend/js/views/settings.js +39 -3
- package/frontend/styles/components.css +19 -0
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/views.css +422 -0
- package/package.json +2 -1
- package/server/middleware/localRequestSecurity.js +84 -0
- package/server/routes/connections.js +18 -1
- package/server/routes/documents.js +163 -0
- package/server/routes/settings.js +22 -6
- package/server/server.js +18 -1
- package/server/services/nativeFileDialogService.js +168 -0
- package/server/services/sqlite/dataBrowserService.js +0 -4
- package/server/services/sqlite/exportService.js +5 -1
- package/server/services/sqlite/sqlExecutor.js +51 -2
- package/server/services/storage/appStateStore.js +313 -0
- package/server/utils/sqliteTypes.js +17 -8
- package/tests/cli-args.test.js +100 -0
- package/tests/connections-file-dialog-route.test.js +46 -0
- package/tests/copy-column-modal.test.js +97 -0
- package/tests/database-documents.test.js +85 -0
- package/tests/export-blob.test.js +46 -0
- package/tests/json-preview.test.js +49 -0
- package/tests/local-request-security.test.js +85 -0
- package/tests/markdown-documents.test.js +79 -0
- package/tests/native-file-dialog.test.js +78 -0
- package/tests/query-results-truncation.test.js +20 -0
- package/tests/row-editor-null-values.test.js +131 -0
- package/tests/settings-metadata.test.js +16 -0
- package/tests/settings-view.test.js +32 -0
- package/tests/sql-identifier-safety.test.js +9 -3
- package/tests/sql-result-limit.test.js +66 -0
- package/tests/table-scroll-state.test.js +70 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { escapeHtml, formatNumber, highlightSql, truncateMiddle } from "../utils/format.js";
|
|
2
2
|
import {
|
|
3
|
+
buildCopyColumnText,
|
|
3
4
|
buildCopyColumnPreviewText,
|
|
4
5
|
getCopyColumnActionLabel,
|
|
5
6
|
getCopyColumnExportMetadata,
|
|
@@ -170,7 +171,7 @@ function renderOpenConnectionForm(modal) {
|
|
|
170
171
|
text: "Open read-only",
|
|
171
172
|
})}
|
|
172
173
|
${renderError(modal.error)}
|
|
173
|
-
<div class="flex items-center justify-
|
|
174
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
174
175
|
<button
|
|
175
176
|
class="standard-button"
|
|
176
177
|
data-action="close-modal"
|
|
@@ -244,7 +245,7 @@ function renderEditConnectionForm(modal) {
|
|
|
244
245
|
text: "Open read-only",
|
|
245
246
|
})}
|
|
246
247
|
${renderError(modal.error)}
|
|
247
|
-
<div class="flex items-center justify-
|
|
248
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
248
249
|
<button
|
|
249
250
|
class="standard-button"
|
|
250
251
|
data-action="close-modal"
|
|
@@ -263,21 +264,41 @@ function renderEditConnectionForm(modal) {
|
|
|
263
264
|
`;
|
|
264
265
|
}
|
|
265
266
|
|
|
266
|
-
function renderCreateDatabaseForm(modal) {
|
|
267
|
+
export function renderCreateDatabaseForm(modal) {
|
|
267
268
|
return `
|
|
268
269
|
<form class="space-y-5" data-form="create-connection">
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
270
|
+
<label class="block space-y-2">
|
|
271
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
272
|
+
New SQLite File Path
|
|
273
|
+
</span>
|
|
274
|
+
<span class="flex items-stretch gap-2">
|
|
275
|
+
<input
|
|
276
|
+
class="control-input min-w-0 flex-1 border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
277
|
+
data-create-database-path
|
|
278
|
+
name="path"
|
|
279
|
+
placeholder="/absolute/path/to/new-database.sqlite"
|
|
280
|
+
type="text"
|
|
281
|
+
/>
|
|
282
|
+
<button
|
|
283
|
+
class="standard-button flex-none"
|
|
284
|
+
data-action="choose-create-database-path"
|
|
285
|
+
type="button"
|
|
286
|
+
>
|
|
287
|
+
<span class="material-symbols-outlined text-sm">folder_open</span>
|
|
288
|
+
<span data-create-database-path-button-label>Browse...</span>
|
|
289
|
+
</button>
|
|
290
|
+
</span>
|
|
291
|
+
<span class="block text-[11px] leading-5 text-on-surface-variant/60">
|
|
292
|
+
Choose a folder and filename, or enter an absolute path manually.
|
|
293
|
+
</span>
|
|
294
|
+
</label>
|
|
274
295
|
${renderField({
|
|
275
296
|
label: "Label",
|
|
276
297
|
name: "label",
|
|
277
298
|
placeholder: "Optional display name",
|
|
278
299
|
})}
|
|
279
300
|
${renderError(modal.error)}
|
|
280
|
-
<div class="flex items-center justify-
|
|
301
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
281
302
|
<button
|
|
282
303
|
class="standard-button"
|
|
283
304
|
data-action="close-modal"
|
|
@@ -363,7 +384,7 @@ function renderImportSqlForm(modal, state) {
|
|
|
363
384
|
explicit path instead of file upload.
|
|
364
385
|
</p>
|
|
365
386
|
${renderError(modal.error)}
|
|
366
|
-
<div class="flex items-center justify-
|
|
387
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
367
388
|
<button
|
|
368
389
|
class="standard-button"
|
|
369
390
|
data-action="close-modal"
|
|
@@ -423,7 +444,7 @@ function renderDeleteRowConfirmForm(modal) {
|
|
|
423
444
|
rowPreviewMarkup,
|
|
424
445
|
"</div>",
|
|
425
446
|
renderError(modal.error),
|
|
426
|
-
'<div class="flex items-center justify-
|
|
447
|
+
'<div class="flex items-center justify-between gap-3 pt-2">',
|
|
427
448
|
'<button class="standard-button" data-action="close-modal" type="button">Cancel</button>',
|
|
428
449
|
'<button class="delete-button" type="submit">',
|
|
429
450
|
modal.submitting ? "Deleting..." : "Delete Row",
|
|
@@ -513,7 +534,7 @@ function renderRowUpdatePreviewForm(modal) {
|
|
|
513
534
|
</div>
|
|
514
535
|
${paramsMarkup}
|
|
515
536
|
${renderError(modal.error)}
|
|
516
|
-
<div class="flex items-center justify-
|
|
537
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
517
538
|
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
518
539
|
<button class="signature-button" type="submit">
|
|
519
540
|
${modal.submitting ? 'Applying...' : 'Apply Changes'}
|
|
@@ -753,7 +774,7 @@ function renderChartEditorForm(modal, state) {
|
|
|
753
774
|
: ""
|
|
754
775
|
}
|
|
755
776
|
${renderError(modal.error)}
|
|
756
|
-
<div class="flex items-center justify-
|
|
777
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
757
778
|
<button
|
|
758
779
|
class="standard-button"
|
|
759
780
|
data-action="close-modal"
|
|
@@ -781,7 +802,7 @@ function renderDeleteChartForm(modal) {
|
|
|
781
802
|
'<p class="text-sm leading-7 text-on-surface-variant/65">The linked query-history entry stays intact. Only this chart definition is removed.</p>',
|
|
782
803
|
"</div>",
|
|
783
804
|
renderError(modal.error),
|
|
784
|
-
'<div class="flex items-center justify-
|
|
805
|
+
'<div class="flex items-center justify-between gap-3 pt-2">',
|
|
785
806
|
'<button class="standard-button" data-action="close-modal" type="button">Cancel</button>',
|
|
786
807
|
'<button class="delete-button" type="submit">',
|
|
787
808
|
modal.submitting ? "Deleting..." : "Delete Chart",
|
|
@@ -798,7 +819,7 @@ function renderDeleteQueryHistoryForm(modal) {
|
|
|
798
819
|
'<p class="text-sm leading-7 text-on-surface-variant/65">This removes the query-history entry and all recorded runs linked to it.</p>',
|
|
799
820
|
"</div>",
|
|
800
821
|
renderError(modal.error),
|
|
801
|
-
'<div class="flex items-center justify-
|
|
822
|
+
'<div class="flex items-center justify-between gap-3 pt-2">',
|
|
802
823
|
'<button class="standard-button" data-action="close-modal" type="button">Cancel</button>',
|
|
803
824
|
'<button class="delete-button" type="submit">',
|
|
804
825
|
modal.submitting ? "Deleting..." : "Delete Query",
|
|
@@ -806,6 +827,147 @@ function renderDeleteQueryHistoryForm(modal) {
|
|
|
806
827
|
].join("");
|
|
807
828
|
}
|
|
808
829
|
|
|
830
|
+
function renderDeleteDocumentForm(modal) {
|
|
831
|
+
return [
|
|
832
|
+
'<form class="space-y-5" data-form="delete-document-confirm"><div class="space-y-3">',
|
|
833
|
+
'<p class="text-sm leading-7 text-on-surface">Delete document <span class="font-bold text-primary-container">',
|
|
834
|
+
escapeHtml(modal.filename ?? "document"),
|
|
835
|
+
"</span>?</p>",
|
|
836
|
+
'<p class="text-sm leading-7 text-on-surface-variant/65">This removes the Markdown document from the active database document folder.</p>',
|
|
837
|
+
'<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">',
|
|
838
|
+
formatNumber(modal.contentLength ?? 0),
|
|
839
|
+
" chars</div>",
|
|
840
|
+
"</div>",
|
|
841
|
+
renderError(modal.error),
|
|
842
|
+
'<div class="flex items-center justify-between gap-3 pt-2">',
|
|
843
|
+
'<button class="standard-button" data-action="close-modal" type="button">Cancel</button>',
|
|
844
|
+
'<button class="delete-button" type="submit">',
|
|
845
|
+
modal.submitting ? "Deleting..." : "Delete Document",
|
|
846
|
+
"</button></div></form>",
|
|
847
|
+
].join("");
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
function getDocumentInsertQueryTitle(query) {
|
|
851
|
+
return query?.displayTitle || query?.title || query?.previewSql || query?.rawSql || "Saved query";
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
function getSelectedDocumentInsertQuery(modal) {
|
|
855
|
+
const selectedHistoryId = String(modal.selectedHistoryId ?? "");
|
|
856
|
+
|
|
857
|
+
return (modal.queries ?? []).find((query) => String(query.id) === selectedHistoryId) ?? null;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
function renderDocumentInsertQuerySelect(modal, emptyText) {
|
|
861
|
+
const queries = modal.queries ?? [];
|
|
862
|
+
|
|
863
|
+
if (modal.loading) {
|
|
864
|
+
return '<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface-variant/65">Loading saved queries...</div>';
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
if (!queries.length) {
|
|
868
|
+
return `<div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface-variant/65">${escapeHtml(emptyText)}</div>`;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
return `
|
|
872
|
+
<label class="block space-y-2">
|
|
873
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
874
|
+
Saved Query
|
|
875
|
+
</span>
|
|
876
|
+
<select
|
|
877
|
+
class="control-select w-full border border-outline-variant/20 bg-surface-container-lowest text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
|
|
878
|
+
data-bind="document-insert-query-select"
|
|
879
|
+
name="historyId"
|
|
880
|
+
>
|
|
881
|
+
${queries
|
|
882
|
+
.map(
|
|
883
|
+
(query) => `
|
|
884
|
+
<option
|
|
885
|
+
value="${escapeHtml(query.id)}"
|
|
886
|
+
${String(query.id) === String(modal.selectedHistoryId) ? "selected" : ""}
|
|
887
|
+
>
|
|
888
|
+
${escapeHtml(getDocumentInsertQueryTitle(query))}
|
|
889
|
+
</option>
|
|
890
|
+
`
|
|
891
|
+
)
|
|
892
|
+
.join("")}
|
|
893
|
+
</select>
|
|
894
|
+
</label>
|
|
895
|
+
`;
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
function renderDocumentInsertQueryPreview(query) {
|
|
899
|
+
if (!query) {
|
|
900
|
+
return "";
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return `
|
|
904
|
+
<div class="space-y-2">
|
|
905
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
906
|
+
Query Preview
|
|
907
|
+
</div>
|
|
908
|
+
<pre class="max-h-44 overflow-auto border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 font-mono text-xs leading-6 text-on-surface-variant/75 custom-scrollbar">${escapeHtml(
|
|
909
|
+
query.rawSql || query.previewSql || ""
|
|
910
|
+
)}</pre>
|
|
911
|
+
</div>
|
|
912
|
+
`;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function renderDocumentInsertTableForm(modal) {
|
|
916
|
+
const selectedQuery = getSelectedDocumentInsertQuery(modal);
|
|
917
|
+
const disabledAttribute = modal.loading || modal.submitting || !(modal.queries ?? []).length
|
|
918
|
+
? 'disabled aria-disabled="true"'
|
|
919
|
+
: "";
|
|
920
|
+
|
|
921
|
+
return `
|
|
922
|
+
<form class="space-y-5" data-form="document-insert-table">
|
|
923
|
+
${renderDocumentInsertQuerySelect(modal, "No saved queries are available for this database.")}
|
|
924
|
+
${renderDocumentInsertQueryPreview(selectedQuery)}
|
|
925
|
+
${renderError(modal.error)}
|
|
926
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
927
|
+
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
928
|
+
<button class="signature-button" type="submit" ${disabledAttribute}>
|
|
929
|
+
${modal.submitting ? "Inserting..." : "Insert Table"}
|
|
930
|
+
</button>
|
|
931
|
+
</div>
|
|
932
|
+
</form>
|
|
933
|
+
`;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function renderDocumentInsertNoteForm(modal) {
|
|
937
|
+
const selectedQuery = getSelectedDocumentInsertQuery(modal);
|
|
938
|
+
const note = String(selectedQuery?.notes ?? "").trim();
|
|
939
|
+
const disabledAttribute = modal.loading || modal.submitting || !(modal.queries ?? []).length
|
|
940
|
+
? 'disabled aria-disabled="true"'
|
|
941
|
+
: "";
|
|
942
|
+
|
|
943
|
+
return `
|
|
944
|
+
<form class="space-y-5" data-form="document-insert-note">
|
|
945
|
+
${renderDocumentInsertQuerySelect(modal, "No saved queries with notes are available for this database.")}
|
|
946
|
+
${
|
|
947
|
+
note
|
|
948
|
+
? `
|
|
949
|
+
<div class="space-y-2">
|
|
950
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
951
|
+
Note Preview
|
|
952
|
+
</div>
|
|
953
|
+
<pre class="max-h-64 overflow-auto whitespace-pre-wrap border border-outline-variant/10 bg-surface-container-lowest px-4 py-3 text-sm leading-6 text-on-surface custom-scrollbar">${escapeHtml(
|
|
954
|
+
note
|
|
955
|
+
)}</pre>
|
|
956
|
+
</div>
|
|
957
|
+
`
|
|
958
|
+
: ""
|
|
959
|
+
}
|
|
960
|
+
${renderError(modal.error)}
|
|
961
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
962
|
+
<button class="standard-button" data-action="close-modal" type="button">Cancel</button>
|
|
963
|
+
<button class="signature-button" type="submit" ${disabledAttribute}>
|
|
964
|
+
${modal.submitting ? "Inserting..." : "Insert Note"}
|
|
965
|
+
</button>
|
|
966
|
+
</div>
|
|
967
|
+
</form>
|
|
968
|
+
`;
|
|
969
|
+
}
|
|
970
|
+
|
|
809
971
|
function renderQueryExportPreview(lines = []) {
|
|
810
972
|
return lines.map((line) => `<span class="block whitespace-pre">${escapeHtml(line)}</span>`).join("");
|
|
811
973
|
}
|
|
@@ -888,7 +1050,7 @@ function renderTextExportModal(modal, action) {
|
|
|
888
1050
|
.join("")}
|
|
889
1051
|
</div>
|
|
890
1052
|
${renderError(modal.error)}
|
|
891
|
-
<div class="flex justify-
|
|
1053
|
+
<div class="flex justify-start">
|
|
892
1054
|
<button class="standard-button" data-action="close-modal" type="button">
|
|
893
1055
|
Cancel
|
|
894
1056
|
</button>
|
|
@@ -911,23 +1073,47 @@ function getCopyColumnResult(state, modal) {
|
|
|
911
1073
|
|
|
912
1074
|
function renderCopyColumnPreview(modal, state) {
|
|
913
1075
|
const result = getCopyColumnResult(state, modal);
|
|
1076
|
+
const isMarkdownTodo = isMarkdownTodoCopyColumnMode(modal.copyMode);
|
|
914
1077
|
const separator = Boolean(modal.lineBreaks) && !isMarkdownTodoCopyColumnMode(modal.copyMode)
|
|
915
1078
|
? "\n"
|
|
916
1079
|
: String(modal.separator ?? ",");
|
|
917
1080
|
const wrapper = String(modal.wrapper ?? '"');
|
|
918
|
-
const preview =
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1081
|
+
const preview = isMarkdownTodo
|
|
1082
|
+
? modal.editedText ?? buildCopyColumnText({
|
|
1083
|
+
result,
|
|
1084
|
+
columnName: modal.columnName,
|
|
1085
|
+
copyMode: modal.copyMode,
|
|
1086
|
+
separator: "\n",
|
|
1087
|
+
wrapper,
|
|
1088
|
+
}).text
|
|
1089
|
+
: buildCopyColumnPreviewText({
|
|
1090
|
+
result,
|
|
1091
|
+
columnName: modal.columnName,
|
|
1092
|
+
copyMode: modal.copyMode,
|
|
1093
|
+
separator,
|
|
1094
|
+
wrapper,
|
|
1095
|
+
maxRows: 4,
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
if (!preview && !isMarkdownTodo) {
|
|
928
1099
|
return "";
|
|
929
1100
|
}
|
|
930
1101
|
|
|
1102
|
+
if (isMarkdownTodo) {
|
|
1103
|
+
return `
|
|
1104
|
+
<label class="block space-y-2">
|
|
1105
|
+
<span class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
1106
|
+
Editable Preview
|
|
1107
|
+
</span>
|
|
1108
|
+
<textarea
|
|
1109
|
+
class="copy-column-preview copy-column-preview--editable custom-scrollbar"
|
|
1110
|
+
name="editedText"
|
|
1111
|
+
spellcheck="true"
|
|
1112
|
+
>${escapeHtml(preview)}</textarea>
|
|
1113
|
+
</label>
|
|
1114
|
+
`;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
931
1117
|
return `
|
|
932
1118
|
<div class="space-y-2">
|
|
933
1119
|
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/60">
|
|
@@ -977,7 +1163,7 @@ function renderCopyColumnFormatField({ label, name, value = "" }) {
|
|
|
977
1163
|
`;
|
|
978
1164
|
}
|
|
979
1165
|
|
|
980
|
-
function renderCopyColumnModal(modal, state) {
|
|
1166
|
+
export function renderCopyColumnModal(modal, state) {
|
|
981
1167
|
const result = getCopyColumnResult(state, modal);
|
|
982
1168
|
const rows = result?.rows ?? [];
|
|
983
1169
|
const valueCount = modal.copyMode === "first-10" ? Math.min(rows.length, 10) : rows.length;
|
|
@@ -1029,7 +1215,7 @@ function renderCopyColumnModal(modal, state) {
|
|
|
1029
1215
|
${formatFieldsMarkup}
|
|
1030
1216
|
${renderCopyColumnPreview(modal, state)}
|
|
1031
1217
|
${renderError(modal.error)}
|
|
1032
|
-
<div class="flex flex-wrap items-center justify-
|
|
1218
|
+
<div class="flex flex-wrap items-center justify-between gap-3 pt-2">
|
|
1033
1219
|
<button
|
|
1034
1220
|
class="standard-button"
|
|
1035
1221
|
data-action="close-modal"
|
|
@@ -1038,24 +1224,41 @@ function renderCopyColumnModal(modal, state) {
|
|
|
1038
1224
|
>
|
|
1039
1225
|
Cancel
|
|
1040
1226
|
</button>
|
|
1041
|
-
<
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1227
|
+
<div class="flex flex-wrap items-center justify-end gap-3">
|
|
1228
|
+
<button
|
|
1229
|
+
class="standard-button"
|
|
1230
|
+
name="intent"
|
|
1231
|
+
type="submit"
|
|
1232
|
+
value="export"
|
|
1233
|
+
${disabledAttribute}
|
|
1234
|
+
>
|
|
1235
|
+
${modal.submitting ? "Working..." : `Export as ${exportMetadata.extension.toUpperCase()}`}
|
|
1236
|
+
</button>
|
|
1237
|
+
${
|
|
1238
|
+
isMarkdownTodo
|
|
1239
|
+
? `
|
|
1240
|
+
<button
|
|
1241
|
+
class="standard-button"
|
|
1242
|
+
name="intent"
|
|
1243
|
+
type="submit"
|
|
1244
|
+
value="document"
|
|
1245
|
+
${disabledAttribute}
|
|
1246
|
+
>
|
|
1247
|
+
${modal.submitting ? "Working..." : "Export to document folder"}
|
|
1248
|
+
</button>
|
|
1249
|
+
`
|
|
1250
|
+
: ""
|
|
1251
|
+
}
|
|
1252
|
+
<button
|
|
1253
|
+
class="signature-button"
|
|
1254
|
+
name="intent"
|
|
1255
|
+
type="submit"
|
|
1256
|
+
value="copy"
|
|
1257
|
+
${disabledAttribute}
|
|
1258
|
+
>
|
|
1259
|
+
${modal.submitting ? "Working..." : "Copy"}
|
|
1260
|
+
</button>
|
|
1261
|
+
</div>
|
|
1059
1262
|
</div>
|
|
1060
1263
|
</form>
|
|
1061
1264
|
`;
|
|
@@ -1230,7 +1433,7 @@ function renderTableDesignerConstraintsModal(modal, state) {
|
|
|
1230
1433
|
<div class="table-designer-constraints-modal__empty">
|
|
1231
1434
|
No active table designer draft.
|
|
1232
1435
|
</div>
|
|
1233
|
-
<div class="flex items-center justify-
|
|
1436
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1234
1437
|
<button class="standard-button" data-action="close-modal" type="button">Close</button>
|
|
1235
1438
|
</div>
|
|
1236
1439
|
`;
|
|
@@ -1291,7 +1494,7 @@ function renderTableDesignerConstraintsModal(modal, state) {
|
|
|
1291
1494
|
: "No multi-column or partial unique constraints detected.",
|
|
1292
1495
|
body: uniqueConstraints.map(renderTableDesignerUniqueConstraintEditor).join(""),
|
|
1293
1496
|
})}
|
|
1294
|
-
<div class="flex items-center justify-
|
|
1497
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1295
1498
|
<button class="standard-button" data-action="close-modal" type="button">Close</button>
|
|
1296
1499
|
</div>
|
|
1297
1500
|
</div>
|
|
@@ -1336,7 +1539,7 @@ function renderCreateMediaTaggingMappingTableForm(modal, state) {
|
|
|
1336
1539
|
${renderSqlPreviewField(MEDIA_TAGGING_DEFAULT_MAPPING_TABLE_SQL)}
|
|
1337
1540
|
</div>
|
|
1338
1541
|
${renderError(modal.error)}
|
|
1339
|
-
<div class="flex items-center justify-
|
|
1542
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1340
1543
|
<button
|
|
1341
1544
|
class="standard-button"
|
|
1342
1545
|
data-action="close-modal"
|
|
@@ -1400,7 +1603,7 @@ function renderCreateMediaTaggingTagTableForm(modal, state) {
|
|
|
1400
1603
|
${renderSqlPreviewField(MEDIA_TAGGING_DEFAULT_TAG_TABLE_SQL)}
|
|
1401
1604
|
</div>
|
|
1402
1605
|
${renderError(modal.error)}
|
|
1403
|
-
<div class="flex items-center justify-
|
|
1606
|
+
<div class="flex items-center justify-between gap-3 pt-2">
|
|
1404
1607
|
<button
|
|
1405
1608
|
class="standard-button"
|
|
1406
1609
|
data-action="close-modal"
|
|
@@ -1479,6 +1682,21 @@ export function renderModal(state) {
|
|
|
1479
1682
|
title: "Delete Query",
|
|
1480
1683
|
body: renderDeleteQueryHistoryForm(modal),
|
|
1481
1684
|
},
|
|
1685
|
+
"delete-document": {
|
|
1686
|
+
eyebrow: "Documents // Confirm deletion",
|
|
1687
|
+
title: "Delete Document",
|
|
1688
|
+
body: renderDeleteDocumentForm(modal),
|
|
1689
|
+
},
|
|
1690
|
+
"document-insert-table": {
|
|
1691
|
+
eyebrow: "Documents // Saved query output",
|
|
1692
|
+
title: "Insert Table",
|
|
1693
|
+
body: renderDocumentInsertTableForm(modal),
|
|
1694
|
+
},
|
|
1695
|
+
"document-insert-note": {
|
|
1696
|
+
eyebrow: "Documents // Saved query notes",
|
|
1697
|
+
title: "Insert Note",
|
|
1698
|
+
body: renderDocumentInsertNoteForm(modal),
|
|
1699
|
+
},
|
|
1482
1700
|
"query-export": {
|
|
1483
1701
|
eyebrow: "SQL Editor // Export query result",
|
|
1484
1702
|
title: "Export Query",
|
|
@@ -1521,6 +1739,8 @@ export function renderModal(state) {
|
|
|
1521
1739
|
<div class="fixed inset-0 z-50 flex items-center justify-center bg-background/85 px-4 backdrop-blur-sm">
|
|
1522
1740
|
<div class="w-full ${
|
|
1523
1741
|
modal.kind === "chart-editor" ||
|
|
1742
|
+
modal.kind === "document-insert-table" ||
|
|
1743
|
+
modal.kind === "document-insert-note" ||
|
|
1524
1744
|
modal.kind === "row-update-preview" ||
|
|
1525
1745
|
modal.kind === "table-designer-constraints"
|
|
1526
1746
|
? "max-w-3xl"
|
|
@@ -1,33 +1,31 @@
|
|
|
1
|
-
import { escapeHtml } from
|
|
1
|
+
import { escapeHtml } from '../utils/format.js';
|
|
2
2
|
|
|
3
|
-
export function renderPageHeader({ eyebrow =
|
|
4
|
-
|
|
3
|
+
export function renderPageHeader({ eyebrow = '', title, subtitle = '', actions = '' }) {
|
|
4
|
+
return `
|
|
5
5
|
<div class="mb-10 flex flex-wrap items-end justify-between gap-6">
|
|
6
6
|
<div>
|
|
7
7
|
${
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
eyebrow
|
|
9
|
+
? `
|
|
10
10
|
<div class="page-eyebrow">
|
|
11
11
|
<span class="text-[#FCE300] text-xs font-mono font-bold tracking-widest uppercase">${escapeHtml(
|
|
12
|
-
|
|
12
|
+
eyebrow,
|
|
13
13
|
)}</span>
|
|
14
14
|
<div class="page-eyebrow-line"></div>
|
|
15
15
|
</div>
|
|
16
16
|
`
|
|
17
|
-
|
|
17
|
+
: ''
|
|
18
18
|
}
|
|
19
|
-
<h1 class="text-5xl font-headline font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(
|
|
20
|
-
title
|
|
21
|
-
)}</h1>
|
|
19
|
+
<h1 class="text-5xl font-headline font-bold text-[#FCE300] tracking-tighter uppercase">${escapeHtml(title)}</h1>
|
|
22
20
|
${
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
subtitle
|
|
22
|
+
? `<p class="text-xs font-mono text-on-surface/40 mt-1 uppercase tracking-widest">${escapeHtml(
|
|
23
|
+
subtitle,
|
|
24
|
+
)}</p>`
|
|
25
|
+
: ''
|
|
28
26
|
}
|
|
29
27
|
</div>
|
|
30
|
-
${actions ? `<div class="flex gap-3">${actions}</div>` :
|
|
28
|
+
${actions ? `<div class="flex gap-3">${actions}</div>` : ''}
|
|
31
29
|
</div>
|
|
32
30
|
`;
|
|
33
31
|
}
|