@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
|
@@ -45,7 +45,9 @@ export const FORMAT_OPTIONS = ["", "image", "date", "color"];
|
|
|
45
45
|
* @returns {string}
|
|
46
46
|
*/
|
|
47
47
|
export function detectFieldType(schema: SchemaProperty) {
|
|
48
|
-
if (schema.$ref)
|
|
48
|
+
if (schema.$ref) {
|
|
49
|
+
return "reference";
|
|
50
|
+
}
|
|
49
51
|
return schema.type || "string";
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -56,7 +58,9 @@ export function detectFieldType(schema: SchemaProperty) {
|
|
|
56
58
|
* @returns {string}
|
|
57
59
|
*/
|
|
58
60
|
export function detectFieldFormat(schema: SchemaProperty) {
|
|
59
|
-
if (schema.type === "array" && schema.items?.format)
|
|
61
|
+
if (schema.type === "array" && schema.items?.format) {
|
|
62
|
+
return schema.items.format;
|
|
63
|
+
}
|
|
60
64
|
return schema.format || "";
|
|
61
65
|
}
|
|
62
66
|
|
|
@@ -99,12 +103,17 @@ export function fieldCardTpl(
|
|
|
99
103
|
@change=${(e: Event) => {
|
|
100
104
|
const target = e.target as HTMLInputElement;
|
|
101
105
|
const newName = target.value.trim();
|
|
102
|
-
if (newName && newName !== fieldName)
|
|
103
|
-
|
|
106
|
+
if (newName && newName !== fieldName) {
|
|
107
|
+
handlers.onRename(fieldName, newName);
|
|
108
|
+
} else {
|
|
109
|
+
target.value = fieldName;
|
|
110
|
+
}
|
|
104
111
|
}}
|
|
105
112
|
@keydown=${(e: KeyboardEvent) => {
|
|
106
113
|
const target = e.target as HTMLInputElement;
|
|
107
|
-
if (e.key === "Enter")
|
|
114
|
+
if (e.key === "Enter") {
|
|
115
|
+
target.blur();
|
|
116
|
+
}
|
|
108
117
|
if (e.key === "Escape") {
|
|
109
118
|
target.value = fieldName;
|
|
110
119
|
target.blur();
|
|
@@ -114,7 +123,9 @@ export function fieldCardTpl(
|
|
|
114
123
|
${typePickerTpl(type, (newType) => handlers.onChangeType(fieldName, newType))}
|
|
115
124
|
${type === "string" || type === "array"
|
|
116
125
|
? formatPickerTpl(format, (f) => {
|
|
117
|
-
if (handlers.onChangeFormat)
|
|
126
|
+
if (handlers.onChangeFormat) {
|
|
127
|
+
handlers.onChangeFormat(fieldName, f);
|
|
128
|
+
}
|
|
118
129
|
})
|
|
119
130
|
: nothing}
|
|
120
131
|
<sp-switch
|
|
@@ -133,7 +144,7 @@ export function fieldCardTpl(
|
|
|
133
144
|
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
134
145
|
</sp-action-button>
|
|
135
146
|
</div>
|
|
136
|
-
${isRef && contentTypeNames.length
|
|
147
|
+
${isRef && contentTypeNames.length > 0
|
|
137
148
|
? html`
|
|
138
149
|
<div class="schema-field-ref-target">
|
|
139
150
|
<sp-picker
|
|
@@ -141,8 +152,9 @@ export function fieldCardTpl(
|
|
|
141
152
|
label="Target"
|
|
142
153
|
value=${refTarget}
|
|
143
154
|
@change=${(e: Event) => {
|
|
144
|
-
if (handlers.onChangeRefTarget)
|
|
155
|
+
if (handlers.onChangeRefTarget) {
|
|
145
156
|
handlers.onChangeRefTarget(fieldName, (e.target as HTMLInputElement).value);
|
|
157
|
+
}
|
|
146
158
|
}}
|
|
147
159
|
>
|
|
148
160
|
${contentTypeNames.map((n) => html`<sp-menu-item value=${n}>${n}</sp-menu-item>`)}
|
|
@@ -210,7 +222,9 @@ function nestedFieldCardTpl(
|
|
|
210
222
|
}}
|
|
211
223
|
@keydown=${(e: KeyboardEvent) => {
|
|
212
224
|
const target = e.target as HTMLInputElement;
|
|
213
|
-
if (e.key === "Enter")
|
|
225
|
+
if (e.key === "Enter") {
|
|
226
|
+
target.blur();
|
|
227
|
+
}
|
|
214
228
|
if (e.key === "Escape") {
|
|
215
229
|
target.value = childName;
|
|
216
230
|
target.blur();
|
|
@@ -218,21 +232,24 @@ function nestedFieldCardTpl(
|
|
|
218
232
|
}}
|
|
219
233
|
></sp-textfield>
|
|
220
234
|
${typePickerTpl(type, (newType) => {
|
|
221
|
-
if (handlers.onChangeNestedType)
|
|
235
|
+
if (handlers.onChangeNestedType) {
|
|
222
236
|
handlers.onChangeNestedType(parentName, childName, newType);
|
|
237
|
+
}
|
|
223
238
|
})}
|
|
224
239
|
${type === "string" || type === "array"
|
|
225
240
|
? formatPickerTpl(format, (f) => {
|
|
226
|
-
if (handlers.onChangeNestedFormat)
|
|
241
|
+
if (handlers.onChangeNestedFormat) {
|
|
227
242
|
handlers.onChangeNestedFormat(parentName, childName, f);
|
|
243
|
+
}
|
|
228
244
|
})
|
|
229
245
|
: nothing}
|
|
230
246
|
<sp-switch
|
|
231
247
|
size="s"
|
|
232
248
|
?checked=${isRequired}
|
|
233
249
|
@change=${() => {
|
|
234
|
-
if (handlers.onToggleNestedRequired)
|
|
250
|
+
if (handlers.onToggleNestedRequired) {
|
|
235
251
|
handlers.onToggleNestedRequired(parentName, childName);
|
|
252
|
+
}
|
|
236
253
|
}}
|
|
237
254
|
>
|
|
238
255
|
Req
|
|
@@ -242,7 +259,9 @@ function nestedFieldCardTpl(
|
|
|
242
259
|
quiet
|
|
243
260
|
title="Delete field"
|
|
244
261
|
@click=${() => {
|
|
245
|
-
if (handlers.onDeleteNested)
|
|
262
|
+
if (handlers.onDeleteNested) {
|
|
263
|
+
handlers.onDeleteNested(parentName, childName);
|
|
264
|
+
}
|
|
246
265
|
}}
|
|
247
266
|
>
|
|
248
267
|
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
@@ -276,8 +295,8 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
|
|
|
276
295
|
if (name && handlers.onAddNestedField) {
|
|
277
296
|
handlers.onAddNestedField(parentName, {
|
|
278
297
|
name,
|
|
279
|
-
type,
|
|
280
298
|
required: false,
|
|
299
|
+
type,
|
|
281
300
|
});
|
|
282
301
|
target.value = "";
|
|
283
302
|
}
|
|
@@ -301,10 +320,12 @@ function nestedAddFieldTpl(parentName: string, handlers: FieldHandlers) {
|
|
|
301
320
|
if (name && handlers.onAddNestedField) {
|
|
302
321
|
handlers.onAddNestedField(parentName, {
|
|
303
322
|
name,
|
|
304
|
-
type,
|
|
305
323
|
required: false,
|
|
324
|
+
type,
|
|
306
325
|
});
|
|
307
|
-
if (nameInput)
|
|
326
|
+
if (nameInput) {
|
|
327
|
+
nameInput.value = "";
|
|
328
|
+
}
|
|
308
329
|
}
|
|
309
330
|
}}
|
|
310
331
|
>
|
|
@@ -381,8 +402,12 @@ export function addFieldFormTpl(
|
|
|
381
402
|
.value=${state.name}
|
|
382
403
|
@input=${(e: Event) => handlers.onInput("name", (e.target as HTMLInputElement).value)}
|
|
383
404
|
@keydown=${(e: KeyboardEvent) => {
|
|
384
|
-
if (e.key === "Enter")
|
|
385
|
-
|
|
405
|
+
if (e.key === "Enter") {
|
|
406
|
+
handlers.onConfirm();
|
|
407
|
+
}
|
|
408
|
+
if (e.key === "Escape") {
|
|
409
|
+
handlers.onCancel();
|
|
410
|
+
}
|
|
386
411
|
}}
|
|
387
412
|
></sp-textfield>
|
|
388
413
|
${typePickerTpl(state.type, (t) => handlers.onInput("type", t))}
|
|
@@ -412,20 +437,26 @@ export function addFieldFormTpl(
|
|
|
412
437
|
*/
|
|
413
438
|
export function schemaForType(type: string, format?: string) {
|
|
414
439
|
switch (type) {
|
|
415
|
-
case "number":
|
|
440
|
+
case "number": {
|
|
416
441
|
return { type: "number" };
|
|
417
|
-
|
|
442
|
+
}
|
|
443
|
+
case "boolean": {
|
|
418
444
|
return { type: "boolean" };
|
|
419
|
-
|
|
445
|
+
}
|
|
446
|
+
case "array": {
|
|
420
447
|
return format
|
|
421
|
-
? {
|
|
422
|
-
: {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
448
|
+
? { items: { format, type: "string" }, type: "array" }
|
|
449
|
+
: { items: { type: "string" }, type: "array" };
|
|
450
|
+
}
|
|
451
|
+
case "object": {
|
|
452
|
+
return { properties: {}, required: [], type: "object" };
|
|
453
|
+
}
|
|
454
|
+
case "reference": {
|
|
426
455
|
return { $ref: "#/contentTypes/" };
|
|
427
|
-
|
|
428
|
-
|
|
456
|
+
}
|
|
457
|
+
default: {
|
|
458
|
+
return format ? { format, type: "string" } : { type: "string" };
|
|
459
|
+
}
|
|
429
460
|
}
|
|
430
461
|
}
|
|
431
462
|
|
|
@@ -437,18 +468,27 @@ export function schemaForType(type: string, format?: string) {
|
|
|
437
468
|
* @returns {string}
|
|
438
469
|
*/
|
|
439
470
|
export function yamlDefault(type: string, format?: string) {
|
|
440
|
-
if (format === "date")
|
|
441
|
-
|
|
471
|
+
if (format === "date") {
|
|
472
|
+
return new Date().toISOString().split("T")[0];
|
|
473
|
+
}
|
|
474
|
+
if (format === "image") {
|
|
475
|
+
return '""';
|
|
476
|
+
}
|
|
442
477
|
switch (type) {
|
|
443
|
-
case "boolean":
|
|
478
|
+
case "boolean": {
|
|
444
479
|
return "false";
|
|
445
|
-
|
|
480
|
+
}
|
|
481
|
+
case "number": {
|
|
446
482
|
return "0";
|
|
447
|
-
|
|
483
|
+
}
|
|
484
|
+
case "array": {
|
|
448
485
|
return "[]";
|
|
449
|
-
|
|
486
|
+
}
|
|
487
|
+
case "object": {
|
|
450
488
|
return "{}";
|
|
451
|
-
|
|
489
|
+
}
|
|
490
|
+
default: {
|
|
452
491
|
return '""';
|
|
492
|
+
}
|
|
453
493
|
}
|
|
454
494
|
}
|
|
@@ -17,26 +17,30 @@ import { openModal } from "../ui/layers";
|
|
|
17
17
|
|
|
18
18
|
let _handle: ReturnType<typeof openModal> | null = null;
|
|
19
19
|
|
|
20
|
-
let _activeSection
|
|
20
|
+
let _activeSection = "general";
|
|
21
21
|
|
|
22
22
|
let _contentEl: HTMLElement | null = null;
|
|
23
23
|
|
|
24
24
|
const sections = [
|
|
25
|
-
{
|
|
26
|
-
{
|
|
27
|
-
{ key: "cssVars", label: "CSS Variables"
|
|
28
|
-
{
|
|
29
|
-
{ key: "contentTypes", label: "Content Types"
|
|
25
|
+
{ icon: "sp-icon-properties", key: "general", label: "General" },
|
|
26
|
+
{ icon: "sp-icon-file-single-web-page", key: "head", label: "Head" },
|
|
27
|
+
{ icon: "sp-icon-brush", key: "cssVars", label: "CSS Variables" },
|
|
28
|
+
{ icon: "sp-icon-data", key: "definitions", label: "Definitions" },
|
|
29
|
+
{ icon: "sp-icon-view-grid", key: "contentTypes", label: "Content Types" },
|
|
30
30
|
];
|
|
31
31
|
|
|
32
32
|
export function openSettingsModal() {
|
|
33
|
-
if (_handle)
|
|
33
|
+
if (_handle) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
34
36
|
_activeSection = "general";
|
|
35
37
|
renderModal();
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
export function closeSettingsModal() {
|
|
39
|
-
if (!_handle)
|
|
41
|
+
if (!_handle) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
40
44
|
_handle.close();
|
|
41
45
|
_handle = null;
|
|
42
46
|
_contentEl = null;
|
|
@@ -54,7 +58,9 @@ function renderModal() {
|
|
|
54
58
|
<div
|
|
55
59
|
class="settings-modal"
|
|
56
60
|
@keydown=${(e: KeyboardEvent) => {
|
|
57
|
-
if (e.key === "Escape")
|
|
61
|
+
if (e.key === "Escape") {
|
|
62
|
+
closeSettingsModal();
|
|
63
|
+
}
|
|
58
64
|
}}
|
|
59
65
|
>
|
|
60
66
|
<div class="settings-modal-header">
|
|
@@ -69,8 +75,8 @@ function renderModal() {
|
|
|
69
75
|
(s) => html`
|
|
70
76
|
<button
|
|
71
77
|
class=${classMap({
|
|
72
|
-
"settings-nav-item": true,
|
|
73
78
|
active: _activeSection === s.key,
|
|
79
|
+
"settings-nav-item": true,
|
|
74
80
|
})}
|
|
75
81
|
@click=${() => onNavClick(s.key)}
|
|
76
82
|
>
|
|
@@ -83,7 +89,9 @@ function renderModal() {
|
|
|
83
89
|
class="settings-modal-content"
|
|
84
90
|
${ref((el: Element | undefined) => {
|
|
85
91
|
_contentEl = (el as HTMLElement) || null;
|
|
86
|
-
if (_contentEl)
|
|
92
|
+
if (_contentEl) {
|
|
93
|
+
requestAnimationFrame(() => renderActiveSection());
|
|
94
|
+
}
|
|
87
95
|
})}
|
|
88
96
|
></div>
|
|
89
97
|
</div>
|
|
@@ -98,23 +106,33 @@ function renderModal() {
|
|
|
98
106
|
}
|
|
99
107
|
|
|
100
108
|
function renderActiveSection() {
|
|
101
|
-
if (!_handle || !_contentEl)
|
|
109
|
+
if (!_handle || !_contentEl) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
102
112
|
|
|
103
113
|
switch (_activeSection) {
|
|
104
|
-
case "general":
|
|
114
|
+
case "general": {
|
|
105
115
|
renderGeneralSettings(_contentEl);
|
|
106
116
|
break;
|
|
107
|
-
|
|
117
|
+
}
|
|
118
|
+
case "head": {
|
|
108
119
|
renderHeadEditor(_contentEl);
|
|
109
120
|
break;
|
|
110
|
-
|
|
121
|
+
}
|
|
122
|
+
case "cssVars": {
|
|
111
123
|
renderCssVarsEditor(_contentEl);
|
|
112
124
|
break;
|
|
113
|
-
|
|
125
|
+
}
|
|
126
|
+
case "definitions": {
|
|
114
127
|
renderDefsEditor(_contentEl);
|
|
115
128
|
break;
|
|
116
|
-
|
|
129
|
+
}
|
|
130
|
+
case "contentTypes": {
|
|
117
131
|
renderContentTypesEditor(_contentEl);
|
|
118
132
|
break;
|
|
133
|
+
}
|
|
134
|
+
default: {
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
119
137
|
}
|
|
120
138
|
}
|
package/src/site-context.ts
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* definitions merge on top (file wins on conflict).
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { projectState,
|
|
9
|
+
import { projectState, requireProjectState, setProjectState } from "./store";
|
|
10
10
|
import { getPlatform } from "./platform";
|
|
11
11
|
|
|
12
12
|
import type {
|
|
13
13
|
JxElement,
|
|
14
|
+
JxHeadEntry,
|
|
14
15
|
JxMutableNode,
|
|
15
16
|
JxStyle,
|
|
16
|
-
JxHeadEntry,
|
|
17
17
|
ProjectConfig,
|
|
18
18
|
} from "@jxsuite/schema/types";
|
|
19
19
|
|
|
@@ -24,10 +24,14 @@ import type {
|
|
|
24
24
|
* undefined)
|
|
25
25
|
* @returns {Record<string, string>}
|
|
26
26
|
*/
|
|
27
|
-
export function getEffectiveMedia(docMedia
|
|
27
|
+
export function getEffectiveMedia(docMedia?: Record<string, string>) {
|
|
28
28
|
const siteMedia = projectState?.projectConfig?.$media;
|
|
29
|
-
if (!siteMedia)
|
|
30
|
-
|
|
29
|
+
if (!siteMedia) {
|
|
30
|
+
return docMedia || {};
|
|
31
|
+
}
|
|
32
|
+
if (!docMedia) {
|
|
33
|
+
return { ...siteMedia };
|
|
34
|
+
}
|
|
31
35
|
return { ...siteMedia, ...docMedia };
|
|
32
36
|
}
|
|
33
37
|
|
|
@@ -38,22 +42,20 @@ export function getEffectiveMedia(docMedia: Record<string, string> | undefined)
|
|
|
38
42
|
* @param {JxStyle | undefined} docStyle - The current document's style (may be undefined)
|
|
39
43
|
* @returns {JxStyle}
|
|
40
44
|
*/
|
|
41
|
-
export function getEffectiveStyle(docStyle
|
|
45
|
+
export function getEffectiveStyle(docStyle?: JxStyle) {
|
|
42
46
|
const siteStyle = projectState?.projectConfig?.style;
|
|
43
|
-
if (!siteStyle)
|
|
44
|
-
|
|
47
|
+
if (!siteStyle) {
|
|
48
|
+
return docStyle || {};
|
|
49
|
+
}
|
|
50
|
+
if (!docStyle) {
|
|
51
|
+
return { ...siteStyle };
|
|
52
|
+
}
|
|
45
53
|
const merged = { ...siteStyle };
|
|
46
54
|
for (const [k, v] of Object.entries(docStyle)) {
|
|
47
|
-
|
|
48
|
-
typeof v === "object" &&
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
merged[k] !== null
|
|
52
|
-
) {
|
|
53
|
-
merged[k] = { ...(merged[k] as JxStyle), ...(v as JxStyle) };
|
|
54
|
-
} else {
|
|
55
|
-
merged[k] = v;
|
|
56
|
-
}
|
|
55
|
+
merged[k] =
|
|
56
|
+
typeof v === "object" && v !== null && typeof merged[k] === "object" && merged[k] !== null
|
|
57
|
+
? { ...(merged[k] as JxStyle), ...(v as JxStyle) }
|
|
58
|
+
: v;
|
|
57
59
|
}
|
|
58
60
|
return merged;
|
|
59
61
|
}
|
|
@@ -65,10 +67,14 @@ export function getEffectiveStyle(docStyle: JxStyle | undefined) {
|
|
|
65
67
|
* undefined)
|
|
66
68
|
* @returns {Record<string, string>}
|
|
67
69
|
*/
|
|
68
|
-
export function getEffectiveImports(docImports
|
|
70
|
+
export function getEffectiveImports(docImports?: Record<string, string>) {
|
|
69
71
|
const siteImports = projectState?.projectConfig?.imports;
|
|
70
|
-
if (!siteImports)
|
|
71
|
-
|
|
72
|
+
if (!siteImports) {
|
|
73
|
+
return docImports || {};
|
|
74
|
+
}
|
|
75
|
+
if (!docImports) {
|
|
76
|
+
return { ...siteImports };
|
|
77
|
+
}
|
|
72
78
|
return { ...siteImports, ...docImports };
|
|
73
79
|
}
|
|
74
80
|
|
|
@@ -81,9 +87,13 @@ export function getEffectiveImports(docImports: Record<string, string> | undefin
|
|
|
81
87
|
*/
|
|
82
88
|
export function getEffectiveElements(docElements?: (JxElement | string)[]) {
|
|
83
89
|
const siteElements = projectState?.projectConfig?.$elements;
|
|
84
|
-
if (!siteElements?.length)
|
|
85
|
-
|
|
86
|
-
|
|
90
|
+
if (!siteElements?.length) {
|
|
91
|
+
return docElements || [];
|
|
92
|
+
}
|
|
93
|
+
if (!docElements?.length) {
|
|
94
|
+
return [...siteElements];
|
|
95
|
+
}
|
|
96
|
+
const seen = new Set<string>();
|
|
87
97
|
const merged: (JxElement | string)[] = [];
|
|
88
98
|
for (const entry of [...siteElements, ...docElements]) {
|
|
89
99
|
const key = typeof entry === "string" ? entry : entry?.$ref;
|
|
@@ -103,9 +113,13 @@ export function getEffectiveElements(docElements?: (JxElement | string)[]) {
|
|
|
103
113
|
*/
|
|
104
114
|
export function getEffectiveHead(docHead?: JxHeadEntry[]) {
|
|
105
115
|
const siteHead = projectState?.projectConfig?.$head;
|
|
106
|
-
if (!siteHead?.length)
|
|
107
|
-
|
|
108
|
-
|
|
116
|
+
if (!siteHead?.length) {
|
|
117
|
+
return docHead || [];
|
|
118
|
+
}
|
|
119
|
+
if (!docHead?.length) {
|
|
120
|
+
return [...siteHead];
|
|
121
|
+
}
|
|
122
|
+
const seen = new Set<string>();
|
|
109
123
|
const merged: JxHeadEntry[] = [];
|
|
110
124
|
for (const entry of [...siteHead, ...docHead]) {
|
|
111
125
|
const key =
|
|
@@ -120,7 +134,7 @@ export function getEffectiveHead(docHead?: JxHeadEntry[]) {
|
|
|
120
134
|
|
|
121
135
|
// ─── Layout resolution ──────────────────────────────────────────────────────
|
|
122
136
|
|
|
123
|
-
const layoutCache
|
|
137
|
+
const layoutCache = new Map<string, JxMutableNode>();
|
|
124
138
|
|
|
125
139
|
export function invalidateLayoutCache() {
|
|
126
140
|
layoutCache.clear();
|
|
@@ -134,7 +148,9 @@ export function invalidateLayoutCache() {
|
|
|
134
148
|
* @returns {string | null} The layout path, or null if no layout applies
|
|
135
149
|
*/
|
|
136
150
|
export function getEffectiveLayoutPath(docLayout: string | false | undefined) {
|
|
137
|
-
if (docLayout === false)
|
|
151
|
+
if (docLayout === false) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
138
154
|
const defaultLayout = projectState?.projectConfig?.defaults?.layout;
|
|
139
155
|
return docLayout || defaultLayout || null;
|
|
140
156
|
}
|
|
@@ -147,8 +163,9 @@ export function getEffectiveLayoutPath(docLayout: string | false | undefined) {
|
|
|
147
163
|
*/
|
|
148
164
|
export async function resolveLayoutDoc(layoutPath: string) {
|
|
149
165
|
const normalized = layoutPath.replace(/^\.\//, "");
|
|
150
|
-
if (layoutCache.has(normalized))
|
|
166
|
+
if (layoutCache.has(normalized)) {
|
|
151
167
|
return structuredClone(layoutCache.get(normalized) as JxMutableNode);
|
|
168
|
+
}
|
|
152
169
|
|
|
153
170
|
try {
|
|
154
171
|
const platform = getPlatform();
|
|
@@ -171,16 +188,18 @@ export async function resolveLayoutDoc(layoutPath: string) {
|
|
|
171
188
|
*/
|
|
172
189
|
export function distributePageIntoLayout(layoutDoc: JxMutableNode, pageDoc: JxMutableNode) {
|
|
173
190
|
const pageChildren = pageDoc.children ?? [];
|
|
174
|
-
const children =
|
|
191
|
+
const children = Array.isArray(pageChildren) ? pageChildren : [];
|
|
175
192
|
|
|
176
|
-
const named = new Map();
|
|
177
|
-
const defaults = [];
|
|
193
|
+
const named = new Map<string, JxMutableNode[]>();
|
|
194
|
+
const defaults: (JxMutableNode | string)[] = [];
|
|
178
195
|
|
|
179
196
|
for (const child of children) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (!named.has(slotName))
|
|
183
|
-
|
|
197
|
+
const slotName = typeof child === "object" ? child.attributes?.slot : undefined;
|
|
198
|
+
if (typeof child === "object" && typeof slotName === "string" && slotName) {
|
|
199
|
+
if (!named.has(slotName)) {
|
|
200
|
+
named.set(slotName, []);
|
|
201
|
+
}
|
|
202
|
+
named.get(slotName)!.push(child);
|
|
184
203
|
} else {
|
|
185
204
|
defaults.push(child);
|
|
186
205
|
}
|
|
@@ -188,10 +207,18 @@ export function distributePageIntoLayout(layoutDoc: JxMutableNode, pageDoc: JxMu
|
|
|
188
207
|
|
|
189
208
|
fillSlots(layoutDoc, named, defaults);
|
|
190
209
|
|
|
191
|
-
if (pageDoc.state)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
if (pageDoc
|
|
210
|
+
if (pageDoc.state) {
|
|
211
|
+
layoutDoc.state = { ...layoutDoc.state, ...pageDoc.state };
|
|
212
|
+
}
|
|
213
|
+
if (pageDoc.$media) {
|
|
214
|
+
layoutDoc.$media = { ...layoutDoc.$media, ...pageDoc.$media };
|
|
215
|
+
}
|
|
216
|
+
if (pageDoc.style) {
|
|
217
|
+
layoutDoc.style = { ...layoutDoc.style, ...pageDoc.style };
|
|
218
|
+
}
|
|
219
|
+
if (pageDoc.attributes) {
|
|
220
|
+
layoutDoc.attributes = { ...layoutDoc.attributes, ...pageDoc.attributes };
|
|
221
|
+
}
|
|
195
222
|
|
|
196
223
|
return layoutDoc;
|
|
197
224
|
}
|
|
@@ -201,23 +228,29 @@ function fillSlots(
|
|
|
201
228
|
named: Map<string, JxMutableNode[]>,
|
|
202
229
|
defaults: (JxMutableNode | string)[],
|
|
203
230
|
) {
|
|
204
|
-
if (!node || typeof node !== "object")
|
|
205
|
-
|
|
231
|
+
if (!node || typeof node !== "object") {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!Array.isArray(node.children)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
206
237
|
|
|
207
238
|
const newChildren: (JxMutableNode | string)[] = [];
|
|
208
239
|
for (const child of node.children) {
|
|
209
240
|
if (child && typeof child === "object" && child.tagName === "slot") {
|
|
210
241
|
const slotName = child.attributes?.name;
|
|
211
|
-
if (slotName && named.has(slotName)) {
|
|
212
|
-
newChildren.push(...
|
|
242
|
+
if (typeof slotName === "string" && slotName && named.has(slotName)) {
|
|
243
|
+
newChildren.push(...named.get(slotName)!);
|
|
213
244
|
named.delete(slotName);
|
|
214
245
|
} else if (!slotName && defaults.length > 0) {
|
|
215
246
|
newChildren.push(...defaults);
|
|
216
|
-
} else if (child.children) {
|
|
247
|
+
} else if (Array.isArray(child.children)) {
|
|
217
248
|
newChildren.push(...child.children);
|
|
218
249
|
}
|
|
219
250
|
} else {
|
|
220
|
-
if (typeof child !== "string")
|
|
251
|
+
if (typeof child !== "string") {
|
|
252
|
+
fillSlots(child, named, defaults);
|
|
253
|
+
}
|
|
221
254
|
newChildren.push(child);
|
|
222
255
|
}
|
|
223
256
|
}
|