@jxsuite/studio 0.28.2 → 0.28.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
package/src/files/files.ts
CHANGED
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { html, nothing } from "lit-html";
|
|
11
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
11
12
|
import { classMap } from "lit-html/directives/class-map.js";
|
|
12
13
|
import { ref } from "lit-html/directives/ref.js";
|
|
13
|
-
import { renderPopover,
|
|
14
|
-
import { createState, projectState,
|
|
14
|
+
import { renderPopover, showConfirmDialog, showDialog } from "../ui/layers";
|
|
15
|
+
import { createState, projectState, requireProjectState, setProjectState } from "../store";
|
|
15
16
|
import { getPlatform } from "../platform";
|
|
16
17
|
import { statusMessage } from "../panels/statusbar";
|
|
17
18
|
import { loadComponentRegistry } from "./components";
|
|
@@ -22,38 +23,44 @@ import {
|
|
|
22
23
|
} from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
23
24
|
import { combine } from "@atlaskit/pragmatic-drag-and-drop/combine";
|
|
24
25
|
import {
|
|
25
|
-
workspace,
|
|
26
|
-
openTab,
|
|
27
26
|
activateTab,
|
|
27
|
+
activeTab,
|
|
28
|
+
openTab,
|
|
28
29
|
renameTab,
|
|
29
30
|
replaceAllTabs,
|
|
30
|
-
|
|
31
|
+
workspace,
|
|
31
32
|
} from "../workspace/workspace";
|
|
32
33
|
import { parseSourceForPath, serializeDocument } from "./file-ops";
|
|
33
34
|
import {
|
|
35
|
+
documentExtensions,
|
|
36
|
+
formatForPath,
|
|
34
37
|
loadFormats,
|
|
38
|
+
noFormatError,
|
|
35
39
|
refreshFormats,
|
|
36
|
-
formatForPath,
|
|
37
|
-
documentExtensions,
|
|
38
40
|
} from "../format/format-host";
|
|
39
41
|
import { view } from "../view";
|
|
40
42
|
import { addRecentProject, trackRecentFile } from "../recent-projects";
|
|
43
|
+
import type { TemplateResult } from "lit-html";
|
|
44
|
+
import type { StudioState } from "../state.js";
|
|
45
|
+
import type { Tab } from "../tabs/tab.js";
|
|
41
46
|
|
|
42
47
|
// ─── File icon map ────────────────────────────────────────────────────────────
|
|
43
48
|
|
|
44
49
|
const fileIconMap = {
|
|
45
|
-
"sp-icon-
|
|
46
|
-
"sp-icon-folder": html`<sp-icon-folder></sp-icon-folder>`,
|
|
50
|
+
"sp-icon-document": html`<sp-icon-document></sp-icon-document>`,
|
|
47
51
|
"sp-icon-file-code": html`<sp-icon-file-code></sp-icon-file-code>`,
|
|
48
52
|
"sp-icon-file-txt": html`<sp-icon-file-txt></sp-icon-file-txt>`,
|
|
53
|
+
"sp-icon-folder": html`<sp-icon-folder></sp-icon-folder>`,
|
|
54
|
+
"sp-icon-folder-open": html`<sp-icon-folder-open></sp-icon-folder-open>`,
|
|
49
55
|
"sp-icon-image": html`<sp-icon-image></sp-icon-image>`,
|
|
50
|
-
|
|
51
|
-
} as Record<string, import("lit-html").TemplateResult>;
|
|
56
|
+
} as Record<string, TemplateResult>;
|
|
52
57
|
|
|
53
58
|
// ─── File management ──────────────────────────────────────────────────────────
|
|
54
59
|
|
|
55
60
|
export async function loadDirectory(dirPath: string) {
|
|
56
|
-
if (!projectState)
|
|
61
|
+
if (!projectState) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
57
64
|
try {
|
|
58
65
|
const platform = getPlatform();
|
|
59
66
|
const entries = await platform.listDirectory(dirPath);
|
|
@@ -68,23 +75,25 @@ export async function loadProject() {
|
|
|
68
75
|
try {
|
|
69
76
|
const platform = getPlatform();
|
|
70
77
|
const result = await platform.probeRootProject();
|
|
71
|
-
if (!result)
|
|
78
|
+
if (!result) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
72
81
|
const { meta, info } = result;
|
|
73
82
|
|
|
74
83
|
refreshFormats();
|
|
75
84
|
void loadFormats();
|
|
76
85
|
|
|
77
86
|
setProjectState({
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
projectRoot: ".",
|
|
87
|
+
dirs: new Map(),
|
|
88
|
+
expanded: new Set(),
|
|
81
89
|
isSiteProject: info.isSiteProject,
|
|
90
|
+
name: info.isSiteProject ? info.projectConfig?.name || meta.name : meta.name,
|
|
82
91
|
projectConfig: (info.isSiteProject ? info.projectConfig : null) || null,
|
|
83
92
|
projectDirs: info.directories || [],
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
selectedPath: null,
|
|
93
|
+
projectRoot: ".",
|
|
94
|
+
root: meta.root,
|
|
87
95
|
searchQuery: "",
|
|
96
|
+
selectedPath: null,
|
|
88
97
|
});
|
|
89
98
|
|
|
90
99
|
if (info.isSiteProject) {
|
|
@@ -118,13 +127,15 @@ export async function openProject({
|
|
|
118
127
|
try {
|
|
119
128
|
const platform = getPlatform();
|
|
120
129
|
const result = await platform.openProject();
|
|
121
|
-
if (!result)
|
|
130
|
+
if (!result) {
|
|
131
|
+
return;
|
|
132
|
+
} // User cancelled
|
|
122
133
|
|
|
123
134
|
const { config, handle } = result;
|
|
124
135
|
|
|
125
136
|
replaceAllTabs({
|
|
137
|
+
document: { children: [], tagName: "div" },
|
|
126
138
|
id: "initial",
|
|
127
|
-
document: { tagName: "div", children: [] },
|
|
128
139
|
});
|
|
129
140
|
|
|
130
141
|
refreshFormats();
|
|
@@ -132,21 +143,21 @@ export async function openProject({
|
|
|
132
143
|
|
|
133
144
|
setProjectState({
|
|
134
145
|
.../** @type {ProjectState} */ projectState,
|
|
135
|
-
projectRoot: handle.root,
|
|
136
|
-
isSiteProject: true,
|
|
137
|
-
projectConfig: config,
|
|
138
|
-
name: config.name || handle.name,
|
|
139
146
|
dirs: new Map(),
|
|
140
147
|
expanded: new Set(),
|
|
141
|
-
|
|
148
|
+
isSiteProject: true,
|
|
149
|
+
name: config.name || handle.name,
|
|
150
|
+
projectConfig: config,
|
|
151
|
+
projectRoot: handle.root,
|
|
142
152
|
searchQuery: "",
|
|
153
|
+
selectedPath: null,
|
|
143
154
|
});
|
|
144
155
|
|
|
145
156
|
await loadDirectory(".");
|
|
146
157
|
await loadComponentRegistry();
|
|
147
158
|
|
|
148
159
|
// Auto-expand key directories and populate projectDirs for Browse view
|
|
149
|
-
const conventionalDirs = [
|
|
160
|
+
const conventionalDirs = new Set([
|
|
150
161
|
"pages",
|
|
151
162
|
"layouts",
|
|
152
163
|
"components",
|
|
@@ -154,11 +165,11 @@ export async function openProject({
|
|
|
154
165
|
"data",
|
|
155
166
|
"public",
|
|
156
167
|
"styles",
|
|
157
|
-
];
|
|
168
|
+
]);
|
|
158
169
|
const entries = requireProjectState().dirs.get(".") || [];
|
|
159
170
|
const foundDirs = [];
|
|
160
171
|
for (const e of entries) {
|
|
161
|
-
if (e.type === "directory" && conventionalDirs.
|
|
172
|
+
if (e.type === "directory" && conventionalDirs.has(e.name)) {
|
|
162
173
|
foundDirs.push(e.name);
|
|
163
174
|
requireProjectState().expanded.add(e.path || e.name);
|
|
164
175
|
await loadDirectory(e.path || e.name);
|
|
@@ -173,8 +184,8 @@ export async function openProject({
|
|
|
173
184
|
statusMessage(`Opened project: ${requireProjectState().name}`);
|
|
174
185
|
|
|
175
186
|
await openHomePage();
|
|
176
|
-
} catch (
|
|
177
|
-
statusMessage(`Error: ${(
|
|
187
|
+
} catch (error) {
|
|
188
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
178
189
|
}
|
|
179
190
|
}
|
|
180
191
|
|
|
@@ -203,30 +214,36 @@ function fileTypeIconTpl(name: string, type: string) {
|
|
|
203
214
|
} else {
|
|
204
215
|
const ext = name.split(".").pop()?.toLowerCase();
|
|
205
216
|
switch (ext) {
|
|
206
|
-
case "json":
|
|
217
|
+
case "json": {
|
|
207
218
|
tag = "sp-icon-file-code";
|
|
208
219
|
break;
|
|
209
|
-
|
|
220
|
+
}
|
|
221
|
+
case "md": {
|
|
210
222
|
tag = "sp-icon-file-txt";
|
|
211
223
|
break;
|
|
224
|
+
}
|
|
212
225
|
case "js":
|
|
213
|
-
case "ts":
|
|
226
|
+
case "ts": {
|
|
214
227
|
tag = "sp-icon-file-code";
|
|
215
228
|
break;
|
|
216
|
-
|
|
229
|
+
}
|
|
230
|
+
case "css": {
|
|
217
231
|
tag = "sp-icon-file-code";
|
|
218
232
|
break;
|
|
233
|
+
}
|
|
219
234
|
case "png":
|
|
220
235
|
case "jpg":
|
|
221
236
|
case "jpeg":
|
|
222
237
|
case "svg":
|
|
223
238
|
case "webp":
|
|
224
|
-
case "gif":
|
|
239
|
+
case "gif": {
|
|
225
240
|
tag = "sp-icon-image";
|
|
226
241
|
break;
|
|
227
|
-
|
|
242
|
+
}
|
|
243
|
+
default: {
|
|
228
244
|
tag = "sp-icon-document";
|
|
229
245
|
break;
|
|
246
|
+
}
|
|
230
247
|
}
|
|
231
248
|
}
|
|
232
249
|
return fileIconMap[tag] || fileIconMap["sp-icon-document"];
|
|
@@ -287,7 +304,9 @@ export function renderFilesTemplate({
|
|
|
287
304
|
@click=${async () => {
|
|
288
305
|
requireProjectState().dirs.clear();
|
|
289
306
|
await loadDirectory(".");
|
|
290
|
-
for (const dir of requireProjectState().expanded)
|
|
307
|
+
for (const dir of requireProjectState().expanded) {
|
|
308
|
+
await loadDirectory(dir);
|
|
309
|
+
}
|
|
291
310
|
renderLeftPanel();
|
|
292
311
|
}}
|
|
293
312
|
>
|
|
@@ -317,7 +336,7 @@ function renderTreeLevelTemplate(
|
|
|
317
336
|
dirPath: string,
|
|
318
337
|
depth: number,
|
|
319
338
|
ctx: { openFileFn: (path: string) => void; renderLeftPanel: () => void },
|
|
320
|
-
):
|
|
339
|
+
): TemplateResult | TemplateResult[] {
|
|
321
340
|
const entries = requireProjectState().dirs.get(dirPath);
|
|
322
341
|
if (!entries) {
|
|
323
342
|
loadDirectory(dirPath).then(() => ctx.renderLeftPanel());
|
|
@@ -329,9 +348,13 @@ function renderTreeLevelTemplate(
|
|
|
329
348
|
</div>`;
|
|
330
349
|
}
|
|
331
350
|
|
|
332
|
-
const sorted = [...entries].
|
|
333
|
-
if (a.type === "directory" && b.type !== "directory")
|
|
334
|
-
|
|
351
|
+
const sorted = [...entries].toSorted((a, b) => {
|
|
352
|
+
if (a.type === "directory" && b.type !== "directory") {
|
|
353
|
+
return -1;
|
|
354
|
+
}
|
|
355
|
+
if (a.type !== "directory" && b.type === "directory") {
|
|
356
|
+
return 1;
|
|
357
|
+
}
|
|
335
358
|
return a.name.localeCompare(b.name);
|
|
336
359
|
});
|
|
337
360
|
|
|
@@ -358,10 +381,13 @@ function renderTreeLevelTemplate(
|
|
|
358
381
|
@click=${async (e: MouseEvent) => {
|
|
359
382
|
e.stopPropagation();
|
|
360
383
|
if (isDir) {
|
|
361
|
-
if (isExpanded)
|
|
362
|
-
|
|
384
|
+
if (isExpanded) {
|
|
385
|
+
requireProjectState().expanded.delete(entry.path);
|
|
386
|
+
} else {
|
|
363
387
|
requireProjectState().expanded.add(entry.path);
|
|
364
|
-
if (!requireProjectState().dirs.has(entry.path))
|
|
388
|
+
if (!requireProjectState().dirs.has(entry.path)) {
|
|
389
|
+
await loadDirectory(entry.path);
|
|
390
|
+
}
|
|
365
391
|
}
|
|
366
392
|
ctx.renderLeftPanel();
|
|
367
393
|
} else {
|
|
@@ -375,7 +401,7 @@ function renderTreeLevelTemplate(
|
|
|
375
401
|
}}
|
|
376
402
|
>
|
|
377
403
|
${isDir
|
|
378
|
-
? html`<span class="file-tree-toggle">${isExpanded ? "\
|
|
404
|
+
? html`<span class="file-tree-toggle">${isExpanded ? "\u25BC" : "\u25B6"}</span>`
|
|
379
405
|
: html`<span class="file-tree-toggle empty"> </span>`}
|
|
380
406
|
<span class="file-tree-icon">${fileTypeIconTpl(entry.path, entry.type)}</span>
|
|
381
407
|
<span class="file-tree-name">${entry.name}</span>
|
|
@@ -391,52 +417,69 @@ export function setupTreeKeyboard(tree: HTMLElement) {
|
|
|
391
417
|
tree.addEventListener("keydown", (e: KeyboardEvent) => {
|
|
392
418
|
const items = [...tree.querySelectorAll(".file-tree-item")] as HTMLElement[];
|
|
393
419
|
const focused = tree.querySelector(".file-tree-item:focus") as HTMLElement | null;
|
|
394
|
-
if (!focused || items.length === 0)
|
|
420
|
+
if (!focused || items.length === 0) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
395
423
|
|
|
396
424
|
const idx = items.indexOf(focused);
|
|
397
425
|
let handled = true;
|
|
398
426
|
|
|
399
427
|
switch (e.key) {
|
|
400
|
-
case "ArrowDown":
|
|
401
|
-
if (idx < items.length - 1)
|
|
428
|
+
case "ArrowDown": {
|
|
429
|
+
if (idx < items.length - 1) {
|
|
430
|
+
items[idx + 1].focus();
|
|
431
|
+
}
|
|
402
432
|
break;
|
|
403
|
-
|
|
404
|
-
|
|
433
|
+
}
|
|
434
|
+
case "ArrowUp": {
|
|
435
|
+
if (idx > 0) {
|
|
436
|
+
items[idx - 1].focus();
|
|
437
|
+
}
|
|
405
438
|
break;
|
|
406
|
-
|
|
439
|
+
}
|
|
440
|
+
case "ArrowRight": {
|
|
407
441
|
if (focused.dataset.type === "directory") {
|
|
408
442
|
const path = focused.dataset.path as string;
|
|
409
443
|
if (!requireProjectState().expanded.has(path)) {
|
|
410
444
|
requireProjectState().expanded.add(path);
|
|
411
445
|
loadDirectory(path).then(() => {
|
|
412
446
|
const panel = tree.closest(".panel-body");
|
|
413
|
-
if (panel)
|
|
447
|
+
if (panel) {
|
|
414
448
|
(panel.querySelector(".file-tree-item:focus") as HTMLElement | null)?.click();
|
|
449
|
+
}
|
|
415
450
|
});
|
|
416
451
|
}
|
|
417
452
|
}
|
|
418
453
|
break;
|
|
419
|
-
|
|
454
|
+
}
|
|
455
|
+
case "ArrowLeft": {
|
|
420
456
|
if (focused.dataset.type === "directory") {
|
|
421
457
|
const path = focused.dataset.path as string;
|
|
422
458
|
if (requireProjectState().expanded.has(path)) {
|
|
423
459
|
requireProjectState().expanded.delete(path);
|
|
424
|
-
//
|
|
460
|
+
// RenderLeftPanel will be called by the caller who sets up keyboard
|
|
425
461
|
}
|
|
426
462
|
}
|
|
427
463
|
break;
|
|
428
|
-
|
|
464
|
+
}
|
|
465
|
+
case "Enter": {
|
|
429
466
|
focused.click();
|
|
430
467
|
break;
|
|
431
|
-
|
|
468
|
+
}
|
|
469
|
+
default: {
|
|
432
470
|
handled = false;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
if (handled) {
|
|
474
|
+
e.preventDefault();
|
|
433
475
|
}
|
|
434
|
-
if (handled) e.preventDefault();
|
|
435
476
|
});
|
|
436
477
|
|
|
437
478
|
// Set first item focusable
|
|
438
479
|
const first = tree.querySelector(".file-tree-item");
|
|
439
|
-
if (first)
|
|
480
|
+
if (first) {
|
|
481
|
+
first.setAttribute("tabindex", "0");
|
|
482
|
+
}
|
|
440
483
|
}
|
|
441
484
|
|
|
442
485
|
let _fileTreeDndCleanups: (() => void)[] = [];
|
|
@@ -448,25 +491,31 @@ let _fileTreeDndCleanups: (() => void)[] = [];
|
|
|
448
491
|
*/
|
|
449
492
|
export function registerFileTreeDnD({ renderLeftPanel }: { renderLeftPanel: () => void }) {
|
|
450
493
|
// Clean up previous registrations
|
|
451
|
-
for (const fn of _fileTreeDndCleanups)
|
|
494
|
+
for (const fn of _fileTreeDndCleanups) {
|
|
495
|
+
fn();
|
|
496
|
+
}
|
|
452
497
|
_fileTreeDndCleanups = [];
|
|
453
498
|
|
|
454
499
|
requestAnimationFrame(() => {
|
|
455
500
|
const tree = document.querySelector(".file-tree") as HTMLElement | null;
|
|
456
|
-
if (!tree)
|
|
501
|
+
if (!tree) {
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
457
504
|
|
|
458
505
|
const items = tree.querySelectorAll(".file-tree-item") as NodeListOf<HTMLElement>;
|
|
459
506
|
|
|
460
|
-
|
|
461
|
-
const path = row.dataset
|
|
462
|
-
const type = row.dataset
|
|
463
|
-
if (!path)
|
|
507
|
+
for (const row of items) {
|
|
508
|
+
const { path } = row.dataset;
|
|
509
|
+
const { type } = row.dataset;
|
|
510
|
+
if (!path) {
|
|
511
|
+
continue;
|
|
512
|
+
}
|
|
464
513
|
|
|
465
514
|
const cleanups = [
|
|
466
515
|
draggable({
|
|
467
516
|
element: row,
|
|
468
517
|
getInitialData() {
|
|
469
|
-
return {
|
|
518
|
+
return { entryType: type, path, type: "file-tree" };
|
|
470
519
|
},
|
|
471
520
|
onDragStart() {
|
|
472
521
|
row.classList.add("dragging");
|
|
@@ -480,21 +529,34 @@ export function registerFileTreeDnD({ renderLeftPanel }: { renderLeftPanel: () =
|
|
|
480
529
|
if (type === "directory") {
|
|
481
530
|
cleanups.push(
|
|
482
531
|
dropTargetForElements({
|
|
483
|
-
element: row,
|
|
484
532
|
canDrop({ source }) {
|
|
485
|
-
if (source.data.type !== "file-tree")
|
|
533
|
+
if (source.data.type !== "file-tree") {
|
|
534
|
+
return false;
|
|
535
|
+
}
|
|
486
536
|
const srcPath = source.data.path as string;
|
|
487
|
-
if (srcPath === path)
|
|
488
|
-
|
|
537
|
+
if (srcPath === path) {
|
|
538
|
+
return false;
|
|
539
|
+
}
|
|
540
|
+
if (srcPath.startsWith(`${path}/`)) {
|
|
541
|
+
return false;
|
|
542
|
+
}
|
|
489
543
|
const srcParent = parentDir(srcPath);
|
|
490
|
-
if (srcParent === path)
|
|
544
|
+
if (srcParent === path) {
|
|
545
|
+
return false;
|
|
546
|
+
}
|
|
491
547
|
return true;
|
|
492
548
|
},
|
|
493
|
-
|
|
494
|
-
|
|
549
|
+
element: row,
|
|
550
|
+
getData() {
|
|
551
|
+
return { targetDir: path, type: "file-tree-target" };
|
|
495
552
|
},
|
|
496
553
|
onDrag() {
|
|
497
|
-
if (!row.classList.contains("drag-over"))
|
|
554
|
+
if (!row.classList.contains("drag-over")) {
|
|
555
|
+
row.classList.add("drag-over");
|
|
556
|
+
}
|
|
557
|
+
},
|
|
558
|
+
onDragEnter() {
|
|
559
|
+
row.classList.add("drag-over");
|
|
498
560
|
},
|
|
499
561
|
onDragLeave() {
|
|
500
562
|
row.classList.remove("drag-over");
|
|
@@ -502,24 +564,26 @@ export function registerFileTreeDnD({ renderLeftPanel }: { renderLeftPanel: () =
|
|
|
502
564
|
onDrop() {
|
|
503
565
|
row.classList.remove("drag-over");
|
|
504
566
|
},
|
|
505
|
-
getData() {
|
|
506
|
-
return { type: "file-tree-target", targetDir: path };
|
|
507
|
-
},
|
|
508
567
|
}),
|
|
509
568
|
);
|
|
510
569
|
}
|
|
511
570
|
|
|
512
571
|
_fileTreeDndCleanups.push(combine(...cleanups));
|
|
513
|
-
}
|
|
572
|
+
}
|
|
514
573
|
|
|
515
574
|
// Root-level drop target (move to project root)
|
|
516
575
|
const rootCleanup = dropTargetForElements({
|
|
517
|
-
element: tree,
|
|
518
576
|
canDrop({ source }) {
|
|
519
|
-
if (source.data.type !== "file-tree")
|
|
577
|
+
if (source.data.type !== "file-tree") {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
520
580
|
const srcPath = source.data.path as string;
|
|
521
581
|
return parentDir(srcPath) !== ".";
|
|
522
582
|
},
|
|
583
|
+
element: tree,
|
|
584
|
+
getData() {
|
|
585
|
+
return { targetDir: ".", type: "file-tree-target" };
|
|
586
|
+
},
|
|
523
587
|
onDragEnter() {
|
|
524
588
|
tree.classList.add("drag-over-root");
|
|
525
589
|
},
|
|
@@ -529,26 +593,31 @@ export function registerFileTreeDnD({ renderLeftPanel }: { renderLeftPanel: () =
|
|
|
529
593
|
onDrop() {
|
|
530
594
|
tree.classList.remove("drag-over-root");
|
|
531
595
|
},
|
|
532
|
-
getData() {
|
|
533
|
-
return { type: "file-tree-target", targetDir: "." };
|
|
534
|
-
},
|
|
535
596
|
});
|
|
536
597
|
_fileTreeDndCleanups.push(rootCleanup);
|
|
537
598
|
|
|
538
599
|
// Monitor for drop events
|
|
539
600
|
const monitorCleanup = monitorForElements({
|
|
540
601
|
onDrop({ source, location }) {
|
|
541
|
-
const target = location.current.dropTargets
|
|
542
|
-
if (!target)
|
|
543
|
-
|
|
544
|
-
|
|
602
|
+
const [target] = location.current.dropTargets;
|
|
603
|
+
if (!target) {
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (source.data.type !== "file-tree") {
|
|
607
|
+
return;
|
|
608
|
+
}
|
|
609
|
+
if (target.data.type !== "file-tree-target") {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
545
612
|
|
|
546
613
|
const srcPath = source.data.path as string;
|
|
547
614
|
const targetDirPath = target.data.targetDir as string;
|
|
548
615
|
const fileName = srcPath.split("/").pop();
|
|
549
616
|
const newPath = targetDirPath === "." ? fileName : `${targetDirPath}/${fileName}`;
|
|
550
617
|
|
|
551
|
-
if (newPath === srcPath)
|
|
618
|
+
if (newPath === srcPath) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
552
621
|
|
|
553
622
|
moveFileEntry(srcPath, newPath!, renderLeftPanel);
|
|
554
623
|
},
|
|
@@ -573,7 +642,7 @@ async function moveFileEntry(oldPath: string, newPath: string, renderLeftPanel:
|
|
|
573
642
|
for (const [id] of workspace.tabs.entries()) {
|
|
574
643
|
if (id === oldPath) {
|
|
575
644
|
renameTab(oldPath, newPath, newPath);
|
|
576
|
-
} else if (id.startsWith(oldPath
|
|
645
|
+
} else if (id.startsWith(`${oldPath}/`)) {
|
|
577
646
|
const newTabPath = newPath + id.slice(oldPath.length);
|
|
578
647
|
renameTab(id, newTabPath, newTabPath);
|
|
579
648
|
}
|
|
@@ -583,15 +652,19 @@ async function moveFileEntry(oldPath: string, newPath: string, renderLeftPanel:
|
|
|
583
652
|
const oldParent = parentDir(oldPath);
|
|
584
653
|
const newParent = parentDir(newPath);
|
|
585
654
|
await loadDirectory(oldParent);
|
|
586
|
-
if (newParent !== oldParent)
|
|
655
|
+
if (newParent !== oldParent) {
|
|
656
|
+
await loadDirectory(newParent);
|
|
657
|
+
}
|
|
587
658
|
|
|
588
659
|
// Auto-expand target directory
|
|
589
|
-
if (newParent !== ".")
|
|
660
|
+
if (newParent !== ".") {
|
|
661
|
+
requireProjectState().expanded.add(newParent);
|
|
662
|
+
}
|
|
590
663
|
|
|
591
664
|
renderLeftPanel();
|
|
592
665
|
statusMessage(`Moved to ${newPath}`);
|
|
593
|
-
} catch (
|
|
594
|
-
statusMessage(`Error: ${(
|
|
666
|
+
} catch (error) {
|
|
667
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
595
668
|
}
|
|
596
669
|
}
|
|
597
670
|
|
|
@@ -599,7 +672,7 @@ async function moveFileEntry(oldPath: string, newPath: string, renderLeftPanel:
|
|
|
599
672
|
function parentDir(path: string) {
|
|
600
673
|
const normalized = path.replaceAll("\\", "/");
|
|
601
674
|
const lastSlash = normalized.lastIndexOf("/");
|
|
602
|
-
return lastSlash === -1 ? "." : normalized.
|
|
675
|
+
return lastSlash === -1 ? "." : normalized.slice(0, lastSlash);
|
|
603
676
|
}
|
|
604
677
|
|
|
605
678
|
// ─── Context menu ─────────────────────────────────────────────────────────────
|
|
@@ -626,23 +699,23 @@ function showFileContextMenu(
|
|
|
626
699
|
const items = [];
|
|
627
700
|
|
|
628
701
|
if (!isDir) {
|
|
629
|
-
items.push({
|
|
702
|
+
items.push({ action: () => ctx.openFileFn(entry.path), label: "Open" });
|
|
630
703
|
}
|
|
631
704
|
if (isDir) {
|
|
632
705
|
items.push({
|
|
633
|
-
label: "New File\u2026",
|
|
634
706
|
action: () => createNewFile(entry.path, ctx.renderLeftPanel),
|
|
707
|
+
label: "New File\u2026",
|
|
635
708
|
});
|
|
636
709
|
}
|
|
637
710
|
items.push({ label: "\u2014" });
|
|
638
711
|
items.push({
|
|
639
|
-
label: "Rename\u2026",
|
|
640
712
|
action: () => renameFile(entry, ctx.renderLeftPanel),
|
|
713
|
+
label: "Rename\u2026",
|
|
641
714
|
});
|
|
642
715
|
items.push({
|
|
643
|
-
label: "Delete",
|
|
644
716
|
action: () => deleteFile(entry, ctx.renderLeftPanel),
|
|
645
717
|
danger: true,
|
|
718
|
+
label: "Delete",
|
|
646
719
|
});
|
|
647
720
|
|
|
648
721
|
let x = e.clientX,
|
|
@@ -653,13 +726,18 @@ function showFileContextMenu(
|
|
|
653
726
|
open
|
|
654
727
|
style="position:fixed;z-index:10000;left:${x}px;top:${y}px"
|
|
655
728
|
${ref((el) => {
|
|
656
|
-
if (!el)
|
|
729
|
+
if (!el) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
657
732
|
requestAnimationFrame(() => {
|
|
658
733
|
const popover = el as HTMLElement;
|
|
659
734
|
const menuRect = popover.getBoundingClientRect();
|
|
660
|
-
if (x + menuRect.width > window.innerWidth)
|
|
661
|
-
|
|
735
|
+
if (x + menuRect.width > window.innerWidth) {
|
|
736
|
+
x = window.innerWidth - menuRect.width - 4;
|
|
737
|
+
}
|
|
738
|
+
if (y + menuRect.height > window.innerHeight) {
|
|
662
739
|
y = window.innerHeight - menuRect.height - 4;
|
|
740
|
+
}
|
|
663
741
|
popover.style.left = `${x}px`;
|
|
664
742
|
popover.style.top = `${y}px`;
|
|
665
743
|
});
|
|
@@ -691,9 +769,12 @@ function showFileContextMenu(
|
|
|
691
769
|
|
|
692
770
|
// ─── File CRUD ────────────────────────────────────────────────────────────────
|
|
693
771
|
|
|
694
|
-
async function createNewFile(dirPath
|
|
772
|
+
async function createNewFile(dirPath: string, renderLeftPanel: () => void) {
|
|
773
|
+
// oxlint-disable-next-line no-alert -- native prompt is the intended quick-input UX here
|
|
695
774
|
const name = prompt("File name:", "untitled.json");
|
|
696
|
-
if (!name)
|
|
775
|
+
if (!name) {
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
697
778
|
const path = dirPath === "." ? name : `${dirPath}/${name}`;
|
|
698
779
|
await loadFormats();
|
|
699
780
|
const format = formatForPath(name);
|
|
@@ -701,15 +782,15 @@ async function createNewFile(dirPath = ".", renderLeftPanel: () => void) {
|
|
|
701
782
|
format?.studio?.newFileTemplate ??
|
|
702
783
|
(format
|
|
703
784
|
? ""
|
|
704
|
-
: JSON.stringify({
|
|
785
|
+
: JSON.stringify({ children: [{ children: [], tagName: "p" }], tagName: "div" }, null, 2));
|
|
705
786
|
try {
|
|
706
787
|
const platform = getPlatform();
|
|
707
788
|
await platform.writeFile(path, content);
|
|
708
789
|
await loadDirectory(dirPath);
|
|
709
790
|
renderLeftPanel();
|
|
710
791
|
statusMessage(`Created ${path}`);
|
|
711
|
-
} catch (
|
|
712
|
-
statusMessage(`Error: ${(
|
|
792
|
+
} catch (error) {
|
|
793
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
713
794
|
}
|
|
714
795
|
}
|
|
715
796
|
|
|
@@ -719,7 +800,9 @@ function showRenameFileDialog(currentName: string): Promise<string | null> {
|
|
|
719
800
|
return showDialog<string | null>((done) => {
|
|
720
801
|
function confirm() {
|
|
721
802
|
const trimmed = value.trim();
|
|
722
|
-
if (!trimmed)
|
|
803
|
+
if (!trimmed) {
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
723
806
|
done(trimmed);
|
|
724
807
|
}
|
|
725
808
|
const tpl = html`
|
|
@@ -741,18 +824,22 @@ function showRenameFileDialog(currentName: string): Promise<string | null> {
|
|
|
741
824
|
value = (e.target as HTMLInputElement).value || "";
|
|
742
825
|
}}
|
|
743
826
|
@keydown=${(e: KeyboardEvent) => {
|
|
744
|
-
if (e.key === "Enter")
|
|
827
|
+
if (e.key === "Enter") {
|
|
828
|
+
confirm();
|
|
829
|
+
}
|
|
745
830
|
}}
|
|
746
831
|
></sp-textfield>
|
|
747
832
|
</sp-dialog-wrapper>
|
|
748
833
|
`;
|
|
749
834
|
requestAnimationFrame(() => {
|
|
750
|
-
const layer = document.
|
|
835
|
+
const layer = document.querySelector("#layer-dialog");
|
|
751
836
|
const tf = layer?.querySelector("sp-textfield") as HTMLElement | null;
|
|
752
837
|
if (tf) {
|
|
753
838
|
tf.focus();
|
|
754
839
|
const input = tf.shadowRoot?.querySelector("input");
|
|
755
|
-
if (input)
|
|
840
|
+
if (input) {
|
|
841
|
+
input.select();
|
|
842
|
+
}
|
|
756
843
|
}
|
|
757
844
|
});
|
|
758
845
|
return tpl;
|
|
@@ -764,16 +851,18 @@ async function renameFile(
|
|
|
764
851
|
renderLeftPanel: () => void,
|
|
765
852
|
) {
|
|
766
853
|
const newName = await showRenameFileDialog(entry.name);
|
|
767
|
-
if (!newName || newName === entry.name)
|
|
854
|
+
if (!newName || newName === entry.name) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
768
857
|
const entryPath = entry.path.replaceAll("\\", "/");
|
|
769
|
-
const
|
|
770
|
-
? entryPath.
|
|
858
|
+
const parentDirPath = entryPath.includes("/")
|
|
859
|
+
? entryPath.slice(0, entryPath.lastIndexOf("/"))
|
|
771
860
|
: ".";
|
|
772
|
-
const newPath =
|
|
861
|
+
const newPath = parentDirPath === "." ? newName : `${parentDirPath}/${newName}`;
|
|
773
862
|
try {
|
|
774
863
|
const platform = getPlatform();
|
|
775
864
|
await platform.renameFile(entry.path, newPath);
|
|
776
|
-
await loadDirectory(
|
|
865
|
+
await loadDirectory(parentDirPath);
|
|
777
866
|
if (requireProjectState().selectedPath === entry.path) {
|
|
778
867
|
requireProjectState().selectedPath = newPath;
|
|
779
868
|
}
|
|
@@ -782,8 +871,8 @@ async function renameFile(
|
|
|
782
871
|
}
|
|
783
872
|
renderLeftPanel();
|
|
784
873
|
statusMessage(`Renamed to ${newName}`);
|
|
785
|
-
} catch (
|
|
786
|
-
statusMessage(`Error: ${(
|
|
874
|
+
} catch (error) {
|
|
875
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
787
876
|
}
|
|
788
877
|
}
|
|
789
878
|
|
|
@@ -795,20 +884,22 @@ async function deleteFile(
|
|
|
795
884
|
confirmLabel: "Delete",
|
|
796
885
|
destructive: true,
|
|
797
886
|
});
|
|
798
|
-
if (!confirmed)
|
|
887
|
+
if (!confirmed) {
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
799
890
|
try {
|
|
800
891
|
const platform = getPlatform();
|
|
801
892
|
await platform.deleteFile(entry.path);
|
|
802
893
|
const delPath = entry.path.replaceAll("\\", "/");
|
|
803
|
-
const
|
|
804
|
-
await loadDirectory(
|
|
894
|
+
const parentDirPath = delPath.includes("/") ? delPath.slice(0, delPath.lastIndexOf("/")) : ".";
|
|
895
|
+
await loadDirectory(parentDirPath);
|
|
805
896
|
if (requireProjectState().selectedPath === entry.path) {
|
|
806
897
|
requireProjectState().selectedPath = null;
|
|
807
898
|
}
|
|
808
899
|
renderLeftPanel();
|
|
809
900
|
statusMessage(`Deleted ${entry.name}`);
|
|
810
|
-
} catch (
|
|
811
|
-
statusMessage(`Error: ${(
|
|
901
|
+
} catch (error) {
|
|
902
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
812
903
|
}
|
|
813
904
|
}
|
|
814
905
|
|
|
@@ -827,8 +918,8 @@ async function deleteFile(
|
|
|
827
918
|
*/
|
|
828
919
|
export async function openFileFromTree(
|
|
829
920
|
ctx: {
|
|
830
|
-
S:
|
|
831
|
-
commit: (s:
|
|
921
|
+
S: StudioState;
|
|
922
|
+
commit: (s: StudioState) => void;
|
|
832
923
|
render: () => void;
|
|
833
924
|
loadMarkdown: (source: string, handle: unknown) => void;
|
|
834
925
|
},
|
|
@@ -841,35 +932,39 @@ export async function openFileFromTree(
|
|
|
841
932
|
try {
|
|
842
933
|
const tabLike = {
|
|
843
934
|
doc: {
|
|
844
|
-
sourceFormat: formatForPath(ctx.S.documentPath)?.name ?? null,
|
|
845
|
-
mode: ctx.S.mode,
|
|
846
935
|
content: ctx.S.content,
|
|
847
936
|
document: ctx.S.document,
|
|
937
|
+
mode: ctx.S.mode,
|
|
938
|
+
sourceFormat: formatForPath(ctx.S.documentPath)?.name ?? null,
|
|
848
939
|
},
|
|
849
|
-
} as unknown as
|
|
940
|
+
} as unknown as Tab;
|
|
850
941
|
const output = await serializeDocument(tabLike);
|
|
851
942
|
await platform.writeFile(ctx.S.documentPath, output);
|
|
852
|
-
} catch (
|
|
853
|
-
statusMessage(`Save error: ${(
|
|
943
|
+
} catch (error) {
|
|
944
|
+
statusMessage(`Save error: ${errorMessage(error)}`);
|
|
854
945
|
}
|
|
855
946
|
}
|
|
856
947
|
|
|
857
948
|
// Fetch the file
|
|
858
949
|
try {
|
|
859
950
|
const content = await platform.readFile(path);
|
|
860
|
-
if (!content)
|
|
951
|
+
if (!content) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
861
954
|
|
|
862
955
|
if (formatForPath(path)) {
|
|
863
956
|
await ctx.loadMarkdown(content, null);
|
|
864
957
|
ctx.S.documentPath = path;
|
|
865
958
|
ctx.S.dirty = false;
|
|
866
959
|
ctx.commit(ctx.S);
|
|
867
|
-
} else {
|
|
960
|
+
} else if (path.endsWith(".json")) {
|
|
868
961
|
const doc = JSON.parse(content);
|
|
869
962
|
const newS = createState(doc);
|
|
870
963
|
newS.documentPath = path;
|
|
871
964
|
newS.dirty = false;
|
|
872
965
|
ctx.commit(newS);
|
|
966
|
+
} else {
|
|
967
|
+
throw noFormatError(path);
|
|
873
968
|
}
|
|
874
969
|
|
|
875
970
|
// Update tree selection
|
|
@@ -877,8 +972,8 @@ export async function openFileFromTree(
|
|
|
877
972
|
|
|
878
973
|
ctx.render();
|
|
879
974
|
statusMessage(`Opened ${path}`);
|
|
880
|
-
} catch (
|
|
881
|
-
statusMessage(`Error: ${(
|
|
975
|
+
} catch (error) {
|
|
976
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
882
977
|
}
|
|
883
978
|
}
|
|
884
979
|
|
|
@@ -899,17 +994,21 @@ export async function openFileInTab(path: string) {
|
|
|
899
994
|
const platform = getPlatform();
|
|
900
995
|
try {
|
|
901
996
|
const content = await platform.readFile(path);
|
|
902
|
-
if (!content)
|
|
997
|
+
if (!content) {
|
|
998
|
+
return;
|
|
999
|
+
}
|
|
903
1000
|
|
|
904
1001
|
await loadFormats();
|
|
905
1002
|
let document, frontmatter;
|
|
906
1003
|
const format = formatForPath(path);
|
|
907
1004
|
if (format) {
|
|
908
1005
|
const result = await parseSourceForPath(path, content);
|
|
909
|
-
document = result
|
|
910
|
-
frontmatter = result
|
|
911
|
-
} else {
|
|
1006
|
+
({ document } = result);
|
|
1007
|
+
({ frontmatter } = result);
|
|
1008
|
+
} else if (path.endsWith(".json")) {
|
|
912
1009
|
document = JSON.parse(content);
|
|
1010
|
+
} else {
|
|
1011
|
+
throw noFormatError(path);
|
|
913
1012
|
}
|
|
914
1013
|
|
|
915
1014
|
const id = path;
|
|
@@ -921,16 +1020,18 @@ export async function openFileInTab(path: string) {
|
|
|
921
1020
|
sourceFormat: format?.name ?? null,
|
|
922
1021
|
});
|
|
923
1022
|
requireProjectState().selectedPath = path;
|
|
924
|
-
trackRecentFile({
|
|
1023
|
+
trackRecentFile({ name: path.split("/").pop() || path, path });
|
|
925
1024
|
|
|
926
1025
|
if (path === "project.json") {
|
|
927
1026
|
const tab = activeTab.value;
|
|
928
|
-
if (tab)
|
|
1027
|
+
if (tab) {
|
|
1028
|
+
tab.session.ui.canvasMode = "stylebook";
|
|
1029
|
+
}
|
|
929
1030
|
}
|
|
930
1031
|
|
|
931
1032
|
statusMessage(`Opened ${path.split("/").pop()}`);
|
|
932
|
-
} catch (
|
|
933
|
-
statusMessage(`Error: ${(
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
statusMessage(`Error: ${errorMessage(error)}`);
|
|
934
1035
|
}
|
|
935
1036
|
}
|
|
936
1037
|
|
|
@@ -946,13 +1047,15 @@ export async function reloadFileInTab(path: string) {
|
|
|
946
1047
|
const platform = getPlatform();
|
|
947
1048
|
try {
|
|
948
1049
|
const content = await platform.readFile(path);
|
|
949
|
-
if (!content)
|
|
1050
|
+
if (!content) {
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
950
1053
|
await loadFormats();
|
|
951
1054
|
if (formatForPath(path)) {
|
|
952
1055
|
const { document, frontmatter } = await parseSourceForPath(path, content);
|
|
953
1056
|
tab.doc.document = document;
|
|
954
1057
|
tab.doc.content.frontmatter = frontmatter;
|
|
955
|
-
} else {
|
|
1058
|
+
} else if (path.endsWith(".json")) {
|
|
956
1059
|
tab.doc.document = JSON.parse(content);
|
|
957
1060
|
}
|
|
958
1061
|
tab.doc.dirty = false;
|