sqlite-hub 0.12.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 +118 -23
- package/bin/sqlite-hub.js +1041 -224
- package/frontend/index.html +1 -0
- package/frontend/js/api.js +32 -2
- package/frontend/js/app.js +989 -13
- package/frontend/js/components/modal.js +644 -15
- package/frontend/js/components/pageHeader.js +14 -16
- package/frontend/js/components/queryEditor.js +9 -1
- package/frontend/js/components/queryHistoryPanel.js +126 -131
- package/frontend/js/components/queryResults.js +79 -17
- package/frontend/js/components/rowEditorPanel.js +204 -10
- package/frontend/js/components/sidebar.js +102 -18
- package/frontend/js/components/tableDesignerEditor.js +69 -11
- package/frontend/js/router.js +8 -0
- package/frontend/js/store.js +868 -9
- package/frontend/js/utils/copyColumnExport.js +117 -0
- package/frontend/js/utils/exportFilenames.js +32 -0
- package/frontend/js/utils/filePathPreview.js +315 -0
- package/frontend/js/utils/format.js +1 -1
- package/frontend/js/utils/markdownDocuments.js +248 -0
- package/frontend/js/utils/rowEditorJson.js +65 -0
- package/frontend/js/utils/sqlFormatter.js +691 -0
- package/frontend/js/utils/tableDesigner.js +178 -6
- package/frontend/js/utils/textCellStats.js +20 -0
- package/frontend/js/utils/timestampPreview.js +264 -0
- package/frontend/js/views/charts.js +3 -1
- package/frontend/js/views/data.js +34 -2
- package/frontend/js/views/documents.js +300 -0
- package/frontend/js/views/editor.js +48 -1
- package/frontend/js/views/settings.js +39 -6
- package/frontend/js/views/structure.js +154 -212
- package/frontend/styles/base.css +6 -0
- package/frontend/styles/components.css +476 -2
- package/frontend/styles/structure-graph.css +0 -3
- package/frontend/styles/tailwind.generated.css +1 -1
- package/frontend/styles/tokens.css +1 -1
- package/frontend/styles/views.css +422 -0
- package/package.json +3 -3
- package/server/routes/documents.js +163 -0
- package/server/routes/settings.js +22 -6
- package/server/server.js +6 -0
- package/server/services/sqlite/introspection.js +10 -0
- package/server/services/sqlite/sqlExecutor.js +29 -0
- package/server/services/sqlite/tableDesigner/changeAnalysis.js +25 -1
- package/server/services/sqlite/tableDesigner/schemaMapping.js +105 -10
- package/server/services/sqlite/tableDesigner/validation.js +60 -2
- package/server/services/sqlite/tableDesignerService.js +1 -1
- package/server/services/storage/appStateStore.js +313 -0
- package/tests/check-constraint-options.test.js +14 -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/export-filenames.test.js +34 -0
- package/tests/file-path-preview.test.js +165 -0
- package/tests/markdown-documents.test.js +79 -0
- package/tests/row-editor-json.test.js +82 -0
- package/tests/row-editor-timestamp-preview.test.js +192 -0
- package/tests/settings-metadata.test.js +16 -0
- package/tests/settings-view.test.js +32 -0
- package/tests/sql-formatter.test.js +173 -0
- package/tests/sql-highlight.test.js +38 -0
- package/tests/table-designer-v2-unique-constraints.test.js +78 -0
- package/tests/text-cell-stats.test.js +38 -0
- package/fill.js +0 -526
|
@@ -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,65 @@
|
|
|
1
|
+
function getOwnValue(row, columnName) {
|
|
2
|
+
if (!row || !Object.prototype.hasOwnProperty.call(row, columnName)) {
|
|
3
|
+
return undefined;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
return row[columnName];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function normalizeColumnName(column) {
|
|
10
|
+
return String(typeof column === "object" ? column?.name : column ?? "").trim();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function buildDataRowEditorJsonObject({ row, columns = [] } = {}) {
|
|
14
|
+
const names = columns.map(normalizeColumnName).filter((name) => name && name !== "__identity");
|
|
15
|
+
const sourceNames = names.length
|
|
16
|
+
? names
|
|
17
|
+
: Object.keys(row ?? {}).filter((name) => name !== "__identity");
|
|
18
|
+
|
|
19
|
+
return Object.fromEntries(
|
|
20
|
+
sourceNames
|
|
21
|
+
.map((name) => [name, getOwnValue(row, name)])
|
|
22
|
+
.filter(([, value]) => value !== undefined)
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function getUniqueEditorRowColumns(columns = []) {
|
|
27
|
+
const uniqueColumns = [];
|
|
28
|
+
const seen = new Set();
|
|
29
|
+
|
|
30
|
+
for (const column of columns) {
|
|
31
|
+
const sourceColumn = String(column?.sourceColumn ?? "").trim();
|
|
32
|
+
|
|
33
|
+
if (!sourceColumn || seen.has(sourceColumn)) {
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
seen.add(sourceColumn);
|
|
38
|
+
uniqueColumns.push(column);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return uniqueColumns;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function buildEditorRowEditorJsonObject({ row, editingColumns = [], resultColumns = [] } = {}) {
|
|
45
|
+
const uniqueColumns = getUniqueEditorRowColumns(editingColumns).filter((column) => column.visible !== false);
|
|
46
|
+
|
|
47
|
+
if (!uniqueColumns.length) {
|
|
48
|
+
return buildDataRowEditorJsonObject({ row, columns: resultColumns });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return Object.fromEntries(
|
|
52
|
+
uniqueColumns
|
|
53
|
+
.map((column) => {
|
|
54
|
+
const sourceColumn = String(column.sourceColumn ?? "").trim();
|
|
55
|
+
const resultName = String(column.resultName ?? sourceColumn).trim();
|
|
56
|
+
|
|
57
|
+
return [sourceColumn, getOwnValue(row, resultName)];
|
|
58
|
+
})
|
|
59
|
+
.filter(([name, value]) => name && name !== "__identity" && value !== undefined)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function stringifyRowEditorJson(rowObject) {
|
|
64
|
+
return JSON.stringify(rowObject ?? {}, null, 2);
|
|
65
|
+
}
|