@jxsuite/studio 0.9.0 → 0.10.2

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.
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Collections Editor — visual schema builder for project content collections.
2
+ * Content Types Editor — visual schema builder for project content types.
3
3
  *
4
- * Renders inside the Settings view "Collections" tab. Two-column layout: left column lists
5
- * collection names, right column edits the selected collection's schema.
4
+ * Renders inside the Settings view "Content Types" tab. Two-column layout: left column lists
5
+ * content type names, right column edits the selected content type's schema.
6
6
  */
7
7
 
8
8
  import { html, render as litRender } from "lit-html";
@@ -13,11 +13,11 @@ import { fieldCardTpl, addFieldFormTpl, schemaForType } from "./schema-field-ui.
13
13
  // ─── Module state ─────────────────────────────────────────────────────────────
14
14
 
15
15
  /** @type {string | null} */
16
- let selectedCollection = null;
16
+ let selectedContentType = null;
17
17
  let showAddField = false;
18
18
  let newFieldState = { name: "", type: "string", required: false };
19
- let showNewCollection = false;
20
- let newCollectionName = "";
19
+ let showNewContentType = false;
20
+ let newContentTypeName = "";
21
21
 
22
22
  // ─── Persistence ──────────────────────────────────────────────────────────────
23
23
 
@@ -29,17 +29,17 @@ async function saveProjectConfig() {
29
29
 
30
30
  // ─── Helpers ─────────────────────────────────────────────────────────────────
31
31
 
32
- /** Get the schema object for the selected collection. */
32
+ /** Get the schema object for the selected content type. */
33
33
  function getSelectedSchema() {
34
34
  const config = projectState?.projectConfig;
35
- return config?.collections?.[/** @type {string} */ (selectedCollection)]?.schema;
35
+ return config?.contentTypes?.[/** @type {string} */ (selectedContentType)]?.schema;
36
36
  }
37
37
 
38
38
  // ─── Handlers ─────────────────────────────────────────────────────────────────
39
39
 
40
40
  /** @param {() => void} rerender */
41
- function handleNewCollection(rerender) {
42
- const slug = newCollectionName
41
+ function handleNewContentType(rerender) {
42
+ const slug = newContentTypeName
43
43
  .toLowerCase()
44
44
  .replace(/\s+/g, "-")
45
45
  .replace(/[^a-z0-9-]/g, "");
@@ -47,30 +47,30 @@ function handleNewCollection(rerender) {
47
47
 
48
48
  const config = projectState?.projectConfig;
49
49
  if (!config) return;
50
- if (!config.collections) config.collections = {};
51
- if (config.collections[slug]) return; // already exists
50
+ if (!config.contentTypes) config.contentTypes = {};
51
+ if (config.contentTypes[slug]) return; // already exists
52
52
 
53
- config.collections[slug] = {
54
- source: `./${slug}/**/*.md`,
53
+ config.contentTypes[slug] = {
54
+ source: `./content/${slug}/**/*.md`,
55
55
  schema: { type: "object", properties: {}, required: [] },
56
56
  };
57
57
 
58
- selectedCollection = slug;
59
- showNewCollection = false;
60
- newCollectionName = "";
58
+ selectedContentType = slug;
59
+ showNewContentType = false;
60
+ newContentTypeName = "";
61
61
  rerender();
62
62
 
63
63
  // Persist in background
64
64
  saveProjectConfig().then(async () => {
65
65
  const platform = getPlatform();
66
- await platform.writeFile(`${slug}/.gitkeep`, "");
66
+ await platform.writeFile(`content/${slug}/.gitkeep`, "");
67
67
  });
68
68
  }
69
69
 
70
70
  /** @param {() => void} rerender */
71
71
  function handleAddField(rerender) {
72
72
  const name = newFieldState.name.trim();
73
- if (!name || !selectedCollection) return;
73
+ if (!name || !selectedContentType) return;
74
74
 
75
75
  const schema = getSelectedSchema();
76
76
  if (!schema) return;
@@ -270,13 +270,13 @@ function handleChangeNestedType(parentName, childName, newType, rerender) {
270
270
  }
271
271
 
272
272
  /** @param {() => void} rerender */
273
- function handleDeleteCollection(rerender) {
274
- if (!selectedCollection) return;
273
+ function handleDeleteContentType(rerender) {
274
+ if (!selectedContentType) return;
275
275
  const config = projectState?.projectConfig;
276
- if (!config?.collections?.[selectedCollection]) return;
276
+ if (!config?.contentTypes?.[selectedContentType]) return;
277
277
 
278
- delete config.collections[selectedCollection];
279
- selectedCollection = null;
278
+ delete config.contentTypes[selectedContentType];
279
+ selectedContentType = null;
280
280
 
281
281
  rerender();
282
282
  saveProjectConfig();
@@ -285,26 +285,26 @@ function handleDeleteCollection(rerender) {
285
285
  // ─── Render ───────────────────────────────────────────────────────────────────
286
286
 
287
287
  /**
288
- * Render the collections editor.
288
+ * Render the content types editor.
289
289
  *
290
290
  * @param {HTMLElement} container
291
291
  */
292
- export function renderCollectionsEditor(container) {
293
- const rerender = () => renderCollectionsEditor(container);
292
+ export function renderContentTypesEditor(container) {
293
+ const rerender = () => renderContentTypesEditor(container);
294
294
  const config = projectState?.projectConfig;
295
- const collections = config?.collections || {};
296
- const collectionNames = Object.keys(collections);
295
+ const contentTypes = config?.contentTypes || {};
296
+ const contentTypeNames = Object.keys(contentTypes);
297
297
 
298
- // Left column — collection list
298
+ // Left column — content type list
299
299
  const listTpl = html`
300
300
  <div class="settings-list-panel">
301
- ${collectionNames.map(
301
+ ${contentTypeNames.map(
302
302
  (name) => html`
303
303
  <sp-action-button
304
304
  size="s"
305
- ?selected=${selectedCollection === name}
305
+ ?selected=${selectedContentType === name}
306
306
  @click=${() => {
307
- selectedCollection = name;
307
+ selectedContentType = name;
308
308
  showAddField = false;
309
309
  rerender();
310
310
  }}
@@ -313,25 +313,25 @@ export function renderCollectionsEditor(container) {
313
313
  </sp-action-button>
314
314
  `,
315
315
  )}
316
- ${showNewCollection
316
+ ${showNewContentType
317
317
  ? html`
318
318
  <div class="settings-inline-form">
319
319
  <sp-textfield
320
320
  size="s"
321
- placeholder="collection-name"
322
- .value=${newCollectionName}
321
+ placeholder="content-type-name"
322
+ .value=${newContentTypeName}
323
323
  @input=${(/** @type {any} */ e) => {
324
- newCollectionName = e.target.value;
324
+ newContentTypeName = e.target.value;
325
325
  }}
326
326
  @keydown=${(/** @type {any} */ e) => {
327
- if (e.key === "Enter") handleNewCollection(rerender);
327
+ if (e.key === "Enter") handleNewContentType(rerender);
328
328
  if (e.key === "Escape") {
329
- showNewCollection = false;
329
+ showNewContentType = false;
330
330
  rerender();
331
331
  }
332
332
  }}
333
333
  ></sp-textfield>
334
- <sp-action-button size="s" @click=${() => handleNewCollection(rerender)}>
334
+ <sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
335
335
  Create
336
336
  </sp-action-button>
337
337
  </div>
@@ -341,11 +341,11 @@ export function renderCollectionsEditor(container) {
341
341
  size="s"
342
342
  quiet
343
343
  @click=${() => {
344
- showNewCollection = true;
344
+ showNewContentType = true;
345
345
  rerender();
346
346
  }}
347
347
  >
348
- <sp-icon-add slot="icon"></sp-icon-add> New Collection
348
+ <sp-icon-add slot="icon"></sp-icon-add> New Content Type
349
349
  </sp-action-button>
350
350
  `}
351
351
  </div>
@@ -353,10 +353,10 @@ export function renderCollectionsEditor(container) {
353
353
 
354
354
  // Right column — schema editor
355
355
  let editorTpl;
356
- if (!selectedCollection || !collections[selectedCollection]) {
357
- editorTpl = html`<div class="settings-empty-state">Select or create a collection</div>`;
356
+ if (!selectedContentType || !contentTypes[selectedContentType]) {
357
+ editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
358
358
  } else {
359
- const col = collections[selectedCollection];
359
+ const col = contentTypes[selectedContentType];
360
360
  const schema = col.schema || {};
361
361
  const properties = schema.properties || {};
362
362
  const required = schema.required || [];
@@ -381,13 +381,13 @@ export function renderCollectionsEditor(container) {
381
381
  editorTpl = html`
382
382
  <div class="settings-editor-panel">
383
383
  <div class="settings-editor-header">
384
- <h3>${selectedCollection}</h3>
384
+ <h3>${selectedContentType}</h3>
385
385
  <sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
386
386
  <sp-action-button
387
387
  size="xs"
388
388
  quiet
389
- title="Delete collection"
390
- @click=${() => handleDeleteCollection(rerender)}
389
+ title="Delete content type"
390
+ @click=${() => handleDeleteContentType(rerender)}
391
391
  >
392
392
  <sp-icon-delete slot="icon"></sp-icon-delete>
393
393
  </sp-action-button>
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Schema field UI — shared field-card and add-field-dialog templates for the collections and
2
+ * Schema field UI — shared field-card and add-field-dialog templates for the content types and
3
3
  * definitions editors.
4
4
  */
5
5
 
package/src/state.js CHANGED
@@ -29,6 +29,7 @@
29
29
  * mode: string;
30
30
  * content: { frontmatter: Record<string, any> };
31
31
  * ui: Record<string, any>;
32
+ * canvas: { status: string; scope: any; error: string | null };
32
33
  * }} StudioState
33
34
  */
34
35
 
@@ -228,12 +229,18 @@ export function createState(doc) {
228
229
  stylebookTab: "elements", // "elements" | "variables"
229
230
  stylebookFilter: "", // search filter text
230
231
  stylebookCustomizedOnly: false, // show only customized elements
231
- settingsTab: "stylebook", // "stylebook" | "definitions" | "collections"
232
+ settingsTab: "stylebook", // "stylebook" | "definitions" | "contentTypes"
232
233
  gitStatus: null, // { branch, ahead, behind, files: [] }
233
234
  gitBranches: null, // { current, branches: [] }
234
235
  gitCommitMessage: "", // commit message input
235
236
  gitLoading: false, // loading indicator during async ops
236
237
  gitError: null, // error message string
238
+ pendingInlineEdit: null, // null | { path, mediaName } — deferred inline edit awaiting canvas readiness
239
+ },
240
+ canvas: {
241
+ status: "idle", // "idle" | "loading" | "ready" | "error"
242
+ scope: null, // $defs scope from runtime buildScope
243
+ error: null, // error message on failure
237
244
  },
238
245
  };
239
246
  }
@@ -272,6 +279,7 @@ export function fromFlat(S) {
272
279
  selection,
273
280
  hover,
274
281
  ui,
282
+ canvas,
275
283
  } = S;
276
284
  return {
277
285
  doc: {
@@ -286,7 +294,7 @@ export function fromFlat(S) {
286
294
  history,
287
295
  historyIndex,
288
296
  },
289
- session: { selection, hover, ui },
297
+ session: { selection, hover, ui, canvas },
290
298
  };
291
299
  }
292
300
 
package/src/store.js CHANGED
@@ -324,6 +324,15 @@ export function updateUi(field, value) {
324
324
  _updateSessionFn({ ui: { [field]: value } });
325
325
  }
326
326
 
327
+ /**
328
+ * Update the canvas async state (status, scope, error).
329
+ *
330
+ * @param {any} patch
331
+ */
332
+ export function updateCanvas(patch) {
333
+ _updateSessionFn({ canvas: patch });
334
+ }
335
+
327
336
  // ─── Subscription system ────────────────────────────────────────────────────
328
337
  // Panels subscribe to state changes and decide when to re-render, rather than
329
338
  // being called unconditionally from _update/_updateSession.