@jxsuite/studio 0.16.1 → 0.18.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 CHANGED
@@ -47856,7 +47856,7 @@ function applyTransform() {
47856
47856
  view.panzoomWrap.style.transform = `translate(${view.panX}px, ${view.panY}px) scale(${zoom})`;
47857
47857
  renderZoomIndicator();
47858
47858
  renderOnly("overlays");
47859
- if (_ctx4.getCanvasMode() === "settings")
47859
+ if (_ctx4.getCanvasMode() === "stylebook")
47860
47860
  _ctx4.renderStylebookOverlays();
47861
47861
  }
47862
47862
  function fitToScreen() {
@@ -176998,12 +176998,6 @@ async function codeService(action, payload) {
176998
176998
  return null;
176999
176999
  return platform3.codeService(action, payload);
177000
177000
  }
177001
- async function locateDocument(name) {
177002
- const platform3 = getPlatform();
177003
- if (!platform3.locateFile)
177004
- return null;
177005
- return platform3.locateFile(name);
177006
- }
177007
177001
  var pluginSchemaCache = new Map;
177008
177002
  async function fetchPluginSchema(def3, state) {
177009
177003
  if (!def3.$src || !def3.$prototype)
@@ -177062,58 +177056,6 @@ function getFunctionArgs(editing, document4) {
177062
177056
  // src/files/file-ops.js
177063
177057
  init_platform();
177064
177058
  init_workspace();
177065
- async function openFile() {
177066
- try {
177067
- if ("showOpenFilePicker" in window) {
177068
- const [handle2] = await window.showOpenFilePicker({
177069
- types: [
177070
- { description: "Jx Component", accept: { "application/json": [".json"] } },
177071
- { description: "Markdown Content", accept: { "text/markdown": [".md"] } }
177072
- ]
177073
- });
177074
- const file = await handle2.getFile();
177075
- const text6 = await file.text();
177076
- const documentPath = await locateDocument(handle2.name);
177077
- if (handle2.name.endsWith(".md")) {
177078
- const { document: document4, frontmatter: frontmatter2 } = await loadMarkdown(text6);
177079
- openTab({
177080
- id: handle2.name,
177081
- documentPath,
177082
- fileHandle: handle2,
177083
- document: document4,
177084
- frontmatter: frontmatter2,
177085
- sourceFormat: "md"
177086
- });
177087
- } else {
177088
- const document4 = JSON.parse(text6);
177089
- openTab({ id: handle2.name, documentPath, fileHandle: handle2, document: document4 });
177090
- }
177091
- statusMessage(`Opened ${handle2.name}`);
177092
- } else {
177093
- const input = document.createElement("input");
177094
- input.type = "file";
177095
- input.accept = ".json,.md";
177096
- input.onchange = async () => {
177097
- const file = input.files?.[0];
177098
- if (!file)
177099
- return;
177100
- const text6 = await file.text();
177101
- if (file.name.endsWith(".md")) {
177102
- const { document: document4, frontmatter: frontmatter2 } = await loadMarkdown(text6);
177103
- openTab({ id: file.name, document: document4, frontmatter: frontmatter2, sourceFormat: "md" });
177104
- } else {
177105
- const document4 = JSON.parse(text6);
177106
- openTab({ id: file.name, document: document4 });
177107
- }
177108
- statusMessage(`Opened ${file.name}`);
177109
- };
177110
- input.click();
177111
- }
177112
- } catch (e) {
177113
- if (e.name !== "AbortError")
177114
- statusMessage(`Error: ${e.message}`);
177115
- }
177116
- }
177117
177059
  async function loadMarkdown(source) {
177118
177060
  const { transpileJxMarkdown: transpileJxMarkdown2 } = await Promise.resolve().then(() => (init_transpile(), exports_transpile));
177119
177061
  const doc = transpileJxMarkdown2(source);
@@ -180158,1071 +180100,6 @@ init_components();
180158
180100
  init_store();
180159
180101
  init_workspace();
180160
180102
  init_components();
180161
-
180162
- // src/utils/studio-utils.js
180163
- function camelToKebab2(str) {
180164
- return str.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase());
180165
- }
180166
- function camelToLabel(prop) {
180167
- return prop.replace(/([A-Z])/g, " $1").replace(/^./, (c) => c.toUpperCase());
180168
- }
180169
- function kebabToLabel(val) {
180170
- return val.replace(/(^|-)(\w)/g, (_, sep2, c) => (sep2 ? " " : "") + c.toUpperCase());
180171
- }
180172
- function propLabel(entry, prop) {
180173
- return entry?.$label || camelToLabel(prop);
180174
- }
180175
- function attrLabel(entry, attr) {
180176
- if (entry?.$label)
180177
- return entry.$label;
180178
- if (attr.includes("-"))
180179
- return attr.replace(/(^|-)(\w)/g, (_, sep2, c) => (sep2 ? " " : "") + c.toUpperCase());
180180
- return camelToLabel(attr);
180181
- }
180182
- function abbreviateValue(val) {
180183
- const map6 = {
180184
- inline: "inl",
180185
- "inline-block": "i-blk",
180186
- "inline-flex": "i-flx",
180187
- "inline-grid": "i-grd",
180188
- contents: "cnt",
180189
- "flow-root": "flow",
180190
- nowrap: "no-wr",
180191
- "wrap-reverse": "wr-rev",
180192
- "flex-start": "start",
180193
- "flex-end": "end",
180194
- "space-between": "betw",
180195
- "space-around": "arnd",
180196
- "space-evenly": "even",
180197
- stretch: "str",
180198
- baseline: "base",
180199
- normal: "norm",
180200
- "row-reverse": "row-r",
180201
- "column-reverse": "col-r",
180202
- column: "col"
180203
- };
180204
- return map6[val] || val;
180205
- }
180206
- function inferInputType(entry) {
180207
- if (entry.$shorthand === true)
180208
- return "shorthand";
180209
- if (entry.$input === "button-group")
180210
- return "button-group";
180211
- if (entry.$input === "media")
180212
- return "media";
180213
- if (entry.format === "color")
180214
- return "color";
180215
- if (entry.format === "uri-reference")
180216
- return "media";
180217
- if (entry.$units !== undefined)
180218
- return "number-unit";
180219
- if (entry.type === "number")
180220
- return "number";
180221
- if (Array.isArray(entry.enum))
180222
- return "select";
180223
- if (Array.isArray(entry.examples) || Array.isArray(entry.presets))
180224
- return "combobox";
180225
- return "text";
180226
- }
180227
- function findContentTypeSchema(documentPath, projectConfig) {
180228
- if (!documentPath || !projectConfig?.contentTypes)
180229
- return null;
180230
- for (const [name, def3] of Object.entries(projectConfig.contentTypes)) {
180231
- if (!def3.source || !def3.schema)
180232
- continue;
180233
- const src = def3.source.replace(/^\.\//, "");
180234
- const dir = src.split("/")[0];
180235
- const ext = src.includes("*.md") ? ".md" : src.includes("*.json") ? ".json" : src.includes("*.csv") ? ".csv" : null;
180236
- if (documentPath.startsWith(dir + "/") && (!ext || documentPath.endsWith(ext))) {
180237
- return { name, schema: def3.schema };
180238
- }
180239
- }
180240
- return null;
180241
- }
180242
- function friendlyNameToVar(name, prefix) {
180243
- const slug = name.trim().toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
180244
- if (!slug)
180245
- return "";
180246
- return `${prefix}${slug}`;
180247
- }
180248
- function varDisplayName(varName, prefix) {
180249
- return varName.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").replace(/^--/, "").replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()) || varName;
180250
- }
180251
- function parseCemType(typeText) {
180252
- if (!typeText)
180253
- return { kind: "text" };
180254
- const t = typeText.trim().replace(/\s*\|\s*undefined\b/g, "").trim();
180255
- if (t === "boolean")
180256
- return { kind: "boolean" };
180257
- if (t === "number")
180258
- return { kind: "number" };
180259
- const enumMatch = t.match(/^'[^']*'(\s*\|\s*'[^']*')+$/);
180260
- if (enumMatch) {
180261
- const options = [...t.matchAll(/'([^']*)'/g)].map((m) => m[1]);
180262
- return { kind: "combobox", options };
180263
- }
180264
- return { kind: "text" };
180265
- }
180266
-
180267
- // src/settings/defs-editor.js
180268
- init_platform();
180269
- init_store();
180270
-
180271
- // src/settings/schema-field-ui.js
180272
- var FIELD_TYPES = ["string", "number", "boolean", "array", "object", "reference"];
180273
- var FORMAT_OPTIONS = ["", "image", "date", "color"];
180274
- function detectFieldType(schema4) {
180275
- if (schema4.$ref)
180276
- return "reference";
180277
- return schema4.type || "string";
180278
- }
180279
- function detectFieldFormat(schema4) {
180280
- if (schema4.type === "array" && schema4.items?.format)
180281
- return schema4.items.format;
180282
- return schema4.format || "";
180283
- }
180284
- function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, contentTypeNames = []) {
180285
- const type = detectFieldType(fieldSchema);
180286
- const format2 = detectFieldFormat(fieldSchema);
180287
- const isNested = type === "object";
180288
- const isRef2 = type === "reference";
180289
- const nestedProps = fieldSchema.properties || {};
180290
- const nestedRequired = fieldSchema.required || [];
180291
- const refTarget = fieldSchema.$ref ? fieldSchema.$ref.replace("#/contentTypes/", "") : "";
180292
- return html`
180293
- <div class="schema-field-card">
180294
- <div class="schema-field-row">
180295
- <sp-textfield
180296
- size="s"
180297
- quiet
180298
- value=${fieldName}
180299
- class="schema-field-name-input"
180300
- @change=${(e) => {
180301
- const newName = e.target.value.trim();
180302
- if (newName && newName !== fieldName)
180303
- handlers.onRename(fieldName, newName);
180304
- else
180305
- e.target.value = fieldName;
180306
- }}
180307
- @keydown=${(e) => {
180308
- if (e.key === "Enter")
180309
- e.target.blur();
180310
- if (e.key === "Escape") {
180311
- e.target.value = fieldName;
180312
- e.target.blur();
180313
- }
180314
- }}
180315
- ></sp-textfield>
180316
- ${typePickerTpl(type, (newType) => handlers.onChangeType(fieldName, newType))}
180317
- ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
180318
- if (handlers.onChangeFormat)
180319
- handlers.onChangeFormat(fieldName, f);
180320
- }) : nothing}
180321
- <sp-switch
180322
- size="s"
180323
- ?checked=${isRequired}
180324
- @change=${() => handlers.onToggleRequired(fieldName)}
180325
- >
180326
- Req
180327
- </sp-switch>
180328
- <sp-action-button
180329
- size="xs"
180330
- quiet
180331
- title="Delete field"
180332
- @click=${() => handlers.onDelete(fieldName)}
180333
- >
180334
- <sp-icon-delete slot="icon"></sp-icon-delete>
180335
- </sp-action-button>
180336
- </div>
180337
- ${isRef2 && contentTypeNames.length ? html`
180338
- <div class="schema-field-ref-target">
180339
- <sp-picker
180340
- size="s"
180341
- label="Target"
180342
- value=${refTarget}
180343
- @change=${(e) => {
180344
- if (handlers.onChangeRefTarget)
180345
- handlers.onChangeRefTarget(fieldName, e.target.value);
180346
- }}
180347
- >
180348
- ${contentTypeNames.map((n2) => html`<sp-menu-item value=${n2}>${n2}</sp-menu-item>`)}
180349
- </sp-picker>
180350
- </div>
180351
- ` : nothing}
180352
- ${isNested ? html`
180353
- <div class="schema-field-nested">
180354
- ${Object.entries(nestedProps).map(([name, sub]) => nestedFieldCardTpl(fieldName, name, sub, nestedRequired.includes(name), handlers))}
180355
- ${nestedAddFieldTpl(fieldName, handlers)}
180356
- </div>
180357
- ` : nothing}
180358
- </div>
180359
- `;
180360
- }
180361
- function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, handlers) {
180362
- const type = detectFieldType(childSchema);
180363
- const format2 = detectFieldFormat(childSchema);
180364
- return html`
180365
- <div class="schema-field-card schema-field-card--nested">
180366
- <div class="schema-field-row">
180367
- <sp-textfield
180368
- size="s"
180369
- quiet
180370
- value=${childName}
180371
- class="schema-field-name-input"
180372
- @change=${(e) => {
180373
- const newName = e.target.value.trim();
180374
- if (newName && newName !== childName && handlers.onRenameNested) {
180375
- handlers.onRenameNested(parentName, childName, newName);
180376
- } else {
180377
- e.target.value = childName;
180378
- }
180379
- }}
180380
- @keydown=${(e) => {
180381
- if (e.key === "Enter")
180382
- e.target.blur();
180383
- if (e.key === "Escape") {
180384
- e.target.value = childName;
180385
- e.target.blur();
180386
- }
180387
- }}
180388
- ></sp-textfield>
180389
- ${typePickerTpl(type, (newType) => {
180390
- if (handlers.onChangeNestedType)
180391
- handlers.onChangeNestedType(parentName, childName, newType);
180392
- })}
180393
- ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
180394
- if (handlers.onChangeNestedFormat)
180395
- handlers.onChangeNestedFormat(parentName, childName, f);
180396
- }) : nothing}
180397
- <sp-switch
180398
- size="s"
180399
- ?checked=${isRequired}
180400
- @change=${() => {
180401
- if (handlers.onToggleNestedRequired)
180402
- handlers.onToggleNestedRequired(parentName, childName);
180403
- }}
180404
- >
180405
- Req
180406
- </sp-switch>
180407
- <sp-action-button
180408
- size="xs"
180409
- quiet
180410
- title="Delete field"
180411
- @click=${() => {
180412
- if (handlers.onDeleteNested)
180413
- handlers.onDeleteNested(parentName, childName);
180414
- }}
180415
- >
180416
- <sp-icon-delete slot="icon"></sp-icon-delete>
180417
- </sp-action-button>
180418
- </div>
180419
- </div>
180420
- `;
180421
- }
180422
- function nestedAddFieldTpl(parentName, handlers) {
180423
- return html`
180424
- <div class="schema-nested-add">
180425
- <sp-textfield
180426
- size="s"
180427
- placeholder="field name"
180428
- class="schema-nested-add-name"
180429
- @keydown=${(e) => {
180430
- if (e.key === "Enter") {
180431
- const row = e.target.closest(".schema-nested-add");
180432
- const name = e.target.value.trim();
180433
- const typePicker = row?.querySelector("sp-picker");
180434
- const type = typePicker?.value || "string";
180435
- if (name && handlers.onAddNestedField) {
180436
- handlers.onAddNestedField(parentName, { name, type, required: false });
180437
- e.target.value = "";
180438
- }
180439
- }
180440
- }}
180441
- ></sp-textfield>
180442
- ${typePickerTpl("string", () => {})}
180443
- <sp-action-button
180444
- size="xs"
180445
- quiet
180446
- title="Add nested field"
180447
- @click=${(e) => {
180448
- const row = e.target.closest(".schema-nested-add");
180449
- const nameInput = row?.querySelector(".schema-nested-add-name");
180450
- const typePicker = row?.querySelector("sp-picker");
180451
- const name = nameInput?.value?.trim();
180452
- const type = typePicker?.value || "string";
180453
- if (name && handlers.onAddNestedField) {
180454
- handlers.onAddNestedField(parentName, { name, type, required: false });
180455
- nameInput.value = "";
180456
- }
180457
- }}
180458
- >
180459
- <sp-icon-add slot="icon"></sp-icon-add>
180460
- </sp-action-button>
180461
- </div>
180462
- `;
180463
- }
180464
- function typePickerTpl(value2, onChange) {
180465
- return html`
180466
- <sp-picker
180467
- size="s"
180468
- label="Type"
180469
- value=${value2}
180470
- @change=${(e) => onChange(e.target.value)}
180471
- >
180472
- ${FIELD_TYPES.map((t) => html`<sp-menu-item value=${t}>${t}</sp-menu-item>`)}
180473
- </sp-picker>
180474
- `;
180475
- }
180476
- function formatPickerTpl(value2, onChange) {
180477
- return html`
180478
- <sp-picker
180479
- size="s"
180480
- label="Format"
180481
- value=${value2}
180482
- @change=${(e) => onChange(e.target.value)}
180483
- >
180484
- ${FORMAT_OPTIONS.map((f) => html`<sp-menu-item value=${f}>${f || "(none)"}</sp-menu-item>`)}
180485
- </sp-picker>
180486
- `;
180487
- }
180488
- function addFieldFormTpl(state, handlers) {
180489
- return html`
180490
- <div class="schema-add-field">
180491
- <sp-textfield
180492
- size="s"
180493
- placeholder="Field name"
180494
- .value=${state.name}
180495
- @input=${(e) => handlers.onInput("name", e.target.value)}
180496
- @keydown=${(e) => {
180497
- if (e.key === "Enter")
180498
- handlers.onConfirm();
180499
- if (e.key === "Escape")
180500
- handlers.onCancel();
180501
- }}
180502
- ></sp-textfield>
180503
- ${typePickerTpl(state.type, (t) => handlers.onInput("type", t))}
180504
- ${state.type === "string" || state.type === "array" ? formatPickerTpl(state.format || "", (f) => handlers.onInput("format", f)) : nothing}
180505
- <sp-switch
180506
- size="s"
180507
- ?checked=${state.required}
180508
- @change=${(e) => handlers.onInput("required", e.target.checked)}
180509
- >
180510
- Required
180511
- </sp-switch>
180512
- <sp-action-button size="s" @click=${handlers.onConfirm}>Add</sp-action-button>
180513
- <sp-action-button size="s" quiet @click=${handlers.onCancel}>Cancel</sp-action-button>
180514
- </div>
180515
- `;
180516
- }
180517
- function schemaForType(type, format2) {
180518
- switch (type) {
180519
- case "number":
180520
- return { type: "number" };
180521
- case "boolean":
180522
- return { type: "boolean" };
180523
- case "array":
180524
- return format2 ? { type: "array", items: { type: "string", format: format2 } } : { type: "array", items: { type: "string" } };
180525
- case "object":
180526
- return { type: "object", properties: {}, required: [] };
180527
- case "reference":
180528
- return { $ref: "#/contentTypes/" };
180529
- default:
180530
- return format2 ? { type: "string", format: format2 } : { type: "string" };
180531
- }
180532
- }
180533
- function yamlDefault(type, format2) {
180534
- if (format2 === "date")
180535
- return new Date().toISOString().split("T")[0];
180536
- if (format2 === "image")
180537
- return '""';
180538
- switch (type) {
180539
- case "boolean":
180540
- return "false";
180541
- case "number":
180542
- return "0";
180543
- case "array":
180544
- return "[]";
180545
- case "object":
180546
- return "{}";
180547
- default:
180548
- return '""';
180549
- }
180550
- }
180551
-
180552
- // src/settings/defs-editor.js
180553
- var selectedDef = null;
180554
- var showAddField = false;
180555
- var newFieldState = { name: "", type: "string", format: "", required: false };
180556
- var showNewDef = false;
180557
- var newDefName = "";
180558
- async function saveProjectConfig() {
180559
- const platform3 = getPlatform();
180560
- const config = projectState.projectConfig;
180561
- await platform3.writeFile("project.json", JSON.stringify(config, null, "\t"));
180562
- }
180563
- function getSelectedDef() {
180564
- const config = projectState?.projectConfig;
180565
- return config?.$defs?.[selectedDef];
180566
- }
180567
- function handleNewDef(rerender) {
180568
- const name = newDefName.trim();
180569
- if (!name)
180570
- return;
180571
- const config = projectState?.projectConfig;
180572
- if (!config)
180573
- return;
180574
- if (!config.$defs)
180575
- config.$defs = {};
180576
- if (config.$defs[name])
180577
- return;
180578
- config.$defs[name] = {
180579
- type: "object",
180580
- properties: {},
180581
- required: []
180582
- };
180583
- selectedDef = name;
180584
- showNewDef = false;
180585
- newDefName = "";
180586
- rerender();
180587
- saveProjectConfig();
180588
- }
180589
- function handleAddField(rerender) {
180590
- const name = newFieldState.name.trim();
180591
- if (!name || !selectedDef)
180592
- return;
180593
- const def3 = getSelectedDef();
180594
- if (!def3)
180595
- return;
180596
- if (!def3.properties)
180597
- def3.properties = {};
180598
- def3.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
180599
- if (newFieldState.required) {
180600
- if (!def3.required)
180601
- def3.required = [];
180602
- if (!def3.required.includes(name))
180603
- def3.required.push(name);
180604
- }
180605
- showAddField = false;
180606
- newFieldState = { name: "", type: "string", format: "", required: false };
180607
- rerender();
180608
- saveProjectConfig();
180609
- }
180610
- function handleDeleteField(fieldName, rerender) {
180611
- const def3 = getSelectedDef();
180612
- if (!def3?.properties)
180613
- return;
180614
- delete def3.properties[fieldName];
180615
- if (def3.required) {
180616
- def3.required = def3.required.filter((r) => r !== fieldName);
180617
- }
180618
- rerender();
180619
- saveProjectConfig();
180620
- }
180621
- function handleToggleRequired(fieldName, rerender) {
180622
- const def3 = getSelectedDef();
180623
- if (!def3)
180624
- return;
180625
- if (!def3.required)
180626
- def3.required = [];
180627
- const idx = def3.required.indexOf(fieldName);
180628
- if (idx >= 0)
180629
- def3.required.splice(idx, 1);
180630
- else
180631
- def3.required.push(fieldName);
180632
- rerender();
180633
- saveProjectConfig();
180634
- }
180635
- function handleRenameField(oldName, newName, rerender) {
180636
- const def3 = getSelectedDef();
180637
- if (!def3?.properties || !newName || def3.properties[newName])
180638
- return;
180639
- const newProps = {};
180640
- for (const [key, val] of Object.entries(def3.properties)) {
180641
- newProps[key === oldName ? newName : key] = val;
180642
- }
180643
- def3.properties = newProps;
180644
- if (def3.required) {
180645
- def3.required = def3.required.map((r) => r === oldName ? newName : r);
180646
- }
180647
- rerender();
180648
- saveProjectConfig();
180649
- }
180650
- function handleChangeType(fieldName, newType, rerender) {
180651
- const def3 = getSelectedDef();
180652
- if (!def3?.properties?.[fieldName])
180653
- return;
180654
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(def3.properties[fieldName]) : undefined;
180655
- def3.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
180656
- rerender();
180657
- saveProjectConfig();
180658
- }
180659
- function handleChangeFormat(fieldName, format2, rerender) {
180660
- const def3 = getSelectedDef();
180661
- if (!def3?.properties?.[fieldName])
180662
- return;
180663
- const prop = def3.properties[fieldName];
180664
- const type = prop.type || "string";
180665
- def3.properties[fieldName] = schemaForType(type, format2 || undefined);
180666
- rerender();
180667
- saveProjectConfig();
180668
- }
180669
- function handleAddNestedField(parentName, fieldState, rerender) {
180670
- const def3 = getSelectedDef();
180671
- const parent = def3?.properties?.[parentName];
180672
- if (!parent)
180673
- return;
180674
- if (!parent.properties)
180675
- parent.properties = {};
180676
- parent.properties[fieldState.name] = schemaForType(fieldState.type);
180677
- if (fieldState.required) {
180678
- if (!parent.required)
180679
- parent.required = [];
180680
- if (!parent.required.includes(fieldState.name))
180681
- parent.required.push(fieldState.name);
180682
- }
180683
- rerender();
180684
- saveProjectConfig();
180685
- }
180686
- function handleDeleteNested(parentName, childName, rerender) {
180687
- const def3 = getSelectedDef();
180688
- const parent = def3?.properties?.[parentName];
180689
- if (!parent?.properties)
180690
- return;
180691
- delete parent.properties[childName];
180692
- if (parent.required) {
180693
- parent.required = parent.required.filter((r) => r !== childName);
180694
- }
180695
- rerender();
180696
- saveProjectConfig();
180697
- }
180698
- function handleToggleNestedRequired(parentName, childName, rerender) {
180699
- const def3 = getSelectedDef();
180700
- const parent = def3?.properties?.[parentName];
180701
- if (!parent)
180702
- return;
180703
- if (!parent.required)
180704
- parent.required = [];
180705
- const idx = parent.required.indexOf(childName);
180706
- if (idx >= 0)
180707
- parent.required.splice(idx, 1);
180708
- else
180709
- parent.required.push(childName);
180710
- rerender();
180711
- saveProjectConfig();
180712
- }
180713
- function handleRenameNested(parentName, oldChild, newChild, rerender) {
180714
- const def3 = getSelectedDef();
180715
- const parent = def3?.properties?.[parentName];
180716
- if (!parent?.properties || !newChild || parent.properties[newChild])
180717
- return;
180718
- const newProps = {};
180719
- for (const [key, val] of Object.entries(parent.properties)) {
180720
- newProps[key === oldChild ? newChild : key] = val;
180721
- }
180722
- parent.properties = newProps;
180723
- if (parent.required) {
180724
- parent.required = parent.required.map((r) => r === oldChild ? newChild : r);
180725
- }
180726
- rerender();
180727
- saveProjectConfig();
180728
- }
180729
- function handleChangeNestedType(parentName, childName, newType, rerender) {
180730
- const def3 = getSelectedDef();
180731
- const parent = def3?.properties?.[parentName];
180732
- if (!parent?.properties?.[childName])
180733
- return;
180734
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
180735
- parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
180736
- rerender();
180737
- saveProjectConfig();
180738
- }
180739
- function handleChangeNestedFormat(parentName, childName, format2, rerender) {
180740
- const def3 = getSelectedDef();
180741
- const parent = def3?.properties?.[parentName];
180742
- if (!parent?.properties?.[childName])
180743
- return;
180744
- const prop = parent.properties[childName];
180745
- const type = prop.type || "string";
180746
- parent.properties[childName] = schemaForType(type, format2 || undefined);
180747
- rerender();
180748
- saveProjectConfig();
180749
- }
180750
- function handleDeleteDef(rerender) {
180751
- if (!selectedDef)
180752
- return;
180753
- const config = projectState?.projectConfig;
180754
- if (!config?.$defs?.[selectedDef])
180755
- return;
180756
- delete config.$defs[selectedDef];
180757
- selectedDef = null;
180758
- rerender();
180759
- saveProjectConfig();
180760
- }
180761
- function renderDefsEditor(container) {
180762
- const rerender = () => renderDefsEditor(container);
180763
- const config = projectState?.projectConfig;
180764
- const defs = config?.$defs || {};
180765
- const defNames = Object.keys(defs);
180766
- const listTpl = html`
180767
- <div class="settings-list-panel">
180768
- ${defNames.map((name) => html`
180769
- <sp-action-button
180770
- size="s"
180771
- ?selected=${selectedDef === name}
180772
- @click=${() => {
180773
- selectedDef = name;
180774
- showAddField = false;
180775
- rerender();
180776
- }}
180777
- >
180778
- ${name}
180779
- </sp-action-button>
180780
- `)}
180781
- ${showNewDef ? html`
180782
- <div class="settings-inline-form">
180783
- <sp-textfield
180784
- size="s"
180785
- placeholder="TypeName"
180786
- .value=${newDefName}
180787
- @input=${(e) => {
180788
- newDefName = e.target.value;
180789
- }}
180790
- @keydown=${(e) => {
180791
- if (e.key === "Enter")
180792
- handleNewDef(rerender);
180793
- if (e.key === "Escape") {
180794
- showNewDef = false;
180795
- rerender();
180796
- }
180797
- }}
180798
- ></sp-textfield>
180799
- <sp-action-button size="s" @click=${() => handleNewDef(rerender)}>
180800
- Create
180801
- </sp-action-button>
180802
- </div>
180803
- ` : html`
180804
- <sp-action-button
180805
- size="s"
180806
- quiet
180807
- @click=${() => {
180808
- showNewDef = true;
180809
- rerender();
180810
- }}
180811
- >
180812
- <sp-icon-add slot="icon"></sp-icon-add> New Definition
180813
- </sp-action-button>
180814
- `}
180815
- </div>
180816
- `;
180817
- let editorTpl;
180818
- if (!selectedDef || !defs[selectedDef]) {
180819
- editorTpl = html`<div class="settings-empty-state">Select or create a type definition</div>`;
180820
- } else {
180821
- const def3 = defs[selectedDef];
180822
- const properties = def3.properties || {};
180823
- const required = def3.required || [];
180824
- const handlers = {
180825
- onDelete: (n2) => handleDeleteField(n2, rerender),
180826
- onToggleRequired: (n2) => handleToggleRequired(n2, rerender),
180827
- onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
180828
- onChangeType: (n2, t) => handleChangeType(n2, t, rerender),
180829
- onChangeFormat: (n2, f) => handleChangeFormat(n2, f, rerender),
180830
- onAddNestedField: (p, s2) => handleAddNestedField(p, s2, rerender),
180831
- onDeleteNested: (p, c) => handleDeleteNested(p, c, rerender),
180832
- onToggleNestedRequired: (p, c) => handleToggleNestedRequired(p, c, rerender),
180833
- onRenameNested: (p, o, n2) => handleRenameNested(p, o, n2, rerender),
180834
- onChangeNestedType: (p, c, t) => handleChangeNestedType(p, c, t, rerender),
180835
- onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat(p, c, f, rerender)
180836
- };
180837
- const fieldCards = Object.entries(properties).map(([name, fieldDef]) => fieldCardTpl(name, fieldDef, required.includes(name), handlers));
180838
- editorTpl = html`
180839
- <div class="settings-editor-panel">
180840
- <div class="settings-editor-header">
180841
- <h3>${selectedDef}</h3>
180842
- <sp-action-button
180843
- size="xs"
180844
- quiet
180845
- title="Delete definition"
180846
- @click=${() => handleDeleteDef(rerender)}
180847
- >
180848
- <sp-icon-delete slot="icon"></sp-icon-delete>
180849
- </sp-action-button>
180850
- </div>
180851
- <div class="schema-field-list">${fieldCards}</div>
180852
- ${showAddField ? addFieldFormTpl(newFieldState, {
180853
- onInput: (field, value2) => {
180854
- newFieldState = { ...newFieldState, [field]: value2 };
180855
- rerender();
180856
- },
180857
- onConfirm: () => handleAddField(rerender),
180858
- onCancel: () => {
180859
- showAddField = false;
180860
- newFieldState = { name: "", type: "string", format: "", required: false };
180861
- rerender();
180862
- }
180863
- }) : html`
180864
- <sp-action-button
180865
- size="s"
180866
- quiet
180867
- @click=${() => {
180868
- showAddField = true;
180869
- rerender();
180870
- }}
180871
- >
180872
- <sp-icon-add slot="icon"></sp-icon-add> Add Field
180873
- </sp-action-button>
180874
- `}
180875
- </div>
180876
- `;
180877
- }
180878
- const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
180879
- render2(tpl, container);
180880
- }
180881
-
180882
- // src/settings/content-types-editor.js
180883
- init_platform();
180884
- init_store();
180885
- var selectedContentType = null;
180886
- var showAddField2 = false;
180887
- var newFieldState2 = { name: "", type: "string", format: "", required: false };
180888
- var showNewContentType = false;
180889
- var newContentTypeName = "";
180890
- async function saveProjectConfig2() {
180891
- const platform3 = getPlatform();
180892
- const config = projectState.projectConfig;
180893
- await platform3.writeFile("project.json", JSON.stringify(config, null, "\t"));
180894
- }
180895
- function getSelectedSchema() {
180896
- const config = projectState?.projectConfig;
180897
- return config?.contentTypes?.[selectedContentType]?.schema;
180898
- }
180899
- function handleNewContentType(rerender) {
180900
- const slug = newContentTypeName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
180901
- if (!slug)
180902
- return;
180903
- const config = projectState?.projectConfig;
180904
- if (!config)
180905
- return;
180906
- if (!config.contentTypes)
180907
- config.contentTypes = {};
180908
- if (config.contentTypes[slug])
180909
- return;
180910
- config.contentTypes[slug] = {
180911
- source: `./content/${slug}/**/*.md`,
180912
- schema: { type: "object", properties: {}, required: [] }
180913
- };
180914
- selectedContentType = slug;
180915
- showNewContentType = false;
180916
- newContentTypeName = "";
180917
- rerender();
180918
- saveProjectConfig2().then(async () => {
180919
- const platform3 = getPlatform();
180920
- await platform3.writeFile(`content/${slug}/.gitkeep`, "");
180921
- });
180922
- }
180923
- function handleAddField2(rerender) {
180924
- const name = newFieldState2.name.trim();
180925
- if (!name || !selectedContentType)
180926
- return;
180927
- const schema4 = getSelectedSchema();
180928
- if (!schema4)
180929
- return;
180930
- if (!schema4.properties)
180931
- schema4.properties = {};
180932
- schema4.properties[name] = schemaForType(newFieldState2.type, newFieldState2.format || undefined);
180933
- if (newFieldState2.required) {
180934
- if (!schema4.required)
180935
- schema4.required = [];
180936
- if (!schema4.required.includes(name))
180937
- schema4.required.push(name);
180938
- }
180939
- showAddField2 = false;
180940
- newFieldState2 = { name: "", type: "string", format: "", required: false };
180941
- rerender();
180942
- saveProjectConfig2();
180943
- }
180944
- function handleDeleteField2(fieldName, rerender) {
180945
- const schema4 = getSelectedSchema();
180946
- if (!schema4?.properties)
180947
- return;
180948
- delete schema4.properties[fieldName];
180949
- if (schema4.required) {
180950
- schema4.required = schema4.required.filter((r) => r !== fieldName);
180951
- }
180952
- rerender();
180953
- saveProjectConfig2();
180954
- }
180955
- function handleToggleRequired2(fieldName, rerender) {
180956
- const schema4 = getSelectedSchema();
180957
- if (!schema4)
180958
- return;
180959
- if (!schema4.required)
180960
- schema4.required = [];
180961
- const idx = schema4.required.indexOf(fieldName);
180962
- if (idx >= 0)
180963
- schema4.required.splice(idx, 1);
180964
- else
180965
- schema4.required.push(fieldName);
180966
- rerender();
180967
- saveProjectConfig2();
180968
- }
180969
- function handleRenameField2(oldName, newName, rerender) {
180970
- const schema4 = getSelectedSchema();
180971
- if (!schema4?.properties || !newName || schema4.properties[newName])
180972
- return;
180973
- const newProps = {};
180974
- for (const [key, val] of Object.entries(schema4.properties)) {
180975
- newProps[key === oldName ? newName : key] = val;
180976
- }
180977
- schema4.properties = newProps;
180978
- if (schema4.required) {
180979
- schema4.required = schema4.required.map((r) => r === oldName ? newName : r);
180980
- }
180981
- rerender();
180982
- saveProjectConfig2();
180983
- }
180984
- function handleChangeType2(fieldName, newType, rerender) {
180985
- const schema4 = getSelectedSchema();
180986
- if (!schema4?.properties?.[fieldName])
180987
- return;
180988
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(schema4.properties[fieldName]) : undefined;
180989
- schema4.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
180990
- rerender();
180991
- saveProjectConfig2();
180992
- }
180993
- function handleChangeFormat2(fieldName, format2, rerender) {
180994
- const schema4 = getSelectedSchema();
180995
- if (!schema4?.properties?.[fieldName])
180996
- return;
180997
- const prop = schema4.properties[fieldName];
180998
- const type = prop.type || "string";
180999
- schema4.properties[fieldName] = schemaForType(type, format2 || undefined);
181000
- rerender();
181001
- saveProjectConfig2();
181002
- }
181003
- function handleChangeRefTarget(fieldName, target, rerender) {
181004
- const schema4 = getSelectedSchema();
181005
- if (!schema4?.properties)
181006
- return;
181007
- schema4.properties[fieldName] = { $ref: `#/contentTypes/${target}` };
181008
- rerender();
181009
- saveProjectConfig2();
181010
- }
181011
- function handleAddNestedField2(parentName, fieldState, rerender) {
181012
- const schema4 = getSelectedSchema();
181013
- const parent = schema4?.properties?.[parentName];
181014
- if (!parent)
181015
- return;
181016
- if (!parent.properties)
181017
- parent.properties = {};
181018
- parent.properties[fieldState.name] = schemaForType(fieldState.type);
181019
- if (fieldState.required) {
181020
- if (!parent.required)
181021
- parent.required = [];
181022
- if (!parent.required.includes(fieldState.name))
181023
- parent.required.push(fieldState.name);
181024
- }
181025
- rerender();
181026
- saveProjectConfig2();
181027
- }
181028
- function handleDeleteNested2(parentName, childName, rerender) {
181029
- const schema4 = getSelectedSchema();
181030
- const parent = schema4?.properties?.[parentName];
181031
- if (!parent?.properties)
181032
- return;
181033
- delete parent.properties[childName];
181034
- if (parent.required) {
181035
- parent.required = parent.required.filter((r) => r !== childName);
181036
- }
181037
- rerender();
181038
- saveProjectConfig2();
181039
- }
181040
- function handleToggleNestedRequired2(parentName, childName, rerender) {
181041
- const schema4 = getSelectedSchema();
181042
- const parent = schema4?.properties?.[parentName];
181043
- if (!parent)
181044
- return;
181045
- if (!parent.required)
181046
- parent.required = [];
181047
- const idx = parent.required.indexOf(childName);
181048
- if (idx >= 0)
181049
- parent.required.splice(idx, 1);
181050
- else
181051
- parent.required.push(childName);
181052
- rerender();
181053
- saveProjectConfig2();
181054
- }
181055
- function handleRenameNested2(parentName, oldChild, newChild, rerender) {
181056
- const schema4 = getSelectedSchema();
181057
- const parent = schema4?.properties?.[parentName];
181058
- if (!parent?.properties || !newChild || parent.properties[newChild])
181059
- return;
181060
- const newProps = {};
181061
- for (const [key, val] of Object.entries(parent.properties)) {
181062
- newProps[key === oldChild ? newChild : key] = val;
181063
- }
181064
- parent.properties = newProps;
181065
- if (parent.required) {
181066
- parent.required = parent.required.map((r) => r === oldChild ? newChild : r);
181067
- }
181068
- rerender();
181069
- saveProjectConfig2();
181070
- }
181071
- function handleChangeNestedType2(parentName, childName, newType, rerender) {
181072
- const schema4 = getSelectedSchema();
181073
- const parent = schema4?.properties?.[parentName];
181074
- if (!parent?.properties?.[childName])
181075
- return;
181076
- const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
181077
- parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
181078
- rerender();
181079
- saveProjectConfig2();
181080
- }
181081
- function handleChangeNestedFormat2(parentName, childName, format2, rerender) {
181082
- const schema4 = getSelectedSchema();
181083
- const parent = schema4?.properties?.[parentName];
181084
- if (!parent?.properties?.[childName])
181085
- return;
181086
- const prop = parent.properties[childName];
181087
- const type = prop.type || "string";
181088
- parent.properties[childName] = schemaForType(type, format2 || undefined);
181089
- rerender();
181090
- saveProjectConfig2();
181091
- }
181092
- function handleDeleteContentType(rerender) {
181093
- if (!selectedContentType)
181094
- return;
181095
- const config = projectState?.projectConfig;
181096
- if (!config?.contentTypes?.[selectedContentType])
181097
- return;
181098
- delete config.contentTypes[selectedContentType];
181099
- selectedContentType = null;
181100
- rerender();
181101
- saveProjectConfig2();
181102
- }
181103
- function renderContentTypesEditor(container) {
181104
- const rerender = () => renderContentTypesEditor(container);
181105
- const config = projectState?.projectConfig;
181106
- const contentTypes = config?.contentTypes || {};
181107
- const contentTypeNames = Object.keys(contentTypes);
181108
- const listTpl = html`
181109
- <div class="settings-list-panel">
181110
- ${contentTypeNames.map((name) => html`
181111
- <sp-action-button
181112
- size="s"
181113
- ?selected=${selectedContentType === name}
181114
- @click=${() => {
181115
- selectedContentType = name;
181116
- showAddField2 = false;
181117
- rerender();
181118
- }}
181119
- >
181120
- ${name}
181121
- </sp-action-button>
181122
- `)}
181123
- ${showNewContentType ? html`
181124
- <div class="settings-inline-form">
181125
- <sp-textfield
181126
- size="s"
181127
- placeholder="content-type-name"
181128
- .value=${newContentTypeName}
181129
- @input=${(e) => {
181130
- newContentTypeName = e.target.value;
181131
- }}
181132
- @keydown=${(e) => {
181133
- if (e.key === "Enter")
181134
- handleNewContentType(rerender);
181135
- if (e.key === "Escape") {
181136
- showNewContentType = false;
181137
- rerender();
181138
- }
181139
- }}
181140
- ></sp-textfield>
181141
- <sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
181142
- Create
181143
- </sp-action-button>
181144
- </div>
181145
- ` : html`
181146
- <sp-action-button
181147
- size="s"
181148
- quiet
181149
- @click=${() => {
181150
- showNewContentType = true;
181151
- rerender();
181152
- }}
181153
- >
181154
- <sp-icon-add slot="icon"></sp-icon-add> New Content Type
181155
- </sp-action-button>
181156
- `}
181157
- </div>
181158
- `;
181159
- let editorTpl;
181160
- if (!selectedContentType || !contentTypes[selectedContentType]) {
181161
- editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
181162
- } else {
181163
- const col = contentTypes[selectedContentType];
181164
- const schema4 = col.schema || {};
181165
- const properties = schema4.properties || {};
181166
- const required = schema4.required || [];
181167
- const handlers = {
181168
- onDelete: (n2) => handleDeleteField2(n2, rerender),
181169
- onToggleRequired: (n2) => handleToggleRequired2(n2, rerender),
181170
- onRename: (oldN, newN) => handleRenameField2(oldN, newN, rerender),
181171
- onChangeType: (n2, t) => handleChangeType2(n2, t, rerender),
181172
- onChangeFormat: (n2, f) => handleChangeFormat2(n2, f, rerender),
181173
- onChangeRefTarget: (n2, target) => handleChangeRefTarget(n2, target, rerender),
181174
- onAddNestedField: (p, s2) => handleAddNestedField2(p, s2, rerender),
181175
- onDeleteNested: (p, c) => handleDeleteNested2(p, c, rerender),
181176
- onToggleNestedRequired: (p, c) => handleToggleNestedRequired2(p, c, rerender),
181177
- onRenameNested: (p, o, n2) => handleRenameNested2(p, o, n2, rerender),
181178
- onChangeNestedType: (p, c, t) => handleChangeNestedType2(p, c, t, rerender),
181179
- onChangeNestedFormat: (p, c, f) => handleChangeNestedFormat2(p, c, f, rerender)
181180
- };
181181
- const fieldCards = Object.entries(properties).map(([name, def3]) => fieldCardTpl(name, def3, required.includes(name), handlers, contentTypeNames));
181182
- editorTpl = html`
181183
- <div class="settings-editor-panel">
181184
- <div class="settings-editor-header">
181185
- <h3>${selectedContentType}</h3>
181186
- <sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
181187
- <sp-action-button
181188
- size="xs"
181189
- quiet
181190
- title="Delete content type"
181191
- @click=${() => handleDeleteContentType(rerender)}
181192
- >
181193
- <sp-icon-delete slot="icon"></sp-icon-delete>
181194
- </sp-action-button>
181195
- </div>
181196
- <div class="schema-field-list">${fieldCards}</div>
181197
- ${showAddField2 ? addFieldFormTpl(newFieldState2, {
181198
- onInput: (field, value2) => {
181199
- newFieldState2 = { ...newFieldState2, [field]: value2 };
181200
- rerender();
181201
- },
181202
- onConfirm: () => handleAddField2(rerender),
181203
- onCancel: () => {
181204
- showAddField2 = false;
181205
- newFieldState2 = { name: "", type: "string", format: "", required: false };
181206
- rerender();
181207
- }
181208
- }) : html`
181209
- <sp-action-button
181210
- size="s"
181211
- quiet
181212
- @click=${() => {
181213
- showAddField2 = true;
181214
- rerender();
181215
- }}
181216
- >
181217
- <sp-icon-add slot="icon"></sp-icon-add> Add Field
181218
- </sp-action-button>
181219
- `}
181220
- </div>
181221
- `;
181222
- }
181223
- const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
181224
- render2(tpl, container);
181225
- }
181226
180103
  // data/stylebook-meta.json
181227
180104
  var stylebook_meta_default = {
181228
180105
  $sections: [
@@ -181433,39 +180310,6 @@ registerBlockType( name, settings );`
181433
180310
  var _ctx6 = null;
181434
180311
  function renderStylebookMode(ctx) {
181435
180312
  _ctx6 = ctx;
181436
- const settingsTab = activeTab.value?.session.ui.settingsTab || "stylebook";
181437
- const settingsChromeBarTpl = html`
181438
- <div
181439
- class="sb-chrome settings-top-chrome"
181440
- style="position:absolute;top:0;left:0;right:0;z-index:16;background:var(--bg-panel);border-bottom:1px solid var(--border)"
181441
- >
181442
- <sp-tabs
181443
- size="s"
181444
- selected=${settingsTab}
181445
- @change=${(e) => {
181446
- updateUi("settingsTab", e.target.selected);
181447
- }}
181448
- >
181449
- <sp-tab label="Stylebook" value="stylebook"></sp-tab>
181450
- <sp-tab label="Definitions" value="definitions"></sp-tab>
181451
- <sp-tab label="Content Types" value="contentTypes"></sp-tab>
181452
- </sp-tabs>
181453
- </div>
181454
- `;
181455
- if (settingsTab === "definitions" || settingsTab === "contentTypes") {
181456
- canvasWrap.style.overflow = "hidden";
181457
- render2(html`${settingsChromeBarTpl}
181458
- <div
181459
- class="settings-editor-container"
181460
- style="position:absolute;inset:40px 0 0 0;overflow:auto"
181461
- ></div>`, canvasWrap);
181462
- const container = canvasWrap.querySelector(".settings-editor-container");
181463
- if (settingsTab === "definitions")
181464
- renderDefsEditor(container);
181465
- else
181466
- renderContentTypesEditor(container);
181467
- return;
181468
- }
181469
180313
  view.stylebookElToTag = new WeakMap;
181470
180314
  const tab = activeTab.value;
181471
180315
  const rootStyle = getEffectiveStyle(tab?.doc.document?.style);
@@ -181473,9 +180317,6 @@ function renderStylebookMode(ctx) {
181473
180317
  const customizedOnly = tab?.session.ui.stylebookCustomizedOnly;
181474
180318
  const { sizeBreakpoints, baseWidth } = parseMediaEntries(getEffectiveMedia(tab?.doc.document?.$media));
181475
180319
  const hasMedia = sizeBreakpoints.length > 0;
181476
- const onTabClick = (t) => {
181477
- updateUi("stylebookTab", t);
181478
- };
181479
180320
  const onFilterInput2 = (e) => {
181480
180321
  updateUi("stylebookFilter", e.target.value);
181481
180322
  };
@@ -181483,38 +180324,25 @@ function renderStylebookMode(ctx) {
181483
180324
  updateUi("stylebookCustomizedOnly", !tab?.session.ui.stylebookCustomizedOnly);
181484
180325
  };
181485
180326
  const chromeBarTpl = html`
181486
- ${settingsChromeBarTpl}
181487
180327
  <div
181488
180328
  class="sb-chrome"
181489
- style="position:absolute;top:36px;left:0;right:0;z-index:15;background:var(--bg-panel);border-bottom:1px solid var(--border)"
180329
+ style="position:absolute;top:0;left:0;right:0;z-index:15;background:var(--bg-panel);border-bottom:1px solid var(--border)"
181490
180330
  >
181491
- <sp-tabs
181492
- size="s"
181493
- selected=${tab?.session.ui.stylebookTab || "elements"}
181494
- @change=${(e) => {
181495
- onTabClick(e.target.selected);
181496
- }}
181497
- >
181498
- ${["elements", "variables"].map((t) => html`
181499
- <sp-tab label=${t.charAt(0).toUpperCase() + t.slice(1)} value=${t}></sp-tab>
181500
- `)}
181501
- </sp-tabs>
181502
- ${tab?.session.ui.stylebookTab === "elements" ? html`
181503
- <input
181504
- class="field-input"
181505
- style="flex:1;max-width:200px;margin-left:8px"
181506
- placeholder="Filter…"
181507
- .value=${tab?.session.ui.stylebookFilter}
181508
- @input=${onFilterInput2}
181509
- />
181510
- <button
181511
- class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
181512
- style="margin-left:4px"
181513
- @click=${onCustomizedToggle}
181514
- >
181515
- Customized
181516
- </button>
181517
- ` : nothing}
180331
+ <div style="display:flex;align-items:center;padding:4px 8px;gap:4px">
180332
+ <input
180333
+ class="field-input"
180334
+ style="flex:1;max-width:200px"
180335
+ placeholder="Filter…"
180336
+ .value=${tab?.session.ui.stylebookFilter}
180337
+ @input=${onFilterInput2}
180338
+ />
180339
+ <button
180340
+ class="tb-toggle${tab?.session.ui.stylebookCustomizedOnly ? " active" : ""}"
180341
+ @click=${onCustomizedToggle}
180342
+ >
180343
+ Customized
180344
+ </button>
180345
+ </div>
181518
180346
  </div>
181519
180347
  `;
181520
180348
  canvasWrap.style.overflow = "hidden";
@@ -181537,16 +180365,11 @@ function renderStylebookMode(ctx) {
181537
180365
  }
181538
180366
  const renderIntoPanel = (panel, activeBreakpoints) => {
181539
180367
  panel.canvas.classList.add("sb-canvas");
181540
- if (tab?.session.ui.stylebookTab === "elements") {
181541
- renderStylebookElementsIntoCanvas(panel.canvas, rootStyle, filter, customizedOnly, activeBreakpoints);
181542
- for (const child of panel.canvas.querySelectorAll("*")) {
181543
- child.style.pointerEvents = "none";
181544
- }
181545
- registerStylebookPanelEvents(panel);
181546
- } else {
181547
- renderStylebookVarsIntoCanvas(panel.canvas, rootStyle);
181548
- panel.overlayClk.style.pointerEvents = "none";
180368
+ renderStylebookElementsIntoCanvas(panel.canvas, rootStyle, filter, customizedOnly, activeBreakpoints);
180369
+ for (const child of panel.canvas.querySelectorAll("*")) {
180370
+ child.style.pointerEvents = "none";
181549
180371
  }
180372
+ registerStylebookPanelEvents(panel);
181550
180373
  };
181551
180374
  let panelEntries;
181552
180375
  if (!hasMedia) {
@@ -181566,7 +180389,7 @@ function renderStylebookMode(ctx) {
181566
180389
  ${chromeBarTpl}
181567
180390
  <div
181568
180391
  class="panzoom-wrap"
181569
- style="transform-origin:0 0;padding-top:72px"
180392
+ style="transform-origin:0 0;padding-top:40px"
181570
180393
  ${ref2((el) => {
181571
180394
  if (el)
181572
180395
  view.panzoomWrap = el;
@@ -181856,363 +180679,6 @@ function renderStylebookElementsIntoCanvas(canvasEl, rootStyle, filter, customiz
181856
180679
  render2(html`${sectionTemplates}`, canvasEl);
181857
180680
  }
181858
180681
  }
181859
- function renderStylebookVarsIntoCanvas(canvasEl, rootStyle) {
181860
- const varCats = stylebook_meta_default.$variables;
181861
- const groups = {};
181862
- for (const key of Object.keys(varCats))
181863
- groups[key] = [];
181864
- for (const [k, v] of Object.entries(rootStyle)) {
181865
- if (!k.startsWith("--"))
181866
- continue;
181867
- if (typeof v !== "string" && typeof v !== "number")
181868
- continue;
181869
- if (k.startsWith("--color"))
181870
- groups.color.push([k, v]);
181871
- else if (k.startsWith("--font"))
181872
- groups.font.push([k, v]);
181873
- else if (k.startsWith("--size") || k.startsWith("--spacing") || k.startsWith("--radius"))
181874
- groups.size.push([k, v]);
181875
- else
181876
- groups.other.push([k, v]);
181877
- }
181878
- const bodyRefs = new Map;
181879
- const sectionTemplates = Object.entries(varCats).map(([catKey, catMeta]) => {
181880
- const vars = groups[catKey];
181881
- const onAdd = () => {
181882
- const bodyEl = bodyRefs.get(catKey);
181883
- if (!bodyEl)
181884
- return;
181885
- const addBtn = bodyEl.querySelector(".sb-var-add-btn");
181886
- const row = renderVarRow(catKey, catMeta, null, "", true);
181887
- bodyEl.insertBefore(row, addBtn);
181888
- if (addBtn)
181889
- addBtn.style.display = "none";
181890
- const nameField = row.querySelector("sp-textfield");
181891
- if (nameField)
181892
- requestAnimationFrame(() => nameField.focus());
181893
- };
181894
- return html`
181895
- <div class="sb-section">
181896
- <div class="sb-label">${catMeta.label}</div>
181897
- <div
181898
- class="sb-body"
181899
- ${ref2((el) => {
181900
- if (el)
181901
- bodyRefs.set(catKey, el);
181902
- })}
181903
- >
181904
- ${vars.length > 0 ? vars.map(([varName, varVal]) => renderVarRow(catKey, catMeta, varName, String(varVal), false)) : html`<div class="sb-var-empty">
181905
- No ${catMeta.label.toLowerCase()} variables yet.
181906
- </div>`}
181907
- <button class="sb-var-add-btn" @click=${onAdd}>
181908
- <span class="sb-var-add-icon">+</span> Add ${catMeta.label}
181909
- </button>
181910
- </div>
181911
- </div>
181912
- `;
181913
- });
181914
- render2(html`${sectionTemplates}`, canvasEl);
181915
- }
181916
- function renderVarRow(catKey, catMeta, varName, varVal, isNew) {
181917
- const row = document.createElement("div");
181918
- row.className = isNew ? "sb-var-row is-new" : "sb-var-row";
181919
- let colorPicker = null;
181920
- let nameField = null;
181921
- let getValueFn;
181922
- let hexField = null;
181923
- const swatchTpl = catKey === "color" ? html`
181924
- <div
181925
- class="sb-var-swatch"
181926
- style=${styleMap({ backgroundColor: varVal || "var(--accent)" })}
181927
- >
181928
- <input
181929
- type="color"
181930
- .value=${varVal && varVal.startsWith("#") ? varVal : "#007acc"}
181931
- ${ref2((el) => {
181932
- if (el)
181933
- colorPicker = el;
181934
- })}
181935
- @input=${() => {
181936
- if (!colorPicker || !hexField)
181937
- return;
181938
- hexField.value = colorPicker.value;
181939
- const swatch = row.querySelector(".sb-var-swatch");
181940
- if (swatch)
181941
- swatch.style.backgroundColor = colorPicker.value;
181942
- if (!isNew && varName) {
181943
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, colorPicker.value));
181944
- }
181945
- }}
181946
- />
181947
- </div>
181948
- ` : nothing;
181949
- const namePlaceholder = catKey === "color" ? "Primary Blue" : catKey === "font" ? "Body Serif" : catKey === "size" ? "Spacing Large" : "Border Radius";
181950
- const nameColTpl = isNew ? html`
181951
- <div class="sb-var-col-name">
181952
- <div class="sb-var-col-label">Name</div>
181953
- <sp-textfield
181954
- size="s"
181955
- placeholder=${namePlaceholder}
181956
- style="pointer-events:auto"
181957
- ${ref2((el) => {
181958
- if (el)
181959
- nameField = el;
181960
- })}
181961
- ></sp-textfield>
181962
- </div>
181963
- ` : nothing;
181964
- let valueContent;
181965
- if (catKey === "color") {
181966
- let debounce;
181967
- valueContent = html`
181968
- <sp-textfield
181969
- size="s"
181970
- .value=${varVal || "#007acc"}
181971
- placeholder="#007acc"
181972
- style="pointer-events:auto"
181973
- ${ref2((el) => {
181974
- if (el)
181975
- hexField = el;
181976
- })}
181977
- @input=${() => {
181978
- clearTimeout(debounce);
181979
- debounce = setTimeout(() => {
181980
- if (!hexField)
181981
- return;
181982
- const v = hexField.value;
181983
- try {
181984
- if (colorPicker)
181985
- colorPicker.value = v.startsWith("#") ? v : colorPicker.value;
181986
- } catch {}
181987
- const swatch = row.querySelector(".sb-var-swatch");
181988
- if (swatch)
181989
- swatch.style.backgroundColor = v;
181990
- if (!isNew && varName) {
181991
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
181992
- }
181993
- }, 400);
181994
- }}
181995
- ></sp-textfield>
181996
- `;
181997
- getValueFn = () => hexField?.value?.trim() || "";
181998
- } else if (catKey === "size") {
181999
- const ui = createUnitInput(varVal || "16px", {
182000
- onChange: (newVal) => {
182001
- const bar = row.querySelector(".sb-var-size-bar");
182002
- if (bar)
182003
- bar.style.width = newVal;
182004
- if (!isNew && varName) {
182005
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, newVal));
182006
- }
182007
- }
182008
- });
182009
- if (isNew)
182010
- ui.textfield.value = "";
182011
- valueContent = html`<div
182012
- ${ref2((el) => {
182013
- if (el && !el.firstChild)
182014
- el.appendChild(ui.wrap);
182015
- })}
182016
- ></div>`;
182017
- getValueFn = () => ui.getValue();
182018
- } else {
182019
- let textFieldEl = null;
182020
- let debounce;
182021
- valueContent = html`
182022
- <sp-textfield
182023
- size="s"
182024
- .value=${varVal}
182025
- placeholder=${catMeta.placeholder}
182026
- style="pointer-events:auto"
182027
- ${ref2((el) => {
182028
- if (el)
182029
- textFieldEl = el;
182030
- })}
182031
- @input=${() => {
182032
- if (!textFieldEl || isNew || !varName)
182033
- return;
182034
- clearTimeout(debounce);
182035
- debounce = setTimeout(() => {
182036
- const v = textFieldEl.value;
182037
- const fontPrev = row.querySelector(".sb-var-font-preview");
182038
- if (fontPrev)
182039
- fontPrev.style.fontFamily = v;
182040
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, v));
182041
- }, 400);
182042
- }}
182043
- ></sp-textfield>
182044
- `;
182045
- getValueFn = () => textFieldEl?.value?.trim() || "";
182046
- }
182047
- const valColTpl = html`
182048
- <div class="sb-var-col-value">
182049
- ${isNew ? html`<div class="sb-var-col-label">Value</div>` : nothing} ${valueContent}
182050
- </div>
182051
- `;
182052
- const actionsTpl = isNew ? html`
182053
- <div class="sb-var-add-actions">
182054
- <sp-action-button
182055
- size="s"
182056
- style="pointer-events:auto"
182057
- @click=${() => {
182058
- const name = (nameField?.value || "").trim();
182059
- const val = getValueFn();
182060
- const generatedVar = friendlyNameToVar(name, catMeta.prefix);
182061
- if (!generatedVar || !val)
182062
- return;
182063
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], generatedVar, val));
182064
- }}
182065
- >Add</sp-action-button
182066
- >
182067
- <sp-action-button
182068
- size="s"
182069
- quiet
182070
- style="pointer-events:auto"
182071
- @click=${() => {
182072
- const body = row.parentElement;
182073
- row.remove();
182074
- const addBtn = body?.querySelector(".sb-var-add-btn");
182075
- if (addBtn)
182076
- addBtn.style.display = "";
182077
- }}
182078
- >
182079
- <sp-icon-close slot="icon"></sp-icon-close>
182080
- </sp-action-button>
182081
- </div>
182082
- ` : nothing;
182083
- const headerTpl = !isNew && varName ? html`
182084
- <div class="sb-var-row-header">
182085
- <span class="sb-var-row-title">${varDisplayName(varName, catMeta.prefix)}</span>
182086
- <span class="sb-var-row-ref">${varName}</span>
182087
- <sp-action-button
182088
- size="s"
182089
- quiet
182090
- class="sb-var-del"
182091
- style="pointer-events:auto"
182092
- @click=${() => {
182093
- transactDoc(activeTab.value, (t) => mutateUpdateStyle(t, [], varName, undefined));
182094
- }}
182095
- >
182096
- <sp-icon-delete slot="icon"></sp-icon-delete>
182097
- </sp-action-button>
182098
- </div>
182099
- ` : nothing;
182100
- const addPreviewTpl = isNew ? html`
182101
- <div
182102
- class="sb-var-add-preview"
182103
- ${ref2((el) => {
182104
- if (!el || !nameField)
182105
- return;
182106
- nameField.addEventListener("input", () => {
182107
- el.textContent = friendlyNameToVar(nameField.value || "", catMeta.prefix);
182108
- });
182109
- })}
182110
- ></div>
182111
- ` : nothing;
182112
- const typePrevTpl = catKey === "font" && varVal ? html`
182113
- <div class="sb-var-preview">
182114
- <div class="sb-var-font-preview" style=${styleMap({ fontFamily: varVal })}>
182115
- The quick brown fox jumps over the lazy dog
182116
- </div>
182117
- </div>
182118
- ` : catKey === "size" && varVal ? html`
182119
- <div class="sb-var-preview">
182120
- <div class="sb-var-size-track">
182121
- <div class="sb-var-size-bar" style=${styleMap({ width: varVal })}></div>
182122
- </div>
182123
- </div>
182124
- ` : nothing;
182125
- render2(html`
182126
- ${headerTpl}
182127
- <div class="sb-var-input-row">${swatchTpl} ${nameColTpl} ${valColTpl} ${actionsTpl}</div>
182128
- ${addPreviewTpl} ${typePrevTpl}
182129
- `, row);
182130
- return row;
182131
- }
182132
- function createUnitInput(initialValue, { onChange, size = "s" } = {}) {
182133
- const match2 = String(initialValue).match(/^(-?[\d.]+)\s*(px|em|rem|vw|vh|%|ch|ex|vmin|vmax|pt|cm|mm|in)?$/);
182134
- let numVal = match2 ? match2[1] : initialValue;
182135
- let unitVal = match2 ? match2[2] || "px" : "";
182136
- const isNumeric = !!match2;
182137
- const wrap4 = document.createElement("div");
182138
- wrap4.className = "sb-unit-input";
182139
- wrap4.style.pointerEvents = "auto";
182140
- let textfield = null;
182141
- let picker = null;
182142
- const units = [
182143
- { value: "px", label: "px" },
182144
- { value: "rem", label: "rem" },
182145
- { value: "em", label: "em" },
182146
- { value: "%", label: "%" },
182147
- { value: "vw", label: "vw" },
182148
- { value: "vh", label: "vh" },
182149
- { value: "ch", label: "ch" },
182150
- { value: "pt", label: "pt" },
182151
- { divider: true },
182152
- { value: "auto", label: "auto" },
182153
- { value: "fit-content", label: "fit-content" }
182154
- ];
182155
- let debounce;
182156
- function getValue() {
182157
- const num = textfield?.value;
182158
- const unit = picker?.value;
182159
- if (unit === "auto" || unit === "fit-content")
182160
- return unit;
182161
- return num ? `${num}${unit}` : "";
182162
- }
182163
- render2(html`
182164
- <sp-textfield
182165
- .value=${numVal}
182166
- size=${size}
182167
- ${ref2((el) => {
182168
- if (el)
182169
- textfield = el;
182170
- })}
182171
- @input=${() => {
182172
- clearTimeout(debounce);
182173
- const raw = textfield?.value?.trim();
182174
- const looksNumeric = /^-?[\d.]+$/.test(raw || "");
182175
- if (picker)
182176
- picker.style.display = looksNumeric ? "" : "none";
182177
- debounce = setTimeout(() => {
182178
- if (onChange)
182179
- onChange(looksNumeric ? `${raw}${picker?.value}` : raw);
182180
- }, 400);
182181
- }}
182182
- ></sp-textfield>
182183
- <sp-picker
182184
- quiet
182185
- size=${size}
182186
- style=${styleMap({ display: isNumeric ? "" : "none" })}
182187
- ${ref2((el) => {
182188
- if (el) {
182189
- picker = el;
182190
- requestAnimationFrame(() => {
182191
- el.value = unitVal || "px";
182192
- });
182193
- }
182194
- })}
182195
- @change=${() => {
182196
- const unit = picker?.value;
182197
- if (unit === "auto" || unit === "fit-content") {
182198
- if (textfield)
182199
- textfield.value = unit;
182200
- if (picker)
182201
- picker.style.display = "none";
182202
- if (onChange)
182203
- onChange(unit);
182204
- } else {
182205
- unitVal = unit;
182206
- if (onChange)
182207
- onChange(getValue());
182208
- }
182209
- }}
182210
- >
182211
- ${units.map((u) => u.divider ? html`<sp-menu-divider></sp-menu-divider>` : html`<sp-menu-item value=${u.value}>${u.label}</sp-menu-item>`)}
182212
- </sp-picker>
182213
- `, wrap4);
182214
- return { wrap: wrap4, textfield, picker, getValue };
182215
- }
182216
180682
  function registerStylebookPanelEvents(panel) {
182217
180683
  const { canvas, overlayClk } = panel;
182218
180684
  overlayClk.addEventListener("click", (e) => {
@@ -183368,6 +181834,289 @@ function updateForcedPseudoPreview() {
183368
181834
  // src/browse/browse.js
183369
181835
  init_platform();
183370
181836
  init_store();
181837
+
181838
+ // src/settings/schema-field-ui.js
181839
+ var FIELD_TYPES = ["string", "number", "boolean", "array", "object", "reference"];
181840
+ var FORMAT_OPTIONS = ["", "image", "date", "color"];
181841
+ function detectFieldType(schema4) {
181842
+ if (schema4.$ref)
181843
+ return "reference";
181844
+ return schema4.type || "string";
181845
+ }
181846
+ function detectFieldFormat(schema4) {
181847
+ if (schema4.type === "array" && schema4.items?.format)
181848
+ return schema4.items.format;
181849
+ return schema4.format || "";
181850
+ }
181851
+ function fieldCardTpl(fieldName, fieldSchema, isRequired, handlers, contentTypeNames = []) {
181852
+ const type = detectFieldType(fieldSchema);
181853
+ const format2 = detectFieldFormat(fieldSchema);
181854
+ const isNested = type === "object";
181855
+ const isRef2 = type === "reference";
181856
+ const nestedProps = fieldSchema.properties || {};
181857
+ const nestedRequired = fieldSchema.required || [];
181858
+ const refTarget = fieldSchema.$ref ? fieldSchema.$ref.replace("#/contentTypes/", "") : "";
181859
+ return html`
181860
+ <div class="schema-field-card">
181861
+ <div class="schema-field-row">
181862
+ <sp-textfield
181863
+ size="s"
181864
+ quiet
181865
+ value=${fieldName}
181866
+ class="schema-field-name-input"
181867
+ @change=${(e) => {
181868
+ const newName = e.target.value.trim();
181869
+ if (newName && newName !== fieldName)
181870
+ handlers.onRename(fieldName, newName);
181871
+ else
181872
+ e.target.value = fieldName;
181873
+ }}
181874
+ @keydown=${(e) => {
181875
+ if (e.key === "Enter")
181876
+ e.target.blur();
181877
+ if (e.key === "Escape") {
181878
+ e.target.value = fieldName;
181879
+ e.target.blur();
181880
+ }
181881
+ }}
181882
+ ></sp-textfield>
181883
+ ${typePickerTpl(type, (newType) => handlers.onChangeType(fieldName, newType))}
181884
+ ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
181885
+ if (handlers.onChangeFormat)
181886
+ handlers.onChangeFormat(fieldName, f);
181887
+ }) : nothing}
181888
+ <sp-switch
181889
+ size="s"
181890
+ ?checked=${isRequired}
181891
+ @change=${() => handlers.onToggleRequired(fieldName)}
181892
+ >
181893
+ Req
181894
+ </sp-switch>
181895
+ <sp-action-button
181896
+ size="xs"
181897
+ quiet
181898
+ title="Delete field"
181899
+ @click=${() => handlers.onDelete(fieldName)}
181900
+ >
181901
+ <sp-icon-delete slot="icon"></sp-icon-delete>
181902
+ </sp-action-button>
181903
+ </div>
181904
+ ${isRef2 && contentTypeNames.length ? html`
181905
+ <div class="schema-field-ref-target">
181906
+ <sp-picker
181907
+ size="s"
181908
+ label="Target"
181909
+ value=${refTarget}
181910
+ @change=${(e) => {
181911
+ if (handlers.onChangeRefTarget)
181912
+ handlers.onChangeRefTarget(fieldName, e.target.value);
181913
+ }}
181914
+ >
181915
+ ${contentTypeNames.map((n2) => html`<sp-menu-item value=${n2}>${n2}</sp-menu-item>`)}
181916
+ </sp-picker>
181917
+ </div>
181918
+ ` : nothing}
181919
+ ${isNested ? html`
181920
+ <div class="schema-field-nested">
181921
+ ${Object.entries(nestedProps).map(([name, sub]) => nestedFieldCardTpl(fieldName, name, sub, nestedRequired.includes(name), handlers))}
181922
+ ${nestedAddFieldTpl(fieldName, handlers)}
181923
+ </div>
181924
+ ` : nothing}
181925
+ </div>
181926
+ `;
181927
+ }
181928
+ function nestedFieldCardTpl(parentName, childName, childSchema, isRequired, handlers) {
181929
+ const type = detectFieldType(childSchema);
181930
+ const format2 = detectFieldFormat(childSchema);
181931
+ return html`
181932
+ <div class="schema-field-card schema-field-card--nested">
181933
+ <div class="schema-field-row">
181934
+ <sp-textfield
181935
+ size="s"
181936
+ quiet
181937
+ value=${childName}
181938
+ class="schema-field-name-input"
181939
+ @change=${(e) => {
181940
+ const newName = e.target.value.trim();
181941
+ if (newName && newName !== childName && handlers.onRenameNested) {
181942
+ handlers.onRenameNested(parentName, childName, newName);
181943
+ } else {
181944
+ e.target.value = childName;
181945
+ }
181946
+ }}
181947
+ @keydown=${(e) => {
181948
+ if (e.key === "Enter")
181949
+ e.target.blur();
181950
+ if (e.key === "Escape") {
181951
+ e.target.value = childName;
181952
+ e.target.blur();
181953
+ }
181954
+ }}
181955
+ ></sp-textfield>
181956
+ ${typePickerTpl(type, (newType) => {
181957
+ if (handlers.onChangeNestedType)
181958
+ handlers.onChangeNestedType(parentName, childName, newType);
181959
+ })}
181960
+ ${type === "string" || type === "array" ? formatPickerTpl(format2, (f) => {
181961
+ if (handlers.onChangeNestedFormat)
181962
+ handlers.onChangeNestedFormat(parentName, childName, f);
181963
+ }) : nothing}
181964
+ <sp-switch
181965
+ size="s"
181966
+ ?checked=${isRequired}
181967
+ @change=${() => {
181968
+ if (handlers.onToggleNestedRequired)
181969
+ handlers.onToggleNestedRequired(parentName, childName);
181970
+ }}
181971
+ >
181972
+ Req
181973
+ </sp-switch>
181974
+ <sp-action-button
181975
+ size="xs"
181976
+ quiet
181977
+ title="Delete field"
181978
+ @click=${() => {
181979
+ if (handlers.onDeleteNested)
181980
+ handlers.onDeleteNested(parentName, childName);
181981
+ }}
181982
+ >
181983
+ <sp-icon-delete slot="icon"></sp-icon-delete>
181984
+ </sp-action-button>
181985
+ </div>
181986
+ </div>
181987
+ `;
181988
+ }
181989
+ function nestedAddFieldTpl(parentName, handlers) {
181990
+ return html`
181991
+ <div class="schema-nested-add">
181992
+ <sp-textfield
181993
+ size="s"
181994
+ placeholder="field name"
181995
+ class="schema-nested-add-name"
181996
+ @keydown=${(e) => {
181997
+ if (e.key === "Enter") {
181998
+ const row = e.target.closest(".schema-nested-add");
181999
+ const name = e.target.value.trim();
182000
+ const typePicker = row?.querySelector("sp-picker");
182001
+ const type = typePicker?.value || "string";
182002
+ if (name && handlers.onAddNestedField) {
182003
+ handlers.onAddNestedField(parentName, { name, type, required: false });
182004
+ e.target.value = "";
182005
+ }
182006
+ }
182007
+ }}
182008
+ ></sp-textfield>
182009
+ ${typePickerTpl("string", () => {})}
182010
+ <sp-action-button
182011
+ size="xs"
182012
+ quiet
182013
+ title="Add nested field"
182014
+ @click=${(e) => {
182015
+ const row = e.target.closest(".schema-nested-add");
182016
+ const nameInput = row?.querySelector(".schema-nested-add-name");
182017
+ const typePicker = row?.querySelector("sp-picker");
182018
+ const name = nameInput?.value?.trim();
182019
+ const type = typePicker?.value || "string";
182020
+ if (name && handlers.onAddNestedField) {
182021
+ handlers.onAddNestedField(parentName, { name, type, required: false });
182022
+ nameInput.value = "";
182023
+ }
182024
+ }}
182025
+ >
182026
+ <sp-icon-add slot="icon"></sp-icon-add>
182027
+ </sp-action-button>
182028
+ </div>
182029
+ `;
182030
+ }
182031
+ function typePickerTpl(value2, onChange) {
182032
+ return html`
182033
+ <sp-picker
182034
+ size="s"
182035
+ label="Type"
182036
+ value=${value2}
182037
+ @change=${(e) => onChange(e.target.value)}
182038
+ >
182039
+ ${FIELD_TYPES.map((t) => html`<sp-menu-item value=${t}>${t}</sp-menu-item>`)}
182040
+ </sp-picker>
182041
+ `;
182042
+ }
182043
+ function formatPickerTpl(value2, onChange) {
182044
+ return html`
182045
+ <sp-picker
182046
+ size="s"
182047
+ label="Format"
182048
+ value=${value2}
182049
+ @change=${(e) => onChange(e.target.value)}
182050
+ >
182051
+ ${FORMAT_OPTIONS.map((f) => html`<sp-menu-item value=${f}>${f || "(none)"}</sp-menu-item>`)}
182052
+ </sp-picker>
182053
+ `;
182054
+ }
182055
+ function addFieldFormTpl(state, handlers) {
182056
+ return html`
182057
+ <div class="schema-add-field">
182058
+ <sp-textfield
182059
+ size="s"
182060
+ placeholder="Field name"
182061
+ .value=${state.name}
182062
+ @input=${(e) => handlers.onInput("name", e.target.value)}
182063
+ @keydown=${(e) => {
182064
+ if (e.key === "Enter")
182065
+ handlers.onConfirm();
182066
+ if (e.key === "Escape")
182067
+ handlers.onCancel();
182068
+ }}
182069
+ ></sp-textfield>
182070
+ ${typePickerTpl(state.type, (t) => handlers.onInput("type", t))}
182071
+ ${state.type === "string" || state.type === "array" ? formatPickerTpl(state.format || "", (f) => handlers.onInput("format", f)) : nothing}
182072
+ <sp-switch
182073
+ size="s"
182074
+ ?checked=${state.required}
182075
+ @change=${(e) => handlers.onInput("required", e.target.checked)}
182076
+ >
182077
+ Required
182078
+ </sp-switch>
182079
+ <sp-action-button size="s" @click=${handlers.onConfirm}>Add</sp-action-button>
182080
+ <sp-action-button size="s" quiet @click=${handlers.onCancel}>Cancel</sp-action-button>
182081
+ </div>
182082
+ `;
182083
+ }
182084
+ function schemaForType(type, format2) {
182085
+ switch (type) {
182086
+ case "number":
182087
+ return { type: "number" };
182088
+ case "boolean":
182089
+ return { type: "boolean" };
182090
+ case "array":
182091
+ return format2 ? { type: "array", items: { type: "string", format: format2 } } : { type: "array", items: { type: "string" } };
182092
+ case "object":
182093
+ return { type: "object", properties: {}, required: [] };
182094
+ case "reference":
182095
+ return { $ref: "#/contentTypes/" };
182096
+ default:
182097
+ return format2 ? { type: "string", format: format2 } : { type: "string" };
182098
+ }
182099
+ }
182100
+ function yamlDefault(type, format2) {
182101
+ if (format2 === "date")
182102
+ return new Date().toISOString().split("T")[0];
182103
+ if (format2 === "image")
182104
+ return '""';
182105
+ switch (type) {
182106
+ case "boolean":
182107
+ return "false";
182108
+ case "number":
182109
+ return "0";
182110
+ case "array":
182111
+ return "[]";
182112
+ case "object":
182113
+ return "{}";
182114
+ default:
182115
+ return '""';
182116
+ }
182117
+ }
182118
+
182119
+ // src/browse/browse.js
183371
182120
  var CATEGORIES = [
183372
182121
  { key: "all", label: "All" },
183373
182122
  { key: "pages", label: "Pages", dir: "pages" },
@@ -183405,6 +182154,19 @@ function extOf(name) {
183405
182154
  const dot = name.lastIndexOf(".");
183406
182155
  return dot > 0 ? name.slice(dot).toLowerCase() : "";
183407
182156
  }
182157
+ var IMAGE_EXTENSIONS2 = new Set([
182158
+ ".jpg",
182159
+ ".jpeg",
182160
+ ".png",
182161
+ ".gif",
182162
+ ".svg",
182163
+ ".webp",
182164
+ ".avif",
182165
+ ".ico"
182166
+ ]);
182167
+ function isImage(ext) {
182168
+ return IMAGE_EXTENSIONS2.has(ext);
182169
+ }
183408
182170
  function categoryFor(dir, ext) {
183409
182171
  if (ext && MEDIA_EXTENSIONS2.has(ext))
183410
182172
  return "Media";
@@ -183662,9 +182424,12 @@ async function renderBrowse(container, ctx) {
183662
182424
  <sp-table-row
183663
182425
  value=${f.path}
183664
182426
  class="browse-row"
183665
- @click=${() => ctx.openFile(f.path)}
182427
+ style=${isImage(f.ext) ? "cursor:default" : ""}
182428
+ @click=${isImage(f.ext) ? nothing : () => ctx.openFile(f.path)}
183666
182429
  >
183667
- <sp-table-cell class="browse-name-cell">${f.name}</sp-table-cell>
182430
+ <sp-table-cell class="browse-name-cell"
182431
+ >${isImage(f.ext) ? html`<img class="browse-thumb" src="/${f.path}" />` : nothing}${f.name}</sp-table-cell
182432
+ >
183668
182433
  <sp-table-cell>${f.category}</sp-table-cell>
183669
182434
  <sp-table-cell>${f.type}</sp-table-cell>
183670
182435
  <sp-table-cell class="browse-path-cell">${f.path}</sp-table-cell>
@@ -183892,7 +182657,7 @@ function _flush() {
183892
182657
  const { selection, hover } = tab.session;
183893
182658
  const { stylebookTab } = tab.session.ui;
183894
182659
  const canvasMode = _ctx9.getCanvasMode();
183895
- if (canvasMode !== "design" && canvasMode !== "edit" && canvasMode !== "settings") {
182660
+ if (canvasMode !== "design" && canvasMode !== "edit" && canvasMode !== "stylebook") {
183896
182661
  for (const p of canvasPanels) {
183897
182662
  render2(nothing, p.overlay);
183898
182663
  p.overlayClk.style.pointerEvents = "none";
@@ -183903,7 +182668,7 @@ function _flush() {
183903
182668
  }
183904
182669
  return;
183905
182670
  }
183906
- if (canvasMode === "settings") {
182671
+ if (canvasMode === "stylebook") {
183907
182672
  const enable = stylebookTab === "elements";
183908
182673
  for (const p of canvasPanels) {
183909
182674
  p.overlayClk.style.pointerEvents = enable ? "" : "none";
@@ -184048,7 +182813,7 @@ function renderCanvas() {
184048
182813
  });
184049
182814
  return;
184050
182815
  }
184051
- if (canvasMode === "settings") {
182816
+ if (canvasMode === "stylebook") {
184052
182817
  renderStylebookMode({
184053
182818
  canvasPanelTemplate,
184054
182819
  applyTransform,
@@ -184355,6 +183120,48 @@ init_store();
184355
183120
  init_platform();
184356
183121
  init_components();
184357
183122
  init_workspace();
183123
+
183124
+ // src/recent-projects.js
183125
+ var STORAGE_KEY = "jx-studio-recent-projects";
183126
+ var FILES_STORAGE_KEY = "jx-studio-recent-files";
183127
+ var MAX_RECENT = 8;
183128
+ var MAX_RECENT_FILES = 10;
183129
+ function getRecentProjects() {
183130
+ try {
183131
+ const raw = localStorage.getItem(STORAGE_KEY);
183132
+ if (!raw)
183133
+ return [];
183134
+ return JSON.parse(raw).sort((a, b) => b.timestamp - a.timestamp);
183135
+ } catch {
183136
+ return [];
183137
+ }
183138
+ }
183139
+ function addRecentProject(name, root2) {
183140
+ const projects = getRecentProjects().filter((p) => p.root !== root2);
183141
+ projects.unshift({ name, root: root2, timestamp: Date.now() });
183142
+ if (projects.length > MAX_RECENT)
183143
+ projects.length = MAX_RECENT;
183144
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(projects));
183145
+ }
183146
+ function getRecentFiles() {
183147
+ try {
183148
+ const raw = localStorage.getItem(FILES_STORAGE_KEY);
183149
+ if (!raw)
183150
+ return [];
183151
+ return JSON.parse(raw).sort((a, b) => b.timestamp - a.timestamp);
183152
+ } catch {
183153
+ return [];
183154
+ }
183155
+ }
183156
+ function trackRecentFile(file) {
183157
+ const recent = getRecentFiles().filter((f) => f.path !== file.path);
183158
+ recent.unshift({ path: file.path, name: file.name, timestamp: Date.now() });
183159
+ if (recent.length > MAX_RECENT_FILES)
183160
+ recent.length = MAX_RECENT_FILES;
183161
+ localStorage.setItem(FILES_STORAGE_KEY, JSON.stringify(recent));
183162
+ }
183163
+
183164
+ // src/files/files.js
184358
183165
  var fileIconMap = {
184359
183166
  "sp-icon-folder-open": html`<sp-icon-folder-open></sp-icon-folder-open>`,
184360
183167
  "sp-icon-folder": html`<sp-icon-folder></sp-icon-folder>`,
@@ -184440,6 +183247,7 @@ async function openProject({ renderActivityBar, renderLeftPanel }) {
184440
183247
  }
184441
183248
  projectState.projectDirs = foundDirs;
184442
183249
  view.leftTab = "files";
183250
+ addRecentProject(projectState.name, projectState.projectRoot);
184443
183251
  renderActivityBar();
184444
183252
  renderLeftPanel();
184445
183253
  statusMessage(`Opened project: ${projectState.name}`);
@@ -184808,6 +183616,12 @@ async function openFileInTab(path2) {
184808
183616
  sourceFormat: path2.endsWith(".md") ? "md" : null
184809
183617
  });
184810
183618
  projectState.selectedPath = path2;
183619
+ trackRecentFile({ path: path2, name: path2.split("/").pop() || path2 });
183620
+ if (path2 === "project.json") {
183621
+ const tab = activeTab.value;
183622
+ if (tab)
183623
+ tab.session.ui.canvasMode = "stylebook";
183624
+ }
184811
183625
  statusMessage(`Opened ${path2.split("/").pop()}`);
184812
183626
  } catch (e) {
184813
183627
  statusMessage(`Error: ${e.message}`);
@@ -185771,6 +184585,16 @@ function createDevServerPlatform() {
185771
184585
  } catch {}
185772
184586
  return null;
185773
184587
  },
184588
+ async searchFiles(query) {
184589
+ const glob = `**/*${query}*.{json,md}`;
184590
+ const res = await fetch(`/__studio/files?dir=${encodeURIComponent(serverPath("."))}&glob=${encodeURIComponent(glob)}`);
184591
+ if (!res.ok)
184592
+ return [];
184593
+ const entries2 = await res.json();
184594
+ for (const e of entries2)
184595
+ e.path = stripRoot(e.path);
184596
+ return entries2;
184597
+ },
185774
184598
  async fetchPluginSchema(src, prototype, base2) {
185775
184599
  const params = new URLSearchParams({ src });
185776
184600
  if (prototype)
@@ -210032,6 +208856,67 @@ class IconRemove extends IconBase {
210032
208856
  }
210033
208857
  }
210034
208858
 
208859
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconFullScreen.js
208860
+ init_index_dev();
208861
+
208862
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/icons-s2/FullScreen.js
208863
+ var FullScreenIcon = ({ width: l = 24, height: r8 = 24, hidden: t15 = false, title: c6 = "Full Screen" } = {}) => tag6`<svg
208864
+ xmlns="http://www.w3.org/2000/svg"
208865
+ width="${l}"
208866
+ height="${r8}"
208867
+ viewBox="0 0 20 20"
208868
+ aria-hidden=${t15 ? "true" : "false"}
208869
+ role="img"
208870
+ fill="currentColor"
208871
+ aria-label="${c6}"
208872
+ >
208873
+ <path
208874
+ d="M12.75,14.93652h-5.5c-1.24072,0-2.25-1.00928-2.25-2.25v-5.5c0-1.24072,1.00928-2.25,2.25-2.25h5.5c1.24072,0,2.25,1.00928,2.25,2.25v5.5c0,1.24072-1.00928,2.25-2.25,2.25ZM7.25,6.43652c-.41357,0-.75.33643-.75.75v5.5c0,.41357.33643.75.75.75h5.5c.41357,0,.75-.33643.75-.75v-5.5c0-.41357-.33643-.75-.75-.75h-5.5Z"
208875
+ fill="currentColor"
208876
+ />
208877
+ <path
208878
+ d="M4.5,19h-2.25c-.68945,0-1.25-.56055-1.25-1.25v-2.25c0-.41406.33594-.75.75-.75s.75.33594.75.75v2h2c.41406,0,.75.33594.75.75s-.33594.75-.75.75Z"
208879
+ fill="currentColor"
208880
+ />
208881
+ <path
208882
+ d="M17.75,19h-2.25c-.41406,0-.75-.33594-.75-.75s.33594-.75.75-.75h2v-2c0-.41406.33594-.75.75-.75s.75.33594.75.75v2.25c0,.68945-.56055,1.25-1.25,1.25Z"
208883
+ fill="currentColor"
208884
+ stroke-width="0"
208885
+ />
208886
+ <path
208887
+ d="M18.25,5.25c-.41406,0-.75-.33594-.75-.75v-2h-2c-.41406,0-.75-.33594-.75-.75s.33594-.75.75-.75h2.25c.68945,0,1.25.56055,1.25,1.25v2.25c0,.41406-.33594.75-.75.75ZM17.75,2.5h.00977-.00977Z"
208888
+ fill="currentColor"
208889
+ />
208890
+ <path
208891
+ d="M1.75,5.25c-.41406,0-.75-.33594-.75-.75v-2.25c0-.68945.56055-1.25,1.25-1.25h2.25c.41406,0,.75.33594.75.75s-.33594.75-.75.75h-2v2c0,.41406-.33594.75-.75.75Z"
208892
+ fill="currentColor"
208893
+ />
208894
+ </svg>`;
208895
+
208896
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/icons/FullScreen.js
208897
+ var FullScreenIcon2 = ({ width: a5 = 24, height: e20 = 24, hidden: t15 = false, title: r8 = "Full Screen" } = {}) => tag6`<svg
208898
+ xmlns="http://www.w3.org/2000/svg"
208899
+ height="${e20}"
208900
+ viewBox="0 0 36 36"
208901
+ width="${a5}"
208902
+ aria-hidden=${t15 ? "true" : "false"}
208903
+ role="img"
208904
+ fill="currentColor"
208905
+ aria-label="${r8}"
208906
+ >
208907
+ <path
208908
+ d="M32 24.5V30h-5.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5H34v-7.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5ZM4 30v-5.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5V32h7.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5ZM26 4.5v1a.5.5 0 0 0 .5.5H32v5.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5V4h-7.5a.5.5 0 0 0-.5.5ZM4 6h5.5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5H2v7.5a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5Z"
208909
+ />
208910
+ <rect height="16" rx=".5" ry=".5" width="20" x="8" y="10" />
208911
+ </svg>`;
208912
+
208913
+ // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconFullScreen.js
208914
+ class IconFullScreen extends IconBase {
208915
+ render() {
208916
+ return setCustomTemplateLiteralTag2(html8), this.spectrumVersion === 2 ? FullScreenIcon({ hidden: !this.label, title: this.label }) : FullScreenIcon2({ hidden: !this.label, title: this.label });
208917
+ }
208918
+ }
208919
+
210035
208920
  // ../../node_modules/@spectrum-web-components/icons-workflow/src/elements/IconViewColumn.js
210036
208921
  init_index_dev();
210037
208922
 
@@ -210832,6 +209717,113 @@ class JxValueSelector extends LitElement {
210832
209717
  init_lit();
210833
209718
  init_store();
210834
209719
  init_workspace();
209720
+
209721
+ // src/utils/studio-utils.js
209722
+ function camelToKebab2(str) {
209723
+ return str.replace(/[A-Z]/g, (c6) => "-" + c6.toLowerCase());
209724
+ }
209725
+ function camelToLabel(prop) {
209726
+ return prop.replace(/([A-Z])/g, " $1").replace(/^./, (c6) => c6.toUpperCase());
209727
+ }
209728
+ function kebabToLabel(val) {
209729
+ return val.replace(/(^|-)(\w)/g, (_, sep2, c6) => (sep2 ? " " : "") + c6.toUpperCase());
209730
+ }
209731
+ function propLabel(entry, prop) {
209732
+ return entry?.$label || camelToLabel(prop);
209733
+ }
209734
+ function attrLabel(entry, attr) {
209735
+ if (entry?.$label)
209736
+ return entry.$label;
209737
+ if (attr.includes("-"))
209738
+ return attr.replace(/(^|-)(\w)/g, (_, sep2, c6) => (sep2 ? " " : "") + c6.toUpperCase());
209739
+ return camelToLabel(attr);
209740
+ }
209741
+ function abbreviateValue(val) {
209742
+ const map6 = {
209743
+ inline: "inl",
209744
+ "inline-block": "i-blk",
209745
+ "inline-flex": "i-flx",
209746
+ "inline-grid": "i-grd",
209747
+ contents: "cnt",
209748
+ "flow-root": "flow",
209749
+ nowrap: "no-wr",
209750
+ "wrap-reverse": "wr-rev",
209751
+ "flex-start": "start",
209752
+ "flex-end": "end",
209753
+ "space-between": "betw",
209754
+ "space-around": "arnd",
209755
+ "space-evenly": "even",
209756
+ stretch: "str",
209757
+ baseline: "base",
209758
+ normal: "norm",
209759
+ "row-reverse": "row-r",
209760
+ "column-reverse": "col-r",
209761
+ column: "col"
209762
+ };
209763
+ return map6[val] || val;
209764
+ }
209765
+ function inferInputType(entry) {
209766
+ if (entry.$shorthand === true)
209767
+ return "shorthand";
209768
+ if (entry.$input === "button-group")
209769
+ return "button-group";
209770
+ if (entry.$input === "media")
209771
+ return "media";
209772
+ if (entry.format === "color")
209773
+ return "color";
209774
+ if (entry.format === "uri-reference")
209775
+ return "media";
209776
+ if (entry.$units !== undefined)
209777
+ return "number-unit";
209778
+ if (entry.type === "number")
209779
+ return "number";
209780
+ if (Array.isArray(entry.enum))
209781
+ return "select";
209782
+ if (Array.isArray(entry.examples) || Array.isArray(entry.presets))
209783
+ return "combobox";
209784
+ return "text";
209785
+ }
209786
+ function findContentTypeSchema(documentPath, projectConfig) {
209787
+ if (!documentPath || !projectConfig?.contentTypes)
209788
+ return null;
209789
+ for (const [name, def3] of Object.entries(projectConfig.contentTypes)) {
209790
+ if (!def3.source || !def3.schema)
209791
+ continue;
209792
+ const src = def3.source.replace(/^\.\//, "");
209793
+ const dir = src.split("/")[0];
209794
+ const ext = src.includes("*.md") ? ".md" : src.includes("*.json") ? ".json" : src.includes("*.csv") ? ".csv" : null;
209795
+ if (documentPath.startsWith(dir + "/") && (!ext || documentPath.endsWith(ext))) {
209796
+ return { name, schema: def3.schema };
209797
+ }
209798
+ }
209799
+ return null;
209800
+ }
209801
+ function friendlyNameToVar(name, prefix) {
209802
+ const slug = name.trim().toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
209803
+ if (!slug)
209804
+ return "";
209805
+ return `${prefix}${slug}`;
209806
+ }
209807
+ function varDisplayName(varName, prefix) {
209808
+ return varName.replace(new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`), "").replace(/^--/, "").replace(/-/g, " ").replace(/\b\w/g, (c6) => c6.toUpperCase()) || varName;
209809
+ }
209810
+ function parseCemType(typeText) {
209811
+ if (!typeText)
209812
+ return { kind: "text" };
209813
+ const t15 = typeText.trim().replace(/\s*\|\s*undefined\b/g, "").trim();
209814
+ if (t15 === "boolean")
209815
+ return { kind: "boolean" };
209816
+ if (t15 === "number")
209817
+ return { kind: "number" };
209818
+ const enumMatch = t15.match(/^'[^']*'(\s*\|\s*'[^']*')+$/);
209819
+ if (enumMatch) {
209820
+ const options = [...t15.matchAll(/'([^']*)'/g)].map((m3) => m3[1]);
209821
+ return { kind: "combobox", options };
209822
+ }
209823
+ return { kind: "text" };
209824
+ }
209825
+
209826
+ // src/ui/color-selector.js
210835
209827
  function getColorVars() {
210836
209828
  const style2 = getEffectiveStyle(activeTab.value?.doc.document?.style);
210837
209829
  if (!style2)
@@ -211157,6 +210149,7 @@ var components = [
211157
210149
  ["sp-icon-text-baseline-shift", IconTextBaselineShift],
211158
210150
  ["sp-icon-flip-vertical", IconFlipVertical],
211159
210151
  ["sp-icon-remove", IconRemove],
210152
+ ["sp-icon-full-screen", IconFullScreen],
211160
210153
  ["sp-icon-download", IconDownload],
211161
210154
  ["sp-icon-checkmark", IconCheckmark],
211162
210155
  ["sp-icon-view-column", IconViewColumn],
@@ -211185,14 +210178,14 @@ Theme.registerThemeFragment("dark", "color", theme_dark_css_default);
211185
210178
  Theme.registerThemeFragment("medium", "scale", scale_medium_css_default);
211186
210179
 
211187
210180
  // src/ui/panel-resize.js
211188
- var STORAGE_KEY = "jx-studio-panel-widths";
210181
+ var STORAGE_KEY2 = "jx-studio-panel-widths";
211189
210182
  var MIN_WIDTH = 160;
211190
210183
  var MAX_RATIO = 0.5;
211191
210184
  var DEFAULT_LEFT = 240;
211192
210185
  var DEFAULT_RIGHT = 280;
211193
210186
  var root2 = document.documentElement;
211194
210187
  try {
211195
- const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
210188
+ const saved = JSON.parse(localStorage.getItem(STORAGE_KEY2) || "{}");
211196
210189
  if (saved.left)
211197
210190
  root2.style.setProperty("--panel-w-left", `${saved.left}px`);
211198
210191
  if (saved.right)
@@ -211238,7 +210231,7 @@ function persistWidths() {
211238
210231
  const left = parseInt(getComputedStyle(root2).getPropertyValue("--panel-w-left")) || DEFAULT_LEFT;
211239
210232
  const right = parseInt(getComputedStyle(root2).getPropertyValue("--panel-w-right")) || DEFAULT_RIGHT;
211240
210233
  try {
211241
- localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, right }));
210234
+ localStorage.setItem(STORAGE_KEY2, JSON.stringify({ left, right }));
211242
210235
  } catch {}
211243
210236
  }
211244
210237
  var resizeLeft = document.getElementById("resize-left");
@@ -211251,11 +210244,162 @@ if (resizeRight)
211251
210244
  // src/editor/shortcuts.js
211252
210245
  init_store();
211253
210246
  init_workspace();
210247
+
210248
+ // src/panels/quick-search.js
210249
+ init_platform();
210250
+ var _open = false;
210251
+ var _query = "";
210252
+ var _results = [];
210253
+ var _selectedIndex = 0;
210254
+ var _debounceTimer = 0;
210255
+ var _container = null;
210256
+ function initQuickSearch() {
210257
+ _container = document.createElement("div");
210258
+ _container.style.display = "contents";
210259
+ (document.querySelector("sp-theme") || document.body).appendChild(_container);
210260
+ }
210261
+ function openQuickSearch() {
210262
+ _open = true;
210263
+ _query = "";
210264
+ _results = [];
210265
+ _selectedIndex = 0;
210266
+ renderOverlay();
210267
+ requestAnimationFrame(() => {
210268
+ const input = _container?.querySelector(".quick-search-input");
210269
+ if (input)
210270
+ input.focus();
210271
+ });
210272
+ }
210273
+ function closeQuickSearch() {
210274
+ _open = false;
210275
+ renderOverlay();
210276
+ }
210277
+ async function doSearch(query3) {
210278
+ if (!query3.trim()) {
210279
+ _results = [];
210280
+ _selectedIndex = 0;
210281
+ renderOverlay();
210282
+ return;
210283
+ }
210284
+ try {
210285
+ const platform4 = getPlatform();
210286
+ _results = await platform4.searchFiles(query3.trim().toLowerCase());
210287
+ _selectedIndex = 0;
210288
+ renderOverlay();
210289
+ } catch {
210290
+ _results = [];
210291
+ renderOverlay();
210292
+ }
210293
+ }
210294
+ function onInput(e20) {
210295
+ _query = e20.target.value;
210296
+ clearTimeout(_debounceTimer);
210297
+ _debounceTimer = setTimeout(() => doSearch(_query), 150);
210298
+ renderOverlay();
210299
+ }
210300
+ function onKeydown2(e20) {
210301
+ const items = _query.trim() ? _results : getRecentFiles();
210302
+ switch (e20.key) {
210303
+ case "ArrowDown":
210304
+ e20.preventDefault();
210305
+ _selectedIndex = Math.min(_selectedIndex + 1, items.length - 1);
210306
+ renderOverlay();
210307
+ break;
210308
+ case "ArrowUp":
210309
+ e20.preventDefault();
210310
+ _selectedIndex = Math.max(_selectedIndex - 1, 0);
210311
+ renderOverlay();
210312
+ break;
210313
+ case "Enter":
210314
+ e20.preventDefault();
210315
+ if (items[_selectedIndex])
210316
+ selectItem(items[_selectedIndex]);
210317
+ break;
210318
+ case "Escape":
210319
+ e20.preventDefault();
210320
+ closeQuickSearch();
210321
+ break;
210322
+ }
210323
+ }
210324
+ function selectItem(item) {
210325
+ closeQuickSearch();
210326
+ const path2 = item.path;
210327
+ trackRecentFile({ path: path2, name: path2.split("/").pop() });
210328
+ openFileInTab(path2);
210329
+ }
210330
+ function fileIcon(name) {
210331
+ const ext = name.split(".").pop()?.toLowerCase();
210332
+ switch (ext) {
210333
+ case "json":
210334
+ return html`<sp-icon-file-code size="s"></sp-icon-file-code>`;
210335
+ case "md":
210336
+ return html`<sp-icon-file-txt size="s"></sp-icon-file-txt>`;
210337
+ default:
210338
+ return html`<sp-icon-document size="s"></sp-icon-document>`;
210339
+ }
210340
+ }
210341
+ function dirPart(path2) {
210342
+ const parts = path2.split("/");
210343
+ parts.pop();
210344
+ return parts.length ? parts.join("/") : "";
210345
+ }
210346
+ function renderOverlay() {
210347
+ if (!_container)
210348
+ return;
210349
+ if (!_open) {
210350
+ render2(nothing, _container);
210351
+ return;
210352
+ }
210353
+ const recentFiles = getRecentFiles();
210354
+ const showRecent = !_query.trim();
210355
+ const items = showRecent ? recentFiles : _results;
210356
+ const tpl = html`
210357
+ <div class="quick-search-overlay" @click=${closeQuickSearch}>
210358
+ <div class="quick-search-panel" @click=${(e20) => e20.stopPropagation()}>
210359
+ <input
210360
+ class="quick-search-input"
210361
+ type="text"
210362
+ placeholder="Search project files…"
210363
+ .value=${_query}
210364
+ @input=${onInput}
210365
+ @keydown=${onKeydown2}
210366
+ />
210367
+ <div class="quick-search-results">
210368
+ ${items.length === 0 && _query.trim() ? html`<div class="quick-search-empty">No results</div>` : nothing}
210369
+ ${items.length === 0 && !_query.trim() && recentFiles.length === 0 ? html`<div class="quick-search-empty">Type to search project files</div>` : nothing}
210370
+ ${showRecent && recentFiles.length ? html`<div class="quick-search-section-label">Recently opened</div>` : nothing}
210371
+ ${items.map((item, i6) => html`
210372
+ <div
210373
+ class="quick-search-item ${i6 === _selectedIndex ? "selected" : ""}"
210374
+ @click=${() => selectItem(item)}
210375
+ @mouseenter=${() => {
210376
+ _selectedIndex = i6;
210377
+ renderOverlay();
210378
+ }}
210379
+ >
210380
+ <span class="quick-search-icon"
210381
+ >${fileIcon(item.name || item.path.split("/").pop())}</span
210382
+ >
210383
+ <span class="quick-search-name">${item.name || item.path.split("/").pop()}</span>
210384
+ <span class="quick-search-path">${dirPart(item.path)}</span>
210385
+ ${showRecent ? html`<span class="quick-search-badge">recent</span>` : nothing}
210386
+ </div>
210387
+ `)}
210388
+ </div>
210389
+ </div>
210390
+ </div>
210391
+ `;
210392
+ render2(tpl, _container);
210393
+ }
210394
+
210395
+ // src/editor/shortcuts.js
211254
210396
  function initShortcuts(getContext) {
211255
210397
  canvasWrap.addEventListener("wheel", (e20) => {
211256
210398
  const { canvasMode, panX, panY, setPan, applyTransform: applyTransform2 } = getContext();
211257
210399
  if (canvasMode === "edit")
211258
210400
  return;
210401
+ if (canvasMode === "manage")
210402
+ return;
211259
210403
  e20.preventDefault();
211260
210404
  if (e20.ctrlKey || e20.metaKey) {
211261
210405
  const rect = canvasWrap.getBoundingClientRect();
@@ -211359,6 +210503,10 @@ function initShortcuts(getContext) {
211359
210503
  e20.preventDefault();
211360
210504
  openProject2();
211361
210505
  break;
210506
+ case "p":
210507
+ e20.preventDefault();
210508
+ openQuickSearch();
210509
+ break;
211362
210510
  case "s":
211363
210511
  e20.preventDefault();
211364
210512
  saveFile2();
@@ -211495,6 +210643,1237 @@ function navigateSelection(direction = -1) {
211495
210643
  init_store();
211496
210644
  init_reactivity();
211497
210645
  init_workspace();
210646
+
210647
+ // src/settings/defs-editor.js
210648
+ init_platform();
210649
+ init_store();
210650
+ var selectedDef = null;
210651
+ var showAddField = false;
210652
+ var newFieldState = { name: "", type: "string", format: "", required: false };
210653
+ var showNewDef = false;
210654
+ var newDefName = "";
210655
+ async function saveProjectConfig() {
210656
+ const platform4 = getPlatform();
210657
+ const config = projectState.projectConfig;
210658
+ await platform4.writeFile("project.json", JSON.stringify(config, null, "\t"));
210659
+ }
210660
+ function getSelectedDef() {
210661
+ const config = projectState?.projectConfig;
210662
+ return config?.$defs?.[selectedDef];
210663
+ }
210664
+ function handleNewDef(rerender) {
210665
+ const name = newDefName.trim();
210666
+ if (!name)
210667
+ return;
210668
+ const config = projectState?.projectConfig;
210669
+ if (!config)
210670
+ return;
210671
+ if (!config.$defs)
210672
+ config.$defs = {};
210673
+ if (config.$defs[name])
210674
+ return;
210675
+ config.$defs[name] = {
210676
+ type: "object",
210677
+ properties: {},
210678
+ required: []
210679
+ };
210680
+ selectedDef = name;
210681
+ showNewDef = false;
210682
+ newDefName = "";
210683
+ rerender();
210684
+ saveProjectConfig();
210685
+ }
210686
+ function handleAddField(rerender) {
210687
+ const name = newFieldState.name.trim();
210688
+ if (!name || !selectedDef)
210689
+ return;
210690
+ const def3 = getSelectedDef();
210691
+ if (!def3)
210692
+ return;
210693
+ if (!def3.properties)
210694
+ def3.properties = {};
210695
+ def3.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
210696
+ if (newFieldState.required) {
210697
+ if (!def3.required)
210698
+ def3.required = [];
210699
+ if (!def3.required.includes(name))
210700
+ def3.required.push(name);
210701
+ }
210702
+ showAddField = false;
210703
+ newFieldState = { name: "", type: "string", format: "", required: false };
210704
+ rerender();
210705
+ saveProjectConfig();
210706
+ }
210707
+ function handleDeleteField(fieldName, rerender) {
210708
+ const def3 = getSelectedDef();
210709
+ if (!def3?.properties)
210710
+ return;
210711
+ delete def3.properties[fieldName];
210712
+ if (def3.required) {
210713
+ def3.required = def3.required.filter((r8) => r8 !== fieldName);
210714
+ }
210715
+ rerender();
210716
+ saveProjectConfig();
210717
+ }
210718
+ function handleToggleRequired(fieldName, rerender) {
210719
+ const def3 = getSelectedDef();
210720
+ if (!def3)
210721
+ return;
210722
+ if (!def3.required)
210723
+ def3.required = [];
210724
+ const idx = def3.required.indexOf(fieldName);
210725
+ if (idx >= 0)
210726
+ def3.required.splice(idx, 1);
210727
+ else
210728
+ def3.required.push(fieldName);
210729
+ rerender();
210730
+ saveProjectConfig();
210731
+ }
210732
+ function handleRenameField(oldName, newName, rerender) {
210733
+ const def3 = getSelectedDef();
210734
+ if (!def3?.properties || !newName || def3.properties[newName])
210735
+ return;
210736
+ const newProps = {};
210737
+ for (const [key, val] of Object.entries(def3.properties)) {
210738
+ newProps[key === oldName ? newName : key] = val;
210739
+ }
210740
+ def3.properties = newProps;
210741
+ if (def3.required) {
210742
+ def3.required = def3.required.map((r8) => r8 === oldName ? newName : r8);
210743
+ }
210744
+ rerender();
210745
+ saveProjectConfig();
210746
+ }
210747
+ function handleChangeType(fieldName, newType, rerender) {
210748
+ const def3 = getSelectedDef();
210749
+ if (!def3?.properties?.[fieldName])
210750
+ return;
210751
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(def3.properties[fieldName]) : undefined;
210752
+ def3.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
210753
+ rerender();
210754
+ saveProjectConfig();
210755
+ }
210756
+ function handleChangeFormat(fieldName, format2, rerender) {
210757
+ const def3 = getSelectedDef();
210758
+ if (!def3?.properties?.[fieldName])
210759
+ return;
210760
+ const prop = def3.properties[fieldName];
210761
+ const type2 = prop.type || "string";
210762
+ def3.properties[fieldName] = schemaForType(type2, format2 || undefined);
210763
+ rerender();
210764
+ saveProjectConfig();
210765
+ }
210766
+ function handleAddNestedField(parentName, fieldState, rerender) {
210767
+ const def3 = getSelectedDef();
210768
+ const parent = def3?.properties?.[parentName];
210769
+ if (!parent)
210770
+ return;
210771
+ if (!parent.properties)
210772
+ parent.properties = {};
210773
+ parent.properties[fieldState.name] = schemaForType(fieldState.type);
210774
+ if (fieldState.required) {
210775
+ if (!parent.required)
210776
+ parent.required = [];
210777
+ if (!parent.required.includes(fieldState.name))
210778
+ parent.required.push(fieldState.name);
210779
+ }
210780
+ rerender();
210781
+ saveProjectConfig();
210782
+ }
210783
+ function handleDeleteNested(parentName, childName, rerender) {
210784
+ const def3 = getSelectedDef();
210785
+ const parent = def3?.properties?.[parentName];
210786
+ if (!parent?.properties)
210787
+ return;
210788
+ delete parent.properties[childName];
210789
+ if (parent.required) {
210790
+ parent.required = parent.required.filter((r8) => r8 !== childName);
210791
+ }
210792
+ rerender();
210793
+ saveProjectConfig();
210794
+ }
210795
+ function handleToggleNestedRequired(parentName, childName, rerender) {
210796
+ const def3 = getSelectedDef();
210797
+ const parent = def3?.properties?.[parentName];
210798
+ if (!parent)
210799
+ return;
210800
+ if (!parent.required)
210801
+ parent.required = [];
210802
+ const idx = parent.required.indexOf(childName);
210803
+ if (idx >= 0)
210804
+ parent.required.splice(idx, 1);
210805
+ else
210806
+ parent.required.push(childName);
210807
+ rerender();
210808
+ saveProjectConfig();
210809
+ }
210810
+ function handleRenameNested(parentName, oldChild, newChild, rerender) {
210811
+ const def3 = getSelectedDef();
210812
+ const parent = def3?.properties?.[parentName];
210813
+ if (!parent?.properties || !newChild || parent.properties[newChild])
210814
+ return;
210815
+ const newProps = {};
210816
+ for (const [key, val] of Object.entries(parent.properties)) {
210817
+ newProps[key === oldChild ? newChild : key] = val;
210818
+ }
210819
+ parent.properties = newProps;
210820
+ if (parent.required) {
210821
+ parent.required = parent.required.map((r8) => r8 === oldChild ? newChild : r8);
210822
+ }
210823
+ rerender();
210824
+ saveProjectConfig();
210825
+ }
210826
+ function handleChangeNestedType(parentName, childName, newType, rerender) {
210827
+ const def3 = getSelectedDef();
210828
+ const parent = def3?.properties?.[parentName];
210829
+ if (!parent?.properties?.[childName])
210830
+ return;
210831
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
210832
+ parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
210833
+ rerender();
210834
+ saveProjectConfig();
210835
+ }
210836
+ function handleChangeNestedFormat(parentName, childName, format2, rerender) {
210837
+ const def3 = getSelectedDef();
210838
+ const parent = def3?.properties?.[parentName];
210839
+ if (!parent?.properties?.[childName])
210840
+ return;
210841
+ const prop = parent.properties[childName];
210842
+ const type2 = prop.type || "string";
210843
+ parent.properties[childName] = schemaForType(type2, format2 || undefined);
210844
+ rerender();
210845
+ saveProjectConfig();
210846
+ }
210847
+ function handleDeleteDef(rerender) {
210848
+ if (!selectedDef)
210849
+ return;
210850
+ const config = projectState?.projectConfig;
210851
+ if (!config?.$defs?.[selectedDef])
210852
+ return;
210853
+ delete config.$defs[selectedDef];
210854
+ selectedDef = null;
210855
+ rerender();
210856
+ saveProjectConfig();
210857
+ }
210858
+ function renderDefsEditor(container) {
210859
+ const rerender = () => renderDefsEditor(container);
210860
+ const config = projectState?.projectConfig;
210861
+ const defs = config?.$defs || {};
210862
+ const defNames = Object.keys(defs);
210863
+ const listTpl = html`
210864
+ <div class="settings-list-panel">
210865
+ ${defNames.map((name) => html`
210866
+ <sp-action-button
210867
+ size="s"
210868
+ ?selected=${selectedDef === name}
210869
+ @click=${() => {
210870
+ selectedDef = name;
210871
+ showAddField = false;
210872
+ rerender();
210873
+ }}
210874
+ >
210875
+ ${name}
210876
+ </sp-action-button>
210877
+ `)}
210878
+ ${showNewDef ? html`
210879
+ <div class="settings-inline-form">
210880
+ <sp-textfield
210881
+ size="s"
210882
+ placeholder="TypeName"
210883
+ .value=${newDefName}
210884
+ @input=${(e20) => {
210885
+ newDefName = e20.target.value;
210886
+ }}
210887
+ @keydown=${(e20) => {
210888
+ if (e20.key === "Enter")
210889
+ handleNewDef(rerender);
210890
+ if (e20.key === "Escape") {
210891
+ showNewDef = false;
210892
+ rerender();
210893
+ }
210894
+ }}
210895
+ ></sp-textfield>
210896
+ <sp-action-button size="s" @click=${() => handleNewDef(rerender)}>
210897
+ Create
210898
+ </sp-action-button>
210899
+ </div>
210900
+ ` : html`
210901
+ <sp-action-button
210902
+ size="s"
210903
+ quiet
210904
+ @click=${() => {
210905
+ showNewDef = true;
210906
+ rerender();
210907
+ }}
210908
+ >
210909
+ <sp-icon-add slot="icon"></sp-icon-add> New Definition
210910
+ </sp-action-button>
210911
+ `}
210912
+ </div>
210913
+ `;
210914
+ let editorTpl;
210915
+ if (!selectedDef || !defs[selectedDef]) {
210916
+ editorTpl = html`<div class="settings-empty-state">Select or create a type definition</div>`;
210917
+ } else {
210918
+ const def3 = defs[selectedDef];
210919
+ const properties = def3.properties || {};
210920
+ const required = def3.required || [];
210921
+ const handlers = {
210922
+ onDelete: (n4) => handleDeleteField(n4, rerender),
210923
+ onToggleRequired: (n4) => handleToggleRequired(n4, rerender),
210924
+ onRename: (oldN, newN) => handleRenameField(oldN, newN, rerender),
210925
+ onChangeType: (n4, t15) => handleChangeType(n4, t15, rerender),
210926
+ onChangeFormat: (n4, f) => handleChangeFormat(n4, f, rerender),
210927
+ onAddNestedField: (p2, s2) => handleAddNestedField(p2, s2, rerender),
210928
+ onDeleteNested: (p2, c6) => handleDeleteNested(p2, c6, rerender),
210929
+ onToggleNestedRequired: (p2, c6) => handleToggleNestedRequired(p2, c6, rerender),
210930
+ onRenameNested: (p2, o19, n4) => handleRenameNested(p2, o19, n4, rerender),
210931
+ onChangeNestedType: (p2, c6, t15) => handleChangeNestedType(p2, c6, t15, rerender),
210932
+ onChangeNestedFormat: (p2, c6, f) => handleChangeNestedFormat(p2, c6, f, rerender)
210933
+ };
210934
+ const fieldCards = Object.entries(properties).map(([name, fieldDef]) => fieldCardTpl(name, fieldDef, required.includes(name), handlers));
210935
+ editorTpl = html`
210936
+ <div class="settings-editor-panel">
210937
+ <div class="settings-editor-header">
210938
+ <h3>${selectedDef}</h3>
210939
+ <sp-action-button
210940
+ size="xs"
210941
+ quiet
210942
+ title="Delete definition"
210943
+ @click=${() => handleDeleteDef(rerender)}
210944
+ >
210945
+ <sp-icon-delete slot="icon"></sp-icon-delete>
210946
+ </sp-action-button>
210947
+ </div>
210948
+ <div class="schema-field-list">${fieldCards}</div>
210949
+ ${showAddField ? addFieldFormTpl(newFieldState, {
210950
+ onInput: (field, value2) => {
210951
+ newFieldState = { ...newFieldState, [field]: value2 };
210952
+ rerender();
210953
+ },
210954
+ onConfirm: () => handleAddField(rerender),
210955
+ onCancel: () => {
210956
+ showAddField = false;
210957
+ newFieldState = { name: "", type: "string", format: "", required: false };
210958
+ rerender();
210959
+ }
210960
+ }) : html`
210961
+ <sp-action-button
210962
+ size="s"
210963
+ quiet
210964
+ @click=${() => {
210965
+ showAddField = true;
210966
+ rerender();
210967
+ }}
210968
+ >
210969
+ <sp-icon-add slot="icon"></sp-icon-add> Add Field
210970
+ </sp-action-button>
210971
+ `}
210972
+ </div>
210973
+ `;
210974
+ }
210975
+ const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
210976
+ render2(tpl, container);
210977
+ }
210978
+
210979
+ // src/settings/content-types-editor.js
210980
+ init_platform();
210981
+ init_store();
210982
+ var selectedContentType = null;
210983
+ var showAddField2 = false;
210984
+ var newFieldState2 = { name: "", type: "string", format: "", required: false };
210985
+ var showNewContentType = false;
210986
+ var newContentTypeName = "";
210987
+ async function saveProjectConfig2() {
210988
+ const platform4 = getPlatform();
210989
+ const config = projectState.projectConfig;
210990
+ await platform4.writeFile("project.json", JSON.stringify(config, null, "\t"));
210991
+ }
210992
+ function getSelectedSchema() {
210993
+ const config = projectState?.projectConfig;
210994
+ return config?.contentTypes?.[selectedContentType]?.schema;
210995
+ }
210996
+ function handleNewContentType(rerender) {
210997
+ const slug = newContentTypeName.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
210998
+ if (!slug)
210999
+ return;
211000
+ const config = projectState?.projectConfig;
211001
+ if (!config)
211002
+ return;
211003
+ if (!config.contentTypes)
211004
+ config.contentTypes = {};
211005
+ if (config.contentTypes[slug])
211006
+ return;
211007
+ config.contentTypes[slug] = {
211008
+ source: `./content/${slug}/**/*.md`,
211009
+ schema: { type: "object", properties: {}, required: [] }
211010
+ };
211011
+ selectedContentType = slug;
211012
+ showNewContentType = false;
211013
+ newContentTypeName = "";
211014
+ rerender();
211015
+ saveProjectConfig2().then(async () => {
211016
+ const platform4 = getPlatform();
211017
+ await platform4.writeFile(`content/${slug}/.gitkeep`, "");
211018
+ });
211019
+ }
211020
+ function handleAddField2(rerender) {
211021
+ const name = newFieldState2.name.trim();
211022
+ if (!name || !selectedContentType)
211023
+ return;
211024
+ const schema4 = getSelectedSchema();
211025
+ if (!schema4)
211026
+ return;
211027
+ if (!schema4.properties)
211028
+ schema4.properties = {};
211029
+ schema4.properties[name] = schemaForType(newFieldState2.type, newFieldState2.format || undefined);
211030
+ if (newFieldState2.required) {
211031
+ if (!schema4.required)
211032
+ schema4.required = [];
211033
+ if (!schema4.required.includes(name))
211034
+ schema4.required.push(name);
211035
+ }
211036
+ showAddField2 = false;
211037
+ newFieldState2 = { name: "", type: "string", format: "", required: false };
211038
+ rerender();
211039
+ saveProjectConfig2();
211040
+ }
211041
+ function handleDeleteField2(fieldName, rerender) {
211042
+ const schema4 = getSelectedSchema();
211043
+ if (!schema4?.properties)
211044
+ return;
211045
+ delete schema4.properties[fieldName];
211046
+ if (schema4.required) {
211047
+ schema4.required = schema4.required.filter((r8) => r8 !== fieldName);
211048
+ }
211049
+ rerender();
211050
+ saveProjectConfig2();
211051
+ }
211052
+ function handleToggleRequired2(fieldName, rerender) {
211053
+ const schema4 = getSelectedSchema();
211054
+ if (!schema4)
211055
+ return;
211056
+ if (!schema4.required)
211057
+ schema4.required = [];
211058
+ const idx = schema4.required.indexOf(fieldName);
211059
+ if (idx >= 0)
211060
+ schema4.required.splice(idx, 1);
211061
+ else
211062
+ schema4.required.push(fieldName);
211063
+ rerender();
211064
+ saveProjectConfig2();
211065
+ }
211066
+ function handleRenameField2(oldName, newName, rerender) {
211067
+ const schema4 = getSelectedSchema();
211068
+ if (!schema4?.properties || !newName || schema4.properties[newName])
211069
+ return;
211070
+ const newProps = {};
211071
+ for (const [key, val] of Object.entries(schema4.properties)) {
211072
+ newProps[key === oldName ? newName : key] = val;
211073
+ }
211074
+ schema4.properties = newProps;
211075
+ if (schema4.required) {
211076
+ schema4.required = schema4.required.map((r8) => r8 === oldName ? newName : r8);
211077
+ }
211078
+ rerender();
211079
+ saveProjectConfig2();
211080
+ }
211081
+ function handleChangeType2(fieldName, newType, rerender) {
211082
+ const schema4 = getSelectedSchema();
211083
+ if (!schema4?.properties?.[fieldName])
211084
+ return;
211085
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(schema4.properties[fieldName]) : undefined;
211086
+ schema4.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
211087
+ rerender();
211088
+ saveProjectConfig2();
211089
+ }
211090
+ function handleChangeFormat2(fieldName, format2, rerender) {
211091
+ const schema4 = getSelectedSchema();
211092
+ if (!schema4?.properties?.[fieldName])
211093
+ return;
211094
+ const prop = schema4.properties[fieldName];
211095
+ const type2 = prop.type || "string";
211096
+ schema4.properties[fieldName] = schemaForType(type2, format2 || undefined);
211097
+ rerender();
211098
+ saveProjectConfig2();
211099
+ }
211100
+ function handleChangeRefTarget(fieldName, target, rerender) {
211101
+ const schema4 = getSelectedSchema();
211102
+ if (!schema4?.properties)
211103
+ return;
211104
+ schema4.properties[fieldName] = { $ref: `#/contentTypes/${target}` };
211105
+ rerender();
211106
+ saveProjectConfig2();
211107
+ }
211108
+ function handleAddNestedField2(parentName, fieldState, rerender) {
211109
+ const schema4 = getSelectedSchema();
211110
+ const parent = schema4?.properties?.[parentName];
211111
+ if (!parent)
211112
+ return;
211113
+ if (!parent.properties)
211114
+ parent.properties = {};
211115
+ parent.properties[fieldState.name] = schemaForType(fieldState.type);
211116
+ if (fieldState.required) {
211117
+ if (!parent.required)
211118
+ parent.required = [];
211119
+ if (!parent.required.includes(fieldState.name))
211120
+ parent.required.push(fieldState.name);
211121
+ }
211122
+ rerender();
211123
+ saveProjectConfig2();
211124
+ }
211125
+ function handleDeleteNested2(parentName, childName, rerender) {
211126
+ const schema4 = getSelectedSchema();
211127
+ const parent = schema4?.properties?.[parentName];
211128
+ if (!parent?.properties)
211129
+ return;
211130
+ delete parent.properties[childName];
211131
+ if (parent.required) {
211132
+ parent.required = parent.required.filter((r8) => r8 !== childName);
211133
+ }
211134
+ rerender();
211135
+ saveProjectConfig2();
211136
+ }
211137
+ function handleToggleNestedRequired2(parentName, childName, rerender) {
211138
+ const schema4 = getSelectedSchema();
211139
+ const parent = schema4?.properties?.[parentName];
211140
+ if (!parent)
211141
+ return;
211142
+ if (!parent.required)
211143
+ parent.required = [];
211144
+ const idx = parent.required.indexOf(childName);
211145
+ if (idx >= 0)
211146
+ parent.required.splice(idx, 1);
211147
+ else
211148
+ parent.required.push(childName);
211149
+ rerender();
211150
+ saveProjectConfig2();
211151
+ }
211152
+ function handleRenameNested2(parentName, oldChild, newChild, rerender) {
211153
+ const schema4 = getSelectedSchema();
211154
+ const parent = schema4?.properties?.[parentName];
211155
+ if (!parent?.properties || !newChild || parent.properties[newChild])
211156
+ return;
211157
+ const newProps = {};
211158
+ for (const [key, val] of Object.entries(parent.properties)) {
211159
+ newProps[key === oldChild ? newChild : key] = val;
211160
+ }
211161
+ parent.properties = newProps;
211162
+ if (parent.required) {
211163
+ parent.required = parent.required.map((r8) => r8 === oldChild ? newChild : r8);
211164
+ }
211165
+ rerender();
211166
+ saveProjectConfig2();
211167
+ }
211168
+ function handleChangeNestedType2(parentName, childName, newType, rerender) {
211169
+ const schema4 = getSelectedSchema();
211170
+ const parent = schema4?.properties?.[parentName];
211171
+ if (!parent?.properties?.[childName])
211172
+ return;
211173
+ const oldFormat = newType === "string" || newType === "array" ? detectFieldFormat(parent.properties[childName]) : undefined;
211174
+ parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
211175
+ rerender();
211176
+ saveProjectConfig2();
211177
+ }
211178
+ function handleChangeNestedFormat2(parentName, childName, format2, rerender) {
211179
+ const schema4 = getSelectedSchema();
211180
+ const parent = schema4?.properties?.[parentName];
211181
+ if (!parent?.properties?.[childName])
211182
+ return;
211183
+ const prop = parent.properties[childName];
211184
+ const type2 = prop.type || "string";
211185
+ parent.properties[childName] = schemaForType(type2, format2 || undefined);
211186
+ rerender();
211187
+ saveProjectConfig2();
211188
+ }
211189
+ function handleDeleteContentType(rerender) {
211190
+ if (!selectedContentType)
211191
+ return;
211192
+ const config = projectState?.projectConfig;
211193
+ if (!config?.contentTypes?.[selectedContentType])
211194
+ return;
211195
+ delete config.contentTypes[selectedContentType];
211196
+ selectedContentType = null;
211197
+ rerender();
211198
+ saveProjectConfig2();
211199
+ }
211200
+ function renderContentTypesEditor(container) {
211201
+ const rerender = () => renderContentTypesEditor(container);
211202
+ const config = projectState?.projectConfig;
211203
+ const contentTypes = config?.contentTypes || {};
211204
+ const contentTypeNames = Object.keys(contentTypes);
211205
+ const listTpl = html`
211206
+ <div class="settings-list-panel">
211207
+ ${contentTypeNames.map((name) => html`
211208
+ <sp-action-button
211209
+ size="s"
211210
+ ?selected=${selectedContentType === name}
211211
+ @click=${() => {
211212
+ selectedContentType = name;
211213
+ showAddField2 = false;
211214
+ rerender();
211215
+ }}
211216
+ >
211217
+ ${name}
211218
+ </sp-action-button>
211219
+ `)}
211220
+ ${showNewContentType ? html`
211221
+ <div class="settings-inline-form">
211222
+ <sp-textfield
211223
+ size="s"
211224
+ placeholder="content-type-name"
211225
+ .value=${newContentTypeName}
211226
+ @input=${(e20) => {
211227
+ newContentTypeName = e20.target.value;
211228
+ }}
211229
+ @keydown=${(e20) => {
211230
+ if (e20.key === "Enter")
211231
+ handleNewContentType(rerender);
211232
+ if (e20.key === "Escape") {
211233
+ showNewContentType = false;
211234
+ rerender();
211235
+ }
211236
+ }}
211237
+ ></sp-textfield>
211238
+ <sp-action-button size="s" @click=${() => handleNewContentType(rerender)}>
211239
+ Create
211240
+ </sp-action-button>
211241
+ </div>
211242
+ ` : html`
211243
+ <sp-action-button
211244
+ size="s"
211245
+ quiet
211246
+ @click=${() => {
211247
+ showNewContentType = true;
211248
+ rerender();
211249
+ }}
211250
+ >
211251
+ <sp-icon-add slot="icon"></sp-icon-add> New Content Type
211252
+ </sp-action-button>
211253
+ `}
211254
+ </div>
211255
+ `;
211256
+ let editorTpl;
211257
+ if (!selectedContentType || !contentTypes[selectedContentType]) {
211258
+ editorTpl = html`<div class="settings-empty-state">Select or create a content type</div>`;
211259
+ } else {
211260
+ const col = contentTypes[selectedContentType];
211261
+ const schema4 = col.schema || {};
211262
+ const properties = schema4.properties || {};
211263
+ const required = schema4.required || [];
211264
+ const handlers = {
211265
+ onDelete: (n4) => handleDeleteField2(n4, rerender),
211266
+ onToggleRequired: (n4) => handleToggleRequired2(n4, rerender),
211267
+ onRename: (oldN, newN) => handleRenameField2(oldN, newN, rerender),
211268
+ onChangeType: (n4, t15) => handleChangeType2(n4, t15, rerender),
211269
+ onChangeFormat: (n4, f) => handleChangeFormat2(n4, f, rerender),
211270
+ onChangeRefTarget: (n4, target) => handleChangeRefTarget(n4, target, rerender),
211271
+ onAddNestedField: (p2, s2) => handleAddNestedField2(p2, s2, rerender),
211272
+ onDeleteNested: (p2, c6) => handleDeleteNested2(p2, c6, rerender),
211273
+ onToggleNestedRequired: (p2, c6) => handleToggleNestedRequired2(p2, c6, rerender),
211274
+ onRenameNested: (p2, o19, n4) => handleRenameNested2(p2, o19, n4, rerender),
211275
+ onChangeNestedType: (p2, c6, t15) => handleChangeNestedType2(p2, c6, t15, rerender),
211276
+ onChangeNestedFormat: (p2, c6, f) => handleChangeNestedFormat2(p2, c6, f, rerender)
211277
+ };
211278
+ const fieldCards = Object.entries(properties).map(([name, def3]) => fieldCardTpl(name, def3, required.includes(name), handlers, contentTypeNames));
211279
+ editorTpl = html`
211280
+ <div class="settings-editor-panel">
211281
+ <div class="settings-editor-header">
211282
+ <h3>${selectedContentType}</h3>
211283
+ <sp-field-label size="s">Source: ${col.source || "—"}</sp-field-label>
211284
+ <sp-action-button
211285
+ size="xs"
211286
+ quiet
211287
+ title="Delete content type"
211288
+ @click=${() => handleDeleteContentType(rerender)}
211289
+ >
211290
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211291
+ </sp-action-button>
211292
+ </div>
211293
+ <div class="schema-field-list">${fieldCards}</div>
211294
+ ${showAddField2 ? addFieldFormTpl(newFieldState2, {
211295
+ onInput: (field, value2) => {
211296
+ newFieldState2 = { ...newFieldState2, [field]: value2 };
211297
+ rerender();
211298
+ },
211299
+ onConfirm: () => handleAddField2(rerender),
211300
+ onCancel: () => {
211301
+ showAddField2 = false;
211302
+ newFieldState2 = { name: "", type: "string", format: "", required: false };
211303
+ rerender();
211304
+ }
211305
+ }) : html`
211306
+ <sp-action-button
211307
+ size="s"
211308
+ quiet
211309
+ @click=${() => {
211310
+ showAddField2 = true;
211311
+ rerender();
211312
+ }}
211313
+ >
211314
+ <sp-icon-add slot="icon"></sp-icon-add> Add Field
211315
+ </sp-action-button>
211316
+ `}
211317
+ </div>
211318
+ `;
211319
+ }
211320
+ const tpl = html` <div class="settings-two-col">${listTpl} ${editorTpl}</div> `;
211321
+ render2(tpl, container);
211322
+ }
211323
+
211324
+ // src/settings/css-vars-editor.js
211325
+ init_store();
211326
+ function renderCssVarsEditor(container) {
211327
+ const config = projectState.projectConfig || {};
211328
+ const rootStyle = config.style || {};
211329
+ const media = getEffectiveMedia(config.$media);
211330
+ const groups = { color: [], font: [], size: [], other: [] };
211331
+ for (const [k, v] of Object.entries(rootStyle)) {
211332
+ if (!k.startsWith("--"))
211333
+ continue;
211334
+ if (typeof v !== "string" && typeof v !== "number")
211335
+ continue;
211336
+ if (k.startsWith("--color"))
211337
+ groups.color.push([k, v]);
211338
+ else if (k.startsWith("--font"))
211339
+ groups.font.push([k, v]);
211340
+ else if (k.startsWith("--size") || k.startsWith("--spacing") || k.startsWith("--radius"))
211341
+ groups.size.push([k, v]);
211342
+ else
211343
+ groups.other.push([k, v]);
211344
+ }
211345
+ const mediaNames = media ? Object.keys(media).filter((m3) => m3 !== "--") : [];
211346
+ const save = () => {
211347
+ updateSiteConfig({ style: { ...rootStyle } });
211348
+ };
211349
+ const updateVar = (name, val) => {
211350
+ rootStyle[name] = val;
211351
+ save();
211352
+ };
211353
+ const deleteVar = (name) => {
211354
+ delete rootStyle[name];
211355
+ save();
211356
+ renderCssVarsEditor(container);
211357
+ };
211358
+ const addVar = (prefix, friendlyName, val) => {
211359
+ const varName = friendlyNameToVar(friendlyName, prefix);
211360
+ if (!varName || !val)
211361
+ return;
211362
+ rootStyle[varName] = val;
211363
+ save();
211364
+ renderCssVarsEditor(container);
211365
+ };
211366
+ const tpl = html`
211367
+ <div class="settings-section">
211368
+ <h3 class="settings-section-title">CSS Variables</h3>
211369
+
211370
+ ${renderColorSection(groups.color, updateVar, deleteVar, addVar)}
211371
+ ${renderFontSection(groups.font, updateVar, deleteVar, addVar)}
211372
+ ${renderSizeSection(groups.size, updateVar, deleteVar, addVar, rootStyle, mediaNames)}
211373
+ ${groups.other.length > 0 ? renderOtherSection(groups.other, updateVar, deleteVar, addVar, rootStyle, mediaNames) : nothing}
211374
+ </div>
211375
+ `;
211376
+ render2(tpl, container);
211377
+ }
211378
+ function renderColorSection(vars, updateVar, deleteVar, addVar) {
211379
+ return html`
211380
+ <div class="css-vars-group">
211381
+ <h4 class="css-vars-group-title">Colors</h4>
211382
+ ${vars.map(([name, val]) => html`
211383
+ <div class="css-var-row">
211384
+ <div class="css-var-swatch" style="background:${val}">
211385
+ <input
211386
+ type="color"
211387
+ .value=${val && val.startsWith("#") ? val : "#007acc"}
211388
+ @input=${(e20) => updateVar(name, e20.target.value)}
211389
+ />
211390
+ </div>
211391
+ <span class="css-var-name">${varDisplayName(name, "--color-")}</span>
211392
+ <sp-textfield
211393
+ size="s"
211394
+ .value=${String(val)}
211395
+ @change=${(e20) => updateVar(name, e20.target.value)}
211396
+ style="flex:1;max-width:160px"
211397
+ ></sp-textfield>
211398
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211399
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211400
+ </sp-action-button>
211401
+ </div>
211402
+ `)}
211403
+ ${renderAddRow("--color-", "Primary Blue", "#007acc", addVar)}
211404
+ </div>
211405
+ `;
211406
+ }
211407
+ function renderFontSection(vars, updateVar, deleteVar, addVar) {
211408
+ return html`
211409
+ <div class="css-vars-group">
211410
+ <h4 class="css-vars-group-title">Fonts</h4>
211411
+ ${vars.map(([name, val]) => html`
211412
+ <div class="css-var-row">
211413
+ <span class="css-var-name">${varDisplayName(name, "--font-")}</span>
211414
+ <sp-textfield
211415
+ size="s"
211416
+ .value=${String(val)}
211417
+ @change=${(e20) => updateVar(name, e20.target.value)}
211418
+ style="flex:1"
211419
+ ></sp-textfield>
211420
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211421
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211422
+ </sp-action-button>
211423
+ </div>
211424
+ <div class="css-var-font-preview" style="font-family:${val}">
211425
+ The quick brown fox jumps over the lazy dog
211426
+ </div>
211427
+ `)}
211428
+ ${renderAddRow("--font-", "Body Serif", "'Georgia', serif", addVar)}
211429
+ </div>
211430
+ `;
211431
+ }
211432
+ function renderSizeSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaNames) {
211433
+ return html`
211434
+ <div class="css-vars-group">
211435
+ <h4 class="css-vars-group-title">Sizes &amp; Spacing</h4>
211436
+ ${vars.map(([name, val]) => html`
211437
+ <div class="css-var-row">
211438
+ <span class="css-var-name"
211439
+ >${varDisplayName(name, "--size-") || varDisplayName(name, "--spacing-") || varDisplayName(name, "--radius-") || name}</span
211440
+ >
211441
+ <sp-textfield
211442
+ size="s"
211443
+ .value=${String(val)}
211444
+ @change=${(e20) => updateVar(name, e20.target.value)}
211445
+ style="max-width:120px"
211446
+ ></sp-textfield>
211447
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211448
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211449
+ </sp-action-button>
211450
+ </div>
211451
+ ${mediaNames.length > 0 ? renderMediaOverrides(name, rootStyle, mediaNames) : nothing}
211452
+ `)}
211453
+ ${renderAddRow("--size-", "Spacing Large", "32px", addVar)}
211454
+ </div>
211455
+ `;
211456
+ }
211457
+ function renderOtherSection(vars, updateVar, deleteVar, addVar, rootStyle, mediaNames) {
211458
+ return html`
211459
+ <div class="css-vars-group">
211460
+ <h4 class="css-vars-group-title">Other</h4>
211461
+ ${vars.map(([name, val]) => html`
211462
+ <div class="css-var-row">
211463
+ <span class="css-var-name">${name}</span>
211464
+ <sp-textfield
211465
+ size="s"
211466
+ .value=${String(val)}
211467
+ @change=${(e20) => updateVar(name, e20.target.value)}
211468
+ style="flex:1"
211469
+ ></sp-textfield>
211470
+ <sp-action-button quiet size="s" @click=${() => deleteVar(name)}>
211471
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211472
+ </sp-action-button>
211473
+ </div>
211474
+ ${mediaNames.length > 0 ? renderMediaOverrides(name, rootStyle, mediaNames) : nothing}
211475
+ `)}
211476
+ ${renderAddRow("--", "Custom Var", "value", addVar)}
211477
+ </div>
211478
+ `;
211479
+ }
211480
+ function renderMediaOverrides(varName, rootStyle, mediaNames) {
211481
+ const overrides = [];
211482
+ for (const mediaName of mediaNames) {
211483
+ const mediaKey = `@${mediaName}`;
211484
+ const mediaBlock = rootStyle[mediaKey];
211485
+ if (mediaBlock && typeof mediaBlock === "object" && mediaBlock[varName]) {
211486
+ overrides.push({ mediaName, value: mediaBlock[varName] });
211487
+ }
211488
+ }
211489
+ if (overrides.length === 0)
211490
+ return nothing;
211491
+ return html`
211492
+ <div class="css-var-media-overrides">
211493
+ ${overrides.map((o19) => html`
211494
+ <div class="css-var-media-row">
211495
+ <span class="css-var-media-label">@${o19.mediaName}</span>
211496
+ <sp-textfield
211497
+ size="s"
211498
+ .value=${String(o19.value)}
211499
+ @change=${(e20) => {
211500
+ if (!rootStyle[`@${o19.mediaName}`])
211501
+ rootStyle[`@${o19.mediaName}`] = {};
211502
+ rootStyle[`@${o19.mediaName}`][varName] = e20.target.value;
211503
+ updateSiteConfig({ style: { ...rootStyle } });
211504
+ }}
211505
+ style="max-width:120px"
211506
+ ></sp-textfield>
211507
+ </div>
211508
+ `)}
211509
+ </div>
211510
+ `;
211511
+ }
211512
+ function renderAddRow(prefix, placeholder, valuePlaceholder, addVar) {
211513
+ let nameEl = null;
211514
+ let valEl = null;
211515
+ return html`
211516
+ <div class="css-var-add-row">
211517
+ <sp-textfield
211518
+ size="s"
211519
+ placeholder=${placeholder}
211520
+ ${ref2((el) => {
211521
+ if (el)
211522
+ nameEl = el;
211523
+ })}
211524
+ ></sp-textfield>
211525
+ <sp-textfield
211526
+ size="s"
211527
+ placeholder=${valuePlaceholder}
211528
+ ${ref2((el) => {
211529
+ if (el)
211530
+ valEl = el;
211531
+ })}
211532
+ ></sp-textfield>
211533
+ <sp-action-button
211534
+ size="s"
211535
+ @click=${() => {
211536
+ if (nameEl && valEl) {
211537
+ addVar(prefix, nameEl.value, valEl.value);
211538
+ }
211539
+ }}
211540
+ >Add</sp-action-button
211541
+ >
211542
+ </div>
211543
+ `;
211544
+ }
211545
+
211546
+ // src/settings/head-editor.js
211547
+ init_store();
211548
+ function renderHeadEditor(container) {
211549
+ const config = projectState.projectConfig || {};
211550
+ const headEntries = config.$head || [];
211551
+ const save = () => {
211552
+ updateSiteConfig({ $head: headEntries });
211553
+ };
211554
+ const addEntry = (tag7) => {
211555
+ const entry = { tag: tag7 };
211556
+ if (tag7 === "link") {
211557
+ entry.rel = "stylesheet";
211558
+ entry.href = "";
211559
+ } else if (tag7 === "meta") {
211560
+ entry.name = "";
211561
+ entry.content = "";
211562
+ } else if (tag7 === "script") {
211563
+ entry.src = "";
211564
+ entry.body = "";
211565
+ } else if (tag7 === "style") {
211566
+ entry.body = "";
211567
+ }
211568
+ headEntries.push(entry);
211569
+ save();
211570
+ renderHeadEditor(container);
211571
+ };
211572
+ const removeEntry = (idx) => {
211573
+ headEntries.splice(idx, 1);
211574
+ save();
211575
+ renderHeadEditor(container);
211576
+ };
211577
+ const updateEntry = (idx, key, val) => {
211578
+ headEntries[idx][key] = val;
211579
+ save();
211580
+ };
211581
+ const tpl = html`
211582
+ <div class="settings-section">
211583
+ <h3 class="settings-section-title">Head</h3>
211584
+ <p class="settings-field-desc">
211585
+ Manage global &lt;head&gt; tags — stylesheets, meta tags, scripts, and inline styles.
211586
+ </p>
211587
+
211588
+ <div class="head-entries">
211589
+ ${headEntries.map((entry, idx) => html`
211590
+ <div class="head-entry">
211591
+ <div class="head-entry-header">
211592
+ <span class="head-entry-tag">&lt;${entry.tag}&gt;</span>
211593
+ <sp-action-button quiet size="s" @click=${() => removeEntry(idx)}>
211594
+ <sp-icon-delete slot="icon"></sp-icon-delete>
211595
+ </sp-action-button>
211596
+ </div>
211597
+ <div class="head-entry-fields">${renderEntryFields(entry, idx, updateEntry)}</div>
211598
+ </div>
211599
+ `)}
211600
+ </div>
211601
+
211602
+ <div class="head-add-actions" style="margin-top:12px;display:flex;gap:8px">
211603
+ <sp-action-button size="s" @click=${() => addEntry("link")}> + Link </sp-action-button>
211604
+ <sp-action-button size="s" @click=${() => addEntry("meta")}> + Meta </sp-action-button>
211605
+ <sp-action-button size="s" @click=${() => addEntry("script")}> + Script </sp-action-button>
211606
+ <sp-action-button size="s" @click=${() => addEntry("style")}> + Style </sp-action-button>
211607
+ </div>
211608
+ </div>
211609
+ `;
211610
+ render2(tpl, container);
211611
+ }
211612
+ function renderEntryFields(entry, idx, updateEntry) {
211613
+ let debounce;
211614
+ const onFieldChange = (key) => (e20) => {
211615
+ clearTimeout(debounce);
211616
+ debounce = setTimeout(() => {
211617
+ updateEntry(idx, key, e20.target.value);
211618
+ }, 300);
211619
+ };
211620
+ switch (entry.tag) {
211621
+ case "link":
211622
+ return html`
211623
+ <div class="settings-field-row">
211624
+ <sp-textfield
211625
+ size="s"
211626
+ label="rel"
211627
+ .value=${entry.rel || ""}
211628
+ @change=${onFieldChange("rel")}
211629
+ ></sp-textfield>
211630
+ <sp-textfield
211631
+ size="s"
211632
+ label="href"
211633
+ .value=${entry.href || ""}
211634
+ @change=${onFieldChange("href")}
211635
+ style="flex:1"
211636
+ ></sp-textfield>
211637
+ </div>
211638
+ `;
211639
+ case "meta":
211640
+ return html`
211641
+ <div class="settings-field-row">
211642
+ <sp-textfield
211643
+ size="s"
211644
+ label="name"
211645
+ .value=${entry.name || ""}
211646
+ @change=${onFieldChange("name")}
211647
+ ></sp-textfield>
211648
+ <sp-textfield
211649
+ size="s"
211650
+ label="content"
211651
+ .value=${entry.content || ""}
211652
+ @change=${onFieldChange("content")}
211653
+ style="flex:1"
211654
+ ></sp-textfield>
211655
+ </div>
211656
+ `;
211657
+ case "script":
211658
+ return html`
211659
+ <div class="settings-field-row">
211660
+ <sp-textfield
211661
+ size="s"
211662
+ label="src"
211663
+ .value=${entry.src || ""}
211664
+ @change=${onFieldChange("src")}
211665
+ placeholder="URL or leave empty for inline"
211666
+ style="flex:1"
211667
+ ></sp-textfield>
211668
+ </div>
211669
+ ${!entry.src ? html`
211670
+ <div class="head-entry-body">
211671
+ <label class="settings-field-label">Script body</label>
211672
+ <textarea
211673
+ class="head-code-editor"
211674
+ .value=${entry.body || ""}
211675
+ @input=${onFieldChange("body")}
211676
+ rows="6"
211677
+ spellcheck="false"
211678
+ ></textarea>
211679
+ </div>
211680
+ ` : nothing}
211681
+ `;
211682
+ case "style":
211683
+ return html`
211684
+ <div class="head-entry-body">
211685
+ <label class="settings-field-label">Style body</label>
211686
+ <textarea
211687
+ class="head-code-editor"
211688
+ .value=${entry.body || ""}
211689
+ @input=${onFieldChange("body")}
211690
+ rows="8"
211691
+ spellcheck="false"
211692
+ ></textarea>
211693
+ </div>
211694
+ `;
211695
+ default:
211696
+ return nothing;
211697
+ }
211698
+ }
211699
+
211700
+ // src/settings/general-settings.js
211701
+ init_store();
211702
+ init_platform();
211703
+ function renderGeneralSettings(container) {
211704
+ const config = projectState.projectConfig || {};
211705
+ const onFaviconUpload = async () => {
211706
+ const input = document.createElement("input");
211707
+ input.type = "file";
211708
+ input.accept = "image/*,.ico,.svg";
211709
+ input.onchange = async () => {
211710
+ const file = input.files?.[0];
211711
+ if (!file)
211712
+ return;
211713
+ const platform4 = getPlatform();
211714
+ await platform4.uploadFile("public/favicon.ico", file);
211715
+ await updateSiteConfig({ favicon: "/favicon.ico" });
211716
+ renderGeneralSettings(container);
211717
+ };
211718
+ input.click();
211719
+ };
211720
+ const onAdapterChange = (e20) => {
211721
+ updateSiteConfig({ adapter: e20.target.value });
211722
+ };
211723
+ const onEditGlobalStyles = () => {
211724
+ closeSettingsModal();
211725
+ openFileInTab("project.json");
211726
+ };
211727
+ const currentFavicon = config.favicon;
211728
+ const tpl = html`
211729
+ <div class="settings-section">
211730
+ <h3 class="settings-section-title">General</h3>
211731
+
211732
+ <div class="settings-field">
211733
+ <label class="settings-field-label">Favicon</label>
211734
+ <p class="settings-field-desc">Upload an image to use as the site favicon.</p>
211735
+ <div style="display:flex;align-items:center;gap:12px">
211736
+ ${currentFavicon ? html`<img
211737
+ src=${currentFavicon}
211738
+ alt="Current favicon"
211739
+ style="width:32px;height:32px;object-fit:contain;border:1px solid var(--border);border-radius:4px;padding:2px"
211740
+ />` : html`<div
211741
+ style="width:32px;height:32px;border:1px dashed var(--border);border-radius:4px;display:flex;align-items:center;justify-content:center;color:var(--fg-dim);font-size:11px"
211742
+ >
211743
+
211744
+ </div>`}
211745
+ <sp-action-button size="s" @click=${onFaviconUpload}> Upload Favicon </sp-action-button>
211746
+ ${currentFavicon ? html`<span style="font-size:11px;color:var(--fg-dim)">${currentFavicon}</span>` : nothing}
211747
+ </div>
211748
+ </div>
211749
+
211750
+ <div class="settings-field">
211751
+ <label class="settings-field-label">Platform Adapter</label>
211752
+ <p class="settings-field-desc">Build adapter for deployment target.</p>
211753
+ <sp-picker
211754
+ size="s"
211755
+ label="Platform Adapter"
211756
+ .value=${config.adapter || "static"}
211757
+ @change=${onAdapterChange}
211758
+ >
211759
+ <sp-menu-item value="static">Static</sp-menu-item>
211760
+ <sp-menu-item value="bun">Bun</sp-menu-item>
211761
+ <sp-menu-item value="node">Node</sp-menu-item>
211762
+ <sp-menu-item value="cloudflare-workers">Cloudflare Workers</sp-menu-item>
211763
+ <sp-menu-item value="cloudflare-pages">Cloudflare Pages</sp-menu-item>
211764
+ </sp-picker>
211765
+ </div>
211766
+
211767
+ <div class="settings-field">
211768
+ <label class="settings-field-label">Global Styles</label>
211769
+ <p class="settings-field-desc">Edit default element styles that apply across all pages.</p>
211770
+ <sp-action-button size="s" @click=${onEditGlobalStyles}>
211771
+ Edit Global Styles
211772
+ </sp-action-button>
211773
+ </div>
211774
+ </div>
211775
+ `;
211776
+ render2(tpl, container);
211777
+ }
211778
+
211779
+ // src/settings/settings-modal.js
211780
+ var _host = null;
211781
+ var _activeSection = "general";
211782
+ var sections = [
211783
+ { key: "general", label: "General", icon: "sp-icon-properties" },
211784
+ { key: "head", label: "Head", icon: "sp-icon-file-single-web-page" },
211785
+ { key: "cssVars", label: "CSS Variables", icon: "sp-icon-brush" },
211786
+ { key: "definitions", label: "Definitions", icon: "sp-icon-data" },
211787
+ { key: "contentTypes", label: "Content Types", icon: "sp-icon-view-grid" }
211788
+ ];
211789
+ function openSettingsModal() {
211790
+ if (_host)
211791
+ return;
211792
+ _host = document.createElement("div");
211793
+ _host.style.display = "contents";
211794
+ const themeRoot = document.querySelector("sp-theme") || document.body;
211795
+ themeRoot.appendChild(_host);
211796
+ _activeSection = "general";
211797
+ renderModal();
211798
+ }
211799
+ function closeSettingsModal() {
211800
+ if (!_host)
211801
+ return;
211802
+ _host.remove();
211803
+ _host = null;
211804
+ }
211805
+ function renderModal() {
211806
+ if (!_host)
211807
+ return;
211808
+ const onNavClick = (key) => {
211809
+ _activeSection = key;
211810
+ renderModal();
211811
+ renderActiveSection();
211812
+ };
211813
+ const tpl = html`
211814
+ <sp-underlay open @close=${closeSettingsModal}></sp-underlay>
211815
+ <div
211816
+ class="settings-modal"
211817
+ @keydown=${(e20) => {
211818
+ if (e20.key === "Escape")
211819
+ closeSettingsModal();
211820
+ }}
211821
+ >
211822
+ <div class="settings-modal-header">
211823
+ <h2 class="settings-modal-title">Settings</h2>
211824
+ <sp-action-button quiet size="s" @click=${closeSettingsModal} title="Close">
211825
+ <sp-icon-close slot="icon"></sp-icon-close>
211826
+ </sp-action-button>
211827
+ </div>
211828
+ <div class="settings-modal-body">
211829
+ <nav class="settings-modal-nav">
211830
+ ${sections.map((s2) => html`
211831
+ <button
211832
+ class="settings-nav-item${_activeSection === s2.key ? " active" : ""}"
211833
+ @click=${() => onNavClick(s2.key)}
211834
+ >
211835
+ ${s2.label}
211836
+ </button>
211837
+ `)}
211838
+ </nav>
211839
+ <div
211840
+ class="settings-modal-content"
211841
+ ${ref2((el) => {
211842
+ if (el)
211843
+ requestAnimationFrame(() => renderActiveSection());
211844
+ })}
211845
+ ></div>
211846
+ </div>
211847
+ </div>
211848
+ `;
211849
+ render2(tpl, _host);
211850
+ }
211851
+ function renderActiveSection() {
211852
+ if (!_host)
211853
+ return;
211854
+ const container = _host.querySelector(".settings-modal-content");
211855
+ if (!container)
211856
+ return;
211857
+ switch (_activeSection) {
211858
+ case "general":
211859
+ renderGeneralSettings(container);
211860
+ break;
211861
+ case "head":
211862
+ renderHeadEditor(container);
211863
+ break;
211864
+ case "cssVars":
211865
+ renderCssVarsEditor(container);
211866
+ break;
211867
+ case "definitions":
211868
+ renderDefsEditor(container);
211869
+ break;
211870
+ case "contentTypes":
211871
+ renderContentTypesEditor(container);
211872
+ break;
211873
+ }
211874
+ }
211875
+
211876
+ // src/panels/activity-bar.js
211498
211877
  var _scope3 = null;
