@jxsuite/studio 0.23.2 → 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 +2230 -714
- 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} +109 -84
- 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} +35 -23
- 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,22 +1,27 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
interface RecentProject {
|
|
3
|
+
name: string;
|
|
4
|
+
root: string;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface RecentFile {
|
|
9
|
+
path: string;
|
|
10
|
+
name: string;
|
|
11
|
+
timestamp: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
1
14
|
const STORAGE_KEY = "jx-studio-recent-projects";
|
|
2
15
|
const FILES_STORAGE_KEY = "jx-studio-recent-files";
|
|
3
16
|
const MAX_RECENT = 8;
|
|
4
17
|
const MAX_RECENT_FILES = 10;
|
|
5
18
|
|
|
6
|
-
/**
|
|
7
|
-
* @typedef {{ name: string; root: string; timestamp: number }} RecentProject
|
|
8
|
-
*
|
|
9
|
-
* @typedef {{ path: string; name: string; timestamp: number }} RecentFile
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
19
|
/** @returns {RecentProject[]} */
|
|
13
20
|
export function getRecentProjects() {
|
|
14
21
|
try {
|
|
15
22
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
16
23
|
if (!raw) return [];
|
|
17
|
-
return
|
|
18
|
-
(a, b) => b.timestamp - a.timestamp,
|
|
19
|
-
);
|
|
24
|
+
return (JSON.parse(raw) as RecentProject[]).sort((a, b) => b.timestamp - a.timestamp);
|
|
20
25
|
} catch {
|
|
21
26
|
return [];
|
|
22
27
|
}
|
|
@@ -26,7 +31,7 @@ export function getRecentProjects() {
|
|
|
26
31
|
* @param {string} name
|
|
27
32
|
* @param {string} root
|
|
28
33
|
*/
|
|
29
|
-
export function addRecentProject(name, root) {
|
|
34
|
+
export function addRecentProject(name: string, root: string) {
|
|
30
35
|
const projects = getRecentProjects().filter((p) => p.root !== root);
|
|
31
36
|
projects.unshift({ name, root, timestamp: Date.now() });
|
|
32
37
|
if (projects.length > MAX_RECENT) projects.length = MAX_RECENT;
|
|
@@ -42,14 +47,14 @@ export function getRecentFiles() {
|
|
|
42
47
|
try {
|
|
43
48
|
const raw = localStorage.getItem(FILES_STORAGE_KEY);
|
|
44
49
|
if (!raw) return [];
|
|
45
|
-
return
|
|
50
|
+
return (JSON.parse(raw) as RecentFile[]).sort((a, b) => b.timestamp - a.timestamp);
|
|
46
51
|
} catch {
|
|
47
52
|
return [];
|
|
48
53
|
}
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
/** @param {{ path: string; name: string }} file */
|
|
52
|
-
export function trackRecentFile(file) {
|
|
57
|
+
export function trackRecentFile(file: { path: string; name: string }) {
|
|
53
58
|
const recent = getRecentFiles().filter((f) => f.path !== file.path);
|
|
54
59
|
recent.unshift({ path: file.path, name: file.name, timestamp: Date.now() });
|
|
55
60
|
if (recent.length > MAX_RECENT_FILES) recent.length = MAX_RECENT_FILES;
|
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
*/
|
|
11
|
-
const g = /** @type {any} */ (globalThis);
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
const g = globalThis as unknown as {
|
|
3
|
+
__jxPlatform?: {
|
|
4
|
+
windowControls?: {
|
|
5
|
+
getFrame: () => Promise<{ x: number; y: number; width: number; height: number }>;
|
|
6
|
+
setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
7
|
+
};
|
|
8
|
+
};
|
|
9
|
+
};
|
|
12
10
|
|
|
13
11
|
const EDGES = [
|
|
14
12
|
"top",
|
|
@@ -52,7 +50,14 @@ export function mountResizeEdges() {
|
|
|
52
50
|
* setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
53
51
|
* }} wc
|
|
54
52
|
*/
|
|
55
|
-
async function startResize(
|
|
53
|
+
async function startResize(
|
|
54
|
+
e: MouseEvent,
|
|
55
|
+
edge: string,
|
|
56
|
+
wc: {
|
|
57
|
+
getFrame: () => Promise<{ x: number; y: number; width: number; height: number }>;
|
|
58
|
+
setFrame: (x: number, y: number, w: number, h: number) => void;
|
|
59
|
+
},
|
|
60
|
+
) {
|
|
56
61
|
e.preventDefault();
|
|
57
62
|
e.stopPropagation();
|
|
58
63
|
|
|
@@ -62,7 +67,7 @@ async function startResize(e, edge, wc) {
|
|
|
62
67
|
const startFrame = { ...frame };
|
|
63
68
|
|
|
64
69
|
/** @param {MouseEvent} me */
|
|
65
|
-
function onMove(me) {
|
|
70
|
+
function onMove(me: MouseEvent) {
|
|
66
71
|
const dx = me.screenX - startX;
|
|
67
72
|
const dy = me.screenY - startY;
|
|
68
73
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
3
|
+
|
|
1
4
|
/** Collect slot elements from the document tree. */
|
|
2
|
-
export function collectSlots(
|
|
3
|
-
/** @type {JxMutableNode | null | undefined} */ node,
|
|
4
|
-
/** @type {string[]} */ slots = [],
|
|
5
|
-
) {
|
|
5
|
+
export function collectSlots(node: JxMutableNode | null | undefined, slots: string[] = []) {
|
|
6
6
|
if (node?.tagName === "slot") {
|
|
7
7
|
slots.push(node.attributes?.name || "");
|
|
8
8
|
}
|
|
9
9
|
if (Array.isArray(node?.children))
|
|
10
|
-
node.children.forEach((
|
|
10
|
+
node.children.forEach((c: JxMutableNode | string) =>
|
|
11
11
|
collectSlots(typeof c === "string" ? undefined : c, slots),
|
|
12
12
|
);
|
|
13
13
|
return slots;
|
|
@@ -23,7 +23,14 @@ export function collectSlots(
|
|
|
23
23
|
* collectCssParts: (node: JxMutableNode) => { name: string }[];
|
|
24
24
|
* }} helpers
|
|
25
25
|
*/
|
|
26
|
-
export function exportCemManifest(
|
|
26
|
+
export function exportCemManifest(
|
|
27
|
+
S: { document: JxMutableNode },
|
|
28
|
+
helpers: {
|
|
29
|
+
defCategory: (d: unknown) => string;
|
|
30
|
+
normParam: (p: unknown) => unknown;
|
|
31
|
+
collectCssParts: (node: JxMutableNode) => { name: string }[];
|
|
32
|
+
},
|
|
33
|
+
) {
|
|
27
34
|
const { defCategory, normParam, collectCssParts } = helpers;
|
|
28
35
|
const doc = S.document;
|
|
29
36
|
const tagName = doc.tagName;
|
|
@@ -88,7 +95,7 @@ export function exportCemManifest(S, helpers) {
|
|
|
88
95
|
|
|
89
96
|
// Slots
|
|
90
97
|
const slotNames = collectSlots(doc);
|
|
91
|
-
const slots = slotNames.map((
|
|
98
|
+
const slots = slotNames.map((name: string) => ({
|
|
92
99
|
name: name || "",
|
|
93
100
|
...(name ? {} : { description: "Default slot" }),
|
|
94
101
|
}));
|
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
/** OXC code services (server-backed) */
|
|
2
2
|
|
|
3
|
-
import { getPlatform } from "../platform
|
|
4
|
-
import { projectState } from "../state
|
|
5
|
-
import { getNodeAtPath } from "../store
|
|
3
|
+
import { getPlatform } from "../platform";
|
|
4
|
+
import { projectState } from "../state";
|
|
5
|
+
import { getNodeAtPath } from "../store";
|
|
6
6
|
import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
|
|
7
|
+
import type { JxPath } from "../state";
|
|
8
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
9
|
+
|
|
10
|
+
export interface OxLintDiagnostic {
|
|
11
|
+
severity: string;
|
|
12
|
+
message: string;
|
|
13
|
+
help?: string;
|
|
14
|
+
code?: string;
|
|
15
|
+
url?: string;
|
|
16
|
+
labels?: { span: { line: number; column: number; length?: number } }[];
|
|
17
|
+
}
|
|
7
18
|
|
|
8
19
|
/**
|
|
9
20
|
* @param {string} action
|
|
10
21
|
* @param {unknown} payload
|
|
11
22
|
*/
|
|
12
|
-
export async function codeService(action, payload) {
|
|
23
|
+
export async function codeService(action: string, payload: unknown) {
|
|
13
24
|
const platform = getPlatform();
|
|
14
25
|
if (!platform.codeService) return null;
|
|
15
26
|
return platform.codeService(action, payload);
|
|
@@ -20,7 +31,7 @@ export async function codeService(action, payload) {
|
|
|
20
31
|
*
|
|
21
32
|
* @param {string} name
|
|
22
33
|
*/
|
|
23
|
-
export async function locateDocument(name) {
|
|
34
|
+
export async function locateDocument(name: string) {
|
|
24
35
|
const platform = getPlatform();
|
|
25
36
|
if (!platform.locateFile) return null;
|
|
26
37
|
return platform.locateFile(name);
|
|
@@ -36,7 +47,10 @@ export const pluginSchemaCache = new Map();
|
|
|
36
47
|
* @param {{ $src?: string; $prototype?: string }} def
|
|
37
48
|
* @param {{ documentPath?: string }} state
|
|
38
49
|
*/
|
|
39
|
-
export async function fetchPluginSchema(
|
|
50
|
+
export async function fetchPluginSchema(
|
|
51
|
+
def: { $src?: string; $prototype?: string },
|
|
52
|
+
state: { documentPath?: string },
|
|
53
|
+
) {
|
|
40
54
|
const importedPath = def.$prototype
|
|
41
55
|
? projectState?.projectConfig?.imports?.[def.$prototype]
|
|
42
56
|
: null;
|
|
@@ -62,22 +76,14 @@ export async function fetchPluginSchema(def, state) {
|
|
|
62
76
|
}
|
|
63
77
|
}
|
|
64
78
|
|
|
65
|
-
/**
|
|
66
|
-
* @typedef {{
|
|
67
|
-
* severity: string;
|
|
68
|
-
* message: string;
|
|
69
|
-
* help?: string;
|
|
70
|
-
* code?: string;
|
|
71
|
-
* url?: string;
|
|
72
|
-
* labels?: { span: { line: number; column: number; length?: number } }[];
|
|
73
|
-
* }} OxLintDiagnostic
|
|
74
|
-
*/
|
|
75
|
-
|
|
76
79
|
/**
|
|
77
80
|
* @param {import("monaco-editor").editor.IStandaloneCodeEditor} editor
|
|
78
81
|
* @param {OxLintDiagnostic[]} diagnostics
|
|
79
82
|
*/
|
|
80
|
-
export function setLintMarkers(
|
|
83
|
+
export function setLintMarkers(
|
|
84
|
+
editor: import("monaco-editor").editor.IStandaloneCodeEditor,
|
|
85
|
+
diagnostics: OxLintDiagnostic[],
|
|
86
|
+
) {
|
|
81
87
|
const model = editor.getModel();
|
|
82
88
|
if (!model) return;
|
|
83
89
|
const markers = diagnostics
|
|
@@ -101,7 +107,7 @@ export function setLintMarkers(editor, diagnostics) {
|
|
|
101
107
|
monaco.editor.setModelMarkers(
|
|
102
108
|
model,
|
|
103
109
|
"oxlint",
|
|
104
|
-
|
|
110
|
+
markers as import("monaco-editor").editor.IMarkerData[],
|
|
105
111
|
);
|
|
106
112
|
}
|
|
107
113
|
|
|
@@ -109,7 +115,10 @@ export function setLintMarkers(editor, diagnostics) {
|
|
|
109
115
|
* @param {{ type: string; defName?: string; path?: JxPath; eventKey?: string }} editing
|
|
110
116
|
* @param {JxMutableNode | null | undefined} document
|
|
111
117
|
*/
|
|
112
|
-
export function getFunctionArgs(
|
|
118
|
+
export function getFunctionArgs(
|
|
119
|
+
editing: { type: string; defName?: string; path?: JxPath; eventKey?: string },
|
|
120
|
+
document: JxMutableNode | null | undefined,
|
|
121
|
+
) {
|
|
113
122
|
if (editing.type === "def") {
|
|
114
123
|
const defName = editing.defName;
|
|
115
124
|
return (defName && document?.state?.[defName]?.parameters) || ["state", "event"];
|
|
@@ -9,8 +9,7 @@ import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution.
|
|
|
9
9
|
import jxSchema from "@jxsuite/schema/schema.json";
|
|
10
10
|
import projectSchema from "@jxsuite/schema/project-schema.json";
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
const WORKER_PATHS = {
|
|
12
|
+
const WORKER_PATHS: Record<string, string> = {
|
|
14
13
|
json: "/monaco-editor/esm/vs/language/json/json.worker.js",
|
|
15
14
|
typescript: "/monaco-editor/esm/vs/language/typescript/ts.worker.js",
|
|
16
15
|
javascript: "/monaco-editor/esm/vs/language/typescript/ts.worker.js",
|
|
@@ -18,7 +17,7 @@ const WORKER_PATHS = {
|
|
|
18
17
|
};
|
|
19
18
|
|
|
20
19
|
self.MonacoEnvironment = {
|
|
21
|
-
getWorker(_,
|
|
20
|
+
getWorker(_, label: string) {
|
|
22
21
|
const path = WORKER_PATHS[label] || WORKER_PATHS.editorWorkerService;
|
|
23
22
|
return new Worker(path, { type: "module" });
|
|
24
23
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Content Types Editor — visual schema builder for project content types.
|
|
3
4
|
*
|
|
@@ -7,19 +8,16 @@
|
|
|
7
8
|
|
|
8
9
|
import { html, render as litRender } from "lit-html";
|
|
9
10
|
import { repeat } from "lit-html/directives/repeat.js";
|
|
10
|
-
import { getPlatform } from "../platform
|
|
11
|
-
import { projectState } from "../store
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
detectFieldFormat,
|
|
17
|
-
} from "./schema-field-ui.js";
|
|
11
|
+
import { getPlatform } from "../platform";
|
|
12
|
+
import { projectState } from "../store";
|
|
13
|
+
import { fieldCardTpl, addFieldFormTpl, schemaForType, detectFieldFormat } from "./schema-field-ui";
|
|
14
|
+
import { toCamelCase } from "../utils/studio-utils";
|
|
15
|
+
|
|
16
|
+
import type { ProjectConfig, ContentTypeSchema } from "@jxsuite/schema/types";
|
|
18
17
|
|
|
19
18
|
// ─── Module state ─────────────────────────────────────────────────────────────
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
let selectedContentType = null;
|
|
20
|
+
let selectedContentType: string | null = null;
|
|
23
21
|
let showAddField = false;
|
|
24
22
|
let newFieldState = { name: "", type: "string", format: "", required: false };
|
|
25
23
|
let showNewContentType = false;
|
|
@@ -29,22 +27,24 @@ let newContentTypeName = "";
|
|
|
29
27
|
|
|
30
28
|
async function saveProjectConfig() {
|
|
31
29
|
const platform = getPlatform();
|
|
32
|
-
const config =
|
|
30
|
+
const config = (projectState as { projectConfig: ProjectConfig }).projectConfig;
|
|
33
31
|
await platform.writeFile("project.json", JSON.stringify(config, null, "\t"));
|
|
34
32
|
}
|
|
35
33
|
|
|
36
34
|
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
37
35
|
|
|
38
36
|
/** Get the schema object for the selected content type. */
|
|
39
|
-
function getSelectedSchema() {
|
|
37
|
+
function getSelectedSchema(): ContentTypeSchema | undefined {
|
|
40
38
|
const config = projectState?.projectConfig;
|
|
41
|
-
return config?.contentTypes?.[
|
|
39
|
+
return config?.contentTypes?.[selectedContentType as string]?.schema as
|
|
40
|
+
| ContentTypeSchema
|
|
41
|
+
| undefined;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// ─── Handlers ─────────────────────────────────────────────────────────────────
|
|
45
45
|
|
|
46
46
|
/** @param {() => void} rerender */
|
|
47
|
-
function handleNewContentType(rerender) {
|
|
47
|
+
function handleNewContentType(rerender: () => void) {
|
|
48
48
|
const slug = newContentTypeName
|
|
49
49
|
.toLowerCase()
|
|
50
50
|
.replace(/\s+/g, "-")
|
|
@@ -74,9 +74,10 @@ function handleNewContentType(rerender) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
/** @param {() => void} rerender */
|
|
77
|
-
function handleAddField(rerender) {
|
|
78
|
-
const
|
|
79
|
-
if (!
|
|
77
|
+
function handleAddField(rerender: () => void) {
|
|
78
|
+
const raw = newFieldState.name.trim();
|
|
79
|
+
if (!raw || !selectedContentType) return;
|
|
80
|
+
const name = toCamelCase(raw);
|
|
80
81
|
|
|
81
82
|
const schema = getSelectedSchema();
|
|
82
83
|
if (!schema) return;
|
|
@@ -99,13 +100,13 @@ function handleAddField(rerender) {
|
|
|
99
100
|
* @param {string} fieldName
|
|
100
101
|
* @param {() => void} rerender
|
|
101
102
|
*/
|
|
102
|
-
function handleDeleteField(fieldName, rerender) {
|
|
103
|
+
function handleDeleteField(fieldName: string, rerender: () => void) {
|
|
103
104
|
const schema = getSelectedSchema();
|
|
104
105
|
if (!schema?.properties) return;
|
|
105
106
|
|
|
106
107
|
delete schema.properties[fieldName];
|
|
107
108
|
if (schema.required) {
|
|
108
|
-
schema.required = schema.required.filter((
|
|
109
|
+
schema.required = schema.required.filter((r: string) => r !== fieldName);
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
rerender();
|
|
@@ -116,7 +117,7 @@ function handleDeleteField(fieldName, rerender) {
|
|
|
116
117
|
* @param {string} fieldName
|
|
117
118
|
* @param {() => void} rerender
|
|
118
119
|
*/
|
|
119
|
-
function handleToggleRequired(fieldName, rerender) {
|
|
120
|
+
function handleToggleRequired(fieldName: string, rerender: () => void) {
|
|
120
121
|
const schema = getSelectedSchema();
|
|
121
122
|
if (!schema) return;
|
|
122
123
|
if (!schema.required) schema.required = [];
|
|
@@ -134,21 +135,19 @@ function handleToggleRequired(fieldName, rerender) {
|
|
|
134
135
|
* @param {string} newName
|
|
135
136
|
* @param {() => void} rerender
|
|
136
137
|
*/
|
|
137
|
-
function handleRenameField(oldName, newName, rerender) {
|
|
138
|
+
function handleRenameField(oldName: string, newName: string, rerender: () => void) {
|
|
138
139
|
const schema = getSelectedSchema();
|
|
139
|
-
|
|
140
|
+
const normalized = toCamelCase(newName);
|
|
141
|
+
if (!schema?.properties || !normalized || schema.properties[normalized]) return;
|
|
140
142
|
|
|
141
|
-
|
|
142
|
-
const newProps = {};
|
|
143
|
+
const newProps: Record<string, unknown> = {};
|
|
143
144
|
for (const [key, val] of Object.entries(schema.properties)) {
|
|
144
|
-
newProps[key === oldName ?
|
|
145
|
+
newProps[key === oldName ? normalized : key] = val;
|
|
145
146
|
}
|
|
146
147
|
schema.properties = newProps;
|
|
147
148
|
|
|
148
149
|
if (schema.required) {
|
|
149
|
-
schema.required = schema.required.map((
|
|
150
|
-
r === oldName ? newName : r,
|
|
151
|
-
);
|
|
150
|
+
schema.required = schema.required.map((r: string) => (r === oldName ? normalized : r));
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
rerender();
|
|
@@ -160,7 +159,7 @@ function handleRenameField(oldName, newName, rerender) {
|
|
|
160
159
|
* @param {string} newType
|
|
161
160
|
* @param {() => void} rerender
|
|
162
161
|
*/
|
|
163
|
-
function handleChangeType(fieldName, newType, rerender) {
|
|
162
|
+
function handleChangeType(fieldName: string, newType: string, rerender: () => void) {
|
|
164
163
|
const schema = getSelectedSchema();
|
|
165
164
|
if (!schema?.properties?.[fieldName]) return;
|
|
166
165
|
|
|
@@ -178,7 +177,7 @@ function handleChangeType(fieldName, newType, rerender) {
|
|
|
178
177
|
* @param {string} format
|
|
179
178
|
* @param {() => void} rerender
|
|
180
179
|
*/
|
|
181
|
-
function handleChangeFormat(fieldName, format, rerender) {
|
|
180
|
+
function handleChangeFormat(fieldName: string, format: string, rerender: () => void) {
|
|
182
181
|
const schema = getSelectedSchema();
|
|
183
182
|
if (!schema?.properties?.[fieldName]) return;
|
|
184
183
|
|
|
@@ -194,7 +193,7 @@ function handleChangeFormat(fieldName, format, rerender) {
|
|
|
194
193
|
* @param {string} target
|
|
195
194
|
* @param {() => void} rerender
|
|
196
195
|
*/
|
|
197
|
-
function handleChangeRefTarget(fieldName, target, rerender) {
|
|
196
|
+
function handleChangeRefTarget(fieldName: string, target: string, rerender: () => void) {
|
|
198
197
|
const schema = getSelectedSchema();
|
|
199
198
|
if (!schema?.properties) return;
|
|
200
199
|
|
|
@@ -210,17 +209,24 @@ function handleChangeRefTarget(fieldName, target, rerender) {
|
|
|
210
209
|
* @param {{ name: string; type: string; required: boolean }} fieldState
|
|
211
210
|
* @param {() => void} rerender
|
|
212
211
|
*/
|
|
213
|
-
function handleAddNestedField(
|
|
212
|
+
function handleAddNestedField(
|
|
213
|
+
parentName: string,
|
|
214
|
+
fieldState: { name: string; type: string; required: boolean },
|
|
215
|
+
rerender: () => void,
|
|
216
|
+
) {
|
|
214
217
|
const schema = getSelectedSchema();
|
|
215
218
|
const parent = schema?.properties?.[parentName];
|
|
216
219
|
if (!parent) return;
|
|
217
220
|
|
|
221
|
+
const name = toCamelCase(fieldState.name);
|
|
222
|
+
if (!name) return;
|
|
223
|
+
|
|
218
224
|
if (!parent.properties) parent.properties = {};
|
|
219
|
-
parent.properties[
|
|
225
|
+
parent.properties[name] = schemaForType(fieldState.type);
|
|
220
226
|
|
|
221
227
|
if (fieldState.required) {
|
|
222
228
|
if (!parent.required) parent.required = [];
|
|
223
|
-
if (!parent.required.includes(
|
|
229
|
+
if (!parent.required.includes(name)) parent.required.push(name);
|
|
224
230
|
}
|
|
225
231
|
|
|
226
232
|
rerender();
|
|
@@ -232,14 +238,14 @@ function handleAddNestedField(parentName, fieldState, rerender) {
|
|
|
232
238
|
* @param {string} childName
|
|
233
239
|
* @param {() => void} rerender
|
|
234
240
|
*/
|
|
235
|
-
function handleDeleteNested(parentName, childName, rerender) {
|
|
241
|
+
function handleDeleteNested(parentName: string, childName: string, rerender: () => void) {
|
|
236
242
|
const schema = getSelectedSchema();
|
|
237
243
|
const parent = schema?.properties?.[parentName];
|
|
238
244
|
if (!parent?.properties) return;
|
|
239
245
|
|
|
240
246
|
delete parent.properties[childName];
|
|
241
247
|
if (parent.required) {
|
|
242
|
-
parent.required = parent.required.filter((
|
|
248
|
+
parent.required = parent.required.filter((r: string) => r !== childName);
|
|
243
249
|
}
|
|
244
250
|
|
|
245
251
|
rerender();
|
|
@@ -251,7 +257,7 @@ function handleDeleteNested(parentName, childName, rerender) {
|
|
|
251
257
|
* @param {string} childName
|
|
252
258
|
* @param {() => void} rerender
|
|
253
259
|
*/
|
|
254
|
-
function handleToggleNestedRequired(parentName, childName, rerender) {
|
|
260
|
+
function handleToggleNestedRequired(parentName: string, childName: string, rerender: () => void) {
|
|
255
261
|
const schema = getSelectedSchema();
|
|
256
262
|
const parent = schema?.properties?.[parentName];
|
|
257
263
|
if (!parent) return;
|
|
@@ -271,22 +277,25 @@ function handleToggleNestedRequired(parentName, childName, rerender) {
|
|
|
271
277
|
* @param {string} newChild
|
|
272
278
|
* @param {() => void} rerender
|
|
273
279
|
*/
|
|
274
|
-
function handleRenameNested(
|
|
280
|
+
function handleRenameNested(
|
|
281
|
+
parentName: string,
|
|
282
|
+
oldChild: string,
|
|
283
|
+
newChild: string,
|
|
284
|
+
rerender: () => void,
|
|
285
|
+
) {
|
|
275
286
|
const schema = getSelectedSchema();
|
|
276
287
|
const parent = schema?.properties?.[parentName];
|
|
277
|
-
|
|
288
|
+
const normalized = toCamelCase(newChild);
|
|
289
|
+
if (!parent?.properties || !normalized || parent.properties[normalized]) return;
|
|
278
290
|
|
|
279
|
-
|
|
280
|
-
const newProps = {};
|
|
291
|
+
const newProps: Record<string, unknown> = {};
|
|
281
292
|
for (const [key, val] of Object.entries(parent.properties)) {
|
|
282
|
-
newProps[key === oldChild ?
|
|
293
|
+
newProps[key === oldChild ? normalized : key] = val;
|
|
283
294
|
}
|
|
284
295
|
parent.properties = newProps;
|
|
285
296
|
|
|
286
297
|
if (parent.required) {
|
|
287
|
-
parent.required = parent.required.map((
|
|
288
|
-
r === oldChild ? newChild : r,
|
|
289
|
-
);
|
|
298
|
+
parent.required = parent.required.map((r: string) => (r === oldChild ? normalized : r));
|
|
290
299
|
}
|
|
291
300
|
|
|
292
301
|
rerender();
|
|
@@ -299,7 +308,12 @@ function handleRenameNested(parentName, oldChild, newChild, rerender) {
|
|
|
299
308
|
* @param {string} newType
|
|
300
309
|
* @param {() => void} rerender
|
|
301
310
|
*/
|
|
302
|
-
function handleChangeNestedType(
|
|
311
|
+
function handleChangeNestedType(
|
|
312
|
+
parentName: string,
|
|
313
|
+
childName: string,
|
|
314
|
+
newType: string,
|
|
315
|
+
rerender: () => void,
|
|
316
|
+
) {
|
|
303
317
|
const schema = getSelectedSchema();
|
|
304
318
|
const parent = schema?.properties?.[parentName];
|
|
305
319
|
if (!parent?.properties?.[childName]) return;
|
|
@@ -319,7 +333,12 @@ function handleChangeNestedType(parentName, childName, newType, rerender) {
|
|
|
319
333
|
* @param {string} format
|
|
320
334
|
* @param {() => void} rerender
|
|
321
335
|
*/
|
|
322
|
-
function handleChangeNestedFormat(
|
|
336
|
+
function handleChangeNestedFormat(
|
|
337
|
+
parentName: string,
|
|
338
|
+
childName: string,
|
|
339
|
+
format: string,
|
|
340
|
+
rerender: () => void,
|
|
341
|
+
) {
|
|
323
342
|
const schema = getSelectedSchema();
|
|
324
343
|
const parent = schema?.properties?.[parentName];
|
|
325
344
|
if (!parent?.properties?.[childName]) return;
|
|
@@ -332,7 +351,7 @@ function handleChangeNestedFormat(parentName, childName, format, rerender) {
|
|
|
332
351
|
}
|
|
333
352
|
|
|
334
353
|
/** @param {() => void} rerender */
|
|
335
|
-
function handleDeleteContentType(rerender) {
|
|
354
|
+
function handleDeleteContentType(rerender: () => void) {
|
|
336
355
|
if (!selectedContentType) return;
|
|
337
356
|
const config = projectState?.projectConfig;
|
|
338
357
|
if (!config?.contentTypes?.[selectedContentType]) return;
|
|
@@ -351,7 +370,7 @@ function handleDeleteContentType(rerender) {
|
|
|
351
370
|
*
|
|
352
371
|
* @param {HTMLElement} container
|
|
353
372
|
*/
|
|
354
|
-
export function renderContentTypesEditor(container) {
|
|
373
|
+
export function renderContentTypesEditor(container: HTMLElement) {
|
|
355
374
|
const rerender = () => renderContentTypesEditor(container);
|
|
356
375
|
const config = projectState?.projectConfig;
|
|
357
376
|
const contentTypes = config?.contentTypes || {};
|
|
@@ -382,10 +401,10 @@ export function renderContentTypesEditor(container) {
|
|
|
382
401
|
size="s"
|
|
383
402
|
placeholder="content-type-name"
|
|
384
403
|
.value=${newContentTypeName}
|
|
385
|
-
@input=${(
|
|
386
|
-
newContentTypeName =
|
|
404
|
+
@input=${(e: Event) => {
|
|
405
|
+
newContentTypeName = (e.target as HTMLInputElement).value;
|
|
387
406
|
}}
|
|
388
|
-
@keydown=${(
|
|
407
|
+
@keydown=${(e: KeyboardEvent) => {
|
|
389
408
|
if (e.key === "Enter") handleNewContentType(rerender);
|
|
390
409
|
if (e.key === "Escape") {
|
|
391
410
|
showNewContentType = false;
|
|
@@ -419,24 +438,26 @@ export function renderContentTypesEditor(container) {
|
|
|
419
438
|
editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
|
|
420
439
|
} else {
|
|
421
440
|
const col = contentTypes[selectedContentType];
|
|
422
|
-
const schema = col.schema || {};
|
|
441
|
+
const schema = (col.schema || {}) as ContentTypeSchema;
|
|
423
442
|
const properties = schema.properties || {};
|
|
424
443
|
const required = schema.required || [];
|
|
425
444
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
onDeleteNested: (p, c) => handleDeleteNested(p, c, rerender),
|
|
436
|
-
onToggleNestedRequired: (p, c) => handleToggleNestedRequired(p, c, rerender),
|
|
437
|
-
onRenameNested: (p, o, n) => handleRenameNested(p, o, n, rerender),
|
|
438
|
-
onChangeNestedType: (p, c, t) =>
|
|
439
|
-
|
|
445
|
+
const handlers: import("./schema-field-ui.js").FieldHandlers = {
|
|
446
|
+
onDelete: (n: string) => handleDeleteField(n, rerender),
|
|
447
|
+
onToggleRequired: (n: string) => handleToggleRequired(n, rerender),
|
|
448
|
+
onRename: (oldN: string, newN: string) => handleRenameField(oldN, newN, rerender),
|
|
449
|
+
onChangeType: (n: string, t: string) => handleChangeType(n, t, rerender),
|
|
450
|
+
onChangeFormat: (n: string, f: string) => handleChangeFormat(n, f, rerender),
|
|
451
|
+
onChangeRefTarget: (n: string, target: string) => handleChangeRefTarget(n, target, rerender),
|
|
452
|
+
onAddNestedField: (p: string, s: { name: string; type: string; required: boolean }) =>
|
|
453
|
+
handleAddNestedField(p, s, rerender),
|
|
454
|
+
onDeleteNested: (p: string, c: string) => handleDeleteNested(p, c, rerender),
|
|
455
|
+
onToggleNestedRequired: (p: string, c: string) => handleToggleNestedRequired(p, c, rerender),
|
|
456
|
+
onRenameNested: (p: string, o: string, n: string) => handleRenameNested(p, o, n, rerender),
|
|
457
|
+
onChangeNestedType: (p: string, c: string, t: string) =>
|
|
458
|
+
handleChangeNestedType(p, c, t, rerender),
|
|
459
|
+
onChangeNestedFormat: (p: string, c: string, f: string) =>
|
|
460
|
+
handleChangeNestedFormat(p, c, f, rerender),
|
|
440
461
|
};
|
|
441
462
|
|
|
442
463
|
const fieldCards = repeat(
|