sqlite-hub 0.10.0 → 0.12.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.
@@ -1,5 +1,7 @@
1
1
  import { escapeHtml } from "../utils/format.js";
2
2
 
3
+ const URL_PATTERN = /^https?:\/\/[^\s<>"']+$/i;
4
+
3
5
  function getJsonPreview(value) {
4
6
  if (value === null || value === undefined) {
5
7
  return null;
@@ -28,6 +30,114 @@ function getJsonPreview(value) {
28
30
  }
29
31
  }
30
32
 
33
+ function getUrlValue(value) {
34
+ const text = String(value ?? "").trim();
35
+
36
+ if (!URL_PATTERN.test(text)) {
37
+ return null;
38
+ }
39
+
40
+ try {
41
+ const url = new URL(text);
42
+
43
+ return ["http:", "https:"].includes(url.protocol) ? url.href : null;
44
+ } catch (error) {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ function withUrlBadge(badges = [], url) {
50
+ if (!url) {
51
+ return badges;
52
+ }
53
+
54
+ const hasUrlBadge = badges.some((badge) => {
55
+ const label = typeof badge === "object" ? badge.label : badge;
56
+ return String(label ?? "").toUpperCase() === "URL";
57
+ });
58
+
59
+ return hasUrlBadge ? badges : [...badges, { label: "URL", tone: "url" }];
60
+ }
61
+
62
+ function getAllowedValues(field) {
63
+ const seen = new Set();
64
+
65
+ return (Array.isArray(field.allowedValues) ? field.allowedValues : [])
66
+ .map((value) => String(value))
67
+ .filter((value) => {
68
+ if (seen.has(value)) {
69
+ return false;
70
+ }
71
+
72
+ seen.add(value);
73
+ return true;
74
+ });
75
+ }
76
+
77
+ function withCheckBadge(badges = [], allowedValues = []) {
78
+ if (!allowedValues.length) {
79
+ return badges;
80
+ }
81
+
82
+ const hasCheckBadge = badges.some((badge) => {
83
+ const label = typeof badge === "object" ? badge.label : badge;
84
+ return String(label ?? "").toUpperCase() === "CHECK";
85
+ });
86
+
87
+ return hasCheckBadge ? badges : [...badges, { label: "CHECK", tone: "check" }];
88
+ }
89
+
90
+ function renderOpenUrlButton(url) {
91
+ if (!url) {
92
+ return "";
93
+ }
94
+
95
+ return `
96
+ <div class="mt-2">
97
+ <button
98
+ class="standard-button"
99
+ data-action="open-row-editor-url"
100
+ data-url="${escapeHtml(url)}"
101
+ type="button"
102
+ >
103
+ <span class="material-symbols-outlined text-sm">open_in_new</span>
104
+ Open in tab
105
+ </button>
106
+ </div>
107
+ `;
108
+ }
109
+
110
+ function renderAllowedValuesSelect(field, allowedValues) {
111
+ const currentValue = String(field.value ?? "");
112
+ const hasCurrentAllowedValue = allowedValues.includes(currentValue);
113
+ const shouldRenderEmptyOption = field.notNull !== true;
114
+ const shouldRenderCurrentOption =
115
+ currentValue !== "" && !hasCurrentAllowedValue;
116
+ const options = [
117
+ shouldRenderEmptyOption
118
+ ? `<option value="" ${currentValue === "" ? "selected" : ""}>NULL / empty</option>`
119
+ : "",
120
+ shouldRenderCurrentOption
121
+ ? `<option value="${escapeHtml(currentValue)}" selected>${escapeHtml(currentValue)}</option>`
122
+ : "",
123
+ ...allowedValues.map(
124
+ (value) =>
125
+ `<option value="${escapeHtml(value)}" ${
126
+ value === currentValue ? "selected" : ""
127
+ }>${escapeHtml(value)}</option>`
128
+ ),
129
+ ].join("");
130
+
131
+ return `
132
+ <select
133
+ class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
134
+ name="field:${escapeHtml(field.name)}"
135
+ >
136
+ ${options}
137
+ </select>
138
+ `;
139
+ }
140
+
31
141
  function renderJsonViewer(prettyJson, title = "JSON Viewer") {
32
142
  return `
33
143
  <div class="border border-outline-variant/10 bg-surface-container px-4 py-4">
@@ -42,12 +152,15 @@ function renderJsonViewer(prettyJson, title = "JSON Viewer") {
42
152
  }
43
153
 
44
154
  function renderReadonlyField(label, value) {
45
- const badges = Array.isArray(label?.badges) ? label.badges : [];
155
+ const url = getUrlValue(value);
156
+ const badges = withUrlBadge(Array.isArray(label?.badges) ? label.badges : [], url);
46
157
  const displayLabel = typeof label === "object" ? label.label : label;
47
158
  const jsonPreview = getJsonPreview(value);
48
159
 
49
160
  return `
50
- <div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3">
161
+ <div class="border border-outline-variant/10 bg-surface-container-lowest px-4 py-3" ${
162
+ url ? "data-row-editor-url-field" : ""
163
+ }>
51
164
  <div class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
52
165
  <span>${escapeHtml(displayLabel)}</span>
53
166
  ${badges.map((badge) => renderFieldBadge(badge)).join("")}
@@ -57,18 +170,22 @@ function renderReadonlyField(label, value) {
57
170
  ? `<div class="mt-2">${renderJsonViewer(jsonPreview)}</div>`
58
171
  : `<div class="mt-2 text-sm text-on-surface">${escapeHtml(value)}</div>`
59
172
  }
173
+ ${renderOpenUrlButton(url)}
60
174
  </div>
61
175
  `;
62
176
  }
63
177
 
64
178
  function renderEditableField(field) {
65
- const badges = Array.isArray(field.badges) ? field.badges : [];
179
+ const url = getUrlValue(field.value);
180
+ const allowedValues = getAllowedValues(field);
181
+ const baseBadges = withCheckBadge(Array.isArray(field.badges) ? field.badges : [], allowedValues);
182
+ const badges = withUrlBadge(baseBadges, url);
66
183
  const jsonPreview = getJsonPreview(field.value);
67
184
  const inputType = field.inputType === "number" ? "number" : "text";
68
185
  const numberStep = field.numberStep === "1" ? "1" : "any";
69
186
 
70
187
  return `
71
- <label class="block space-y-2">
188
+ <div class="block space-y-2" ${url ? "data-row-editor-url-field" : ""}>
72
189
  <span class="flex flex-wrap items-center gap-2 text-[10px] font-mono uppercase tracking-[0.18em] text-on-surface-variant/55">
73
190
  <span>${escapeHtml(field.label ?? field.name)}</span>
74
191
  ${badges.map((badge) => renderFieldBadge(badge)).join("")}
@@ -79,7 +196,9 @@ function renderEditableField(field) {
79
196
  : ""
80
197
  }
81
198
  ${
82
- inputType === "number" && !jsonPreview
199
+ allowedValues.length && !jsonPreview
200
+ ? renderAllowedValuesSelect(field, allowedValues)
201
+ : inputType === "number" && !jsonPreview
83
202
  ? `
84
203
  <input
85
204
  class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
@@ -89,6 +208,18 @@ function renderEditableField(field) {
89
208
  value="${escapeHtml(field.value ?? "")}"
90
209
  />
91
210
  `
211
+ : url && !jsonPreview
212
+ ? `
213
+ <input
214
+ class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container"
215
+ name="field:${escapeHtml(field.name)}"
216
+ data-row-editor-url-input
217
+ spellcheck="false"
218
+ type="text"
219
+ value="${escapeHtml(field.value ?? "")}"
220
+ />
221
+ ${renderOpenUrlButton(url)}
222
+ `
92
223
  : `
93
224
  <textarea
94
225
  class="w-full border border-outline-variant/20 bg-surface-container-lowest px-4 py-3 text-sm text-on-surface outline-none transition-colors focus:border-primary-container ${
@@ -99,7 +230,7 @@ function renderEditableField(field) {
99
230
  >${escapeHtml(field.value ?? "")}</textarea>
100
231
  `
101
232
  }
102
- </label>
233
+ </div>
103
234
  `;
104
235
  }
105
236
 
@@ -112,6 +243,14 @@ function getFieldBadgeClassName(tone) {
112
243
  return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
113
244
  }
114
245
 
246
+ if (tone === "url") {
247
+ return "border-primary-container/35 bg-primary-container/15 text-primary-container";
248
+ }
249
+
250
+ if (tone === "check") {
251
+ return "border-tertiary-fixed-dim/35 bg-tertiary-fixed-dim/15 text-tertiary-fixed-dim";
252
+ }
253
+
115
254
  return "border-outline-variant/20 bg-surface-container text-on-surface-variant";
116
255
  }
117
256
 
@@ -6,16 +6,16 @@ const sidebarItems = [
6
6
  { label: 'Overview', href: '#/overview', key: 'overview', icon: 'dashboard' },
7
7
  { label: 'Data', href: '#/data', key: 'data', icon: 'table_rows' },
8
8
  { label: 'Structure', href: '#/structure', key: 'structure', icon: 'account_tree' },
9
- { label: 'SQL Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
9
+ { label: 'SQL_Editor', href: '#/editor', key: 'editor', icon: 'terminal' },
10
10
  { label: 'Charts', href: '#/charts', key: 'charts', icon: 'bar_chart' },
11
- { label: 'Table Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
11
+ { label: 'Table_Designer', href: '#/table-designer', key: 'tableDesigner', icon: 'table_chart' },
12
12
  {
13
- label: 'Media Tagging',
13
+ label: 'MEDIA_TAGGING',
14
14
  key: 'mediaTagging',
15
15
  icon: 'sell',
16
16
  children: [
17
- { label: 'Setup', href: '#/media-tagging', key: 'mediaTaggingSetup' },
18
- { label: 'Tagging Queue', href: '#/media-tagging/queue', key: 'mediaTaggingQueue' },
17
+ { label: 'SETUP', href: '#/media-tagging', key: 'mediaTaggingSetup' },
18
+ { label: 'TAGGING_QUEUE', href: '#/media-tagging/queue', key: 'mediaTaggingQueue' },
19
19
  ],
20
20
  },
21
21
  { label: 'Settings', href: '#/settings', key: 'settings', icon: 'settings' },