sqlite-hub 0.17.2 → 1.1.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 +143 -47
- package/bin/sqlite-hub.js +206 -444
- package/examples/api/queries.js +31 -0
- package/examples/api/rows.js +27 -0
- package/frontend/assets/mockups/charts_1_bars_1200.webp +0 -0
- package/frontend/assets/mockups/charts_2_pie_1200.webp +0 -0
- package/frontend/assets/mockups/charts_3_scatter_plot_1200.webp +0 -0
- package/frontend/assets/mockups/connections_1200.webp +0 -0
- package/frontend/assets/mockups/data_1_1200.webp +0 -0
- package/frontend/assets/mockups/data_2_row_editor_1200.webp +0 -0
- package/frontend/assets/mockups/documents_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_1_setup_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_2_tagging_queue_1200.webp +0 -0
- package/frontend/assets/mockups/media_tagging_3_media_viewer_1200.webp +0 -0
- package/frontend/assets/mockups/overview_1200.webp +0 -0
- package/frontend/assets/mockups/settings_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_1_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_2_query_details_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_3_export_column_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_4_export_column_as_markdown_1200.webp +0 -0
- package/frontend/assets/mockups/sql_editor_5_export_query_result_1200.webp +0 -0
- package/frontend/assets/mockups/structure_1_1200.webp +0 -0
- package/frontend/assets/mockups/structure_2_inspector_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_1_1200.webp +0 -0
- package/frontend/assets/mockups/table_designer_2_checks_1200.webp +0 -0
- package/frontend/js/api.js +25 -0
- package/frontend/js/app.js +263 -34
- package/frontend/js/components/formControls.js +38 -0
- package/frontend/js/components/modal.js +77 -21
- package/frontend/js/components/queryChartRenderer.js +28 -3
- package/frontend/js/components/queryEditor.js +20 -24
- package/frontend/js/components/queryHistoryDetail.js +1 -1
- package/frontend/js/components/queryHistoryHeader.js +48 -0
- package/frontend/js/components/queryHistoryList.js +132 -0
- package/frontend/js/components/queryHistoryPanel.js +72 -136
- package/frontend/js/components/structureGraph.js +699 -89
- package/frontend/js/components/tableDesignerEditor.js +3 -5
- package/frontend/js/store.js +188 -7
- package/frontend/js/utils/exportFilenames.js +3 -1
- package/frontend/js/views/charts.js +320 -169
- package/frontend/js/views/connections.js +59 -64
- package/frontend/js/views/data.js +12 -20
- package/frontend/js/views/editor.js +0 -2
- package/frontend/js/views/settings.js +219 -5
- package/frontend/js/views/structure.js +27 -13
- package/frontend/styles/components.css +155 -0
- package/frontend/styles/structure-graph.css +140 -35
- package/frontend/styles/tailwind.generated.css +2 -2
- package/frontend/styles/views.css +12 -6
- package/package.json +7 -6
- package/server/middleware/apiTokenAuth.js +30 -0
- package/server/routes/connections.js +17 -0
- package/server/routes/export.js +89 -22
- package/server/routes/externalApi.js +304 -0
- package/server/routes/settings.js +90 -21
- package/server/server.js +22 -1
- package/server/services/apiTokenService.js +101 -0
- package/server/services/appInfoService.js +215 -0
- package/server/services/databaseCommandService.js +443 -0
- package/server/services/nativeFileDialogService.js +93 -1
- package/server/services/sqlite/exportService.js +307 -22
- package/server/services/storage/appStateStore.js +113 -0
- package/server/utils/errors.js +7 -0
- package/tests/api-token-auth.test.js +236 -0
- package/tests/cli-args.test.js +16 -3
- package/tests/cli-service-delegation.test.js +43 -0
- package/tests/connections-file-dialog-route.test.js +43 -0
- package/tests/copy-column-modal.test.js +34 -0
- package/tests/database-command-service.test.js +139 -0
- package/tests/export-blob.test.js +54 -1
- package/tests/export-filenames.test.js +4 -0
- package/tests/form-controls.test.js +34 -0
- package/tests/native-file-dialog.test.js +27 -0
- package/tests/settings-api-tokens-route.test.js +97 -0
- package/tests/settings-metadata.test.js +99 -1
- package/tests/settings-view.test.js +75 -0
- package/frontend/assets/mockups/connections.png +0 -0
- package/frontend/assets/mockups/data.png +0 -0
- package/frontend/assets/mockups/data_row_editor.png +0 -0
- package/frontend/assets/mockups/home.png +0 -0
- package/frontend/assets/mockups/sql_editor.png +0 -0
- package/frontend/assets/mockups/sql_editor_croped.png +0 -0
- package/frontend/assets/mockups/sql_editor_querydetail.png +0 -0
- package/frontend/assets/mockups/structure.png +0 -0
- package/frontend/assets/mockups/structure_inspector.png +0 -0
|
@@ -351,11 +351,9 @@ export function renderTableDesignerEditor(state) {
|
|
|
351
351
|
value="${escapeHtml(draft.tableName)}"
|
|
352
352
|
/>
|
|
353
353
|
${
|
|
354
|
-
draft.mode === "
|
|
355
|
-
? `<div class="status-badge status-badge--
|
|
356
|
-
|
|
357
|
-
)}</div>`
|
|
358
|
-
: `<div class="status-badge status-badge--primary">CREATE</div>`
|
|
354
|
+
draft.mode === "create"
|
|
355
|
+
? `<div class="status-badge status-badge--primary">CREATE</div>`
|
|
356
|
+
: ""
|
|
359
357
|
}
|
|
360
358
|
</div>
|
|
361
359
|
<div class="table-designer-main__subtitle">
|
package/frontend/js/store.js
CHANGED
|
@@ -49,6 +49,7 @@ const UI_PREFERENCE_STORAGE_KEYS = {
|
|
|
49
49
|
dataTablesVisible: 'sqlite_hub_data_tables_visible',
|
|
50
50
|
structureTablesVisible: 'sqlite_hub_structure_tables_visible',
|
|
51
51
|
chartsHistoryVisible: 'sqlite_hub_charts_history_visible',
|
|
52
|
+
chartsQueryVisible: 'sqlite_hub_charts_query_visible',
|
|
52
53
|
chartsResultsVisible: 'sqlite_hub_charts_results_visible',
|
|
53
54
|
tableDesignerSqlPreviewVisible: 'sqlite_hub_table_designer_sql_preview_visible',
|
|
54
55
|
documentsEditorVisible: 'sqlite_hub_documents_editor_visible',
|
|
@@ -63,6 +64,8 @@ const TEXT_EXPORT_FORMAT_LABELS = {
|
|
|
63
64
|
csv: 'CSV',
|
|
64
65
|
tsv: 'TSV',
|
|
65
66
|
md: 'Markdown',
|
|
67
|
+
json: 'JSON',
|
|
68
|
+
parquet: 'Parquet',
|
|
66
69
|
};
|
|
67
70
|
const MISSING_DATABASE_ERROR = {
|
|
68
71
|
code: 'ACTIVE_DATABASE_REQUIRED',
|
|
@@ -253,10 +256,18 @@ const state = {
|
|
|
253
256
|
},
|
|
254
257
|
settings: {
|
|
255
258
|
data: { ...DEFAULT_SETTINGS },
|
|
259
|
+
section: 'information',
|
|
260
|
+
apiTokens: [],
|
|
261
|
+
tokenDatabase: null,
|
|
262
|
+
createdApiToken: null,
|
|
263
|
+
tokenSaving: false,
|
|
256
264
|
loading: false,
|
|
257
265
|
error: null,
|
|
258
266
|
appVersion: null,
|
|
259
267
|
sqliteVersion: null,
|
|
268
|
+
versionCheck: null,
|
|
269
|
+
versionCheckLoading: false,
|
|
270
|
+
versionCheckError: null,
|
|
260
271
|
},
|
|
261
272
|
overview: {
|
|
262
273
|
data: null,
|
|
@@ -326,10 +337,13 @@ const state = {
|
|
|
326
337
|
loading: false,
|
|
327
338
|
error: null,
|
|
328
339
|
historyTab: readStoredChartsHistoryTab(),
|
|
340
|
+
historySearchInput: '',
|
|
341
|
+
historySearch: '',
|
|
329
342
|
historyPanelVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsHistoryVisible, true),
|
|
343
|
+
detailPanelVisible: false,
|
|
330
344
|
selectedHistoryId: null,
|
|
331
345
|
chartHeightPreset: 'medium',
|
|
332
|
-
|
|
346
|
+
queryVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsQueryVisible, true),
|
|
333
347
|
resultsVisible: readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsResultsVisible, true),
|
|
334
348
|
detail: null,
|
|
335
349
|
detailLoading: false,
|
|
@@ -924,10 +938,13 @@ function resetChartsState() {
|
|
|
924
938
|
state.charts.loading = false;
|
|
925
939
|
state.charts.error = null;
|
|
926
940
|
state.charts.historyTab = readStoredChartsHistoryTab(state.charts.historyTab);
|
|
941
|
+
state.charts.historySearchInput = '';
|
|
942
|
+
state.charts.historySearch = '';
|
|
927
943
|
state.charts.historyPanelVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsHistoryVisible, true);
|
|
944
|
+
state.charts.detailPanelVisible = false;
|
|
928
945
|
state.charts.selectedHistoryId = null;
|
|
929
946
|
state.charts.chartHeightPreset = 'medium';
|
|
930
|
-
state.charts.
|
|
947
|
+
state.charts.queryVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsQueryVisible, true);
|
|
931
948
|
state.charts.resultsVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsResultsVisible, true);
|
|
932
949
|
state.charts.detail = null;
|
|
933
950
|
state.charts.detailLoading = false;
|
|
@@ -1014,7 +1031,7 @@ function normalizeChartsHeightPreset(value) {
|
|
|
1014
1031
|
}
|
|
1015
1032
|
|
|
1016
1033
|
function normalizeChartsHistoryTab(value) {
|
|
1017
|
-
return ['recent', 'saved'].includes(value) ? value : 'recent';
|
|
1034
|
+
return ['recent', 'saved', 'unsaved'].includes(value) ? value : 'recent';
|
|
1018
1035
|
}
|
|
1019
1036
|
|
|
1020
1037
|
function mergeQueryHistoryItemWithChartSummary(updatedItem, fallbackItem = null) {
|
|
@@ -1310,6 +1327,9 @@ async function refreshSettingsState() {
|
|
|
1310
1327
|
};
|
|
1311
1328
|
state.settings.appVersion = response.metadata?.appVersion ?? null;
|
|
1312
1329
|
state.settings.sqliteVersion = response.metadata?.sqliteVersion ?? null;
|
|
1330
|
+
state.settings.apiTokens = response.metadata?.apiTokens ?? [];
|
|
1331
|
+
state.settings.tokenDatabase = response.metadata?.activeDatabase ?? null;
|
|
1332
|
+
state.settings.createdApiToken = null;
|
|
1313
1333
|
} catch (error) {
|
|
1314
1334
|
state.settings.error = normalizeError(error);
|
|
1315
1335
|
} finally {
|
|
@@ -1318,6 +1338,130 @@ async function refreshSettingsState() {
|
|
|
1318
1338
|
}
|
|
1319
1339
|
}
|
|
1320
1340
|
|
|
1341
|
+
export async function createSettingsApiToken(name) {
|
|
1342
|
+
state.settings.tokenSaving = true;
|
|
1343
|
+
state.settings.error = null;
|
|
1344
|
+
emitChange();
|
|
1345
|
+
|
|
1346
|
+
try {
|
|
1347
|
+
const response = await api.createApiToken(name);
|
|
1348
|
+
state.settings.apiTokens = response.metadata?.apiTokens ?? state.settings.apiTokens;
|
|
1349
|
+
state.settings.tokenDatabase = response.metadata?.activeDatabase ?? state.settings.tokenDatabase;
|
|
1350
|
+
state.settings.createdApiToken = response.data ?? null;
|
|
1351
|
+
pushToast('API token created. Store it now; it will not be shown again.', 'success');
|
|
1352
|
+
return response.data ?? null;
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
state.settings.error = normalizeError(error);
|
|
1355
|
+
return null;
|
|
1356
|
+
} finally {
|
|
1357
|
+
state.settings.tokenSaving = false;
|
|
1358
|
+
emitChange();
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
export function setSettingsSection(section) {
|
|
1363
|
+
const normalizedSection = section === 'api-tokens' ? 'api-tokens' : 'information';
|
|
1364
|
+
|
|
1365
|
+
if (state.settings.section === normalizedSection) {
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
state.settings.section = normalizedSection;
|
|
1370
|
+
emitChange();
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
export async function checkSettingsAppVersion() {
|
|
1374
|
+
state.settings.versionCheckLoading = true;
|
|
1375
|
+
state.settings.versionCheckError = null;
|
|
1376
|
+
emitChange();
|
|
1377
|
+
|
|
1378
|
+
try {
|
|
1379
|
+
const response = await api.checkAppVersion();
|
|
1380
|
+
const result = response.data ?? null;
|
|
1381
|
+
|
|
1382
|
+
state.settings.versionCheck = result;
|
|
1383
|
+
pushToast(
|
|
1384
|
+
result?.updateAvailable
|
|
1385
|
+
? `SQLite Hub v${result.latestVersion} is available.`
|
|
1386
|
+
: 'SQLite Hub is up to date.',
|
|
1387
|
+
'success',
|
|
1388
|
+
);
|
|
1389
|
+
return result;
|
|
1390
|
+
} catch (error) {
|
|
1391
|
+
state.settings.versionCheckError = normalizeError(error);
|
|
1392
|
+
pushToast('Version check failed.', 'alert');
|
|
1393
|
+
return null;
|
|
1394
|
+
} finally {
|
|
1395
|
+
state.settings.versionCheckLoading = false;
|
|
1396
|
+
emitChange();
|
|
1397
|
+
}
|
|
1398
|
+
}
|
|
1399
|
+
|
|
1400
|
+
export async function deleteSettingsApiToken(tokenId) {
|
|
1401
|
+
state.settings.tokenSaving = true;
|
|
1402
|
+
state.settings.error = null;
|
|
1403
|
+
emitChange();
|
|
1404
|
+
|
|
1405
|
+
try {
|
|
1406
|
+
const response = await api.deleteApiToken(tokenId);
|
|
1407
|
+
state.settings.apiTokens = response.metadata?.apiTokens ?? [];
|
|
1408
|
+
state.settings.tokenDatabase = response.metadata?.activeDatabase ?? state.settings.tokenDatabase;
|
|
1409
|
+
|
|
1410
|
+
if (state.settings.createdApiToken?.id === tokenId) {
|
|
1411
|
+
state.settings.createdApiToken = null;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
pushToast('API token deleted.', 'success');
|
|
1415
|
+
return true;
|
|
1416
|
+
} catch (error) {
|
|
1417
|
+
if (state.modal?.kind === 'delete-api-token') {
|
|
1418
|
+
withModalError(error);
|
|
1419
|
+
} else {
|
|
1420
|
+
state.settings.error = normalizeError(error);
|
|
1421
|
+
}
|
|
1422
|
+
return false;
|
|
1423
|
+
} finally {
|
|
1424
|
+
state.settings.tokenSaving = false;
|
|
1425
|
+
emitChange();
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
export function openDeleteSettingsApiTokenModal(tokenId) {
|
|
1430
|
+
const token = state.settings.apiTokens.find(candidate => candidate.id === tokenId);
|
|
1431
|
+
|
|
1432
|
+
if (!token) {
|
|
1433
|
+
pushToast('The selected API token could not be loaded.', 'alert');
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
state.modal = {
|
|
1438
|
+
kind: 'delete-api-token',
|
|
1439
|
+
tokenId: token.id,
|
|
1440
|
+
tokenName: token.name,
|
|
1441
|
+
tokenPrefix: token.tokenPrefix,
|
|
1442
|
+
databaseLabel: state.settings.tokenDatabase?.label ?? 'active database',
|
|
1443
|
+
error: null,
|
|
1444
|
+
submitting: false,
|
|
1445
|
+
};
|
|
1446
|
+
emitChange();
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
export async function submitDeleteSettingsApiTokenConfirmation() {
|
|
1450
|
+
if (state.modal?.kind !== 'delete-api-token') {
|
|
1451
|
+
return false;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
const tokenId = state.modal.tokenId;
|
|
1455
|
+
startModalSubmission();
|
|
1456
|
+
const deleted = await deleteSettingsApiToken(tokenId);
|
|
1457
|
+
|
|
1458
|
+
if (deleted) {
|
|
1459
|
+
closeModalInternal();
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
return deleted;
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1321
1465
|
async function loadQueryHistoryDetail(historyId, options = {}) {
|
|
1322
1466
|
const normalizedId = String(historyId ?? '').trim();
|
|
1323
1467
|
const numericId = Number(normalizedId);
|
|
@@ -1452,7 +1596,8 @@ async function loadChartsDetail(historyId) {
|
|
|
1452
1596
|
|
|
1453
1597
|
if (!Number.isInteger(numericId) || numericId < 1) {
|
|
1454
1598
|
state.charts.selectedHistoryId = null;
|
|
1455
|
-
state.charts.
|
|
1599
|
+
state.charts.detailPanelVisible = false;
|
|
1600
|
+
state.charts.queryVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsQueryVisible, true);
|
|
1456
1601
|
state.charts.resultsVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsResultsVisible, true);
|
|
1457
1602
|
state.charts.detail = null;
|
|
1458
1603
|
state.charts.detailLoading = false;
|
|
@@ -1465,7 +1610,7 @@ async function loadChartsDetail(historyId) {
|
|
|
1465
1610
|
}
|
|
1466
1611
|
|
|
1467
1612
|
state.charts.selectedHistoryId = numericId;
|
|
1468
|
-
state.charts.
|
|
1613
|
+
state.charts.queryVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsQueryVisible, true);
|
|
1469
1614
|
state.charts.resultsVisible = readStoredBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsResultsVisible, true);
|
|
1470
1615
|
state.charts.detail = null;
|
|
1471
1616
|
state.charts.detailLoading = true;
|
|
@@ -3343,7 +3488,8 @@ export async function openEditorRowUpdatePreview(rowIndex, values) {
|
|
|
3343
3488
|
}
|
|
3344
3489
|
|
|
3345
3490
|
export function toggleChartsSqlPanel() {
|
|
3346
|
-
state.charts.
|
|
3491
|
+
state.charts.queryVisible = !state.charts.queryVisible;
|
|
3492
|
+
storeBoolean(UI_PREFERENCE_STORAGE_KEYS.chartsQueryVisible, state.charts.queryVisible);
|
|
3347
3493
|
emitChange();
|
|
3348
3494
|
}
|
|
3349
3495
|
|
|
@@ -3365,6 +3511,17 @@ export function setChartsHistoryPanelVisibility(visible) {
|
|
|
3365
3511
|
emitChange();
|
|
3366
3512
|
}
|
|
3367
3513
|
|
|
3514
|
+
export function setChartsDetailPanelVisibility(visible) {
|
|
3515
|
+
const nextValue = typeof visible === 'boolean' ? visible : !Boolean(state.charts.detailPanelVisible);
|
|
3516
|
+
|
|
3517
|
+
if (state.charts.detailPanelVisible === nextValue) {
|
|
3518
|
+
return;
|
|
3519
|
+
}
|
|
3520
|
+
|
|
3521
|
+
state.charts.detailPanelVisible = nextValue;
|
|
3522
|
+
emitChange();
|
|
3523
|
+
}
|
|
3524
|
+
|
|
3368
3525
|
export function setChartsHeightPreset(preset) {
|
|
3369
3526
|
const nextPreset = normalizeChartsHeightPreset(preset);
|
|
3370
3527
|
|
|
@@ -3392,6 +3549,18 @@ export function setChartsHistoryTab(tab) {
|
|
|
3392
3549
|
emitChange();
|
|
3393
3550
|
}
|
|
3394
3551
|
|
|
3552
|
+
export function setChartsHistorySearchInput(query) {
|
|
3553
|
+
const nextQuery = String(query ?? '');
|
|
3554
|
+
|
|
3555
|
+
if (state.charts.historySearchInput === nextQuery && state.charts.historySearch === nextQuery.trim()) {
|
|
3556
|
+
return;
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
state.charts.historySearchInput = nextQuery;
|
|
3560
|
+
state.charts.historySearch = nextQuery.trim();
|
|
3561
|
+
emitChange();
|
|
3562
|
+
}
|
|
3563
|
+
|
|
3395
3564
|
export function updateCurrentQueryChartDraftField(field, value) {
|
|
3396
3565
|
if (state.modal?.kind !== 'chart-editor' || !state.modal.draft) {
|
|
3397
3566
|
return;
|
|
@@ -3490,6 +3659,16 @@ export async function submitCreateConnection(payload) {
|
|
|
3490
3659
|
}
|
|
3491
3660
|
}
|
|
3492
3661
|
|
|
3662
|
+
export async function chooseOpenDatabasePath() {
|
|
3663
|
+
try {
|
|
3664
|
+
const response = await api.chooseOpenDatabasePath();
|
|
3665
|
+
return response.data?.path ?? null;
|
|
3666
|
+
} catch (error) {
|
|
3667
|
+
pushToast(normalizeError(error).message || 'Native file dialog could not be opened.', 'alert');
|
|
3668
|
+
return null;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
|
|
3493
3672
|
export async function chooseCreateDatabasePath() {
|
|
3494
3673
|
try {
|
|
3495
3674
|
const response = await api.chooseCreateDatabasePath();
|
|
@@ -3820,7 +3999,9 @@ export function openQueryHistoryInEditor(historyId, options = {}) {
|
|
|
3820
3999
|
clearQueryHistoryDetailState();
|
|
3821
4000
|
state.editor.sqlText = options.append ? [state.editor.sqlText.trim(), rawSql].filter(Boolean).join('\n\n') : rawSql;
|
|
3822
4001
|
storeString(UI_PREFERENCE_STORAGE_KEYS.sqlEditorQueryDraft, state.editor.sqlText);
|
|
3823
|
-
|
|
4002
|
+
if (options.notify !== false) {
|
|
4003
|
+
emitChange();
|
|
4004
|
+
}
|
|
3824
4005
|
return true;
|
|
3825
4006
|
}
|
|
3826
4007
|
|
|
@@ -2,6 +2,8 @@ export const TEXT_EXPORT_EXTENSIONS = {
|
|
|
2
2
|
csv: "csv",
|
|
3
3
|
tsv: "tsv",
|
|
4
4
|
md: "md",
|
|
5
|
+
json: "json",
|
|
6
|
+
parquet: "parquet",
|
|
5
7
|
};
|
|
6
8
|
|
|
7
9
|
export function normalizeTextExportFormat(format = "csv") {
|
|
@@ -10,7 +12,7 @@ export function normalizeTextExportFormat(format = "csv") {
|
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
function stripKnownTextExportExtension(value = "") {
|
|
13
|
-
return String(value).replace(/\.(csv|tsv|md)$/i, "");
|
|
15
|
+
return String(value).replace(/\.(csv|tsv|md|json|parquet)$/i, "");
|
|
14
16
|
}
|
|
15
17
|
|
|
16
18
|
export function sanitizeExportFilenameBase(value, fallback = "export") {
|