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
|
@@ -1,133 +1,130 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
formatNumber,
|
|
4
|
-
} from "../utils/format.js";
|
|
5
|
-
import { renderStatusBadge } from "./badges.js";
|
|
1
|
+
import { escapeHtml, formatNumber } from '../utils/format.js';
|
|
2
|
+
import { renderStatusBadge } from './badges.js';
|
|
6
3
|
|
|
7
4
|
function getQueryTypeTone(queryType) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
if (queryType === 'select' || queryType === 'update') {
|
|
6
|
+
return 'success';
|
|
7
|
+
}
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
if (queryType === 'pragma') {
|
|
10
|
+
return 'primary';
|
|
11
|
+
}
|
|
15
12
|
|
|
16
|
-
|
|
13
|
+
return 'muted';
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
export function renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
17
|
+
const isActive = Number(activeHistoryId) === Number(item.id);
|
|
18
|
+
const isSelected = Number(selectedHistoryId) === Number(item.id);
|
|
19
|
+
const visibleTables = (item.tablesDetected ?? []).slice(0, 3);
|
|
20
|
+
const itemClasses = [
|
|
21
|
+
'query-history-item',
|
|
22
|
+
isActive ? 'is-active' : '',
|
|
23
|
+
item.lastRun?.status === 'error' ? 'is-error' : '',
|
|
24
|
+
]
|
|
25
|
+
.filter(Boolean)
|
|
26
|
+
.join(' ');
|
|
27
|
+
const tableMarkup = visibleTables
|
|
28
|
+
.map(tableName =>
|
|
29
|
+
[
|
|
30
|
+
'<span class="border border-outline-variant/20 bg-surface-highest px-2 py-1">',
|
|
31
|
+
escapeHtml(tableName),
|
|
32
|
+
'</span>',
|
|
33
|
+
].join(''),
|
|
34
|
+
)
|
|
35
|
+
.join('');
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
37
|
+
return [
|
|
38
|
+
'<article class="',
|
|
39
|
+
itemClasses,
|
|
40
|
+
'"><button class="query-history-item-hit ',
|
|
41
|
+
isActive ? 'is-active' : '',
|
|
42
|
+
'" data-action="select-query-history-item" data-history-id="',
|
|
43
|
+
escapeHtml(item.id),
|
|
44
|
+
'" type="button">',
|
|
45
|
+
'<div class="flex flex-wrap items-center gap-2"><span class="truncate font-headline text-sm font-bold uppercase tracking-tight text-on-surface w-full">',
|
|
46
|
+
escapeHtml(item.displayTitle),
|
|
47
|
+
'</span>',
|
|
48
|
+
renderStatusBadge(item.queryType, getQueryTypeTone(item.queryType)),
|
|
49
|
+
item.isSaved ? renderStatusBadge('saved', 'primary') : '',
|
|
50
|
+
item.isDestructive ? renderStatusBadge('destructive', 'warning') : '',
|
|
51
|
+
'</div>',
|
|
52
|
+
'<p class="query-history-sql-preview mt-2 text-left font-mono text-xs leading-5 text-on-surface-variant/75">',
|
|
53
|
+
escapeHtml(item.previewSql),
|
|
54
|
+
'</p></button>',
|
|
55
|
+
'<div class="flex items-center justify-between gap-3 border-t border-outline-variant/10 px-3 pb-3 pt-2">',
|
|
56
|
+
'<div class="min-w-0 flex flex-wrap gap-2 text-[10px] font-mono uppercase tracking-[0.14em] text-on-surface-variant/55">',
|
|
57
|
+
tableMarkup,
|
|
58
|
+
'</div><div class="flex items-center gap-1">',
|
|
59
|
+
'<button class="query-history-icon-button" data-action="open-query-history" data-history-id="',
|
|
60
|
+
escapeHtml(item.id),
|
|
61
|
+
'" title="Open in editor" type="button"><span class="material-symbols-outlined text-[18px]">edit_note</span></button>',
|
|
62
|
+
'<button class="query-history-icon-button" data-action="run-query-history" data-history-id="',
|
|
63
|
+
escapeHtml(item.id),
|
|
64
|
+
'" title="Run query" type="button"><span class="material-symbols-outlined text-[18px]">play_arrow</span></button>',
|
|
65
|
+
'<button class="query-history-icon-button ',
|
|
66
|
+
item.isSaved ? 'is-active' : '',
|
|
67
|
+
'" data-action="toggle-query-history-saved" data-history-id="',
|
|
68
|
+
escapeHtml(item.id),
|
|
69
|
+
'" data-next-value="',
|
|
70
|
+
item.isSaved ? 'false' : 'true',
|
|
71
|
+
'" title="',
|
|
72
|
+
item.isSaved ? 'Remove from saved' : 'Save query',
|
|
73
|
+
'" type="button"><span class="material-symbols-outlined text-[18px]">',
|
|
74
|
+
item.isSaved ? 'bookmark' : 'bookmark_add',
|
|
75
|
+
'</span></button>',
|
|
76
|
+
'<button class="query-history-icon-button ',
|
|
77
|
+
isSelected ? 'is-active' : '',
|
|
78
|
+
'" data-action="select-query-history-item" data-history-id="',
|
|
79
|
+
escapeHtml(item.id),
|
|
80
|
+
'" title="Open query detail" type="button"><span class="material-symbols-outlined text-[18px]">info</span></button>',
|
|
81
|
+
'</div></div></article>',
|
|
82
|
+
].join('');
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
function renderQueryHistoryTabs(activeTab, historyTotal) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
86
|
+
const tabs = [
|
|
87
|
+
{ id: 'recent', label: 'Recent' },
|
|
88
|
+
{ id: 'saved', label: 'Saved' },
|
|
89
|
+
{ id: 'unsaved', label: 'Unsaved' },
|
|
90
|
+
{ id: 'failed', label: 'Failed' },
|
|
91
|
+
];
|
|
95
92
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
93
|
+
return [
|
|
94
|
+
'<div class="flex items-center gap-2">',
|
|
95
|
+
tabs
|
|
96
|
+
.map(tab =>
|
|
97
|
+
[
|
|
98
|
+
'<button class="query-history-tab ',
|
|
99
|
+
activeTab === tab.id ? 'is-active' : '',
|
|
100
|
+
'" data-action="set-query-history-tab" data-tab="',
|
|
101
|
+
tab.id,
|
|
102
|
+
'" type="button">',
|
|
103
|
+
escapeHtml(tab.label),
|
|
104
|
+
'</button>',
|
|
105
|
+
].join(''),
|
|
106
|
+
)
|
|
107
|
+
.join(''),
|
|
108
|
+
'<span class="ml-auto text-[10px] font-mono uppercase tracking-[0.16em] text-on-surface-variant/50">',
|
|
109
|
+
escapeHtml(formatNumber(historyTotal)),
|
|
110
|
+
'</span></div>',
|
|
111
|
+
].join('');
|
|
115
112
|
}
|
|
116
113
|
|
|
117
114
|
export function renderQueryHistoryPanel({
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
115
|
+
items = [],
|
|
116
|
+
loading = false,
|
|
117
|
+
loadingMore = false,
|
|
118
|
+
error = null,
|
|
119
|
+
activeTab = 'recent',
|
|
120
|
+
search = '',
|
|
121
|
+
committedSearch = '',
|
|
122
|
+
total = 0,
|
|
123
|
+
hasMore = false,
|
|
124
|
+
activeHistoryId = null,
|
|
125
|
+
selectedHistoryId = null,
|
|
129
126
|
}) {
|
|
130
|
-
|
|
127
|
+
return `
|
|
131
128
|
<aside class="query-history-panel border-l border-outline-variant/10 bg-surface-container-lowest">
|
|
132
129
|
<div class="border-b border-outline-variant/10 px-4 py-4">
|
|
133
130
|
<div class="flex items-center justify-between gap-3">
|
|
@@ -162,23 +159,23 @@ export function renderQueryHistoryPanel({
|
|
|
162
159
|
<div
|
|
163
160
|
class="custom-scrollbar min-h-0 flex-1 overflow-auto px-3 py-3"
|
|
164
161
|
data-query-history-committed-search="${escapeHtml(committedSearch)}"
|
|
165
|
-
data-query-history-loading-more="${loadingMore ?
|
|
162
|
+
data-query-history-loading-more="${loadingMore ? 'true' : 'false'}"
|
|
166
163
|
data-query-history-search="${escapeHtml(search)}"
|
|
167
164
|
data-query-history-scroll
|
|
168
165
|
data-query-history-tab="${escapeHtml(activeTab)}"
|
|
169
166
|
>
|
|
170
167
|
${
|
|
171
|
-
|
|
172
|
-
|
|
168
|
+
error
|
|
169
|
+
? `
|
|
173
170
|
<div class="border border-error/30 bg-error-container/20 px-4 py-3 text-sm text-error">
|
|
174
171
|
${escapeHtml(error.message)}
|
|
175
172
|
</div>
|
|
176
173
|
`
|
|
177
|
-
|
|
174
|
+
: ''
|
|
178
175
|
}
|
|
179
176
|
${
|
|
180
|
-
|
|
181
|
-
|
|
177
|
+
!loading && !items.length
|
|
178
|
+
? `
|
|
182
179
|
<div class="flex h-full min-h-[240px] flex-col items-center justify-center px-6 text-center">
|
|
183
180
|
<span class="material-symbols-outlined text-4xl text-on-surface-variant/25">manage_search</span>
|
|
184
181
|
<p class="mt-4 font-headline text-lg font-bold uppercase tracking-tight text-on-surface">
|
|
@@ -189,36 +186,34 @@ export function renderQueryHistoryPanel({
|
|
|
189
186
|
</p>
|
|
190
187
|
</div>
|
|
191
188
|
`
|
|
192
|
-
|
|
189
|
+
: ''
|
|
193
190
|
}
|
|
194
191
|
<div class="space-y-3">
|
|
195
|
-
${items
|
|
196
|
-
.map((item) => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId))
|
|
197
|
-
.join("")}
|
|
192
|
+
${items.map(item => renderQueryHistoryListItem(item, activeHistoryId, selectedHistoryId)).join('')}
|
|
198
193
|
</div>
|
|
199
194
|
${
|
|
200
|
-
|
|
201
|
-
|
|
195
|
+
loading
|
|
196
|
+
? `
|
|
202
197
|
<div class="mt-4 text-center text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/50">
|
|
203
198
|
Loading query history...
|
|
204
199
|
</div>
|
|
205
200
|
`
|
|
206
|
-
|
|
201
|
+
: ''
|
|
207
202
|
}
|
|
208
203
|
${
|
|
209
|
-
|
|
210
|
-
|
|
204
|
+
hasMore
|
|
205
|
+
? `
|
|
211
206
|
<div class="mt-4 flex justify-center">
|
|
212
207
|
<button
|
|
213
208
|
class="standard-button"
|
|
214
209
|
data-action="load-more-query-history"
|
|
215
210
|
type="button"
|
|
216
211
|
>
|
|
217
|
-
${loadingMore ?
|
|
212
|
+
${loadingMore ? 'Loading...' : 'Load More'}
|
|
218
213
|
</button>
|
|
219
214
|
</div>
|
|
220
215
|
`
|
|
221
|
-
|
|
216
|
+
: ''
|
|
222
217
|
}
|
|
223
218
|
</div>
|
|
224
219
|
</aside>
|
|
@@ -8,6 +8,7 @@ const sidebarItems = [
|
|
|
8
8
|
{ label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
|
|
9
9
|
{ label: 'SQL_Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
|
|
10
10
|
{ label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
|
|
11
|
+
{ label: 'Documents', href: '#/documents', key: 'documents', icon: 'description' },
|
|
11
12
|
{ label: 'Table_Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
|
|
12
13
|
{
|
|
13
14
|
label: 'MEDIA_TAGGING',
|
package/frontend/js/router.js
CHANGED
|
@@ -21,6 +21,14 @@ export function parseHash(hash = window.location.hash) {
|
|
|
21
21
|
historyId: segments[1] ? decodeURIComponent(segments[1]) : null,
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
|
+
case 'documents':
|
|
25
|
+
return {
|
|
26
|
+
name: 'documents',
|
|
27
|
+
path: cleanPath,
|
|
28
|
+
params: {
|
|
29
|
+
documentId: segments[1] ? decodeURIComponent(segments[1]) : null,
|
|
30
|
+
},
|
|
31
|
+
};
|
|
24
32
|
case 'editor':
|
|
25
33
|
if (segments[1] === 'results') {
|
|
26
34
|
return { name: 'editorResults', path: '/editor/results', params: {} };
|