211499
211878
  function mount4() {
211500
211879
  _scope3 = effectScope();
@@ -211576,6 +211955,17 @@ function renderActivityBar() {
211576
211955
  </sp-tab>
211577
211956
  `)}
211578
211957
  </sp-tabs>
211958
+ <div style="margin-top:auto;padding:8px 0;display:flex;justify-content:center">
211959
+ <sp-action-button
211960
+ quiet
211961
+ size="m"
211962
+ title="Settings"
211963
+ aria-label="Settings"
211964
+ @click=${() => openSettingsModal()}
211965
+ >
211966
+ <sp-icon-gears slot="icon"></sp-icon-gears>
211967
+ </sp-action-button>
211968
+ </div>
211579
211969
  `;
211580
211970
  render2(tpl, activityBar);
211581
211971
  }
@@ -211614,6 +212004,9 @@ function tbBtnTpl(label4, onClick, iconTag) {
211614
212004
  function mount5(rootEl, ctx) {
211615
212005
  _rootEl = rootEl;
211616
212006
  _ctx11 = ctx;
212007
+ if (globalThis.__jxPlatform?.windowControls) {
212008
+ rootEl.classList.add("electrobun-webkit-app-region-drag");
212009
+ }
211617
212010
  _scope4 = effectScope();
211618
212011
  _scope4.run(() => {
211619
212012
  effect(() => {
@@ -211715,17 +212108,22 @@ function toolbarTemplate() {
211715
212108
  { key: "design", label: "Design", iconTag: "sp-icon-artboard" },
211716
212109
  { key: "preview", label: "Preview", iconTag: "sp-icon-preview" },
211717
212110
  { key: "source", label: "Code", iconTag: "sp-icon-code" },
211718
- { key: "settings", label: "Settings", iconTag: "sp-icon-gears" }
212111
+ { key: "stylebook", label: "Stylebook", iconTag: "sp-icon-brush" }
211719
212112
  ];
212113
+ const isProjectFile = S.documentPath === "project.json";
212114
+ const allowedModes = isProjectFile ? new Set(["stylebook", "source"]) : null;
211720
212115
  const modeSwitcherTpl = html`
211721
212116
  <sp-action-group selects="single" size="s" compact>
211722
212117
  ${modes.map((m3) => html`
211723
212118
  <sp-action-button
211724
212119
  size="s"
211725
212120
  ?selected=${canvasMode === m3.key}
212121
+ ?disabled=${allowedModes && !allowedModes.has(m3.key)}
211726
212122
  @click=${() => {
211727
212123
  if (canvasMode === m3.key)
211728
212124
  return;
212125
+ if (allowedModes && !allowedModes.has(m3.key))
212126
+ return;
211729
212127
  if (S.ui.editingFunction) {
211730
212128
  if (view.functionEditor) {
211731
212129
  view.functionEditor.dispose();
@@ -211736,7 +212134,7 @@ function toolbarTemplate() {
211736
212134
  view.panX = 0;
211737
212135
  view.panY = 0;
211738
212136
  const uiPatch = { editingFunction: null };
211739
- if (m3.key === "settings")
212137
+ if (m3.key === "stylebook")
211740
212138
  uiPatch.rightTab = "style";
211741
212139
  if (m3.key === "manage")
211742
212140
  view.leftTab = "files";
@@ -211750,10 +212148,52 @@ function toolbarTemplate() {
211750
212148
  `)}
211751
212149
  </sp-action-group>
211752
212150
  `;
212151
+ const windowControls = globalThis.__jxPlatform?.windowControls;
212152
+ const csdTpl = windowControls ? html`
212153
+ <sp-action-group class="window-controls" size="s">
212154
+ <sp-action-button
212155
+ quiet
212156
+ size="s"
212157
+ title="Minimize"
212158
+ @click=${() => windowControls.minimize()}
212159
+ >
212160
+ <sp-icon-remove slot="icon"></sp-icon-remove>
212161
+ </sp-action-button>
212162
+ <sp-action-button
212163
+ quiet
212164
+ size="s"
212165
+ title="Maximize"
212166
+ @click=${() => windowControls.maximize()}
212167
+ >
212168
+ <sp-icon-full-screen slot="icon"></sp-icon-full-screen>
212169
+ </sp-action-button>
212170
+ <sp-action-button
212171
+ quiet
212172
+ size="s"
212173
+ title="Close"
212174
+ class="csd-close"
212175
+ @click=${() => windowControls.close()}
212176
+ >
212177
+ <sp-icon-close slot="icon"></sp-icon-close>
212178
+ </sp-action-button>
212179
+ </sp-action-group>
212180
+ ` : nothing;
212181
+ const recentProjects = getRecentProjects();
212182
+ const recentProjectsTpl = recentProjects.length ? html`
212183
+ <overlay-trigger placement="bottom-start">
212184
+ <sp-action-button size="s" slot="trigger" title="Recent projects">
212185
+ <sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>
212186
+ </sp-action-button>
212187
+ <sp-popover slot="click-content" tip>
212188
+ <sp-menu @change=${(e20) => _ctx11.openRecentProject(e20.target.value)}>
212189
+ ${recentProjects.map((p2) => html`<sp-menu-item value=${p2.root}>${p2.name}</sp-menu-item>`)}
212190
+ </sp-menu>
212191
+ </sp-popover>
212192
+ </overlay-trigger>
212193
+ ` : nothing;
211753
212194
  return html`
211754
212195
  <sp-action-group compact size="s">
211755
- ${tbBtnTpl("Open Project", _ctx11.openProject, "sp-icon-folder-open")}
211756
- ${tbBtnTpl("Open File", _ctx11.openFile, "sp-icon-document")}
212196
+ ${tbBtnTpl("Open Project", _ctx11.openProject, "sp-icon-folder-open")} ${recentProjectsTpl}
211757
212197
  ${tbBtnTpl("Save", _ctx11.saveFile, "sp-icon-save-floppy")}
211758
212198
  </sp-action-group>
211759
212199
  <sp-action-group compact size="s">
@@ -211761,14 +212201,12 @@ function toolbarTemplate() {
211761
212201
  ${tbBtnTpl("Redo", () => redo(activeTab.value), "sp-icon-redo")}
211762
212202
  </sp-action-group>
211763
212203
  <div class="tb-spacer"></div>
211764
- ${S.documentPath ? html`<span class="tb-file-title" title=${S.documentPath}
211765
- >${S.documentPath}${S.dirty ? html`<span class="tb-dirty">●</span>` : nothing}</span
211766
- >` : S.fileHandle ? html`<span class="tb-file-title"
211767
- >${S.fileHandle.name}${S.dirty ? html`<span class="tb-dirty">●</span>` : nothing}</span
211768
- >` : nothing}
211769
- ${breadcrumbTpl}
212204
+ <sp-action-button class="tb-search-trigger" size="s" quiet @click=${openQuickSearch}>
212205
+ <sp-icon-search slot="icon"></sp-icon-search>
212206
+ <span class="tb-search-label">Search files… <kbd>⌘P</kbd></span>
212207
+ </sp-action-button>
211770
212208
  <div class="tb-spacer"></div>
211771
- ${togglesTpl} ${modeSwitcherTpl}
212209
+ ${breadcrumbTpl} ${togglesTpl} ${modeSwitcherTpl} ${csdTpl}
211772
212210
  `;
211773
212211
  }
211774
212212
 
@@ -214505,7 +214943,7 @@ function renderStylePanelTemplate(ctx) {
214505
214943
  const tab = activeTab.value;
214506
214944
  if (!tab)
214507
214945
  return html`<div class="empty-state">No document loaded</div>`;
214508
- if (ctx.getCanvasMode() === "settings" && tab.session.ui.stylebookSelection) {
214946
+ if (ctx.getCanvasMode() === "stylebook" && tab.session.ui.stylebookSelection) {
214509
214947
  const node3 = tab.doc.document;
214510
214948
  if (!node3)
214511
214949
  return html`<div class="empty-state">No document loaded</div>`;
@@ -214908,15 +215346,15 @@ function bindableFieldRow(label4, type2, rawValue, onChange, filterFn = null, ex
214908
215346
  const isBound = typeof rawValue === "object" && rawValue !== null && rawValue.$ref;
214909
215347
  const signalDefs = Object.entries(defs).filter(([, d5]) => filterFn ? filterFn(d5) : !d5.$handler && d5.$prototype !== "Function");
214910
215348
  let debounce;
214911
- const onInput = (e20) => {
215349
+ const onInput2 = (e20) => {
214912
215350
  clearTimeout(debounce);
214913
215351
  debounce = setTimeout(() => onChange(e20.target.value), 400);
214914
215352
  };
214915
215353
  const staticVal = isBound ? "" : rawValue ?? "";
214916
- const staticTpl = type2 === "textarea" ? html`<sp-textfield multiline size="s" value=${staticVal} @input=${onInput}></sp-textfield>` : type2 === "checkbox" ? html`<sp-checkbox
215354
+ const staticTpl = type2 === "textarea" ? html`<sp-textfield multiline size="s" value=${staticVal} @input=${onInput2}></sp-textfield>` : type2 === "checkbox" ? html`<sp-checkbox
214917
215355
  ?checked=${!!staticVal}
214918
215356
  @change=${(e20) => onChange(e20.target.checked)}
214919
- ></sp-checkbox>` : html`<sp-textfield size="s" value=${staticVal} @input=${onInput}></sp-textfield>`;
215357
+ ></sp-checkbox>` : html`<sp-textfield size="s" value=${staticVal} @input=${onInput2}></sp-textfield>`;
214920
215358
  const boundTpl = html`
214921
215359
  <sp-picker
214922
215360
  size="s"
@@ -216763,7 +217201,7 @@ function _render() {
216763
217201
  const tab = view.leftTab;
216764
217202
  let content3;
216765
217203
  if (tab === "layers")
216766
- content3 = ctx.getCanvasMode() === "settings" ? renderStylebookLayersTemplate({
217204
+ content3 = ctx.getCanvasMode() === "stylebook" ? renderStylebookLayersTemplate({
216767
217205
  selectStylebookTag,
216768
217206
  stylebookMeta: stylebook_meta_default
216769
217207
  }) : renderLayersTemplate({
@@ -216825,7 +217263,7 @@ function _render() {
216825
217263
  else
216826
217264
  content3 = nothing;
216827
217265
  render2(html`<div class="panel-body">${content3}</div>`, leftPanel);
216828
- if (tab === "layers" && ctx.getCanvasMode() !== "settings")
217266
+ if (tab === "layers" && ctx.getCanvasMode() !== "stylebook")
216829
217267
  ctx.registerLayersDnD();
216830
217268
  else if (tab === "imports") {} else if (tab === "blocks") {
216831
217269
  ctx.registerElementsDnD();
@@ -216840,10 +217278,10 @@ function _render() {
216840
217278
  // src/panels/tab-strip.js
216841
217279
  init_reactivity();
216842
217280
  init_workspace();
216843
- var _host = null;
217281
+ var _host2 = null;
216844
217282
  var _scope7 = null;
216845
217283
  function mount8(host2) {
216846
- _host = host2;
217284
+ _host2 = host2;
216847
217285
  _scope7 = effectScope();
216848
217286
  _scope7.run(() => {
216849
217287
  effect(() => {
@@ -216858,10 +217296,10 @@ function mount8(host2) {
216858
217296
  });
216859
217297
  }
216860
217298
  function render10() {
216861
- if (!_host)
217299
+ if (!_host2)
216862
217300
  return;
216863
217301
  if (workspace.tabOrder.length <= 1) {
216864
- render2(nothing, _host);
217302
+ render2(nothing, _host2);
216865
217303
  return;
216866
217304
  }
216867
217305
  render2(html`
@@ -216900,7 +217338,7 @@ function render10() {
216900
217338
  `;
216901
217339
  })}
216902
217340
  </div>
216903
- `, _host);
217341
+ `, _host2);
216904
217342
  }
216905
217343
  function tabLabel(tab) {
216906
217344
  const path2 = tab.documentPath;
@@ -217056,7 +217494,7 @@ mount5(toolbarEl, {
217056
217494
  navigateBack: () => navigateBack(),
217057
217495
  closeFunctionEditor: () => closeFunctionEditor(),
217058
217496
  openProject: () => openProject2(),
217059
- openFile: () => openFile(),
217497
+ openRecentProject: (root3) => openRecentProject(root3),
217060
217498
  saveFile: () => saveFile(),
217061
217499
  parseMediaEntries,
217062
217500
  getCanvasMode,
@@ -217064,6 +217502,7 @@ mount5(toolbarEl, {
217064
217502
  renderCanvas: () => renderCanvas(),
217065
217503
  safeRenderRightPanel: () => safeRenderRightPanel()
217066
217504
  });
217505
+ initQuickSearch();
217067
217506
  mount8(document.querySelector("#tab-strip"));
217068
217507
  mount3({
217069
217508
  getCanvasMode,
@@ -217284,6 +217723,51 @@ function openProject2() {
217284
217723
  renderLeftPanel
217285
217724
  });
217286
217725
  }
217726
+ async function openRecentProject(root3) {
217727
+ try {
217728
+ const platform4 = getPlatform();
217729
+ platform4.projectRoot = root3;
217730
+ const content3 = await platform4.readFile("project.json");
217731
+ const config = JSON.parse(content3);
217732
+ replaceAllTabs({ id: "initial", document: { tagName: "div", children: [] } });
217733
+ setProjectState({
217734
+ ...projectState,
217735
+ projectRoot: root3,
217736
+ isSiteProject: true,
217737
+ projectConfig: config,
217738
+ name: config.name || root3.split("/").pop(),
217739
+ dirs: new Map,
217740
+ expanded: new Set,
217741
+ selectedPath: null,
217742
+ searchQuery: ""
217743
+ });
217744
+ await loadDirectory(".");
217745
+ await loadComponentRegistry();
217746
+ const conventionalDirs = [
217747
+ "pages",
217748
+ "layouts",
217749
+ "components",
217750
+ "content",
217751
+ "data",
217752
+ "public",
217753
+ "styles"
217754
+ ];
217755
+ const entries2 = projectState.dirs.get(".") || [];
217756
+ for (const e20 of entries2) {
217757
+ if (e20.type === "directory" && conventionalDirs.includes(e20.name)) {
217758
+ projectState.expanded.add(e20.path || e20.name);
217759
+ await loadDirectory(e20.path || e20.name);
217760
+ }
217761
+ }
217762
+ addRecentProject(projectState.name, root3);
217763
+ view.leftTab = "files";
217764
+ renderActivityBar();
217765
+ renderLeftPanel();
217766
+ statusMessage(`Opened project: ${projectState.name}`);
217767
+ } catch (e20) {
217768
+ statusMessage(`Error: ${e20.message}`);
217769
+ }
217770
+ }
217287
217771
  function renderFilesTemplate2() {
217288
217772
  return renderFilesTemplate({ openProject: openProject2, openFileFromTree, renderLeftPanel });
217289
217773
  }
@@ -217340,5 +217824,5 @@ effect(() => {
217340
217824
  scheduleAutosave();
217341
217825
  });
217342
217826
 
217343
- //# debugId=E697E6D3C357567164756E2164756E21
217827
+ //# debugId=2C5B0A9299ACFF5964756E2164756E21
217344
217828
  //# sourceMappingURL=studio.js.map