@jxsuite/studio 0.37.1 → 1.1.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/iframe-entry.js +622 -49
- package/dist/iframe-entry.js.map +15 -12
- package/dist/studio.css +1333 -0
- package/dist/studio.js +103709 -81001
- package/dist/studio.js.map +201 -157
- package/package.json +47 -45
- package/src/account-status.ts +39 -0
- package/src/browse/browse.ts +10 -14
- package/src/canvas/canvas-live-render.ts +21 -2
- package/src/canvas/canvas-render.ts +35 -16
- package/src/canvas/canvas-utils.ts +118 -72
- package/src/canvas/iframe-entry.ts +20 -0
- package/src/canvas/iframe-eval.ts +155 -0
- package/src/canvas/iframe-host.ts +132 -6
- package/src/canvas/iframe-inline-edit.ts +107 -1
- package/src/canvas/iframe-protocol.ts +43 -3
- package/src/canvas/iframe-render.ts +18 -0
- package/src/collab/collab-session.ts +23 -8
- package/src/collab/collab-state.ts +6 -3
- package/src/component-props.ts +104 -0
- package/src/editor/context-menu.ts +1 -1
- package/src/editor/inline-edit-apply.ts +36 -1
- package/src/editor/inline-edit.ts +58 -1
- package/src/editor/repeater-scope.ts +8 -13
- package/src/editor/shortcuts.ts +41 -12
- package/src/files/file-ops.ts +14 -0
- package/src/files/files.ts +56 -1
- package/src/format/format-host.ts +63 -1
- package/src/grid/cell-editors.ts +288 -0
- package/src/grid/cell-popovers.ts +108 -0
- package/src/grid/csv-codec.ts +122 -0
- package/src/grid/edit-buffer.ts +490 -0
- package/src/grid/grid-controller.ts +417 -0
- package/src/grid/grid-layout.ts +83 -0
- package/src/grid/grid-open.ts +167 -0
- package/src/grid/grid-panel.ts +302 -0
- package/src/grid/grid-source.ts +210 -0
- package/src/grid/grid-view.ts +367 -0
- package/src/grid/schema-columns.ts +261 -0
- package/src/grid/sources/connector-source.ts +217 -0
- package/src/grid/sources/content-source.ts +542 -0
- package/src/grid/sources/csv-file-source.ts +247 -0
- package/src/grid/tabulator-tables.d.ts +120 -0
- package/src/new-project/add-repo-modal.ts +183 -0
- package/src/new-project/new-project-modal.ts +22 -3
- package/src/page-params.ts +34 -8
- package/src/panels/ai-chat/chat-markdown.ts +2 -2
- package/src/panels/ai-panel.ts +61 -4
- package/src/panels/block-action-bar.ts +8 -3
- package/src/panels/chat-panel.ts +98 -0
- package/src/panels/data-grid.ts +241 -0
- package/src/panels/editors.ts +43 -17
- package/src/panels/events-panel.ts +137 -44
- package/src/panels/formula-workspace.ts +379 -0
- package/src/panels/frontmatter-fields.ts +231 -0
- package/src/panels/frontmatter-panel.ts +132 -0
- package/src/panels/git-panel.ts +1 -1
- package/src/panels/head-panel.ts +11 -175
- package/src/panels/properties-panel.ts +154 -144
- package/src/panels/right-panel.ts +9 -47
- package/src/panels/signals-panel.ts +217 -460
- package/src/panels/statement-editor.ts +710 -0
- package/src/panels/statusbar.ts +1 -1
- package/src/panels/style-panel.ts +25 -1
- package/src/panels/stylebook-panel.ts +0 -2
- package/src/panels/tab-bar.ts +175 -6
- package/src/panels/tab-strip.ts +62 -6
- package/src/panels/toolbar.ts +37 -3
- package/src/panels/welcome-screen.ts +50 -0
- package/src/platform-errors.ts +30 -0
- package/src/platforms/cloud.ts +172 -89
- package/src/platforms/devserver.ts +172 -0
- package/src/services/ai-project-tools.ts +541 -0
- package/src/services/ai-session-store.ts +28 -0
- package/src/services/ai-system-prompt.ts +194 -24
- package/src/services/ai-tools.ts +88 -21
- package/src/services/automation.ts +16 -0
- package/src/services/context-resolver.ts +73 -0
- package/src/services/data-service.ts +155 -0
- package/src/services/document-assistant.ts +81 -11
- package/src/services/gated-registry.ts +72 -0
- package/src/services/live-preview.ts +0 -0
- package/src/services/monaco-setup.ts +75 -26
- package/src/services/preview-eval.ts +68 -0
- package/src/services/project-adoption.ts +31 -0
- package/src/settings/contributed-section.ts +406 -0
- package/src/settings/extension-sections.ts +145 -0
- package/src/settings/schema-field-ui.ts +4 -2
- package/src/settings/settings-modal.ts +101 -42
- package/src/site-context.ts +12 -1
- package/src/store.ts +4 -0
- package/src/studio.ts +107 -52
- package/src/tabs/patch-ops.ts +25 -0
- package/src/tabs/tab.ts +39 -1
- package/src/tabs/transact.ts +61 -14
- package/src/types.ts +132 -1
- package/src/ui/dynamic-slot.ts +272 -0
- package/src/ui/expression-editor.ts +423 -125
- package/src/ui/field-row.ts +5 -0
- package/src/ui/form-controls.ts +322 -0
- package/src/ui/formula-catalog.ts +557 -0
- package/src/ui/formula-chips.ts +216 -0
- package/src/ui/formula-palette.ts +211 -0
- package/src/ui/layers.ts +40 -0
- package/src/ui/media-picker.ts +1 -1
- package/src/ui/panel-resize.ts +15 -1
- package/src/ui/progress-modal.ts +2 -2
- package/src/ui/schema-form.ts +524 -0
- package/src/utils/preview-format.ts +26 -0
- package/src/utils/studio-utils.ts +4 -3
- package/src/view.ts +4 -4
- package/src/workspace/workspace.ts +14 -0
- package/src/settings/content-types-editor.ts +0 -599
|
@@ -1,599 +0,0 @@
|
|
|
1
|
-
/// <reference lib="dom" />
|
|
2
|
-
/**
|
|
3
|
-
* Content Types Editor — visual schema builder for project content types.
|
|
4
|
-
*
|
|
5
|
-
* Renders inside the Settings view "Content Types" tab. Two-column layout: left column lists
|
|
6
|
-
* content type names, right column edits the selected content type's schema.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { html, render as litRender } from "lit-html";
|
|
10
|
-
import { repeat } from "lit-html/directives/repeat.js";
|
|
11
|
-
import { getPlatform } from "../platform";
|
|
12
|
-
import { projectState } from "../store";
|
|
13
|
-
import { addFieldFormTpl, detectFieldFormat, fieldCardTpl, schemaForType } from "./schema-field-ui";
|
|
14
|
-
import { toCamelCase } from "../utils/studio-utils";
|
|
15
|
-
|
|
16
|
-
import type { FieldHandlers } from "./schema-field-ui.js";
|
|
17
|
-
import type {
|
|
18
|
-
ContentTypeSchema,
|
|
19
|
-
ContentTypeSchemaField,
|
|
20
|
-
ProjectConfig,
|
|
21
|
-
} from "@jxsuite/schema/types";
|
|
22
|
-
|
|
23
|
-
// ─── Module state ─────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
let selectedContentType: string | null = null;
|
|
26
|
-
let showAddField = false;
|
|
27
|
-
let newFieldState = { format: "", name: "", required: false, type: "string" };
|
|
28
|
-
let showNewContentType = false;
|
|
29
|
-
let newContentTypeName = "";
|
|
30
|
-
|
|
31
|
-
// ─── Persistence ──────────────────────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
async function saveProjectConfig() {
|
|
34
|
-
const platform = getPlatform();
|
|
35
|
-
const config = (projectState as { projectConfig: ProjectConfig }).projectConfig;
|
|
36
|
-
await platform.writeFile("project.json", JSON.stringify(config, null, "\t"));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
/** Get the schema object for the selected content type. */
|
|
42
|
-
function getSelectedSchema(): ContentTypeSchema | undefined {
|
|
43
|
-
const config = projectState?.projectConfig;
|
|
44
|
-
return config?.contentTypes?.[selectedContentType as string]?.schema as
|
|
45
|
-
| ContentTypeSchema
|
|
46
|
-
| undefined;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ─── Handlers ─────────────────────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
/** @param {() => void} rerender */
|
|
52
|
-
function handleNewContentType(rerender: () => void) {
|
|
53
|
-
const slug = newContentTypeName
|
|
54
|
-
.toLowerCase()
|
|
55
|
-
.replaceAll(/\s+/g, "-")
|
|
56
|
-
.replaceAll(/[^a-z0-9-]/g, "");
|
|
57
|
-
if (!slug) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const config = projectState?.projectConfig;
|
|
62
|
-
if (!config) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
if (!config.contentTypes) {
|
|
66
|
-
config.contentTypes = {};
|
|
67
|
-
}
|
|
68
|
-
if (config.contentTypes[slug]) {
|
|
69
|
-
return;
|
|
70
|
-
} // Already exists
|
|
71
|
-
|
|
72
|
-
config.contentTypes[slug] = {
|
|
73
|
-
schema: { properties: {}, required: [], type: "object" },
|
|
74
|
-
source: `./content/${slug}/`,
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
selectedContentType = slug;
|
|
78
|
-
showNewContentType = false;
|
|
79
|
-
newContentTypeName = "";
|
|
80
|
-
rerender();
|
|
81
|
-
|
|
82
|
-
// Persist in background
|
|
83
|
-
void saveProjectConfig().then(async () => {
|
|
84
|
-
const platform = getPlatform();
|
|
85
|
-
await platform.writeFile(`content/${slug}/.gitkeep`, "");
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** @param {() => void} rerender */
|
|
90
|
-
function handleAddField(rerender: () => void) {
|
|
91
|
-
const raw = newFieldState.name.trim();
|
|
92
|
-
if (!raw || !selectedContentType) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
const name = toCamelCase(raw);
|
|
96
|
-
|
|
97
|
-
const schema = getSelectedSchema();
|
|
98
|
-
if (!schema) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (!schema.properties) {
|
|
103
|
-
schema.properties = {};
|
|
104
|
-
}
|
|
105
|
-
schema.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
|
|
106
|
-
|
|
107
|
-
if (newFieldState.required) {
|
|
108
|
-
if (!schema.required) {
|
|
109
|
-
schema.required = [];
|
|
110
|
-
}
|
|
111
|
-
if (!schema.required.includes(name)) {
|
|
112
|
-
schema.required.push(name);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
showAddField = false;
|
|
117
|
-
newFieldState = { format: "", name: "", required: false, type: "string" };
|
|
118
|
-
rerender();
|
|
119
|
-
void saveProjectConfig();
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* @param {string} fieldName
|
|
124
|
-
* @param {() => void} rerender
|
|
125
|
-
*/
|
|
126
|
-
function handleDeleteField(fieldName: string, rerender: () => void) {
|
|
127
|
-
const schema = getSelectedSchema();
|
|
128
|
-
if (!schema?.properties) {
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
delete schema.properties[fieldName];
|
|
133
|
-
if (schema.required) {
|
|
134
|
-
schema.required = schema.required.filter((r: string) => r !== fieldName);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
rerender();
|
|
138
|
-
void saveProjectConfig();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* @param {string} fieldName
|
|
143
|
-
* @param {() => void} rerender
|
|
144
|
-
*/
|
|
145
|
-
function handleToggleRequired(fieldName: string, rerender: () => void) {
|
|
146
|
-
const schema = getSelectedSchema();
|
|
147
|
-
if (!schema) {
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
if (!schema.required) {
|
|
151
|
-
schema.required = [];
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const idx = schema.required.indexOf(fieldName);
|
|
155
|
-
if (idx !== -1) {
|
|
156
|
-
schema.required.splice(idx, 1);
|
|
157
|
-
} else {
|
|
158
|
-
schema.required.push(fieldName);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
rerender();
|
|
162
|
-
void saveProjectConfig();
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* @param {string} oldName
|
|
167
|
-
* @param {string} newName
|
|
168
|
-
* @param {() => void} rerender
|
|
169
|
-
*/
|
|
170
|
-
function handleRenameField(oldName: string, newName: string, rerender: () => void) {
|
|
171
|
-
const schema = getSelectedSchema();
|
|
172
|
-
const normalized = toCamelCase(newName);
|
|
173
|
-
if (!schema?.properties || !normalized || schema.properties[normalized]) {
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const newProps: Record<string, ContentTypeSchemaField> = {};
|
|
178
|
-
for (const [key, val] of Object.entries(schema.properties)) {
|
|
179
|
-
newProps[key === oldName ? normalized : key] = val;
|
|
180
|
-
}
|
|
181
|
-
schema.properties = newProps;
|
|
182
|
-
|
|
183
|
-
if (schema.required) {
|
|
184
|
-
schema.required = schema.required.map((r: string) => (r === oldName ? normalized : r));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
rerender();
|
|
188
|
-
void saveProjectConfig();
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @param {string} fieldName
|
|
193
|
-
* @param {string} newType
|
|
194
|
-
* @param {() => void} rerender
|
|
195
|
-
*/
|
|
196
|
-
function handleChangeType(fieldName: string, newType: string, rerender: () => void) {
|
|
197
|
-
const schema = getSelectedSchema();
|
|
198
|
-
if (!schema?.properties?.[fieldName]) {
|
|
199
|
-
return;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const oldFormat =
|
|
203
|
-
newType === "string" || newType === "array"
|
|
204
|
-
? detectFieldFormat(schema.properties[fieldName])
|
|
205
|
-
: undefined;
|
|
206
|
-
schema.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
|
|
207
|
-
rerender();
|
|
208
|
-
void saveProjectConfig();
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* @param {string} fieldName
|
|
213
|
-
* @param {string} format
|
|
214
|
-
* @param {() => void} rerender
|
|
215
|
-
*/
|
|
216
|
-
function handleChangeFormat(fieldName: string, format: string, rerender: () => void) {
|
|
217
|
-
const schema = getSelectedSchema();
|
|
218
|
-
if (!schema?.properties?.[fieldName]) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
const prop = schema.properties[fieldName];
|
|
223
|
-
const type = prop.type || "string";
|
|
224
|
-
schema.properties[fieldName] = schemaForType(type, format || undefined);
|
|
225
|
-
rerender();
|
|
226
|
-
void saveProjectConfig();
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* @param {string} fieldName
|
|
231
|
-
* @param {string} target
|
|
232
|
-
* @param {() => void} rerender
|
|
233
|
-
*/
|
|
234
|
-
function handleChangeRefTarget(fieldName: string, target: string, rerender: () => void) {
|
|
235
|
-
const schema = getSelectedSchema();
|
|
236
|
-
if (!schema?.properties) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
schema.properties[fieldName] = { $ref: `#/contentTypes/${target}` };
|
|
241
|
-
rerender();
|
|
242
|
-
void saveProjectConfig();
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// ─── Nested field handlers ───────────────────────────────────────────────────
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* @param {string} parentName
|
|
249
|
-
* @param {{ name: string; type: string; required: boolean }} fieldState
|
|
250
|
-
* @param {() => void} rerender
|
|
251
|
-
*/
|
|
252
|
-
function handleAddNestedField(
|
|
253
|
-
parentName: string,
|
|
254
|
-
fieldState: { name: string; type: string; required: boolean },
|
|
255
|
-
rerender: () => void,
|
|
256
|
-
) {
|
|
257
|
-
const schema = getSelectedSchema();
|
|
258
|
-
const parent = schema?.properties?.[parentName];
|
|
259
|
-
if (!parent) {
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const name = toCamelCase(fieldState.name);
|
|
264
|
-
if (!name) {
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
if (!parent.properties) {
|
|
269
|
-
parent.properties = {};
|
|
270
|
-
}
|
|
271
|
-
parent.properties[name] = schemaForType(fieldState.type);
|
|
272
|
-
|
|
273
|
-
if (fieldState.required) {
|
|
274
|
-
if (!parent.required) {
|
|
275
|
-
parent.required = [];
|
|
276
|
-
}
|
|
277
|
-
if (!parent.required.includes(name)) {
|
|
278
|
-
parent.required.push(name);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
rerender();
|
|
283
|
-
void saveProjectConfig();
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* @param {string} parentName
|
|
288
|
-
* @param {string} childName
|
|
289
|
-
* @param {() => void} rerender
|
|
290
|
-
*/
|
|
291
|
-
function handleDeleteNested(parentName: string, childName: string, rerender: () => void) {
|
|
292
|
-
const schema = getSelectedSchema();
|
|
293
|
-
const parent = schema?.properties?.[parentName];
|
|
294
|
-
if (!parent?.properties) {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
delete parent.properties[childName];
|
|
299
|
-
if (parent.required) {
|
|
300
|
-
parent.required = parent.required.filter((r: string) => r !== childName);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
rerender();
|
|
304
|
-
void saveProjectConfig();
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/**
|
|
308
|
-
* @param {string} parentName
|
|
309
|
-
* @param {string} childName
|
|
310
|
-
* @param {() => void} rerender
|
|
311
|
-
*/
|
|
312
|
-
function handleToggleNestedRequired(parentName: string, childName: string, rerender: () => void) {
|
|
313
|
-
const schema = getSelectedSchema();
|
|
314
|
-
const parent = schema?.properties?.[parentName];
|
|
315
|
-
if (!parent) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
if (!parent.required) {
|
|
319
|
-
parent.required = [];
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const idx = parent.required.indexOf(childName);
|
|
323
|
-
if (idx !== -1) {
|
|
324
|
-
parent.required.splice(idx, 1);
|
|
325
|
-
} else {
|
|
326
|
-
parent.required.push(childName);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
rerender();
|
|
330
|
-
void saveProjectConfig();
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* @param {string} parentName
|
|
335
|
-
* @param {string} oldChild
|
|
336
|
-
* @param {string} newChild
|
|
337
|
-
* @param {() => void} rerender
|
|
338
|
-
*/
|
|
339
|
-
function handleRenameNested(
|
|
340
|
-
parentName: string,
|
|
341
|
-
oldChild: string,
|
|
342
|
-
newChild: string,
|
|
343
|
-
rerender: () => void,
|
|
344
|
-
) {
|
|
345
|
-
const schema = getSelectedSchema();
|
|
346
|
-
const parent = schema?.properties?.[parentName];
|
|
347
|
-
const normalized = toCamelCase(newChild);
|
|
348
|
-
if (!parent?.properties || !normalized || parent.properties[normalized]) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const newProps: Record<string, ContentTypeSchemaField> = {};
|
|
353
|
-
for (const [key, val] of Object.entries(parent.properties)) {
|
|
354
|
-
newProps[key === oldChild ? normalized : key] = val;
|
|
355
|
-
}
|
|
356
|
-
parent.properties = newProps;
|
|
357
|
-
|
|
358
|
-
if (parent.required) {
|
|
359
|
-
parent.required = parent.required.map((r: string) => (r === oldChild ? normalized : r));
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
rerender();
|
|
363
|
-
void saveProjectConfig();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* @param {string} parentName
|
|
368
|
-
* @param {string} childName
|
|
369
|
-
* @param {string} newType
|
|
370
|
-
* @param {() => void} rerender
|
|
371
|
-
*/
|
|
372
|
-
function handleChangeNestedType(
|
|
373
|
-
parentName: string,
|
|
374
|
-
childName: string,
|
|
375
|
-
newType: string,
|
|
376
|
-
rerender: () => void,
|
|
377
|
-
) {
|
|
378
|
-
const schema = getSelectedSchema();
|
|
379
|
-
const parent = schema?.properties?.[parentName];
|
|
380
|
-
if (!parent?.properties?.[childName]) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
const oldFormat =
|
|
385
|
-
newType === "string" || newType === "array"
|
|
386
|
-
? detectFieldFormat(parent.properties[childName])
|
|
387
|
-
: undefined;
|
|
388
|
-
parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
|
|
389
|
-
rerender();
|
|
390
|
-
void saveProjectConfig();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
/**
|
|
394
|
-
* @param {string} parentName
|
|
395
|
-
* @param {string} childName
|
|
396
|
-
* @param {string} format
|
|
397
|
-
* @param {() => void} rerender
|
|
398
|
-
*/
|
|
399
|
-
function handleChangeNestedFormat(
|
|
400
|
-
parentName: string,
|
|
401
|
-
childName: string,
|
|
402
|
-
format: string,
|
|
403
|
-
rerender: () => void,
|
|
404
|
-
) {
|
|
405
|
-
const schema = getSelectedSchema();
|
|
406
|
-
const parent = schema?.properties?.[parentName];
|
|
407
|
-
if (!parent?.properties?.[childName]) {
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
const prop = parent.properties[childName];
|
|
412
|
-
const type = prop.type || "string";
|
|
413
|
-
parent.properties[childName] = schemaForType(type, format || undefined);
|
|
414
|
-
rerender();
|
|
415
|
-
void saveProjectConfig();
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/** @param {() => void} rerender */
|
|
419
|
-
function handleDeleteContentType(rerender: () => void) {
|
|
420
|
-
if (!selectedContentType) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const config = projectState?.projectConfig;
|
|
424
|
-
if (!config?.contentTypes?.[selectedContentType]) {
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
delete config.contentTypes[selectedContentType];
|
|
429
|
-
selectedContentType = null;
|
|
430
|
-
|
|
431
|
-
rerender();
|
|
432
|
-
void saveProjectConfig();
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// ─── Render ───────────────────────────────────────────────────────────────────
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Render the content types editor.
|
|
439
|
-
*
|
|
440
|
-
* @param {HTMLElement} container
|
|
441
|
-
*/
|
|
442
|
-
export function renderContentTypesEditor(container: HTMLElement) {
|
|
443
|
-
const rerender = () => renderContentTypesEditor(container);
|
|
444
|
-
const config = projectState?.projectConfig;
|
|
445
|
-
const contentTypes = config?.contentTypes || {};
|
|
446
|
-
const contentTypeNames = Object.keys(contentTypes);
|
|
447
|
-
|
|
448
|
-
// Left column — content type list
|
|
449
|
-
const listTpl = html`
|
|
450
|
-
<div class="settings-list-panel">
|
|
451
|
-
${contentTypeNames.map(
|
|
452
|
-
(name) => html`
|
|
453
|
-
<sp-action-button
|
|
454
|
-
size="s"
|
|
455
|
-
?selected=${selectedContentType === name}
|
|
456
|
-
@click=${() => {
|
|
457
|
-
selectedContentType = name;
|
|
458
|
-
showAddField = false;
|
|
459
|
-
rerender();
|
|
460
|
-
}}
|
|
461
|
-
>
|
|
462
|
-
${name}
|
|
463
|
-
</sp-action-button>
|
|
464
|
-
`,
|
|
465
|
-
)}
|
|
466
|
-
${showNewContentType
|
|
467
|
-
? html`
|
|
468
|
-
<div class="settings-inline-form">
|
|
469
|
-
<sp-textfield
|
|
470
|
-
size="s"
|
|
471
|
-
placeholder="content-type-name"
|
|
472
|
-
.value=${newContentTypeName}
|
|
473
|
-
@input=${(e: Event) => {
|
|
474
|
-
newContentTypeName = (e.target as HTMLInputElement).value;
|
|
475
|
-
}}
|
|
476
|
-
@keydown=${(e: KeyboardEvent) => {
|
|
477
|
-
if (e.key === "Enter") {
|
|
478
|
-
handleNewContentType(rerender);
|
|
479
|
-
}
|
|
480
|
-
if (e.key === "Escape") {
|
|
481
|
-
showNewContentType = false;
|
|
482
|
-
rerender();
|
|
483
|
-
}
|
|
484
|
-
}}
|
|
485
|
-
></sp-textfield>
|
|
486
|
-
<sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
|
|
487
|
-
Create
|
|
488
|
-
</sp-action-button>
|
|
489
|
-
</div>
|
|
490
|
-
`
|
|
491
|
-
: html`
|
|
492
|
-
<sp-action-button
|
|
493
|
-
size="s"
|
|
494
|
-
quiet
|
|
495
|
-
@click=${() => {
|
|
496
|
-
showNewContentType = true;
|
|
497
|
-
rerender();
|
|
498
|
-
}}
|
|
499
|
-
>
|
|
500
|
-
<sp-icon-add slot="icon"></sp-icon-add> New Content Type
|
|
501
|
-
</sp-action-button>
|
|
502
|
-
`}
|
|
503
|
-
</div>
|
|
504
|
-
`;
|
|
505
|
-
|
|
506
|
-
// Right column — schema editor
|
|
507
|
-
let editorTpl;
|
|
508
|
-
if (!selectedContentType || !contentTypes[selectedContentType]) {
|
|
509
|
-
editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
|
|
510
|
-
} else {
|
|
511
|
-
const col = contentTypes[selectedContentType]!;
|
|
512
|
-
const schema = (col.schema || {}) as ContentTypeSchema;
|
|
513
|
-
const properties = schema.properties || {};
|
|
514
|
-
const required = schema.required || [];
|
|
515
|
-
|
|
516
|
-
const handlers: FieldHandlers = {
|
|
517
|
-
onAddNestedField: (p: string, s: { name: string; type: string; required: boolean }) =>
|
|
518
|
-
handleAddNestedField(p, s, rerender),
|
|
519
|
-
onChangeFormat: (n: string, f: string) => handleChangeFormat(n, f, rerender),
|
|
520
|
-
onChangeNestedFormat: (p: string, c: string, f: string) =>
|
|
521
|
-
handleChangeNestedFormat(p, c, f, rerender),
|
|
522
|
-
onChangeNestedType: (p: string, c: string, t: string) =>
|
|
523
|
-
handleChangeNestedType(p, c, t, rerender),
|
|
524
|
-
onChangeRefTarget: (n: string, target: string) => handleChangeRefTarget(n, target, rerender),
|
|
525
|
-
onChangeType: (n: string, t: string) => handleChangeType(n, t, rerender),
|
|
526
|
-
onDelete: (n: string) => handleDeleteField(n, rerender),
|
|
527
|
-
onDeleteNested: (p: string, c: string) => handleDeleteNested(p, c, rerender),
|
|
528
|
-
onRename: (oldN: string, newN: string) => handleRenameField(oldN, newN, rerender),
|
|
529
|
-
onRenameNested: (p: string, o: string, n: string) => handleRenameNested(p, o, n, rerender),
|
|
530
|
-
onToggleNestedRequired: (p: string, c: string) => handleToggleNestedRequired(p, c, rerender),
|
|
531
|
-
onToggleRequired: (n: string) => handleToggleRequired(n, rerender),
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const fieldCards = repeat(
|
|
535
|
-
Object.entries(properties),
|
|
536
|
-
([name]) => name,
|
|
537
|
-
([name, def]) =>
|
|
538
|
-
fieldCardTpl(
|
|
539
|
-
name,
|
|
540
|
-
/** @type {import("./schema-field-ui.js").SchemaProperty} */ def,
|
|
541
|
-
required.includes(name),
|
|
542
|
-
handlers,
|
|
543
|
-
contentTypeNames,
|
|
544
|
-
),
|
|
545
|
-
);
|
|
546
|
-
|
|
547
|
-
editorTpl = html`
|
|
548
|
-
<div class="settings-editor-panel">
|
|
549
|
-
<div class="settings-editor-header">
|
|
550
|
-
<h3>${selectedContentType}</h3>
|
|
551
|
-
<sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
|
|
552
|
-
<sp-action-button
|
|
553
|
-
size="xs"
|
|
554
|
-
quiet
|
|
555
|
-
title="Delete content type"
|
|
556
|
-
@click=${() => handleDeleteContentType(rerender)}
|
|
557
|
-
>
|
|
558
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
559
|
-
</sp-action-button>
|
|
560
|
-
</div>
|
|
561
|
-
<div class="schema-field-list">${fieldCards}</div>
|
|
562
|
-
${showAddField
|
|
563
|
-
? addFieldFormTpl(newFieldState, {
|
|
564
|
-
onCancel: () => {
|
|
565
|
-
showAddField = false;
|
|
566
|
-
newFieldState = {
|
|
567
|
-
format: "",
|
|
568
|
-
name: "",
|
|
569
|
-
required: false,
|
|
570
|
-
type: "string",
|
|
571
|
-
};
|
|
572
|
-
rerender();
|
|
573
|
-
},
|
|
574
|
-
onConfirm: () => handleAddField(rerender),
|
|
575
|
-
onInput: (field, value) => {
|
|
576
|
-
newFieldState = { ...newFieldState, [field]: value };
|
|
577
|
-
rerender();
|
|
578
|
-
},
|
|
579
|
-
})
|
|
580
|
-
: html`
|
|
581
|
-
<sp-action-button
|
|
582
|
-
size="s"
|
|
583
|
-
quiet
|
|
584
|
-
@click=${() => {
|
|
585
|
-
showAddField = true;
|
|
586
|
-
rerender();
|
|
587
|
-
}}
|
|
588
|
-
>
|
|
589
|
-
<sp-icon-add slot="icon"></sp-icon-add> Add Field
|
|
590
|
-
</sp-action-button>
|
|
591
|
-
`}
|
|
592
|
-
</div>
|
|
593
|
-
`;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
|
|
597
|
-
|
|
598
|
-
litRender(tpl, container);
|
|
599
|
-
}
|