@jxsuite/studio 0.23.1 → 0.24.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.js +2231 -715
- package/dist/studio.js.map +121 -104
- package/package.json +8 -7
- package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
- package/src/browse/{browse.js → browse.ts} +110 -85
- package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
- package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
- package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
- package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
- package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
- package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
- package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
- package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
- package/src/editor/{context-menu.js → context-menu.ts} +38 -36
- package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
- package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
- package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
- package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
- package/src/editor/{inline-format.js → inline-format.ts} +46 -51
- package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
- package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
- package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
- package/src/files/{components.js → components.ts} +12 -15
- package/src/files/{file-ops.js → file-ops.ts} +36 -29
- package/src/files/{files.js → files.ts} +102 -84
- package/src/github/{github-auth.js → github-auth.ts} +3 -3
- package/src/github/{github-publish.js → github-publish.ts} +20 -18
- package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
- package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
- package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
- package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
- package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
- package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
- package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
- package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
- package/src/panels/dnd.ts +417 -0
- package/src/panels/{editors.js → editors.ts} +43 -50
- package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
- package/src/panels/{events-panel.js → events-panel.ts} +33 -25
- package/src/panels/{git-panel.js → git-panel.ts} +97 -83
- package/src/panels/{head-panel.js → head-panel.ts} +117 -139
- package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
- package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
- package/src/panels/{left-panel.js → left-panel.ts} +56 -63
- package/src/panels/{overlays.js → overlays.ts} +28 -36
- package/src/panels/{panel-events.js → panel-events.ts} +53 -51
- package/src/panels/{preview-render.js → preview-render.ts} +20 -15
- package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
- package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
- package/src/panels/{quick-search.js → quick-search.ts} +16 -17
- package/src/panels/{right-panel.js → right-panel.ts} +41 -59
- package/src/panels/{shared.js → shared.ts} +7 -5
- package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
- package/src/panels/{statusbar.js → statusbar.ts} +14 -14
- package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
- package/src/panels/{style-panel.js → style-panel.ts} +235 -290
- package/src/panels/{style-utils.js → style-utils.ts} +32 -31
- package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
- package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
- package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
- package/src/panels/{toolbar.js → toolbar.ts} +68 -70
- package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
- package/src/{platform.js → platform.ts} +5 -3
- package/src/platforms/{devserver.js → devserver.ts} +53 -44
- package/src/{recent-projects.js → recent-projects.ts} +17 -12
- package/src/{resize-edges.js → resize-edges.ts} +18 -13
- package/src/services/{cem-export.js → cem-export.ts} +14 -7
- package/src/services/{code-services.js → code-services.ts} +29 -20
- package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
- package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
- package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
- package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
- package/src/settings/{general-settings.js → general-settings.ts} +29 -28
- package/src/settings/{head-editor.js → head-editor.ts} +37 -35
- package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
- package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
- package/src/{site-context.js → site-context.ts} +35 -32
- package/src/{state.js → state.ts} +95 -78
- package/src/{store.js → store.ts} +60 -63
- package/src/{studio.js → studio.ts} +117 -119
- package/src/tabs/tab.ts +196 -0
- package/src/tabs/{transact.js → transact.ts} +107 -60
- package/src/types.ts +185 -0
- package/src/ui/{button-group.js → button-group.ts} +21 -21
- package/src/ui/{color-selector.js → color-selector.ts} +44 -37
- package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
- package/src/ui/{field-row.js → field-row.ts} +19 -2
- package/src/ui/{icons.js → icons.ts} +2 -2
- package/src/ui/{layers.js → layers.ts} +33 -24
- package/src/ui/{media-picker.js → media-picker.ts} +13 -9
- package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
- package/src/ui/{spectrum.js → spectrum.ts} +4 -4
- package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
- package/src/ui/{value-selector.js → value-selector.ts} +28 -28
- package/src/ui/{widgets.js → widgets.ts} +62 -15
- package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
- package/src/utils/{edit-display.js → edit-display.ts} +23 -20
- package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
- package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
- package/src/utils/{studio-utils.js → studio-utils.ts} +38 -26
- package/src/{view.js → view.ts} +53 -55
- package/src/workspace/{workspace.js → workspace.ts} +44 -33
- package/src/panels/dnd.js +0 -426
- package/src/tabs/tab.js +0 -192
- /package/src/{reactivity.js → reactivity.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Head panel — Page meta, OpenGraph, Frontmatter, and custom `$head` entries.
|
|
3
4
|
*
|
|
@@ -7,31 +8,46 @@
|
|
|
7
8
|
|
|
8
9
|
import { html, nothing } from "lit-html";
|
|
9
10
|
import { live } from "lit-html/directives/live.js";
|
|
10
|
-
import { renderFieldRow } from "../ui/field-row
|
|
11
|
-
import { renderMediaPicker } from "../ui/media-picker
|
|
12
|
-
import { debouncedStyleCommit, renderOnly, projectState } from "../store
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
11
|
+
import { renderFieldRow } from "../ui/field-row";
|
|
12
|
+
import { renderMediaPicker } from "../ui/media-picker";
|
|
13
|
+
import { debouncedStyleCommit, renderOnly, projectState } from "../store";
|
|
14
|
+
import type { JsonValue, DirEntry } from "../types";
|
|
15
|
+
import { activeTab } from "../workspace/workspace";
|
|
16
|
+
import { transactDoc, mutateUpdateFrontmatter } from "../tabs/transact";
|
|
17
|
+
import { findContentTypeSchema } from "../utils/studio-utils";
|
|
18
|
+
import { isGoogleFontEntry, isGoogleFontPreconnect } from "../utils/google-fonts";
|
|
19
|
+
import { invalidateLayoutCache } from "../site-context";
|
|
20
|
+
import { getPlatform } from "../platform";
|
|
21
|
+
|
|
22
|
+
import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
|
|
23
|
+
|
|
24
|
+
interface MetaField {
|
|
25
|
+
label: string;
|
|
26
|
+
attr: "name" | "property";
|
|
27
|
+
key: string;
|
|
28
|
+
multiline?: boolean;
|
|
29
|
+
media?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface FmSchemaEntry {
|
|
33
|
+
type?: string;
|
|
34
|
+
enum?: string[];
|
|
35
|
+
format?: string;
|
|
36
|
+
properties?: Record<string, unknown>;
|
|
37
|
+
}
|
|
19
38
|
|
|
20
39
|
// ─── Layout picker ──────────────────────────────────────────────────────────
|
|
21
40
|
|
|
22
41
|
/** @type {{ name: string; path: string }[] | null} */
|
|
23
|
-
let layoutEntries = null;
|
|
42
|
+
let layoutEntries: { name: string; path: string }[] | null = null;
|
|
24
43
|
|
|
25
44
|
async function loadLayoutEntries() {
|
|
26
45
|
try {
|
|
27
46
|
const platform = getPlatform();
|
|
28
47
|
const listing = await platform.listDirectory("layouts");
|
|
29
48
|
layoutEntries = listing
|
|
30
|
-
.filter(
|
|
31
|
-
|
|
32
|
-
f.type === "file" && f.name.endsWith(".json"),
|
|
33
|
-
)
|
|
34
|
-
.map((/** @type {{ type: string; name: string }} */ f) => ({
|
|
49
|
+
.filter((f: DirEntry) => f.type === "file" && f.name.endsWith(".json"))
|
|
50
|
+
.map((f: DirEntry) => ({
|
|
35
51
|
name: f.name
|
|
36
52
|
.replace(/\.json$/, "")
|
|
37
53
|
.replace(/[-_]+/g, " ")
|
|
@@ -50,33 +66,12 @@ export function invalidateLayoutPickerCache() {
|
|
|
50
66
|
|
|
51
67
|
// ─── Field definitions ───────────────────────────────────────────────────
|
|
52
68
|
|
|
53
|
-
|
|
54
|
-
* @typedef {{
|
|
55
|
-
* label: string;
|
|
56
|
-
* attr: "name" | "property";
|
|
57
|
-
* key: string;
|
|
58
|
-
* multiline?: boolean;
|
|
59
|
-
* media?: boolean;
|
|
60
|
-
* }} MetaField
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @typedef {{
|
|
65
|
-
* type?: string;
|
|
66
|
-
* enum?: string[];
|
|
67
|
-
* format?: string;
|
|
68
|
-
* properties?: Record<string, unknown>;
|
|
69
|
-
* }} FmSchemaEntry
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
/** @type {MetaField[]} */
|
|
73
|
-
const PAGE_FIELDS = [
|
|
69
|
+
const PAGE_FIELDS: MetaField[] = [
|
|
74
70
|
{ label: "Description", attr: "name", key: "description" },
|
|
75
71
|
{ label: "Viewport", attr: "name", key: "viewport" },
|
|
76
72
|
];
|
|
77
73
|
|
|
78
|
-
|
|
79
|
-
const OG_FIELDS = [
|
|
74
|
+
const OG_FIELDS: MetaField[] = [
|
|
80
75
|
{ label: "Title", attr: "property", key: "og:title" },
|
|
81
76
|
{ label: "Description", attr: "property", key: "og:description", multiline: true },
|
|
82
77
|
{ label: "Image", attr: "property", key: "og:image", media: true },
|
|
@@ -99,11 +94,9 @@ const RESERVED_FM_KEYS = new Set(["title"]);
|
|
|
99
94
|
* @param {string} key
|
|
100
95
|
* @returns {JxHeadEntry | undefined}
|
|
101
96
|
*/
|
|
102
|
-
function findMetaEntry(head, attr, key) {
|
|
97
|
+
function findMetaEntry(head: JxHeadEntry[], attr: "name" | "property", key: string) {
|
|
103
98
|
if (!head) return undefined;
|
|
104
|
-
return head.find(
|
|
105
|
-
(/** @type {JxHeadEntry} */ e) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
|
|
106
|
-
);
|
|
99
|
+
return head.find((e: JxHeadEntry) => e?.tagName === "meta" && e?.attributes?.[attr] === key);
|
|
107
100
|
}
|
|
108
101
|
|
|
109
102
|
/**
|
|
@@ -113,11 +106,9 @@ function findMetaEntry(head, attr, key) {
|
|
|
113
106
|
* @param {string} rel
|
|
114
107
|
* @returns {JxHeadEntry | undefined}
|
|
115
108
|
*/
|
|
116
|
-
function findLinkEntry(head, rel) {
|
|
109
|
+
function findLinkEntry(head: JxHeadEntry[], rel: string) {
|
|
117
110
|
if (!head) return undefined;
|
|
118
|
-
return head.find(
|
|
119
|
-
(/** @type {JxHeadEntry} */ e) => e?.tagName === "link" && e?.attributes?.rel === rel,
|
|
120
|
-
);
|
|
111
|
+
return head.find((e: JxHeadEntry) => e?.tagName === "link" && e?.attributes?.rel === rel);
|
|
121
112
|
}
|
|
122
113
|
|
|
123
114
|
/**
|
|
@@ -126,7 +117,7 @@ function findLinkEntry(head, rel) {
|
|
|
126
117
|
* @param {JxHeadEntry} entry
|
|
127
118
|
* @returns {boolean}
|
|
128
119
|
*/
|
|
129
|
-
function isManagedEntry(entry) {
|
|
120
|
+
function isManagedEntry(entry: JxHeadEntry) {
|
|
130
121
|
if (!entry?.tagName) return false;
|
|
131
122
|
// Managed meta tags
|
|
132
123
|
if (entry.tagName === "meta") {
|
|
@@ -147,10 +138,10 @@ function isManagedEntry(entry) {
|
|
|
147
138
|
* @param {string} key
|
|
148
139
|
* @param {string} content
|
|
149
140
|
*/
|
|
150
|
-
function upsertMeta(doc, attr, key, content) {
|
|
141
|
+
function upsertMeta(doc: JxMutableNode, attr: "name" | "property", key: string, content: string) {
|
|
151
142
|
if (!doc.$head) doc.$head = [];
|
|
152
143
|
const idx = doc.$head.findIndex(
|
|
153
|
-
(
|
|
144
|
+
(e: JxHeadEntry) => e?.tagName === "meta" && e?.attributes?.[attr] === key,
|
|
154
145
|
);
|
|
155
146
|
if (content) {
|
|
156
147
|
const entry = { tagName: "meta", attributes: { [attr]: key, content } };
|
|
@@ -171,10 +162,10 @@ function upsertMeta(doc, attr, key, content) {
|
|
|
171
162
|
* @param {string} rel
|
|
172
163
|
* @param {string} href
|
|
173
164
|
*/
|
|
174
|
-
function upsertLink(doc, rel, href) {
|
|
165
|
+
function upsertLink(doc: JxMutableNode, rel: string, href: string) {
|
|
175
166
|
if (!doc.$head) doc.$head = [];
|
|
176
167
|
const idx = doc.$head.findIndex(
|
|
177
|
-
(
|
|
168
|
+
(e: JxHeadEntry) => e?.tagName === "link" && e?.attributes?.rel === rel,
|
|
178
169
|
);
|
|
179
170
|
if (href) {
|
|
180
171
|
const entry = { tagName: "link", attributes: { rel, href } };
|
|
@@ -194,7 +185,7 @@ function upsertLink(doc, rel, href) {
|
|
|
194
185
|
* @param {JxHeadEntry} entry
|
|
195
186
|
* @returns {string}
|
|
196
187
|
*/
|
|
197
|
-
function entryLabel(entry) {
|
|
188
|
+
function entryLabel(entry: JxHeadEntry) {
|
|
198
189
|
if (!entry?.tagName) return "unknown";
|
|
199
190
|
const a = entry.attributes ?? {};
|
|
200
191
|
if (a.name) return `<meta name="${String(a.name)}">`;
|
|
@@ -211,7 +202,7 @@ function entryLabel(entry) {
|
|
|
211
202
|
* @param {JxHeadEntry} entry
|
|
212
203
|
* @returns {string}
|
|
213
204
|
*/
|
|
214
|
-
function entryValue(entry) {
|
|
205
|
+
function entryValue(entry: JxHeadEntry) {
|
|
215
206
|
const a = entry?.attributes ?? {};
|
|
216
207
|
return String(a.content ?? a.href ?? a.src ?? entry?.textContent ?? "");
|
|
217
208
|
}
|
|
@@ -226,7 +217,11 @@ function entryValue(entry) {
|
|
|
226
217
|
* @param {(fn: (doc: JxMutableNode) => void) => void} applyMutation
|
|
227
218
|
* @returns {import("lit-html").TemplateResult}
|
|
228
219
|
*/
|
|
229
|
-
function renderMetaFieldRow(
|
|
220
|
+
function renderMetaFieldRow(
|
|
221
|
+
field: MetaField,
|
|
222
|
+
head: JxHeadEntry[],
|
|
223
|
+
applyMutation: (fn: (doc: JxMutableNode) => void) => void,
|
|
224
|
+
) {
|
|
230
225
|
const entry = findMetaEntry(head, field.attr, field.key);
|
|
231
226
|
const val = String(entry?.attributes?.content ?? "");
|
|
232
227
|
|
|
@@ -235,12 +230,9 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
235
230
|
prop: field.key,
|
|
236
231
|
label: field.label,
|
|
237
232
|
hasValue: !!val,
|
|
238
|
-
onClear: () =>
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
applyMutation((/** @type {JxMutableNode} */ d) =>
|
|
242
|
-
upsertMeta(d, field.attr, field.key, v || ""),
|
|
243
|
-
);
|
|
233
|
+
onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
|
|
234
|
+
widget: renderMediaPicker(field.key, val, (v: string) => {
|
|
235
|
+
applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, v || ""));
|
|
244
236
|
}),
|
|
245
237
|
});
|
|
246
238
|
}
|
|
@@ -252,11 +244,9 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
252
244
|
multiline
|
|
253
245
|
.value=${live(val)}
|
|
254
246
|
placeholder="${field.label}…"
|
|
255
|
-
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (
|
|
256
|
-
const content =
|
|
257
|
-
applyMutation((
|
|
258
|
-
upsertMeta(d, field.attr, field.key, content),
|
|
259
|
-
);
|
|
247
|
+
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (e: Event) => {
|
|
248
|
+
const content = (e.target as HTMLInputElement).value?.trim() ?? "";
|
|
249
|
+
applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, content));
|
|
260
250
|
})}
|
|
261
251
|
></sp-textfield>
|
|
262
252
|
`
|
|
@@ -267,11 +257,9 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
267
257
|
placeholder=${field.key === "viewport"
|
|
268
258
|
? "width=device-width, initial-scale=1"
|
|
269
259
|
: `${field.label}…`}
|
|
270
|
-
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (
|
|
271
|
-
const content =
|
|
272
|
-
applyMutation((
|
|
273
|
-
upsertMeta(d, field.attr, field.key, content),
|
|
274
|
-
);
|
|
260
|
+
@input=${debouncedStyleCommit(`head:${field.key}`, 400, (e: Event) => {
|
|
261
|
+
const content = (e.target as HTMLInputElement).value?.trim() ?? "";
|
|
262
|
+
applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, content));
|
|
275
263
|
})}
|
|
276
264
|
></sp-textfield>
|
|
277
265
|
`;
|
|
@@ -280,8 +268,7 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
280
268
|
prop: field.key,
|
|
281
269
|
label: field.label,
|
|
282
270
|
hasValue: !!val,
|
|
283
|
-
onClear: () =>
|
|
284
|
-
applyMutation((/** @type {JxMutableNode} */ d) => upsertMeta(d, field.attr, field.key, "")),
|
|
271
|
+
onClear: () => applyMutation((d: JxMutableNode) => upsertMeta(d, field.attr, field.key, "")),
|
|
285
272
|
widget,
|
|
286
273
|
});
|
|
287
274
|
}
|
|
@@ -296,7 +283,15 @@ function renderMetaFieldRow(field, head, applyMutation) {
|
|
|
296
283
|
* }} ctx
|
|
297
284
|
* @returns {import("lit-html").TemplateResult}
|
|
298
285
|
*/
|
|
299
|
-
export function renderHeadTemplate({
|
|
286
|
+
export function renderHeadTemplate({
|
|
287
|
+
document: doc,
|
|
288
|
+
applyMutation,
|
|
289
|
+
renderLeftPanel,
|
|
290
|
+
}: {
|
|
291
|
+
document: JxMutableNode;
|
|
292
|
+
applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
293
|
+
renderLeftPanel: () => void;
|
|
294
|
+
}) {
|
|
300
295
|
const head = doc.$head ?? [];
|
|
301
296
|
const title = doc.title ?? "";
|
|
302
297
|
|
|
@@ -306,8 +301,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
306
301
|
|
|
307
302
|
// Custom entries not managed by structured forms, fonts, or preconnects
|
|
308
303
|
const customEntries = head.filter(
|
|
309
|
-
(
|
|
310
|
-
!isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
|
|
304
|
+
(e: JxHeadEntry) => !isManagedEntry(e) && !isGoogleFontEntry(e) && !isGoogleFontPreconnect(e),
|
|
311
305
|
);
|
|
312
306
|
|
|
313
307
|
// Frontmatter section (content mode only)
|
|
@@ -321,8 +315,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
321
315
|
projectState?.isSiteProject &&
|
|
322
316
|
(tab.documentPath.startsWith("pages/") || tab.documentPath.startsWith("./pages/"));
|
|
323
317
|
|
|
324
|
-
|
|
325
|
-
let layoutSection = nothing;
|
|
318
|
+
let layoutSection: import("lit-html").TemplateResult | symbol = nothing;
|
|
326
319
|
if (isPage) {
|
|
327
320
|
if (layoutEntries === null) {
|
|
328
321
|
loadLayoutEntries();
|
|
@@ -336,7 +329,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
336
329
|
.replace(/^\.\/layouts\//, "")
|
|
337
330
|
.replace(/\.json$/, "")
|
|
338
331
|
.replace(/[-_]+/g, " ")
|
|
339
|
-
.replace(/\b\w/g, (
|
|
332
|
+
.replace(/\b\w/g, (c: string) => c.toUpperCase())
|
|
340
333
|
: "";
|
|
341
334
|
|
|
342
335
|
layoutSection = html`
|
|
@@ -350,16 +343,16 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
350
343
|
label: "Layout",
|
|
351
344
|
hasValue: currentLayout !== undefined,
|
|
352
345
|
onClear: () =>
|
|
353
|
-
applyMutation((
|
|
346
|
+
applyMutation((d: JxMutableNode) => {
|
|
354
347
|
delete d.$layout;
|
|
355
348
|
}),
|
|
356
349
|
widget: html`
|
|
357
350
|
<sp-picker
|
|
358
351
|
size="s"
|
|
359
352
|
value=${displayValue}
|
|
360
|
-
@change=${(
|
|
361
|
-
const val =
|
|
362
|
-
applyMutation((
|
|
353
|
+
@change=${(e: Event) => {
|
|
354
|
+
const val = (e.target as HTMLInputElement).value;
|
|
355
|
+
applyMutation((d: JxMutableNode) => {
|
|
363
356
|
if (val === "__default__") delete d.$layout;
|
|
364
357
|
else if (val === "__none__") d.$layout = false;
|
|
365
358
|
else d.$layout = val;
|
|
@@ -373,7 +366,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
373
366
|
<sp-menu-item value="__none__">None</sp-menu-item>
|
|
374
367
|
<sp-menu-divider></sp-menu-divider>
|
|
375
368
|
${layoutEntries.map(
|
|
376
|
-
(
|
|
369
|
+
(l: { name: string; path: string }) =>
|
|
377
370
|
html`<sp-menu-item value=${l.path}>${l.name}</sp-menu-item>`,
|
|
378
371
|
)}
|
|
379
372
|
</sp-picker>
|
|
@@ -400,7 +393,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
400
393
|
label: "Title",
|
|
401
394
|
hasValue: !!title,
|
|
402
395
|
onClear: () =>
|
|
403
|
-
applyMutation((
|
|
396
|
+
applyMutation((d: JxMutableNode) => {
|
|
404
397
|
delete d.title;
|
|
405
398
|
}),
|
|
406
399
|
widget: html`
|
|
@@ -408,9 +401,9 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
408
401
|
size="s"
|
|
409
402
|
.value=${live(title)}
|
|
410
403
|
placeholder="Page title…"
|
|
411
|
-
@input=${debouncedStyleCommit("head:title", 400, (
|
|
412
|
-
const val =
|
|
413
|
-
applyMutation((
|
|
404
|
+
@input=${debouncedStyleCommit("head:title", 400, (e: Event) => {
|
|
405
|
+
const val = (e.target as HTMLInputElement).value?.trim() ?? "";
|
|
406
|
+
applyMutation((d: JxMutableNode) => {
|
|
414
407
|
if (val) d.title = val;
|
|
415
408
|
else delete d.title;
|
|
416
409
|
});
|
|
@@ -423,10 +416,9 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
423
416
|
prop: "icon",
|
|
424
417
|
label: "Icon",
|
|
425
418
|
hasValue: !!iconHref,
|
|
426
|
-
onClear: () =>
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
applyMutation((/** @type {JxMutableNode} */ d) => upsertLink(d, "icon", v || ""));
|
|
419
|
+
onClear: () => applyMutation((d: JxMutableNode) => upsertLink(d, "icon", "")),
|
|
420
|
+
widget: renderMediaPicker("icon", iconHref, (v: string) => {
|
|
421
|
+
applyMutation((d: JxMutableNode) => upsertLink(d, "icon", v || ""));
|
|
430
422
|
}),
|
|
431
423
|
})}
|
|
432
424
|
</div>
|
|
@@ -451,7 +443,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
451
443
|
${customEntries.length > 0
|
|
452
444
|
? html`
|
|
453
445
|
<div class="imports-list">
|
|
454
|
-
${customEntries.map((
|
|
446
|
+
${customEntries.map((entry: JxHeadEntry) => {
|
|
455
447
|
const label = entryLabel(entry);
|
|
456
448
|
const value = entryValue(entry);
|
|
457
449
|
return html`
|
|
@@ -463,7 +455,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
463
455
|
size="xs"
|
|
464
456
|
title="Remove"
|
|
465
457
|
@click=${() => {
|
|
466
|
-
applyMutation((
|
|
458
|
+
applyMutation((d: JxMutableNode) => {
|
|
467
459
|
if (!d.$head) return;
|
|
468
460
|
const idx = d.$head.indexOf(entry);
|
|
469
461
|
if (idx >= 0) d.$head.splice(idx, 1);
|
|
@@ -497,17 +489,11 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
497
489
|
quiet
|
|
498
490
|
size="xs"
|
|
499
491
|
title="Add tag"
|
|
500
|
-
@click=${(
|
|
501
|
-
const form =
|
|
502
|
-
const tagPicker =
|
|
503
|
-
|
|
504
|
-
);
|
|
505
|
-
const attrField = /** @type {HTMLInputElement | null} */ (
|
|
506
|
-
form?.querySelector(".head-add-attr")
|
|
507
|
-
);
|
|
508
|
-
const valField = /** @type {HTMLInputElement | null} */ (
|
|
509
|
-
form?.querySelector(".head-add-val")
|
|
510
|
-
);
|
|
492
|
+
@click=${(e: Event) => {
|
|
493
|
+
const form = (e.target as HTMLElement).closest(".head-add-form");
|
|
494
|
+
const tagPicker = form?.querySelector(".head-add-tag") as HTMLInputElement | null;
|
|
495
|
+
const attrField = form?.querySelector(".head-add-attr") as HTMLInputElement | null;
|
|
496
|
+
const valField = form?.querySelector(".head-add-val") as HTMLInputElement | null;
|
|
511
497
|
const tagName = tagPicker?.value || "meta";
|
|
512
498
|
const attrKey = attrField?.value?.trim();
|
|
513
499
|
const attrVal = valField?.value?.trim();
|
|
@@ -515,8 +501,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
515
501
|
if (attrField) attrField.value = "";
|
|
516
502
|
if (valField) valField.value = "";
|
|
517
503
|
|
|
518
|
-
|
|
519
|
-
const entry = { tagName, attributes: {} };
|
|
504
|
+
const entry: JxHeadEntry = { tagName, attributes: {} };
|
|
520
505
|
if (tagName === "meta") {
|
|
521
506
|
entry.attributes = { name: attrKey, content: attrVal };
|
|
522
507
|
} else if (tagName === "link") {
|
|
@@ -525,7 +510,7 @@ export function renderHeadTemplate({ document: doc, applyMutation, renderLeftPan
|
|
|
525
510
|
entry.attributes = { [attrKey]: attrVal };
|
|
526
511
|
}
|
|
527
512
|
|
|
528
|
-
applyMutation((
|
|
513
|
+
applyMutation((d: JxMutableNode) => {
|
|
529
514
|
if (!d.$head) d.$head = [];
|
|
530
515
|
d.$head.push(entry);
|
|
531
516
|
});
|
|
@@ -548,8 +533,11 @@ function renderFrontmatterSection() {
|
|
|
548
533
|
|
|
549
534
|
const fm = tab.doc.content?.frontmatter || {};
|
|
550
535
|
const col = findContentTypeSchema(tab.documentPath, projectState?.projectConfig);
|
|
551
|
-
const
|
|
552
|
-
|
|
536
|
+
const schema = col?.schema as
|
|
537
|
+
| { properties?: Record<string, FmSchemaEntry>; required?: string[] }
|
|
538
|
+
| undefined;
|
|
539
|
+
const schemaProps = schema?.properties;
|
|
540
|
+
const requiredFields = new Set(schema?.required || []);
|
|
553
541
|
|
|
554
542
|
/** @type {{ field: string; entry: FmSchemaEntry; value: JsonValue }[]} */
|
|
555
543
|
const fields = [];
|
|
@@ -558,7 +546,7 @@ function renderFrontmatterSection() {
|
|
|
558
546
|
/** @type {Record<string, FmSchemaEntry>} */ (schemaProps),
|
|
559
547
|
)) {
|
|
560
548
|
if (RESERVED_FM_KEYS.has(field)) continue;
|
|
561
|
-
fields.push({ field, entry: fieldSchema, value:
|
|
549
|
+
fields.push({ field, entry: fieldSchema, value: fm[field] as JsonValue });
|
|
562
550
|
}
|
|
563
551
|
for (const [field, value] of Object.entries(fm)) {
|
|
564
552
|
if (schemaProps[field] || field.startsWith("$") || RESERVED_FM_KEYS.has(field)) continue;
|
|
@@ -589,17 +577,17 @@ function renderFrontmatterSection() {
|
|
|
589
577
|
>
|
|
590
578
|
</div>
|
|
591
579
|
<div class="head-section-body">
|
|
592
|
-
${fields.map((f) => renderFmField(f.field, f.entry, f.value, requiredFields))}
|
|
580
|
+
${fields.map((f) => renderFmField(f.field, f.entry, f.value as JsonValue, requiredFields))}
|
|
593
581
|
</div>
|
|
594
582
|
</div>
|
|
595
583
|
`;
|
|
596
584
|
}
|
|
597
585
|
|
|
598
586
|
function renderFmField(
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
587
|
+
field: string,
|
|
588
|
+
entry: FmSchemaEntry,
|
|
589
|
+
value: JsonValue,
|
|
590
|
+
requiredFields: Set<string>,
|
|
603
591
|
) {
|
|
604
592
|
const isRequired = requiredFields.has(field);
|
|
605
593
|
const label = field.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase());
|
|
@@ -618,12 +606,12 @@ function renderFmField(
|
|
|
618
606
|
<sp-checkbox
|
|
619
607
|
size="s"
|
|
620
608
|
.checked=${live(!!value)}
|
|
621
|
-
@change=${(
|
|
609
|
+
@change=${(e: Event) =>
|
|
622
610
|
transactDoc(activeTab.value, (t) =>
|
|
623
611
|
mutateUpdateFrontmatter(
|
|
624
612
|
t,
|
|
625
613
|
field,
|
|
626
|
-
|
|
614
|
+
(e.target as HTMLInputElement).checked || undefined,
|
|
627
615
|
),
|
|
628
616
|
)}
|
|
629
617
|
></sp-checkbox>
|
|
@@ -643,11 +631,11 @@ function renderFmField(
|
|
|
643
631
|
size="s"
|
|
644
632
|
placeholder="comma, separated"
|
|
645
633
|
.value=${live(display)}
|
|
646
|
-
@input=${debouncedStyleCommit(`fm:${field}`, 400, (
|
|
647
|
-
const arr =
|
|
648
|
-
?
|
|
634
|
+
@input=${debouncedStyleCommit(`fm:${field}`, 400, (e: Event) => {
|
|
635
|
+
const arr = (e.target as HTMLInputElement).value
|
|
636
|
+
? (e.target as HTMLInputElement).value
|
|
649
637
|
.split(",")
|
|
650
|
-
.map((
|
|
638
|
+
.map((s: string) => s.trim())
|
|
651
639
|
.filter(Boolean)
|
|
652
640
|
: undefined;
|
|
653
641
|
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, arr));
|
|
@@ -667,18 +655,12 @@ function renderFmField(
|
|
|
667
655
|
<sp-picker
|
|
668
656
|
size="s"
|
|
669
657
|
.value=${live(value || "")}
|
|
670
|
-
@change=${(
|
|
658
|
+
@change=${(e: Event) =>
|
|
671
659
|
transactDoc(activeTab.value, (t) =>
|
|
672
|
-
mutateUpdateFrontmatter(
|
|
673
|
-
t,
|
|
674
|
-
field,
|
|
675
|
-
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
676
|
-
),
|
|
660
|
+
mutateUpdateFrontmatter(t, field, (e.target as HTMLInputElement).value || undefined),
|
|
677
661
|
)}
|
|
678
662
|
>
|
|
679
|
-
${entry.enum.map(
|
|
680
|
-
(/** @type {string} */ opt) => html`<sp-menu-item value=${opt}>${opt}</sp-menu-item>`,
|
|
681
|
-
)}
|
|
663
|
+
${entry.enum.map((opt: string) => html`<sp-menu-item value=${opt}>${opt}</sp-menu-item>`)}
|
|
682
664
|
</sp-picker>
|
|
683
665
|
`,
|
|
684
666
|
});
|
|
@@ -690,7 +672,7 @@ function renderFmField(
|
|
|
690
672
|
label: displayLabel,
|
|
691
673
|
hasValue: hasVal,
|
|
692
674
|
onClear,
|
|
693
|
-
widget: renderMediaPicker(field,
|
|
675
|
+
widget: renderMediaPicker(field, value as string, (v: string) =>
|
|
694
676
|
transactDoc(activeTab.value, (t) => mutateUpdateFrontmatter(t, field, v || undefined)),
|
|
695
677
|
),
|
|
696
678
|
});
|
|
@@ -707,8 +689,8 @@ function renderFmField(
|
|
|
707
689
|
size="s"
|
|
708
690
|
hide-stepper
|
|
709
691
|
.value=${live(value !== undefined ? Number(value) : undefined)}
|
|
710
|
-
@change=${debouncedStyleCommit(`fm:${field}`, 400, (
|
|
711
|
-
const v =
|
|
692
|
+
@change=${debouncedStyleCommit(`fm:${field}`, 400, (e: Event) => {
|
|
693
|
+
const v = (e.target as HTMLInputElement).value;
|
|
712
694
|
transactDoc(activeTab.value, (t) =>
|
|
713
695
|
mutateUpdateFrontmatter(t, field, isNaN(Number(v)) ? undefined : Number(v)),
|
|
714
696
|
);
|
|
@@ -728,13 +710,9 @@ function renderFmField(
|
|
|
728
710
|
size="s"
|
|
729
711
|
placeholder=${entry.format === "date" ? "YYYY-MM-DD" : ""}
|
|
730
712
|
.value=${live(value || "")}
|
|
731
|
-
@input=${debouncedStyleCommit(`fm:${field}`, 400, (
|
|
713
|
+
@input=${debouncedStyleCommit(`fm:${field}`, 400, (e: Event) => {
|
|
732
714
|
transactDoc(activeTab.value, (t) =>
|
|
733
|
-
mutateUpdateFrontmatter(
|
|
734
|
-
t,
|
|
735
|
-
field,
|
|
736
|
-
/** @type {HTMLInputElement} */ (e.target).value || undefined,
|
|
737
|
-
),
|
|
715
|
+
mutateUpdateFrontmatter(t, field, (e.target as HTMLInputElement).value || undefined),
|
|
738
716
|
);
|
|
739
717
|
})}
|
|
740
718
|
></sp-textfield>
|