@jxsuite/studio 0.28.2 → 0.28.4
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.js +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
package/src/panels/head-panel.ts
CHANGED
|
@@ -9,18 +9,19 @@
|
|
|
9
9
|
import { html, nothing } from "lit-html";
|
|
10
10
|
import { live } from "lit-html/directives/live.js";
|
|
11
11
|
import { renderFieldRow } from "../ui/field-row";
|
|
12
|
-
import {
|
|
12
|
+
import { spNumberField, spTextArea, spTextField } from "../ui/field-input";
|
|
13
13
|
import { renderMediaPicker } from "../ui/media-picker";
|
|
14
|
-
import {
|
|
15
|
-
import type {
|
|
14
|
+
import { projectState, renderOnly } from "../store";
|
|
15
|
+
import type { DirEntry, JsonValue } from "../types";
|
|
16
16
|
import { activeTab } from "../workspace/workspace";
|
|
17
|
-
import {
|
|
17
|
+
import { mutateUpdateFrontmatter, transactDoc } from "../tabs/transact";
|
|
18
18
|
import { findContentTypeSchema } from "../utils/studio-utils";
|
|
19
19
|
import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts";
|
|
20
20
|
import { invalidateLayoutCache } from "../site-context";
|
|
21
21
|
import { getPlatform } from "../platform";
|
|
22
22
|
|
|
23
23
|
import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
|
|
24
|
+
import type { TemplateResult } from "lit-html";
|
|
24
25
|
|
|
25
26
|
interface MetaField {
|
|
26
27
|
label: string;
|
|
@@ -51,8 +52,8 @@ async function loadLayoutEntries() {
|
|
|
51
52
|
.map((f: DirEntry) => ({
|
|
52
53
|
name: f.name
|
|
53
54
|
.replace(/\.json$/, "")
|
|
54
|
-
.
|
|
55
|
-
.
|
|
55
|
+
.replaceAll(/[-_]+/g, " ")
|
|
56
|
+
.replaceAll(/\b\w/g, (c) => c.toUpperCase()),
|
|
56
57
|
path: `./layouts/${f.name}`,
|
|
57
58
|
}));
|
|
58
59
|
} catch {
|
|
@@ -68,20 +69,20 @@ export function invalidateLayoutPickerCache() {
|
|
|
68
69
|
// ─── Field definitions ───────────────────────────────────────────────────
|
|
69
70
|
|
|
70
71
|
const PAGE_FIELDS: MetaField[] = [
|
|
71
|
-
{
|
|
72
|
-
{
|
|
72
|
+
{ attr: "name", key: "description", label: "Description" },
|
|
73
|
+
{ attr: "name", key: "viewport", label: "Viewport" },
|
|
73
74
|
];
|
|
74
75
|
|
|
75
76
|
const OG_FIELDS: MetaField[] = [
|
|
76
|
-
{
|
|
77
|
+
{ attr: "property", key: "og:title", label: "Title" },
|
|
77
78
|
{
|
|
78
|
-
label: "Description",
|
|
79
79
|
attr: "property",
|
|
80
80
|
key: "og:description",
|
|
81
|
+
label: "Description",
|
|
81
82
|
multiline: true,
|
|
82
83
|
},
|
|
83
|
-
{
|
|
84
|
-
{
|
|
84
|
+
{ attr: "property", key: "og:image", label: "Image", media: true },
|
|
85
|
+
{ attr: "property", key: "og:type", label: "Type" },
|
|
85
86
|
];
|
|
86
87
|
|
|
87
88
|
/** Set of `name`/`property` values managed by the structured forms. */
|
|
@@ -101,7 +102,9 @@ const RESERVED_FM_KEYS = new Set(["title"]);
|
|
|
101
102
|
* @returns {JxHeadEntry | undefined}
|
|
102
103
|
*/
|
|
103
104
|
function findMetaEntry(head: JxHeadEntry[], attr: "name" | "property", key: string) {
|
|
104
|
-
if (!head)
|
|
105
|
+
if (!head) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
105
108
|
return head.find((e: JxHeadEntry) => e?.tagName === "meta" && e?.attributes?.[attr] === key);
|
|
106
109
|
}
|
|
107
110
|
|
|
@@ -113,7 +116,9 @@ function findMetaEntry(head: JxHeadEntry[], attr: "name" | "property", key: stri
|
|
|
113
116
|
* @returns {JxHeadEntry | undefined}
|
|
114
117
|
*/
|
|
115
118
|
function findLinkEntry(head: JxHeadEntry[], rel: string) {
|
|
116
|
-
if (!head)
|
|
119
|
+
if (!head) {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
117
122
|
return head.find((e: JxHeadEntry) => e?.tagName === "link" && e?.attributes?.rel === rel);
|
|
118
123
|
}
|
|
119
124
|
|
|
@@ -124,15 +129,21 @@ function findLinkEntry(head: JxHeadEntry[], rel: string) {
|
|
|
124
129
|
* @returns {boolean}
|
|
125
130
|
*/
|
|
126
131
|
function isManagedEntry(entry: JxHeadEntry) {
|
|
127
|
-
if (!entry?.tagName)
|
|
132
|
+
if (!entry?.tagName) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
128
135
|
// Managed meta tags
|
|
129
136
|
if (entry.tagName === "meta") {
|
|
130
137
|
const name = String(entry?.attributes?.name ?? "");
|
|
131
138
|
const prop = String(entry?.attributes?.property ?? "");
|
|
132
|
-
return
|
|
139
|
+
return (
|
|
140
|
+
Boolean(name && MANAGED_META_KEYS.has(name)) || Boolean(prop && MANAGED_META_KEYS.has(prop))
|
|
141
|
+
);
|
|
133
142
|
}
|
|
134
143
|
// Managed link: favicon
|
|
135
|
-
if (entry.tagName === "link" && entry?.attributes?.rel === "icon")
|
|
144
|
+
if (entry.tagName === "link" && entry?.attributes?.rel === "icon") {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
136
147
|
return false;
|
|
137
148
|
}
|
|
138
149
|
|
|
@@ -145,18 +156,20 @@ function isManagedEntry(entry: JxHeadEntry) {
|
|
|
145
156
|
* @param {string} content
|
|
146
157
|
*/
|
|
147
158
|
function upsertMeta(doc: JxMutableNode, attr: "name" | "property", key: string, content: string) {
|
|
148
|
-
if (!doc.$head)
|
|
159
|
+
if (!doc.$head) {
|
|
160
|
+
doc.$head = [];
|
|
161
|
+
}
|
|
149
162
|
const idx = doc.$head.findIndex(
|
|
150
163
|
(e: JxHeadEntry) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
|
|
151
164
|
);
|
|
152
165
|
if (content) {
|
|
153
|
-
const entry = {
|
|
154
|
-
if (idx
|
|
166
|
+
const entry = { attributes: { [attr]: key, content }, tagName: "meta" };
|
|
167
|
+
if (idx !== -1) {
|
|
155
168
|
doc.$head[idx] = entry;
|
|
156
169
|
} else {
|
|
157
170
|
doc.$head.push(entry);
|
|
158
171
|
}
|
|
159
|
-
} else if (idx
|
|
172
|
+
} else if (idx !== -1) {
|
|
160
173
|
doc.$head.splice(idx, 1);
|
|
161
174
|
}
|
|
162
175
|
}
|
|
@@ -169,18 +182,20 @@ function upsertMeta(doc: JxMutableNode, attr: "name" | "property", key: string,
|
|
|
169
182
|
* @param {string} href
|
|
170
183
|
*/
|
|
171
184
|
function upsertLink(doc: JxMutableNode, rel: string, href: string) {
|
|
172
|
-
if (!doc.$head)
|
|
185
|
+
if (!doc.$head) {
|
|
186
|
+
doc.$head = [];
|
|
187
|
+
}
|
|
173
188
|
const idx = doc.$head.findIndex(
|
|
174
189
|
(e: JxHeadEntry) => e?.tagName === "link" && e?.attributes?.rel === rel,
|
|
175
190
|
);
|
|
176
191
|
if (href) {
|
|
177
|
-
const entry = {
|
|
178
|
-
if (idx
|
|
192
|
+
const entry = { attributes: { href, rel }, tagName: "link" };
|
|
193
|
+
if (idx !== -1) {
|
|
179
194
|
doc.$head[idx] = entry;
|
|
180
195
|
} else {
|
|
181
196
|
doc.$head.push(entry);
|
|
182
197
|
}
|
|
183
|
-
} else if (idx
|
|
198
|
+
} else if (idx !== -1) {
|
|
184
199
|
doc.$head.splice(idx, 1);
|
|
185
200
|
}
|
|
186
201
|
}
|
|
@@ -192,13 +207,25 @@ function upsertLink(doc: JxMutableNode, rel: string, href: string) {
|
|
|
192
207
|
* @returns {string}
|
|
193
208
|
*/
|
|
194
209
|
function entryLabel(entry: JxHeadEntry) {
|
|
195
|
-
if (!entry?.tagName)
|
|
210
|
+
if (!entry?.tagName) {
|
|
211
|
+
return "unknown";
|
|
212
|
+
}
|
|
196
213
|
const a = entry.attributes ?? {};
|
|
197
|
-
if (a.name)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (a.
|
|
201
|
-
|
|
214
|
+
if (a.name) {
|
|
215
|
+
return `<meta name="${String(a.name)}">`;
|
|
216
|
+
}
|
|
217
|
+
if (a.property) {
|
|
218
|
+
return `<meta property="${String(a.property)}">`;
|
|
219
|
+
}
|
|
220
|
+
if (a.rel && a.href) {
|
|
221
|
+
return `<link rel="${String(a.rel)}">`;
|
|
222
|
+
}
|
|
223
|
+
if (a.src) {
|
|
224
|
+
return `<script src="${String(a.src)}">`;
|
|
225
|
+
}
|
|
226
|
+
if (a.charset) {
|
|
227
|
+
return `<meta charset="${String(a.charset)}">`;
|
|
228
|
+
}
|
|
202
229
|
return `<${entry.tagName}>`;
|
|
203
230
|
}
|
|
204
231
|
|
|
@@ -233,10 +260,10 @@ function renderMetaFieldRow(
|
|
|
233
260
|
|
|
234
261
|
if (field.media) {
|
|
235
262
|
return renderFieldRow({
|
|
236
|
-
|
|
263
|
+
hasValue: Boolean(val),
|
|
237
264
|
label: field.label,
|
|
238
|
-
hasValue: !!val,
|
|
239
265
|
onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
|
|
266
|
+
prop: field.key,
|
|
240
267
|
widget: renderMediaPicker(field.key, val, (v: string) => {
|
|
241
268
|
applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, v || ""));
|
|
242
269
|
}),
|
|
@@ -254,10 +281,10 @@ function renderMetaFieldRow(
|
|
|
254
281
|
: spTextField(`head:${field.key}`, val, commit, { placeholder });
|
|
255
282
|
|
|
256
283
|
return renderFieldRow({
|
|
257
|
-
|
|
284
|
+
hasValue: Boolean(val),
|
|
258
285
|
label: field.label,
|
|
259
|
-
hasValue: !!val,
|
|
260
286
|
onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
|
|
287
|
+
prop: field.key,
|
|
261
288
|
widget,
|
|
262
289
|
});
|
|
263
290
|
}
|
|
@@ -304,21 +331,20 @@ export function renderHeadTemplate({
|
|
|
304
331
|
projectState?.isSiteProject &&
|
|
305
332
|
(tab.documentPath.startsWith("pages/") || tab.documentPath.startsWith("./pages/"));
|
|
306
333
|
|
|
307
|
-
let layoutSection:
|
|
334
|
+
let layoutSection: TemplateResult | symbol = nothing;
|
|
308
335
|
if (isPage) {
|
|
309
336
|
if (layoutEntries === null) {
|
|
310
337
|
loadLayoutEntries();
|
|
311
338
|
} else {
|
|
312
339
|
const currentLayout = doc.$layout;
|
|
313
340
|
const defaultLayout = projectState?.projectConfig?.defaults?.layout;
|
|
314
|
-
const displayValue =
|
|
315
|
-
currentLayout === false ? "__none__" : currentLayout ? currentLayout : "__default__";
|
|
341
|
+
const displayValue = currentLayout === false ? "__none__" : currentLayout || "__default__";
|
|
316
342
|
const defaultLabel = defaultLayout
|
|
317
343
|
? defaultLayout
|
|
318
344
|
.replace(/^\.\/layouts\//, "")
|
|
319
345
|
.replace(/\.json$/, "")
|
|
320
|
-
.
|
|
321
|
-
.
|
|
346
|
+
.replaceAll(/[-_]+/g, " ")
|
|
347
|
+
.replaceAll(/\b\w/g, (c: string) => c.toUpperCase())
|
|
322
348
|
: "";
|
|
323
349
|
|
|
324
350
|
layoutSection = html`
|
|
@@ -328,13 +354,13 @@ export function renderHeadTemplate({
|
|
|
328
354
|
</div>
|
|
329
355
|
<div class="head-section-body">
|
|
330
356
|
${renderFieldRow({
|
|
331
|
-
prop: "layout",
|
|
332
|
-
label: "Layout",
|
|
333
357
|
hasValue: currentLayout !== undefined,
|
|
358
|
+
label: "Layout",
|
|
334
359
|
onClear: () =>
|
|
335
360
|
applyMutation((d: JxMutableNode) => {
|
|
336
361
|
delete d.$layout;
|
|
337
362
|
}),
|
|
363
|
+
prop: "layout",
|
|
338
364
|
widget: html`
|
|
339
365
|
<sp-picker
|
|
340
366
|
size="s"
|
|
@@ -342,9 +368,13 @@ export function renderHeadTemplate({
|
|
|
342
368
|
@change=${(e: Event) => {
|
|
343
369
|
const val = (e.target as HTMLInputElement).value;
|
|
344
370
|
applyMutation((d: JxMutableNode) => {
|
|
345
|
-
if (val === "__default__")
|
|
346
|
-
|
|
347
|
-
else
|
|
371
|
+
if (val === "__default__") {
|
|
372
|
+
delete d.$layout;
|
|
373
|
+
} else if (val === "__none__") {
|
|
374
|
+
d.$layout = false;
|
|
375
|
+
} else {
|
|
376
|
+
d.$layout = val;
|
|
377
|
+
}
|
|
348
378
|
});
|
|
349
379
|
invalidateLayoutCache();
|
|
350
380
|
}}
|
|
@@ -378,31 +408,34 @@ export function renderHeadTemplate({
|
|
|
378
408
|
</div>
|
|
379
409
|
<div class="head-section-body">
|
|
380
410
|
${renderFieldRow({
|
|
381
|
-
|
|
411
|
+
hasValue: Boolean(title),
|
|
382
412
|
label: "Title",
|
|
383
|
-
hasValue: !!title,
|
|
384
413
|
onClear: () =>
|
|
385
414
|
applyMutation((d: JxMutableNode) => {
|
|
386
415
|
delete d.title;
|
|
387
416
|
}),
|
|
417
|
+
prop: "title",
|
|
388
418
|
widget: spTextField(
|
|
389
419
|
"head:title",
|
|
390
420
|
title,
|
|
391
421
|
(v: string) =>
|
|
392
422
|
applyMutation((d: JxMutableNode) => {
|
|
393
423
|
const val = v.trim();
|
|
394
|
-
if (val)
|
|
395
|
-
|
|
424
|
+
if (val) {
|
|
425
|
+
d.title = val;
|
|
426
|
+
} else {
|
|
427
|
+
delete d.title;
|
|
428
|
+
}
|
|
396
429
|
}),
|
|
397
430
|
{ placeholder: "Page title…" },
|
|
398
431
|
),
|
|
399
432
|
})}
|
|
400
433
|
${PAGE_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
|
|
401
434
|
${renderFieldRow({
|
|
402
|
-
|
|
435
|
+
hasValue: Boolean(iconHref),
|
|
403
436
|
label: "Icon",
|
|
404
|
-
hasValue: !!iconHref,
|
|
405
437
|
onClear: () => applyMutation((d: JxMutableNode) => upsertLink(d, "icon", "")),
|
|
438
|
+
prop: "icon",
|
|
406
439
|
widget: renderMediaPicker("icon", iconHref, (v: string) => {
|
|
407
440
|
applyMutation((d: JxMutableNode) => upsertLink(d, "icon", v || ""));
|
|
408
441
|
}),
|
|
@@ -442,9 +475,13 @@ export function renderHeadTemplate({
|
|
|
442
475
|
title="Remove"
|
|
443
476
|
@click=${() => {
|
|
444
477
|
applyMutation((d: JxMutableNode) => {
|
|
445
|
-
if (!d.$head)
|
|
478
|
+
if (!d.$head) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
446
481
|
const idx = d.$head.indexOf(entry);
|
|
447
|
-
if (idx
|
|
482
|
+
if (idx !== -1) {
|
|
483
|
+
d.$head.splice(idx, 1);
|
|
484
|
+
}
|
|
448
485
|
});
|
|
449
486
|
renderLeftPanel();
|
|
450
487
|
}}
|
|
@@ -483,21 +520,29 @@ export function renderHeadTemplate({
|
|
|
483
520
|
const tagName = tagPicker?.value || "meta";
|
|
484
521
|
const attrKey = attrField?.value?.trim();
|
|
485
522
|
const attrVal = valField?.value?.trim();
|
|
486
|
-
if (!attrKey || !attrVal)
|
|
487
|
-
|
|
488
|
-
|
|
523
|
+
if (!attrKey || !attrVal) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (attrField) {
|
|
527
|
+
attrField.value = "";
|
|
528
|
+
}
|
|
529
|
+
if (valField) {
|
|
530
|
+
valField.value = "";
|
|
531
|
+
}
|
|
489
532
|
|
|
490
|
-
const entry: JxHeadEntry = {
|
|
533
|
+
const entry: JxHeadEntry = { attributes: {}, tagName };
|
|
491
534
|
if (tagName === "meta") {
|
|
492
|
-
entry.attributes = {
|
|
535
|
+
entry.attributes = { content: attrVal, name: attrKey };
|
|
493
536
|
} else if (tagName === "link") {
|
|
494
|
-
entry.attributes = {
|
|
537
|
+
entry.attributes = { href: attrVal, rel: attrKey };
|
|
495
538
|
} else if (tagName === "script") {
|
|
496
539
|
entry.attributes = { [attrKey]: attrVal };
|
|
497
540
|
}
|
|
498
541
|
|
|
499
542
|
applyMutation((d: JxMutableNode) => {
|
|
500
|
-
if (!d.$head)
|
|
543
|
+
if (!d.$head) {
|
|
544
|
+
d.$head = [];
|
|
545
|
+
}
|
|
501
546
|
d.$head.push(entry);
|
|
502
547
|
});
|
|
503
548
|
renderLeftPanel();
|
|
@@ -515,7 +560,9 @@ export function renderHeadTemplate({
|
|
|
515
560
|
|
|
516
561
|
function renderFrontmatterSection() {
|
|
517
562
|
const tab = activeTab.value;
|
|
518
|
-
if (!tab)
|
|
563
|
+
if (!tab) {
|
|
564
|
+
return nothing;
|
|
565
|
+
}
|
|
519
566
|
|
|
520
567
|
const fm = tab.doc.content?.frontmatter || {};
|
|
521
568
|
const col = findContentTypeSchema(tab.documentPath, projectState?.projectConfig);
|
|
@@ -531,29 +578,37 @@ function renderFrontmatterSection() {
|
|
|
531
578
|
for (const [field, fieldSchema] of Object.entries(
|
|
532
579
|
/** @type {Record<string, FmSchemaEntry>} */ schemaProps,
|
|
533
580
|
)) {
|
|
534
|
-
if (RESERVED_FM_KEYS.has(field))
|
|
535
|
-
|
|
581
|
+
if (RESERVED_FM_KEYS.has(field)) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
fields.push({ entry: fieldSchema, field, value: fm[field] as JsonValue });
|
|
536
585
|
}
|
|
537
586
|
for (const [field, value] of Object.entries(fm)) {
|
|
538
|
-
if (schemaProps[field] || field.startsWith("$") || RESERVED_FM_KEYS.has(field))
|
|
587
|
+
if (schemaProps[field] || field.startsWith("$") || RESERVED_FM_KEYS.has(field)) {
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
539
590
|
fields.push({
|
|
540
|
-
field,
|
|
541
591
|
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
592
|
+
field,
|
|
542
593
|
value: /** @type {JsonValue} */ value,
|
|
543
594
|
});
|
|
544
595
|
}
|
|
545
596
|
} else {
|
|
546
597
|
for (const [field, value] of Object.entries(fm)) {
|
|
547
|
-
if (field.startsWith("$") || RESERVED_FM_KEYS.has(field))
|
|
598
|
+
if (field.startsWith("$") || RESERVED_FM_KEYS.has(field)) {
|
|
599
|
+
continue;
|
|
600
|
+
}
|
|
548
601
|
fields.push({
|
|
549
|
-
field,
|
|
550
602
|
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
603
|
+
field,
|
|
551
604
|
value: /** @type {JsonValue} */ value,
|
|
552
605
|
});
|
|
553
606
|
}
|
|
554
607
|
}
|
|
555
608
|
|
|
556
|
-
if (fields.length === 0 && !schemaProps)
|
|
609
|
+
if (fields.length === 0 && !schemaProps) {
|
|
610
|
+
return nothing;
|
|
611
|
+
}
|
|
557
612
|
|
|
558
613
|
return html`
|
|
559
614
|
<div class="imports-section">
|
|
@@ -576,22 +631,21 @@ function renderFmField(
|
|
|
576
631
|
requiredFields: Set<string>,
|
|
577
632
|
) {
|
|
578
633
|
const isRequired = requiredFields.has(field);
|
|
579
|
-
const label = field.
|
|
634
|
+
const label = field.replaceAll(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
|
|
580
635
|
const displayLabel = label + (isRequired ? " *" : "");
|
|
581
636
|
const hasVal = value !== undefined && value !== "" && value !== false;
|
|
582
|
-
const onClear = () =>
|
|
583
|
-
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, undefined));
|
|
637
|
+
const onClear = () => transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field));
|
|
584
638
|
|
|
585
639
|
if (entry.type === "boolean") {
|
|
586
640
|
return renderFieldRow({
|
|
587
|
-
prop: field,
|
|
588
|
-
label: displayLabel,
|
|
589
641
|
hasValue: hasVal,
|
|
642
|
+
label: displayLabel,
|
|
590
643
|
onClear,
|
|
644
|
+
prop: field,
|
|
591
645
|
widget: html`
|
|
592
646
|
<sp-checkbox
|
|
593
647
|
size="s"
|
|
594
|
-
.checked=${live(
|
|
648
|
+
.checked=${live(Boolean(value))}
|
|
595
649
|
@change=${(e: Event) =>
|
|
596
650
|
transactDoc(activeTab.value, (t) =>
|
|
597
651
|
mutateUpdateFrontmatter(
|
|
@@ -608,10 +662,10 @@ function renderFmField(
|
|
|
608
662
|
if (entry.type === "array") {
|
|
609
663
|
const display = Array.isArray(value) ? value.join(", ") : (value as string) || "";
|
|
610
664
|
return renderFieldRow({
|
|
611
|
-
prop: field,
|
|
612
|
-
label: displayLabel,
|
|
613
665
|
hasValue: hasVal,
|
|
666
|
+
label: displayLabel,
|
|
614
667
|
onClear,
|
|
668
|
+
prop: field,
|
|
615
669
|
widget: spTextField(
|
|
616
670
|
`fm:${field}`,
|
|
617
671
|
display,
|
|
@@ -631,10 +685,10 @@ function renderFmField(
|
|
|
631
685
|
|
|
632
686
|
if (Array.isArray(entry.enum)) {
|
|
633
687
|
return renderFieldRow({
|
|
634
|
-
prop: field,
|
|
635
|
-
label: displayLabel,
|
|
636
688
|
hasValue: hasVal,
|
|
689
|
+
label: displayLabel,
|
|
637
690
|
onClear,
|
|
691
|
+
prop: field,
|
|
638
692
|
widget: html`
|
|
639
693
|
<sp-picker
|
|
640
694
|
size="s"
|
|
@@ -652,10 +706,10 @@ function renderFmField(
|
|
|
652
706
|
|
|
653
707
|
if (entry.format === "image") {
|
|
654
708
|
return renderFieldRow({
|
|
655
|
-
prop: field,
|
|
656
|
-
label: displayLabel,
|
|
657
709
|
hasValue: hasVal,
|
|
710
|
+
label: displayLabel,
|
|
658
711
|
onClear,
|
|
712
|
+
prop: field,
|
|
659
713
|
widget: renderMediaPicker(field, value as string, (v: string) =>
|
|
660
714
|
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
|
|
661
715
|
),
|
|
@@ -664,10 +718,10 @@ function renderFmField(
|
|
|
664
718
|
|
|
665
719
|
if (entry.type === "number") {
|
|
666
720
|
return renderFieldRow({
|
|
667
|
-
prop: field,
|
|
668
|
-
label: displayLabel,
|
|
669
721
|
hasValue: hasVal,
|
|
722
|
+
label: displayLabel,
|
|
670
723
|
onClear,
|
|
724
|
+
prop: field,
|
|
671
725
|
widget: spNumberField(value !== undefined ? Number(value) : undefined, (n) =>
|
|
672
726
|
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, n)),
|
|
673
727
|
),
|
|
@@ -675,10 +729,10 @@ function renderFmField(
|
|
|
675
729
|
}
|
|
676
730
|
|
|
677
731
|
return renderFieldRow({
|
|
678
|
-
prop: field,
|
|
679
|
-
label: displayLabel,
|
|
680
732
|
hasValue: hasVal,
|
|
733
|
+
label: displayLabel,
|
|
681
734
|
onClear,
|
|
735
|
+
prop: field,
|
|
682
736
|
widget: spTextField(
|
|
683
737
|
`fm:${field}`,
|
|
684
738
|
(value as string) || "",
|