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
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { escapeHtml } from './format.js';
|
|
2
|
+
|
|
3
|
+
const FENCED_CODE_PATTERN = /^\s*(```|~~~)/;
|
|
4
|
+
const TASK_LINE_PATTERN = /^(\s*)([-*+])\s+\[([ xX])\]\s+(.*?)(\r?)$/;
|
|
5
|
+
|
|
6
|
+
function decodeBasicHtmlEntities(value = '') {
|
|
7
|
+
return String(value)
|
|
8
|
+
.replace(/:/gi, ':')
|
|
9
|
+
.replace(/:/g, ':')
|
|
10
|
+
.replace(/:/gi, ':')
|
|
11
|
+
.replace(/&/gi, '&')
|
|
12
|
+
.replace(/"/gi, '"')
|
|
13
|
+
.replace(/'/gi, "'");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function sanitizeRenderedMarkdown(html = '') {
|
|
17
|
+
return String(html).replace(/\s(href|src)=("([^"]*)"|'([^']*)'|([^\s>]+))/gi, (match, attribute, raw, doubleQuoted, singleQuoted, bare) => {
|
|
18
|
+
const value = doubleQuoted ?? singleQuoted ?? bare ?? '';
|
|
19
|
+
const decodedValue = decodeBasicHtmlEntities(value).trim();
|
|
20
|
+
|
|
21
|
+
if (/^(javascript|data|vbscript):/i.test(decodedValue)) {
|
|
22
|
+
return ` ${attribute}="#"`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return match;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function renderFallbackMarkdown(markdown = '') {
|
|
30
|
+
const lines = String(markdown).split(/\r?\n/);
|
|
31
|
+
const html = [];
|
|
32
|
+
let listItems = [];
|
|
33
|
+
let listTag = 'ul';
|
|
34
|
+
|
|
35
|
+
const flushList = () => {
|
|
36
|
+
if (!listItems.length) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
html.push(`<${listTag}>${listItems.map(item => `<li>${item}</li>`).join('')}</${listTag}>`);
|
|
41
|
+
listItems = [];
|
|
42
|
+
listTag = 'ul';
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
|
|
47
|
+
const unorderedMatch = line.match(/^\s*[-*+]\s+(.+)$/);
|
|
48
|
+
const orderedMatch = line.match(/^\s*\d+[.)]\s+(.+)$/);
|
|
49
|
+
|
|
50
|
+
if (headingMatch) {
|
|
51
|
+
flushList();
|
|
52
|
+
const level = Math.min(6, headingMatch[1].length);
|
|
53
|
+
html.push(`<h${level}>${escapeHtml(headingMatch[2])}</h${level}>`);
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (unorderedMatch) {
|
|
58
|
+
if (listItems.length && listTag !== 'ul') {
|
|
59
|
+
flushList();
|
|
60
|
+
}
|
|
61
|
+
listTag = 'ul';
|
|
62
|
+
listItems.push(escapeHtml(unorderedMatch[1]));
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (orderedMatch) {
|
|
67
|
+
if (listItems.length && listTag !== 'ol') {
|
|
68
|
+
flushList();
|
|
69
|
+
}
|
|
70
|
+
listTag = 'ol';
|
|
71
|
+
listItems.push(escapeHtml(orderedMatch[1]));
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
flushList();
|
|
76
|
+
|
|
77
|
+
if (line.trim()) {
|
|
78
|
+
html.push(`<p>${escapeHtml(line)}</p>`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
flushList();
|
|
83
|
+
return html.join('\n');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function renderMarkdownBlock(markdown = '') {
|
|
87
|
+
const source = escapeHtml(markdown);
|
|
88
|
+
const markedRuntime = globalThis.marked;
|
|
89
|
+
const parser = typeof markedRuntime?.parse === 'function' ? markedRuntime.parse.bind(markedRuntime) : null;
|
|
90
|
+
|
|
91
|
+
if (!parser) {
|
|
92
|
+
return renderFallbackMarkdown(markdown);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return sanitizeRenderedMarkdown(
|
|
96
|
+
parser(source, {
|
|
97
|
+
async: false,
|
|
98
|
+
breaks: false,
|
|
99
|
+
gfm: true,
|
|
100
|
+
}),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function renderMarkdownInline(markdown = '') {
|
|
105
|
+
const source = escapeHtml(markdown);
|
|
106
|
+
const markedRuntime = globalThis.marked;
|
|
107
|
+
const parser =
|
|
108
|
+
typeof markedRuntime?.parseInline === 'function' ? markedRuntime.parseInline.bind(markedRuntime) : null;
|
|
109
|
+
|
|
110
|
+
if (!parser) {
|
|
111
|
+
return source;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return sanitizeRenderedMarkdown(
|
|
115
|
+
parser(source, {
|
|
116
|
+
async: false,
|
|
117
|
+
breaks: false,
|
|
118
|
+
gfm: true,
|
|
119
|
+
}),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderTaskList(items = []) {
|
|
124
|
+
if (!items.length) {
|
|
125
|
+
return '';
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return `
|
|
129
|
+
<ul class="document-markdown-task-list">
|
|
130
|
+
${items
|
|
131
|
+
.map(item => {
|
|
132
|
+
const checked = item.checked;
|
|
133
|
+
|
|
134
|
+
return `
|
|
135
|
+
<li class="document-markdown-task-item ${checked ? 'is-checked' : ''}">
|
|
136
|
+
<label class="document-markdown-task-label">
|
|
137
|
+
<input
|
|
138
|
+
${checked ? 'checked' : ''}
|
|
139
|
+
class="document-markdown-task-checkbox"
|
|
140
|
+
data-action="toggle-document-todo"
|
|
141
|
+
data-line-index="${escapeHtml(String(item.lineIndex))}"
|
|
142
|
+
type="checkbox"
|
|
143
|
+
/>
|
|
144
|
+
<span class="document-markdown-task-text">${renderMarkdownInline(item.text)}</span>
|
|
145
|
+
</label>
|
|
146
|
+
</li>
|
|
147
|
+
`;
|
|
148
|
+
})
|
|
149
|
+
.join('')}
|
|
150
|
+
</ul>
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function renderMarkdownPreview(markdown = '') {
|
|
155
|
+
const lines = String(markdown ?? '').split('\n');
|
|
156
|
+
const blocks = [];
|
|
157
|
+
let markdownLines = [];
|
|
158
|
+
let taskItems = [];
|
|
159
|
+
let inFence = false;
|
|
160
|
+
|
|
161
|
+
const flushMarkdown = () => {
|
|
162
|
+
if (!markdownLines.length) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
blocks.push(renderMarkdownBlock(markdownLines.join('\n')));
|
|
167
|
+
markdownLines = [];
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const flushTasks = () => {
|
|
171
|
+
if (!taskItems.length) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
blocks.push(renderTaskList(taskItems));
|
|
176
|
+
taskItems = [];
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
lines.forEach((line, lineIndex) => {
|
|
180
|
+
const fenceLine = FENCED_CODE_PATTERN.test(line);
|
|
181
|
+
|
|
182
|
+
if (fenceLine) {
|
|
183
|
+
flushTasks();
|
|
184
|
+
markdownLines.push(line);
|
|
185
|
+
inFence = !inFence;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (!inFence) {
|
|
190
|
+
const taskMatch = line.match(TASK_LINE_PATTERN);
|
|
191
|
+
|
|
192
|
+
if (taskMatch) {
|
|
193
|
+
flushMarkdown();
|
|
194
|
+
taskItems.push({
|
|
195
|
+
checked: taskMatch[3].toLowerCase() === 'x',
|
|
196
|
+
lineIndex,
|
|
197
|
+
text: taskMatch[4],
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
flushTasks();
|
|
204
|
+
markdownLines.push(line);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
flushTasks();
|
|
208
|
+
flushMarkdown();
|
|
209
|
+
|
|
210
|
+
return blocks.join('\n').trim() || '<p class="document-markdown-empty">Empty document</p>';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function isLineInsideFencedCode(lines, lineIndex) {
|
|
214
|
+
let inFence = false;
|
|
215
|
+
|
|
216
|
+
for (let index = 0; index < lineIndex; index += 1) {
|
|
217
|
+
if (FENCED_CODE_PATTERN.test(lines[index] ?? '')) {
|
|
218
|
+
inFence = !inFence;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return inFence;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function toggleMarkdownTodoLine(markdown = '', lineIndex) {
|
|
226
|
+
const lines = String(markdown ?? '').split('\n');
|
|
227
|
+
const numericLineIndex = Number(lineIndex);
|
|
228
|
+
|
|
229
|
+
if (!Number.isInteger(numericLineIndex) || numericLineIndex < 0 || numericLineIndex >= lines.length) {
|
|
230
|
+
return String(markdown ?? '');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (isLineInsideFencedCode(lines, numericLineIndex)) {
|
|
234
|
+
return String(markdown ?? '');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const line = lines[numericLineIndex];
|
|
238
|
+
const match = line.match(TASK_LINE_PATTERN);
|
|
239
|
+
|
|
240
|
+
if (!match) {
|
|
241
|
+
return String(markdown ?? '');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const nextMarker = match[3].toLowerCase() === 'x' ? ' ' : 'x';
|
|
245
|
+
lines[numericLineIndex] = `${match[1]}${match[2]} [${nextMarker}] ${match[4]}${match[5]}`;
|
|
246
|
+
|
|
247
|
+
return lines.join('\n');
|
|
248
|
+
}
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import { escapeHtml, formatCompactDateTime, formatNumber } from '../utils/format.js';
|
|
2
|
+
import { renderMarkdownPreview } from '../utils/markdownDocuments.js';
|
|
3
|
+
|
|
4
|
+
function renderMissingDatabase() {
|
|
5
|
+
return `
|
|
6
|
+
<section class="view-surface">
|
|
7
|
+
<div class="view-frame flex min-h-full items-center justify-center">
|
|
8
|
+
<div class="text-center">
|
|
9
|
+
<span class="material-symbols-outlined mb-3 text-5xl text-on-surface-variant/25">database_off</span>
|
|
10
|
+
<p class="font-headline text-xl font-black uppercase tracking-tight text-primary-container">
|
|
11
|
+
No Active SQLite Database
|
|
12
|
+
</p>
|
|
13
|
+
<p class="mt-3 max-w-xl text-sm leading-7 text-on-surface-variant/65">
|
|
14
|
+
Select a database before opening its document folder.
|
|
15
|
+
</p>
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</section>
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderDocumentListItem(item, selectedId) {
|
|
23
|
+
const isSelected = String(item.id) === String(selectedId);
|
|
24
|
+
|
|
25
|
+
return `
|
|
26
|
+
<button
|
|
27
|
+
class="documents-list-item ${isSelected ? 'is-selected' : ''}"
|
|
28
|
+
data-action="select-document"
|
|
29
|
+
data-document-id="${escapeHtml(item.id)}"
|
|
30
|
+
type="button"
|
|
31
|
+
>
|
|
32
|
+
<span class="material-symbols-outlined documents-list-item__icon">description</span>
|
|
33
|
+
<span class="documents-list-item__body">
|
|
34
|
+
<span class="documents-list-item__title" title="${escapeHtml(item.filename)}">
|
|
35
|
+
${escapeHtml(item.filename)}
|
|
36
|
+
</span>
|
|
37
|
+
<span class="documents-list-item__meta">
|
|
38
|
+
${escapeHtml(formatCompactDateTime(item.updatedAt))} // ${formatNumber(item.contentLength ?? 0)} chars
|
|
39
|
+
</span>
|
|
40
|
+
</span>
|
|
41
|
+
</button>
|
|
42
|
+
`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function renderDocumentsSidebar(documents) {
|
|
46
|
+
const items = documents.items ?? [];
|
|
47
|
+
|
|
48
|
+
return `
|
|
49
|
+
<aside class="documents-view__sidebar">
|
|
50
|
+
<div class="documents-view__sidebar-header">
|
|
51
|
+
<div>
|
|
52
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.22em] text-on-surface-variant/50">
|
|
53
|
+
Documents
|
|
54
|
+
</div>
|
|
55
|
+
<div class="mt-1 font-mono text-xs text-primary-container">${formatNumber(items.length)} files</div>
|
|
56
|
+
</div>
|
|
57
|
+
<button
|
|
58
|
+
class="icon-button"
|
|
59
|
+
data-action="create-document"
|
|
60
|
+
title="New document"
|
|
61
|
+
type="button"
|
|
62
|
+
${documents.saving ? 'disabled aria-disabled="true"' : ''}
|
|
63
|
+
>
|
|
64
|
+
<span class="material-symbols-outlined">add</span>
|
|
65
|
+
</button>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="documents-view__sidebar-body custom-scrollbar">
|
|
68
|
+
${
|
|
69
|
+
items.length
|
|
70
|
+
? items.map(item => renderDocumentListItem(item, documents.selectedId)).join('')
|
|
71
|
+
: `<div class="documents-list-empty">No documents yet</div>`
|
|
72
|
+
}
|
|
73
|
+
</div>
|
|
74
|
+
</aside>
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function renderDocumentToolbar(documents) {
|
|
79
|
+
const disabled = documents.saving || documents.detailLoading || !documents.selectedId;
|
|
80
|
+
|
|
81
|
+
return ` <label class="documents-filename-field">
|
|
82
|
+
<input
|
|
83
|
+
class="control-input"
|
|
84
|
+
data-bind="document-field"
|
|
85
|
+
data-field="filename"
|
|
86
|
+
name="filename"
|
|
87
|
+
spellcheck="false"
|
|
88
|
+
type="text"
|
|
89
|
+
value="${escapeHtml(documents.draftFilename)}"
|
|
90
|
+
${!documents.selectedId ? 'disabled' : ''}
|
|
91
|
+
/>
|
|
92
|
+
</label>
|
|
93
|
+
<div class="documents-toolbar">
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
<div class="documents-toolbar__actions">
|
|
98
|
+
<button
|
|
99
|
+
class="standard-button"
|
|
100
|
+
data-action="toggle-document-pane"
|
|
101
|
+
data-pane="editor"
|
|
102
|
+
type="button"
|
|
103
|
+
>
|
|
104
|
+
<span class="material-symbols-outlined">${documents.editorVisible ? 'visibility_off' : 'edit_note'}</span>
|
|
105
|
+
${documents.editorVisible ? 'Hide Editor' : 'Show Editor'}
|
|
106
|
+
</button>
|
|
107
|
+
<button
|
|
108
|
+
class="standard-button"
|
|
109
|
+
data-action="toggle-document-pane"
|
|
110
|
+
data-pane="preview"
|
|
111
|
+
type="button"
|
|
112
|
+
>
|
|
113
|
+
<span class="material-symbols-outlined">${documents.previewVisible ? 'visibility_off' : 'visibility'}</span>
|
|
114
|
+
${documents.previewVisible ? 'Hide Preview' : 'Show Preview'}
|
|
115
|
+
</button>
|
|
116
|
+
<input
|
|
117
|
+
accept=".md,.markdown,text/markdown,text/plain"
|
|
118
|
+
data-bind="document-import-file"
|
|
119
|
+
hidden
|
|
120
|
+
type="file"
|
|
121
|
+
/>
|
|
122
|
+
</div>
|
|
123
|
+
<div class="documents-toolbar__actions">
|
|
124
|
+
<button
|
|
125
|
+
class="standard-button"
|
|
126
|
+
data-action="open-document-insert-table-modal"
|
|
127
|
+
type="button"
|
|
128
|
+
${disabled ? 'disabled aria-disabled="true"' : ''}
|
|
129
|
+
>
|
|
130
|
+
<span class="material-symbols-outlined">table_chart</span>
|
|
131
|
+
Insert Table
|
|
132
|
+
</button>
|
|
133
|
+
<button
|
|
134
|
+
class="standard-button"
|
|
135
|
+
data-action="open-document-insert-note-modal"
|
|
136
|
+
type="button"
|
|
137
|
+
${disabled ? 'disabled aria-disabled="true"' : ''}
|
|
138
|
+
>
|
|
139
|
+
<span class="material-symbols-outlined">note_add</span>
|
|
140
|
+
Insert Note
|
|
141
|
+
</button>
|
|
142
|
+
<button
|
|
143
|
+
class="standard-button"
|
|
144
|
+
data-action="export-document-markdown"
|
|
145
|
+
type="button"
|
|
146
|
+
${disabled ? 'disabled aria-disabled="true"' : ''}
|
|
147
|
+
>
|
|
148
|
+
<span class="material-symbols-outlined">download</span>
|
|
149
|
+
Export .md
|
|
150
|
+
</button>
|
|
151
|
+
<button
|
|
152
|
+
class="standard-button"
|
|
153
|
+
data-action="import-document-markdown"
|
|
154
|
+
type="button"
|
|
155
|
+
${documents.saving ? 'disabled aria-disabled="true"' : ''}
|
|
156
|
+
>
|
|
157
|
+
<span class="material-symbols-outlined">upload_file</span>
|
|
158
|
+
Import .md
|
|
159
|
+
</button>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="documents-toolbar__actions">
|
|
162
|
+
<button
|
|
163
|
+
class="standard-button"
|
|
164
|
+
data-action="delete-document"
|
|
165
|
+
type="button"
|
|
166
|
+
${disabled || documents.deleting ? 'disabled aria-disabled="true"' : ''}
|
|
167
|
+
>
|
|
168
|
+
<span class="material-symbols-outlined">delete</span>
|
|
169
|
+
Delete
|
|
170
|
+
</button>
|
|
171
|
+
<button
|
|
172
|
+
class="signature-button"
|
|
173
|
+
data-action="save-document"
|
|
174
|
+
type="button"
|
|
175
|
+
${disabled || !documents.dirty ? 'disabled aria-disabled="true"' : ''}
|
|
176
|
+
>
|
|
177
|
+
<span class="material-symbols-outlined">save</span>
|
|
178
|
+
${documents.saving ? 'Saving...' : 'Save'}
|
|
179
|
+
</button>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function renderDocumentEditor(documents) {
|
|
186
|
+
if (!documents.editorVisible) {
|
|
187
|
+
return '';
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return `
|
|
191
|
+
<section class="documents-pane documents-pane--editor">
|
|
192
|
+
<div class="documents-pane__header">
|
|
193
|
+
<span>Editor</span>
|
|
194
|
+
<span>${formatNumber(documents.draftContent.length)} chars</span>
|
|
195
|
+
</div>
|
|
196
|
+
<textarea
|
|
197
|
+
class="documents-editor-input custom-scrollbar"
|
|
198
|
+
data-bind="document-field"
|
|
199
|
+
data-field="content"
|
|
200
|
+
name="content"
|
|
201
|
+
spellcheck="true"
|
|
202
|
+
${!documents.selectedId ? 'disabled' : ''}
|
|
203
|
+
>${escapeHtml(documents.draftContent)}</textarea>
|
|
204
|
+
</section>
|
|
205
|
+
`;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function renderDocumentPreview(documents) {
|
|
209
|
+
if (!documents.previewVisible) {
|
|
210
|
+
return '';
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return `
|
|
214
|
+
<section class="documents-pane documents-pane--preview">
|
|
215
|
+
<div class="documents-pane__header">
|
|
216
|
+
<span>Preview</span>
|
|
217
|
+
<span>${escapeHtml(documents.dirty ? 'unsaved' : 'saved')}</span>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="document-markdown-preview custom-scrollbar" data-document-preview>
|
|
220
|
+
${renderMarkdownPreview(documents.draftContent)}
|
|
221
|
+
</div>
|
|
222
|
+
</section>
|
|
223
|
+
`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderEmptyDocumentsState(documents) {
|
|
227
|
+
return `
|
|
228
|
+
<div class="documents-empty-state">
|
|
229
|
+
<span class="material-symbols-outlined">description</span>
|
|
230
|
+
<p class="font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
|
|
231
|
+
No Documents
|
|
232
|
+
</p>
|
|
233
|
+
<button
|
|
234
|
+
class="signature-button mt-4"
|
|
235
|
+
data-action="create-document"
|
|
236
|
+
type="button"
|
|
237
|
+
${documents.saving ? 'disabled aria-disabled="true"' : ''}
|
|
238
|
+
>
|
|
239
|
+
<span class="material-symbols-outlined">add</span>
|
|
240
|
+
New Document
|
|
241
|
+
</button>
|
|
242
|
+
</div>
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function renderDocumentDetail(documents) {
|
|
247
|
+
if (documents.loading && !documents.items.length) {
|
|
248
|
+
return `
|
|
249
|
+
<main class="documents-view__detail">
|
|
250
|
+
<div class="documents-empty-state">
|
|
251
|
+
<span class="material-symbols-outlined">sync</span>
|
|
252
|
+
<p class="font-headline text-2xl font-black uppercase tracking-tight text-primary-container">
|
|
253
|
+
Loading Documents
|
|
254
|
+
</p>
|
|
255
|
+
</div>
|
|
256
|
+
</main>
|
|
257
|
+
`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (!documents.selectedId) {
|
|
261
|
+
return `
|
|
262
|
+
<main class="documents-view__detail">
|
|
263
|
+
${renderEmptyDocumentsState(documents)}
|
|
264
|
+
</main>
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const paneCount = Number(documents.editorVisible) + Number(documents.previewVisible);
|
|
269
|
+
|
|
270
|
+
return `
|
|
271
|
+
<main class="documents-view__detail">
|
|
272
|
+
${renderDocumentToolbar(documents)}
|
|
273
|
+
${documents.saveError ? `<div class="documents-error">${escapeHtml(documents.saveError.message)}</div>` : ''}
|
|
274
|
+
<div class="documents-workspace ${paneCount > 1 ? 'documents-workspace--split' : ''}">
|
|
275
|
+
${renderDocumentEditor(documents)}
|
|
276
|
+
${renderDocumentPreview(documents)}
|
|
277
|
+
</div>
|
|
278
|
+
</main>
|
|
279
|
+
`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function renderDocumentsView(state) {
|
|
283
|
+
if (!state.connections.active) {
|
|
284
|
+
return {
|
|
285
|
+
main: renderMissingDatabase(),
|
|
286
|
+
panel: '',
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const documents = state.documents;
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
main: `<div class="documents-view">
|
|
294
|
+
${renderDocumentsSidebar(documents)}
|
|
295
|
+
${renderDocumentDetail(documents)}
|
|
296
|
+
</div>
|
|
297
|
+
</div>`,
|
|
298
|
+
panel: '',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
@@ -24,6 +24,9 @@ function renderSettingsContent(state) {
|
|
|
24
24
|
`;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
const appVersion = escapeHtml(state.settings.appVersion ?? '0.0.0');
|
|
28
|
+
const sqliteVersion = escapeHtml(state.settings.sqliteVersion ?? 'unknown');
|
|
29
|
+
|
|
27
30
|
return `
|
|
28
31
|
<div class="grid grid-cols-1 gap-6 xl:grid-cols-2">
|
|
29
32
|
<section class="shell-section overflow-hidden">
|
|
@@ -32,14 +35,47 @@ function renderSettingsContent(state) {
|
|
|
32
35
|
<span class="material-symbols-outlined text-xs text-primary-container">deployed_code</span>
|
|
33
36
|
</div>
|
|
34
37
|
<div class="space-y-5 p-6">
|
|
38
|
+
<div class="grid grid-cols-1 gap-5 sm:grid-cols-2">
|
|
39
|
+
<div>
|
|
40
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
41
|
+
Current_Version
|
|
42
|
+
</div>
|
|
43
|
+
<div class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
|
|
44
|
+
v${appVersion}
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
<div>
|
|
48
|
+
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
49
|
+
SQLite_Runtime
|
|
50
|
+
</div>
|
|
51
|
+
<div class="mt-2 font-headline text-4xl font-black uppercase tracking-tight text-primary-container">
|
|
52
|
+
${sqliteVersion}
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div class="border-t border-outline-variant/10 py-4 text-sm leading-6 text-on-surface-variant">
|
|
57
|
+
SQL functions and syntax are evaluated with this SQLite runtime.
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
<section class="shell-section overflow-hidden">
|
|
63
|
+
<div class="flex items-center justify-between border-b border-outline-variant/10 bg-surface-container-highest px-4 py-2">
|
|
64
|
+
<span class="text-[10px] font-bold uppercase tracking-[0.25em]">CLI</span>
|
|
65
|
+
<span class="material-symbols-outlined text-xs text-primary-container">terminal</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div class="space-y-4 p-6">
|
|
35
68
|
<div>
|
|
36
69
|
<div class="text-[10px] font-mono uppercase tracking-[0.2em] text-on-surface-variant/60">
|
|
37
|
-
|
|
70
|
+
Custom_Port
|
|
38
71
|
</div>
|
|
39
|
-
<div class="mt-
|
|
40
|
-
|
|
72
|
+
<div class="mt-3 border border-outline-variant/10 bg-surface-container-high px-4 py-3 font-mono text-sm text-primary-container">
|
|
73
|
+
sqlite-hub --port:PORT
|
|
41
74
|
</div>
|
|
42
75
|
</div>
|
|
76
|
+
<div class="text-sm leading-6 text-on-surface-variant">
|
|
77
|
+
Start SQLite Hub with a different local port when the default port is already in use.
|
|
78
|
+
</div>
|
|
43
79
|
</div>
|
|
44
80
|
</section>
|
|
45
81
|
|
|
@@ -588,6 +588,19 @@
|
|
|
588
588
|
white-space: pre-wrap;
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
.copy-column-preview--editable {
|
|
592
|
+
display: block;
|
|
593
|
+
min-height: 10rem;
|
|
594
|
+
outline: none;
|
|
595
|
+
resize: vertical;
|
|
596
|
+
width: 100%;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.copy-column-preview--editable:focus {
|
|
600
|
+
border-color: rgb(var(--rgb-primary) / 0.55);
|
|
601
|
+
color: rgb(var(--rgb-on-surface) / 0.9);
|
|
602
|
+
}
|
|
603
|
+
|
|
591
604
|
.metric-card {
|
|
592
605
|
background: var(--color-surface-low);
|
|
593
606
|
display: flex;
|