@jxsuite/studio 0.19.0 → 0.20.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/dist/studio.css +98 -98
- package/dist/studio.js +12577 -12183
- package/dist/studio.js.map +146 -144
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +17 -6
- package/src/browse/browse.js +25 -19
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +94 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +38 -15
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +1 -1
- package/src/editor/convert-to-component.js +12 -10
- package/src/editor/inline-edit.js +66 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +4 -2
- package/src/editor/slash-menu.js +6 -4
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +78 -52
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +115 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +22 -20
- package/src/panels/ai-panel.js +399 -0
- package/src/panels/block-action-bar.js +5 -5
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +469 -98
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +88 -64
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +85 -47
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +365 -249
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +7 -5
- package/src/panels/right-panel.js +26 -2
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +295 -166
- package/src/panels/statusbar.js +4 -4
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +233 -132
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +7 -7
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +21 -26
- package/src/studio.js +103 -81
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +31 -20
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +7 -7
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +10 -7
package/src/panels/head-panel.js
CHANGED
|
@@ -27,6 +27,15 @@ import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts
|
|
|
27
27
|
* }} MetaField
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {{
|
|
32
|
+
* type?: string;
|
|
33
|
+
* enum?: string[];
|
|
34
|
+
* format?: string;
|
|
35
|
+
* properties?: Record<string, unknown>;
|
|
36
|
+
* }} FmSchemaEntry
|
|
37
|
+
*/
|
|
38
|
+
|
|
30
39
|
/** @type {MetaField[]} */
|
|
31
40
|
const PAGE_FIELDS = [
|
|
32
41
|
{ label: "Description", attr: "name", key: "description" },
|
|
@@ -44,48 +53,53 @@ const OG_FIELDS = [
|
|
|
44
53
|
/** Set of `name`/`property` values managed by the structured forms. */
|
|
45
54
|
const MANAGED_META_KEYS = new Set([...PAGE_FIELDS, ...OG_FIELDS].map((f) => f.key));
|
|
46
55
|
|
|
56
|
+
/** Frontmatter keys managed by the PAGE dedicated controls (others live inside $head). */
|
|
57
|
+
const RESERVED_FM_KEYS = new Set(["title"]);
|
|
58
|
+
|
|
47
59
|
// ─── Helpers ─────────────────────────────────────────────────────────────
|
|
48
60
|
|
|
49
61
|
/**
|
|
50
62
|
* Find a `$head` meta entry by attribute match.
|
|
51
63
|
*
|
|
52
|
-
* @param {
|
|
64
|
+
* @param {JxHeadEntry[]} head
|
|
53
65
|
* @param {"name" | "property"} attr
|
|
54
66
|
* @param {string} key
|
|
55
|
-
* @returns {
|
|
67
|
+
* @returns {JxHeadEntry | undefined}
|
|
56
68
|
*/
|
|
57
69
|
function findMetaEntry(head, attr, key) {
|
|
58
70
|
if (!head) return undefined;
|
|
59
71
|
return head.find(
|
|
60
|
-
(/** @type {
|
|
72
|
+
(/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
|
|
61
73
|
);
|
|
62
74
|
}
|
|
63
75
|
|
|
64
76
|
/**
|
|
65
77
|
* Find a `$head` link entry by `rel` attribute.
|
|
66
78
|
*
|
|
67
|
-
* @param {
|
|
79
|
+
* @param {JxHeadEntry[]} head
|
|
68
80
|
* @param {string} rel
|
|
69
|
-
* @returns {
|
|
81
|
+
* @returns {JxHeadEntry | undefined}
|
|
70
82
|
*/
|
|
71
83
|
function findLinkEntry(head, rel) {
|
|
72
84
|
if (!head) return undefined;
|
|
73
|
-
return head.find(
|
|
85
|
+
return head.find(
|
|
86
|
+
(/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
|
|
87
|
+
);
|
|
74
88
|
}
|
|
75
89
|
|
|
76
90
|
/**
|
|
77
91
|
* Check if a `$head` entry is managed by the structured forms.
|
|
78
92
|
*
|
|
79
|
-
* @param {
|
|
93
|
+
* @param {JxHeadEntry} entry
|
|
80
94
|
* @returns {boolean}
|
|
81
95
|
*/
|
|
82
96
|
function isManagedEntry(entry) {
|
|
83
97
|
if (!entry?.tagName) return false;
|
|
84
98
|
// Managed meta tags
|
|
85
99
|
if (entry.tagName === "meta") {
|
|
86
|
-
const name = entry?.attributes?.name;
|
|
87
|
-
const prop = entry?.attributes?.property;
|
|
88
|
-
return (name && MANAGED_META_KEYS.has(name)) || (prop && MANAGED_META_KEYS.has(prop));
|
|
100
|
+
const name = String(entry?.attributes?.name ?? "");
|
|
101
|
+
const prop = String(entry?.attributes?.property ?? "");
|
|
102
|
+
return !!(name && MANAGED_META_KEYS.has(name)) || !!(prop && MANAGED_META_KEYS.has(prop));
|
|
89
103
|
}
|
|
90
104
|
// Managed link: favicon
|
|
91
105
|
if (entry.tagName === "link" && entry?.attributes?.rel === "icon") return true;
|
|
@@ -95,7 +109,7 @@ function isManagedEntry(entry) {
|
|
|
95
109
|
/**
|
|
96
110
|
* Upsert or remove a meta entry in `doc.$head`.
|
|
97
111
|
*
|
|
98
|
-
* @param {
|
|
112
|
+
* @param {JxMutableNode} doc
|
|
99
113
|
* @param {"name" | "property"} attr
|
|
100
114
|
* @param {string} key
|
|
101
115
|
* @param {string} content
|
|
@@ -103,7 +117,7 @@ function isManagedEntry(entry) {
|
|
|
103
117
|
function upsertMeta(doc, attr, key, content) {
|
|
104
118
|
if (!doc.$head) doc.$head = [];
|
|
105
119
|
const idx = doc.$head.findIndex(
|
|
106
|
-
(/** @type {
|
|
120
|
+
(/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
|
|
107
121
|
);
|
|
108
122
|
if (content) {
|
|
109
123
|
const entry = { tagName: "meta", attributes: { [attr]: key, content } };
|
|
@@ -120,14 +134,14 @@ function upsertMeta(doc, attr, key, content) {
|
|
|
120
134
|
/**
|
|
121
135
|
* Upsert or remove a link entry in `doc.$head`.
|
|
122
136
|
*
|
|
123
|
-
* @param {
|
|
137
|
+
* @param {JxMutableNode} doc
|
|
124
138
|
* @param {string} rel
|
|
125
139
|
* @param {string} href
|
|
126
140
|
*/
|
|
127
141
|
function upsertLink(doc, rel, href) {
|
|
128
142
|
if (!doc.$head) doc.$head = [];
|
|
129
143
|
const idx = doc.$head.findIndex(
|
|
130
|
-
(/** @type {
|
|
144
|
+
(/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
|
|
131
145
|
);
|
|
132
146
|
if (href) {
|
|
133
147
|
const entry = { tagName: "link", attributes: { rel, href } };
|
|
@@ -144,29 +158,29 @@ function upsertLink(doc, rel, href) {
|
|
|
144
158
|
/**
|
|
145
159
|
* Get a display label for an arbitrary $head entry.
|
|
146
160
|
*
|
|
147
|
-
* @param {
|
|
161
|
+
* @param {JxHeadEntry} entry
|
|
148
162
|
* @returns {string}
|
|
149
163
|
*/
|
|
150
164
|
function entryLabel(entry) {
|
|
151
165
|
if (!entry?.tagName) return "unknown";
|
|
152
166
|
const a = entry.attributes ?? {};
|
|
153
|
-
if (a.name) return `<meta name="${a.name}">`;
|
|
154
|
-
if (a.property) return `<meta property="${a.property}">`;
|
|
155
|
-
if (a.rel && a.href) return `<link rel="${a.rel}">`;
|
|
156
|
-
if (a.src) return `<script src="${a.src}">`;
|
|
157
|
-
if (a.charset) return `<meta charset="${a.charset}">`;
|
|
167
|
+
if (a.name) return `<meta name="${String(a.name)}">`;
|
|
168
|
+
if (a.property) return `<meta property="${String(a.property)}">`;
|
|
169
|
+
if (a.rel && a.href) return `<link rel="${String(a.rel)}">`;
|
|
170
|
+
if (a.src) return `<script src="${String(a.src)}">`;
|
|
171
|
+
if (a.charset) return `<meta charset="${String(a.charset)}">`;
|
|
158
172
|
return `<${entry.tagName}>`;
|
|
159
173
|
}
|
|
160
174
|
|
|
161
175
|
/**
|
|
162
176
|
* Get a display value for an arbitrary $head entry.
|
|
163
177
|
*
|
|
164
|
-
* @param {
|
|
178
|
+
* @param {JxHeadEntry} entry
|
|
165
179
|
* @returns {string}
|
|
166
180
|
*/
|
|
167
181
|
function entryValue(entry) {
|
|
168
182
|
const a = entry?.attributes ?? {};
|
|
169
|
-
return a.content ?? a.href ?? a.src ?? entry?.textContent ?? "";
|
|
183
|
+
return String(a.content ?? a.href ?? a.src ?? entry?.textContent ?? "");
|
|
170
184
|
}
|
|
171
185
|
|
|
172
186
|
// ─── Field renderers ─────────────────────────────────────────────────────
|
|
@@ -175,13 +189,13 @@ function entryValue(entry) {
|
|
|
175
189
|
* Render a meta field row using renderFieldRow.
|
|
176
190
|
*
|
|
177
191
|
* @param {MetaField} field
|
|
178
|
-
* @param {
|
|
179
|
-
* @param {(fn: (doc:
|
|
180
|
-
* @returns {
|
|
192
|
+
* @param {JxHeadEntry[]} head
|
|
193
|
+
* @param {(fn: (doc: JxMutableNode) => void) => void} applyMutation
|
|
194
|
+
* @returns {import("lit-html").TemplateResult}
|
|
181
195
|
*/
|
|
182
196
|
function renderMetaFieldRow(field, head, applyMutation) {
|
|
183
197
|
const entry = findMetaEntry(head, field.attr, field.key);
|
|
184
|
-
const val = entry?.attributes?.content ?? "";
|
|
198
|
+
const val = String(entry?.attributes?.content ?? "");
|
|
185
199
|
|
|
186
200
|
if (field.media) {
|
|
187
201
|
return renderFieldRow({
|
|
@@ -189,9 +203,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
189
203
|
label: field.label,
|
|
190
204
|
hasValue: !!val,
|
|
191
205
|
onClear: () =>
|
|
192
|
-
applyMutation((/** @type {
|
|
193
|
-
widget: renderMediaPicker(field.key, val, (/** @type {
|
|
194
|
-
applyMutation((/** @type {
|
|
206
|
+
applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
|
|
207
|
+
widget: renderMediaPicker(field.key, val, (/** @type {string} */ v) => {
|
|
208
|
+
applyMutation((/** @type {JxMutableNode} */ d) =>
|
|
209
|
+
upsertMeta(d, field.attr, field.key, v || ""),
|
|
210
|
+
);
|
|
195
211
|
}),
|
|
196
212
|
});
|
|
197
213
|
}
|
|
@@ -203,9 +219,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
203
219
|
multiline
|
|
204
220
|
.value=${live(val)}
|
|
205
221
|
placeholder="${field.label}…"
|
|
206
|
-
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {
|
|
207
|
-
const content = e.target.value?.trim() ?? "";
|
|
208
|
-
applyMutation((/** @type {
|
|
222
|
+
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {Event} */ e) => {
|
|
223
|
+
const content = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
|
|
224
|
+
applyMutation((/** @type {JxMutableNode} */ d) =>
|
|
225
|
+
upsertMeta(d, field.attr, field.key, content),
|
|
226
|
+
);
|
|
209
227
|
})}
|
|
210
228
|
></sp-textfield>
|
|
211
229
|
`
|
|
@@ -216,9 +234,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
216
234
|
placeholder=${field.key === "viewport"
|
|
217
235
|
? "width=device-width, initial-scale=1"
|
|
218
236
|
: `${field.label}…`}
|
|
219
|
-
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {
|
|
220
|
-
const content = e.target.value?.trim() ?? "";
|
|
221
|
-
applyMutation((/** @type {
|
|
237
|
+
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (/** @type {Event} */ e) => {
|
|
238
|
+
const content = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
|
|
239
|
+
applyMutation((/** @type {JxMutableNode} */ d) =>
|
|
240
|
+
upsertMeta(d, field.attr, field.key, content),
|
|
241
|
+
);
|
|
222
242
|
})}
|
|
223
243
|
></sp-textfield>
|
|
224
244
|
`;
|
|
@@ -228,7 +248,7 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
228
248
|
label: field.label,
|
|
229
249
|
hasValue: !!val,
|
|
230
250
|
onClear: () =>
|
|
231
|
-
applyMutation((/** @type {
|
|
251
|
+
applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
|
|
232
252
|
widget,
|
|
233
253
|
});
|
|
234
254
|
}
|
|
@@ -237,11 +257,11 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
237
257
|
|
|
238
258
|
/**
|
|
239
259
|
* @param {{
|
|
240
|
-
* document:
|
|
241
|
-
* applyMutation: (fn: (doc:
|
|
260
|
+
* document: JxMutableNode;
|
|
261
|
+
* applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
242
262
|
* renderLeftPanel: () => void;
|
|
243
263
|
* }} ctx
|
|
244
|
-
* @returns {
|
|
264
|
+
* @returns {import("lit-html").TemplateResult}
|
|
245
265
|
*/
|
|
246
266
|
export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPanel }) {
|
|
247
267
|
const head = doc.$head ?? [];
|
|
@@ -249,11 +269,11 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
249
269
|
|
|
250
270
|
// Icon (favicon) link
|
|
251
271
|
const iconEntry = findLinkEntry(head, "icon");
|
|
252
|
-
const iconHref = iconEntry?.attributes?.href ?? "";
|
|
272
|
+
const iconHref = String(iconEntry?.attributes?.href ?? "");
|
|
253
273
|
|
|
254
274
|
// Custom entries not managed by structured forms, fonts, or preconnects
|
|
255
275
|
const customEntries = head.filter(
|
|
256
|
-
(/** @type {
|
|
276
|
+
(/** @type {JxHeadEntry} */ e) =>
|
|
257
277
|
!isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
|
|
258
278
|
);
|
|
259
279
|
|
|
@@ -277,7 +297,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
277
297
|
label: "Title",
|
|
278
298
|
hasValue: !!title,
|
|
279
299
|
onClear: () =>
|
|
280
|
-
applyMutation((/** @type {
|
|
300
|
+
applyMutation((/** @type {JxMutableNode} */ d) => {
|
|
281
301
|
delete d.title;
|
|
282
302
|
}),
|
|
283
303
|
widget: html`
|
|
@@ -285,9 +305,9 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
285
305
|
size="s"
|
|
286
306
|
.value=${live(title)}
|
|
287
307
|
placeholder="Page title…"
|
|
288
|
-
@input=${debouncedStyleCommit("head:title", 400, (/** @type {
|
|
289
|
-
const val = e.target.value?.trim() ?? "";
|
|
290
|
-
applyMutation((/** @type {
|
|
308
|
+
@input=${debouncedStyleCommit("head:title", 400, (/** @type {Event} */ e) => {
|
|
309
|
+
const val = /** @type {HTMLInputElement} */ (e.target).value?.trim() ?? "";
|
|
310
|
+
applyMutation((/** @type {JxMutableNode} */ d) => {
|
|
291
311
|
if (val) d.title = val;
|
|
292
312
|
else delete d.title;
|
|
293
313
|
});
|
|
@@ -300,9 +320,10 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
300
320
|
prop: "icon",
|
|
301
321
|
label: "Icon",
|
|
302
322
|
hasValue: !!iconHref,
|
|
303
|
-
onClear: () =>
|
|
304
|
-
|
|
305
|
-
|
|
323
|
+
onClear: () =>
|
|
324
|
+
applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", "")),
|
|
325
|
+
widget: renderMediaPicker("icon", iconHref, (/** @type {string} */ v) => {
|
|
326
|
+
applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", v || ""));
|
|
306
327
|
}),
|
|
307
328
|
})}
|
|
308
329
|
</div>
|
|
@@ -327,7 +348,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
327
348
|
${customEntries.length > 0
|
|
328
349
|
? html`
|
|
329
350
|
<div class="imports-list">
|
|
330
|
-
${customEntries.map((/** @type {
|
|
351
|
+
${customEntries.map((/** @type {JxHeadEntry} */ entry) => {
|
|
331
352
|
const label = entryLabel(entry);
|
|
332
353
|
const value = entryValue(entry);
|
|
333
354
|
return html`
|
|
@@ -339,7 +360,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
339
360
|
size="xs"
|
|
340
361
|
title="Remove"
|
|
341
362
|
@click=${() => {
|
|
342
|
-
applyMutation((/** @type {
|
|
363
|
+
applyMutation((/** @type {JxMutableNode} */ d) => {
|
|
343
364
|
if (!d.$head) return;
|
|
344
365
|
const idx = d.$head.indexOf(entry);
|
|
345
366
|
if (idx >= 0) d.$head.splice(idx, 1);
|
|
@@ -373,19 +394,25 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
373
394
|
quiet
|
|
374
395
|
size="xs"
|
|
375
396
|
title="Add tag"
|
|
376
|
-
@click=${(/** @type {
|
|
377
|
-
const form = e.target.closest(".head-add-form");
|
|
378
|
-
const tagPicker =
|
|
379
|
-
|
|
380
|
-
|
|
397
|
+
@click=${(/** @type {Event} */ e) => {
|
|
398
|
+
const form = /** @type {HTMLElement} */ (e.target).closest(".head-add-form");
|
|
399
|
+
const tagPicker = /** @type {HTMLInputElement | null} */ (
|
|
400
|
+
form?.querySelector(".head-add-tag")
|
|
401
|
+
);
|
|
402
|
+
const attrField = /** @type {HTMLInputElement | null} */ (
|
|
403
|
+
form?.querySelector(".head-add-attr")
|
|
404
|
+
);
|
|
405
|
+
const valField = /** @type {HTMLInputElement | null} */ (
|
|
406
|
+
form?.querySelector(".head-add-val")
|
|
407
|
+
);
|
|
381
408
|
const tagName = tagPicker?.value || "meta";
|
|
382
409
|
const attrKey = attrField?.value?.trim();
|
|
383
410
|
const attrVal = valField?.value?.trim();
|
|
384
411
|
if (!attrKey || !attrVal) return;
|
|
385
|
-
attrField.value = "";
|
|
386
|
-
valField.value = "";
|
|
412
|
+
if (attrField) attrField.value = "";
|
|
413
|
+
if (valField) valField.value = "";
|
|
387
414
|
|
|
388
|
-
/** @type {
|
|
415
|
+
/** @type {JxHeadEntry} */
|
|
389
416
|
const entry = { tagName, attributes: {} };
|
|
390
417
|
if (tagName === "meta") {
|
|
391
418
|
entry.attributes = { name: attrKey, content: attrVal };
|
|
@@ -395,7 +422,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
395
422
|
entry.attributes = { [attrKey]: attrVal };
|
|
396
423
|
}
|
|
397
424
|
|
|
398
|
-
applyMutation((/** @type {
|
|
425
|
+
applyMutation((/** @type {JxMutableNode} */ d) => {
|
|
399
426
|
if (!d.$head) d.$head = [];
|
|
400
427
|
d.$head.push(entry);
|
|
401
428
|
});
|
|
@@ -421,29 +448,30 @@ function renderFrontmatterSection() {
|
|
|
421
448
|
const schemaProps = col?.schema?.properties;
|
|
422
449
|
const requiredFields = new Set(col?.schema?.required || []);
|
|
423
450
|
|
|
424
|
-
/** @type {{ field: string; entry:
|
|
451
|
+
/** @type {{ field: string; entry: FmSchemaEntry; value: JsonValue }[]} */
|
|
425
452
|
const fields = [];
|
|
426
453
|
if (schemaProps) {
|
|
427
454
|
for (const [field, fieldSchema] of Object.entries(
|
|
428
|
-
/** @type {Record<string,
|
|
455
|
+
/** @type {Record<string, FmSchemaEntry>} */ (schemaProps),
|
|
429
456
|
)) {
|
|
430
|
-
|
|
457
|
+
if (RESERVED_FM_KEYS.has(field)) continue;
|
|
458
|
+
fields.push({ field, entry: fieldSchema, value: /** @type {JsonValue} */ (fm[field]) });
|
|
431
459
|
}
|
|
432
460
|
for (const [field, value] of Object.entries(fm)) {
|
|
433
|
-
if (schemaProps[field] || field.startsWith("$")) continue;
|
|
461
|
+
if (schemaProps[field] || field.startsWith("$") || RESERVED_FM_KEYS.has(field)) continue;
|
|
434
462
|
fields.push({
|
|
435
463
|
field,
|
|
436
464
|
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
437
|
-
value,
|
|
465
|
+
value: /** @type {JsonValue} */ (value),
|
|
438
466
|
});
|
|
439
467
|
}
|
|
440
468
|
} else {
|
|
441
469
|
for (const [field, value] of Object.entries(fm)) {
|
|
442
|
-
if (field.startsWith("$")) continue;
|
|
470
|
+
if (field.startsWith("$") || RESERVED_FM_KEYS.has(field)) continue;
|
|
443
471
|
fields.push({
|
|
444
472
|
field,
|
|
445
473
|
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
446
|
-
value,
|
|
474
|
+
value: /** @type {JsonValue} */ (value),
|
|
447
475
|
});
|
|
448
476
|
}
|
|
449
477
|
}
|
|
@@ -466,8 +494,8 @@ function renderFrontmatterSection() {
|
|
|
466
494
|
|
|
467
495
|
function renderFmField(
|
|
468
496
|
/** @type {string} */ field,
|
|
469
|
-
/** @type {
|
|
470
|
-
/** @type {
|
|
497
|
+
/** @type {FmSchemaEntry} */ entry,
|
|
498
|
+
/** @type {JsonValue} */ value,
|
|
471
499
|
/** @type {Set<string>} */ requiredFields,
|
|
472
500
|
) {
|
|
473
501
|
const isRequired = requiredFields.has(field);
|
|
@@ -487,9 +515,13 @@ function renderFmField(
|
|
|
487
515
|
<sp-checkbox
|
|
488
516
|
size="s"
|
|
489
517
|
.checked=${live(!!value)}
|
|
490
|
-
@change=${(/** @type {
|
|
518
|
+
@change=${(/** @type {Event} */ e) =>
|
|
491
519
|
transactDoc(activeTab.value, (t) =>
|
|
492
|
-
mutateUpdateFrontmatter(
|
|
520
|
+
mutateUpdateFrontmatter(
|
|
521
|
+
t,
|
|
522
|
+
field,
|
|
523
|
+
/** @type {HTMLInputElement} */ (e.target).checked || undefined,
|
|
524
|
+
),
|
|
493
525
|
)}
|
|
494
526
|
></sp-checkbox>
|
|
495
527
|
`,
|
|
@@ -508,9 +540,9 @@ function renderFmField(
|
|
|
508
540
|
size="s"
|
|
509
541
|
placeholder="comma, separated"
|
|
510
542
|
.value=${live(display)}
|
|
511
|
-
@input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {
|
|
512
|
-
const arr = e.target.value
|
|
513
|
-
? e.target.value
|
|
543
|
+
@input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
|
|
544
|
+
const arr = /** @type {HTMLInputElement} */ (e.target).value
|
|
545
|
+
? /** @type {HTMLInputElement} */ (e.target).value
|
|
514
546
|
.split(",")
|
|
515
547
|
.map((/** @type {string} */ s) => s.trim())
|
|
516
548
|
.filter(Boolean)
|
|
@@ -532,9 +564,13 @@ function renderFmField(
|
|
|
532
564
|
<sp-picker
|
|
533
565
|
size="s"
|
|
534
566
|
.value=${live(value || "")}
|
|
535
|
-
@change=${(/** @type {
|
|
567
|
+
@change=${(/** @type {Event} */ e) =>
|
|
536
568
|
transactDoc(activeTab.value, (t) =>
|
|
537
|
-
mutateUpdateFrontmatter(
|
|
569
|
+
mutateUpdateFrontmatter(
|
|
570
|
+
t,
|
|
571
|
+
field,
|
|
572
|
+
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
573
|
+
),
|
|
538
574
|
)}
|
|
539
575
|
>
|
|
540
576
|
${entry.enum.map(
|
|
@@ -551,7 +587,7 @@ function renderFmField(
|
|
|
551
587
|
label: displayLabel,
|
|
552
588
|
hasValue: hasVal,
|
|
553
589
|
onClear,
|
|
554
|
-
widget: renderMediaPicker(field, value, (v) =>
|
|
590
|
+
widget: renderMediaPicker(field, /** @type {string} */ (value), (/** @type {string} */ v) =>
|
|
555
591
|
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
|
|
556
592
|
),
|
|
557
593
|
});
|
|
@@ -568,10 +604,10 @@ function renderFmField(
|
|
|
568
604
|
size="s"
|
|
569
605
|
hide-stepper
|
|
570
606
|
.value=${live(value !== undefined ? Number(value) : undefined)}
|
|
571
|
-
@change=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {
|
|
572
|
-
const v = e.target.value;
|
|
607
|
+
@change=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
|
|
608
|
+
const v = /** @type {HTMLInputElement} */ (e.target).value;
|
|
573
609
|
transactDoc(activeTab.value, (t) =>
|
|
574
|
-
mutateUpdateFrontmatter(t, field, isNaN(v) ? undefined : Number(v)),
|
|
610
|
+
mutateUpdateFrontmatter(t, field, isNaN(Number(v)) ? undefined : Number(v)),
|
|
575
611
|
);
|
|
576
612
|
})}
|
|
577
613
|
></sp-number-field>
|
|
@@ -589,9 +625,13 @@ function renderFmField(
|
|
|
589
625
|
size="s"
|
|
590
626
|
placeholder=${entry.format === "date" ? "YYYY-MM-DD" : ""}
|
|
591
627
|
.value=${live(value || "")}
|
|
592
|
-
@input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {
|
|
628
|
+
@input=${debouncedStyleCommit(`fm:${field}`, 400, (/** @type {Event} */ e) => {
|
|
593
629
|
transactDoc(activeTab.value, (t) =>
|
|
594
|
-
mutateUpdateFrontmatter(
|
|
630
|
+
mutateUpdateFrontmatter(
|
|
631
|
+
t,
|
|
632
|
+
field,
|
|
633
|
+
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
634
|
+
),
|
|
595
635
|
);
|
|
596
636
|
})}
|
|
597
637
|
></sp-textfield>
|