@jxsuite/studio 0.15.0 → 0.16.1
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 +10211 -10122
- package/dist/studio.js.map +42 -42
- package/package.json +2 -2
- package/src/canvas/canvas-live-render.js +1 -27
- package/src/canvas/canvas-render.js +50 -40
- package/src/files/file-ops.js +4 -2
- package/src/files/files.js +70 -68
- package/src/panels/editors.js +10 -6
- package/src/panels/git-panel.js +17 -15
- package/src/panels/left-panel.js +4 -0
- package/src/panels/panel-events.js +24 -19
- package/src/panels/properties-panel.js +14 -14
- package/src/panels/signals-panel.js +63 -40
- package/src/platforms/devserver.js +10 -0
- package/src/services/code-services.js +4 -4
- package/src/studio.js +18 -4
- package/src/tabs/transact.js +6 -2
- package/src/workspace/workspace.js +44 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
|
32
32
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
|
33
|
-
"@jxsuite/runtime": "^0.
|
|
33
|
+
"@jxsuite/runtime": "^0.15.0",
|
|
34
34
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
35
35
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
36
36
|
"@spectrum-web-components/action-button": "^1.12.1",
|
|
@@ -390,38 +390,12 @@ export async function renderCanvasLive(gen, doc, canvasEl) {
|
|
|
390
390
|
canvasEl.removeAttribute("data-content-mode");
|
|
391
391
|
}
|
|
392
392
|
|
|
393
|
-
// Resolve relative asset URLs through the PAL for environments that can't
|
|
394
|
-
// serve project files natively (e.g. ElectroBun's views:// scheme).
|
|
395
|
-
const platform = /** @type {any} */ (globalThis).__jxPlatform;
|
|
396
|
-
if (platform?.resolveAssetUrl && docBase) {
|
|
397
|
-
const mediaEls = el.querySelectorAll("img[src], video[src], source[src], video[poster]");
|
|
398
|
-
for (const node of mediaEls) {
|
|
399
|
-
for (const attr of ["src", "poster"]) {
|
|
400
|
-
const val = node.getAttribute(attr);
|
|
401
|
-
if (
|
|
402
|
-
val &&
|
|
403
|
-
!val.startsWith("data:") &&
|
|
404
|
-
!val.startsWith("blob:") &&
|
|
405
|
-
!val.startsWith("http")
|
|
406
|
-
) {
|
|
407
|
-
try {
|
|
408
|
-
const resolved = new URL(val, docBase).pathname.replace(/^\//, "");
|
|
409
|
-
const dataUrl = await platform.resolveAssetUrl(resolved);
|
|
410
|
-
if (dataUrl) node.setAttribute(attr, dataUrl);
|
|
411
|
-
} catch {}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
393
|
canvasEl.appendChild(el);
|
|
394
|
+
|
|
418
395
|
if (canvasMode === "design" || canvasMode === "edit") {
|
|
419
|
-
// Custom element connectedCallbacks render children asynchronously —
|
|
420
|
-
// sweep again after they've had a chance to run
|
|
421
396
|
requestAnimationFrame(() => {
|
|
422
397
|
const editingEl = getActiveElement();
|
|
423
398
|
for (const child of canvasEl.querySelectorAll("*")) {
|
|
424
|
-
// Preserve pointer-events on the actively-edited element
|
|
425
399
|
if (view.componentInlineEdit && child === view.componentInlineEdit.el) continue;
|
|
426
400
|
if (editingEl && child === editingEl) continue;
|
|
427
401
|
/** @type {any} */ (child).style.pointerEvents = "none";
|
|
@@ -10,6 +10,7 @@ import * as monaco from "monaco-editor/esm/vs/editor/editor.api.js";
|
|
|
10
10
|
import { canvasWrap, canvasPanels, updateCanvas } from "../store.js";
|
|
11
11
|
import { activeTab } from "../workspace/workspace.js";
|
|
12
12
|
import { view } from "../view.js";
|
|
13
|
+
import { loadMarkdown } from "../files/file-ops.js";
|
|
13
14
|
import {
|
|
14
15
|
canvasPanelTemplate,
|
|
15
16
|
applyTransform,
|
|
@@ -144,6 +145,7 @@ export function renderCanvas() {
|
|
|
144
145
|
// Reset inline style overrides from other modes
|
|
145
146
|
canvasWrap.style.padding = "";
|
|
146
147
|
canvasWrap.style.alignItems = "";
|
|
148
|
+
canvasWrap.style.flexDirection = "";
|
|
147
149
|
canvasWrap.style.display = "";
|
|
148
150
|
canvasWrap.style.overflow = "";
|
|
149
151
|
canvasWrap.style.overflow = "";
|
|
@@ -247,72 +249,79 @@ export function renderCanvas() {
|
|
|
247
249
|
return;
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
// Git diff mode — render original (left) and current (right) side-by-side
|
|
252
|
+
// Git diff mode — render original (left) and current (right) side-by-side on panzoom surface
|
|
251
253
|
if (canvasMode === "git-diff") {
|
|
252
254
|
if (!_ctx.gitDiffState) {
|
|
253
|
-
// Fallback to design mode if diff state is missing
|
|
254
255
|
_ctx.setCanvasMode("design");
|
|
255
256
|
renderCanvas();
|
|
256
257
|
return;
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
if (modeChanged) {
|
|
261
|
+
canvasWrap.style.padding = "0";
|
|
262
|
+
canvasWrap.style.overflow = "hidden";
|
|
263
|
+
}
|
|
261
264
|
|
|
262
265
|
const gitDiffState = _ctx.gitDiffState;
|
|
263
|
-
const
|
|
266
|
+
const panelWidth = 800;
|
|
267
|
+
|
|
268
|
+
const { tpl: origTpl, panel: origPanel } = canvasPanelTemplate(
|
|
264
269
|
"git-diff-original",
|
|
265
270
|
"Original",
|
|
266
271
|
false,
|
|
267
|
-
|
|
272
|
+
panelWidth,
|
|
268
273
|
);
|
|
269
|
-
const { tpl:
|
|
274
|
+
const { tpl: currTpl, panel: currPanel } = canvasPanelTemplate(
|
|
270
275
|
"git-diff-current",
|
|
271
276
|
"Current",
|
|
272
277
|
false,
|
|
273
|
-
|
|
278
|
+
panelWidth,
|
|
274
279
|
);
|
|
275
280
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
<div
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
litRender(
|
|
282
|
+
html`
|
|
283
|
+
<div
|
|
284
|
+
class="panzoom-wrap"
|
|
285
|
+
style="transform-origin:0 0"
|
|
286
|
+
${ref((el) => {
|
|
287
|
+
if (el) view.panzoomWrap = /** @type {HTMLDivElement} */ (el);
|
|
288
|
+
})}
|
|
289
|
+
>
|
|
290
|
+
${origTpl} ${currTpl}
|
|
283
291
|
</div>
|
|
284
|
-
|
|
285
|
-
|
|
292
|
+
`,
|
|
293
|
+
canvasWrap,
|
|
294
|
+
);
|
|
286
295
|
|
|
287
|
-
litRender(diffTpl, canvasWrap);
|
|
288
296
|
canvasPanels.push(/** @type {any} */ (origPanel));
|
|
289
297
|
canvasPanels.push(/** @type {any} */ (currPanel));
|
|
290
298
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
try {
|
|
299
|
+
/** @param {string} content */
|
|
300
|
+
const parseContent = (content) => {
|
|
294
301
|
if (gitDiffState.isMarkdown) {
|
|
295
|
-
|
|
296
|
-
// For now, use a simple placeholder until we have markdown parsing
|
|
297
|
-
originalDoc = {
|
|
298
|
-
tagName: "div",
|
|
299
|
-
children: [{ tagName: "p", textContent: "Original (markdown)" }],
|
|
300
|
-
};
|
|
301
|
-
} else {
|
|
302
|
-
// Parse as JSON
|
|
303
|
-
originalDoc = JSON.parse(gitDiffState.originalContent);
|
|
302
|
+
return loadMarkdown(content).then((r) => r.document);
|
|
304
303
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
304
|
+
return Promise.resolve().then(() => {
|
|
305
|
+
try {
|
|
306
|
+
return JSON.parse(content);
|
|
307
|
+
} catch {
|
|
308
|
+
return { tagName: "div", children: [{ tagName: "p", textContent: "Failed to parse" }] };
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
const featureToggles = S.ui.featureToggles;
|
|
314
|
+
Promise.all([
|
|
315
|
+
parseContent(gitDiffState.originalContent || ""),
|
|
316
|
+
parseContent(gitDiffState.currentContent || ""),
|
|
317
|
+
]).then(([originalDoc, currentDoc]) => {
|
|
318
|
+
renderCanvasIntoPanel(origPanel, new Set(), featureToggles, originalDoc, gitDiffState);
|
|
319
|
+
renderCanvasIntoPanel(currPanel, new Set(), featureToggles, currentDoc, gitDiffState);
|
|
320
|
+
});
|
|
313
321
|
|
|
314
|
-
|
|
315
|
-
|
|
322
|
+
applyTransform();
|
|
323
|
+
if (modeChanged) observeCenterUntilStable();
|
|
324
|
+
renderZoomIndicator();
|
|
316
325
|
return;
|
|
317
326
|
}
|
|
318
327
|
|
|
@@ -498,6 +507,7 @@ function renderCanvasIntoPanel(
|
|
|
498
507
|
} else {
|
|
499
508
|
// Fallback to structural preview
|
|
500
509
|
updateCanvas({ status: "ready", scope: null, error: null });
|
|
510
|
+
panel.canvas.innerHTML = "";
|
|
501
511
|
renderCanvasNode(docToRender, [], panel.canvas, activeBreakpoints, featureToggles);
|
|
502
512
|
}
|
|
503
513
|
registerPanelDnD(panel);
|
package/src/files/file-ops.js
CHANGED
|
@@ -174,9 +174,11 @@ export async function exportFile() {
|
|
|
174
174
|
* @param {import("../tabs/tab.js").Tab} tab
|
|
175
175
|
* @returns {string}
|
|
176
176
|
*/
|
|
177
|
-
function serializeDocument(tab) {
|
|
177
|
+
export function serializeDocument(tab) {
|
|
178
178
|
if (tab.doc.sourceFormat === "md") {
|
|
179
|
-
|
|
179
|
+
const fm = tab.doc.content?.frontmatter || {};
|
|
180
|
+
const fullDoc = { ...fm, children: tab.doc.document.children ?? [] };
|
|
181
|
+
return jxDocToMd(fullDoc);
|
|
180
182
|
}
|
|
181
183
|
if (tab.doc.mode === "content") {
|
|
182
184
|
const mdast = jxToMd(tab.doc.document);
|
package/src/files/files.js
CHANGED
|
@@ -15,7 +15,7 @@ import { createState, projectState, setProjectState } from "../store.js";
|
|
|
15
15
|
import { getPlatform } from "../platform.js";
|
|
16
16
|
import { statusMessage } from "../panels/statusbar.js";
|
|
17
17
|
import { loadComponentRegistry } from "./components.js";
|
|
18
|
-
import { workspace, openTab, activateTab } from "../workspace/workspace.js";
|
|
18
|
+
import { workspace, openTab, activateTab, replaceAllTabs } from "../workspace/workspace.js";
|
|
19
19
|
import { loadMarkdown } from "./file-ops.js";
|
|
20
20
|
import { view } from "../view.js";
|
|
21
21
|
|
|
@@ -92,6 +92,8 @@ export async function openProject({ renderActivityBar, renderLeftPanel }) {
|
|
|
92
92
|
|
|
93
93
|
const { config, handle } = result;
|
|
94
94
|
|
|
95
|
+
replaceAllTabs({ id: "initial", document: { tagName: "div", children: [] } });
|
|
96
|
+
|
|
95
97
|
setProjectState({
|
|
96
98
|
...projectState,
|
|
97
99
|
projectRoot: handle.root,
|
|
@@ -383,83 +385,83 @@ export function setupTreeKeyboard(/** @type {HTMLElement} */ tree) {
|
|
|
383
385
|
// ─── Context menu ─────────────────────────────────────────────────────────────
|
|
384
386
|
|
|
385
387
|
/** @type {HTMLElement | null} */
|
|
386
|
-
let
|
|
388
|
+
let _fileCtxHost = null;
|
|
389
|
+
|
|
390
|
+
function getFileCtxHost() {
|
|
391
|
+
if (!_fileCtxHost) {
|
|
392
|
+
_fileCtxHost = document.createElement("div");
|
|
393
|
+
_fileCtxHost.style.display = "contents";
|
|
394
|
+
(document.querySelector("sp-theme") || document.body).appendChild(_fileCtxHost);
|
|
395
|
+
document.addEventListener("click", dismissFileContextMenu);
|
|
396
|
+
}
|
|
397
|
+
return _fileCtxHost;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function dismissFileContextMenu() {
|
|
401
|
+
const host = _fileCtxHost;
|
|
402
|
+
if (!host) return;
|
|
403
|
+
const popover = host.querySelector("sp-popover");
|
|
404
|
+
if (popover) popover.removeAttribute("open");
|
|
405
|
+
}
|
|
387
406
|
|
|
388
407
|
function showFileContextMenu(
|
|
389
408
|
/** @type {MouseEvent} */ e,
|
|
390
409
|
/** @type {{ name: string; path: string; type: string }} */ entry,
|
|
391
410
|
/** @type {{ openFileFn: (path: string) => void; renderLeftPanel: () => void }} */ ctx,
|
|
392
411
|
) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
fileContextPopover = null;
|
|
396
|
-
}
|
|
397
|
-
|
|
412
|
+
e.preventDefault();
|
|
413
|
+
const host = getFileCtxHost();
|
|
398
414
|
const isDir = entry.type === "directory";
|
|
399
|
-
fileContextPopover = document.createElement("div");
|
|
400
|
-
|
|
401
|
-
const tpl = html`
|
|
402
|
-
<sp-popover
|
|
403
|
-
placement="right-start"
|
|
404
|
-
open
|
|
405
|
-
style="position:fixed;left:${e.clientX}px;top:${e.clientY}px;z-index:9999"
|
|
406
|
-
>
|
|
407
|
-
<sp-menu style="min-width:160px">
|
|
408
|
-
${!isDir
|
|
409
|
-
? html`<sp-menu-item
|
|
410
|
-
@click=${() => {
|
|
411
|
-
closeFileContextMenu();
|
|
412
|
-
ctx.openFileFn(entry.path);
|
|
413
|
-
}}
|
|
414
|
-
>Open</sp-menu-item
|
|
415
|
-
>`
|
|
416
|
-
: nothing}
|
|
417
|
-
${isDir
|
|
418
|
-
? html`<sp-menu-item
|
|
419
|
-
@click=${() => {
|
|
420
|
-
closeFileContextMenu();
|
|
421
|
-
createNewFile(entry.path, ctx.renderLeftPanel);
|
|
422
|
-
}}
|
|
423
|
-
>New File…</sp-menu-item
|
|
424
|
-
>`
|
|
425
|
-
: nothing}
|
|
426
|
-
<sp-menu-divider></sp-menu-divider>
|
|
427
|
-
<sp-menu-item
|
|
428
|
-
@click=${() => {
|
|
429
|
-
closeFileContextMenu();
|
|
430
|
-
renameFile(entry, ctx.renderLeftPanel);
|
|
431
|
-
}}
|
|
432
|
-
>Rename…</sp-menu-item
|
|
433
|
-
>
|
|
434
|
-
<sp-menu-item
|
|
435
|
-
style="color:var(--danger)"
|
|
436
|
-
@click=${() => {
|
|
437
|
-
closeFileContextMenu();
|
|
438
|
-
deleteFile(entry, ctx.renderLeftPanel);
|
|
439
|
-
}}
|
|
440
|
-
>Delete</sp-menu-item
|
|
441
|
-
>
|
|
442
|
-
</sp-menu>
|
|
443
|
-
</sp-popover>
|
|
444
|
-
`;
|
|
445
|
-
|
|
446
|
-
litRender(tpl, fileContextPopover);
|
|
447
|
-
document.body.appendChild(fileContextPopover);
|
|
448
415
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
closeFileContextMenu();
|
|
452
|
-
document.removeEventListener("mousedown", closeHandler, true);
|
|
453
|
-
}
|
|
454
|
-
};
|
|
455
|
-
setTimeout(() => document.addEventListener("mousedown", closeHandler, true), 0);
|
|
456
|
-
}
|
|
416
|
+
/** @type {{ label: string; action?: () => void; danger?: boolean }[]} */
|
|
417
|
+
const items = [];
|
|
457
418
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
419
|
+
if (!isDir) {
|
|
420
|
+
items.push({ label: "Open", action: () => ctx.openFileFn(entry.path) });
|
|
421
|
+
}
|
|
422
|
+
if (isDir) {
|
|
423
|
+
items.push({
|
|
424
|
+
label: "New File\u2026",
|
|
425
|
+
action: () => createNewFile(entry.path, ctx.renderLeftPanel),
|
|
426
|
+
});
|
|
462
427
|
}
|
|
428
|
+
items.push({ label: "\u2014" });
|
|
429
|
+
items.push({ label: "Rename\u2026", action: () => renameFile(entry, ctx.renderLeftPanel) });
|
|
430
|
+
items.push({
|
|
431
|
+
label: "Delete",
|
|
432
|
+
action: () => deleteFile(entry, ctx.renderLeftPanel),
|
|
433
|
+
danger: true,
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
litRender(
|
|
437
|
+
html`<sp-popover style="position:fixed;z-index:10000">
|
|
438
|
+
<sp-menu>
|
|
439
|
+
${items.map((item) =>
|
|
440
|
+
item.label === "\u2014"
|
|
441
|
+
? html`<sp-menu-divider></sp-menu-divider>`
|
|
442
|
+
: html`<sp-menu-item
|
|
443
|
+
style=${item.danger ? "color: var(--danger)" : ""}
|
|
444
|
+
@click=${() => {
|
|
445
|
+
dismissFileContextMenu();
|
|
446
|
+
item.action?.();
|
|
447
|
+
}}
|
|
448
|
+
>${item.label}</sp-menu-item
|
|
449
|
+
>`,
|
|
450
|
+
)}
|
|
451
|
+
</sp-menu>
|
|
452
|
+
</sp-popover>`,
|
|
453
|
+
host,
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
const popover = /** @type {HTMLElement} */ (host.querySelector("sp-popover"));
|
|
457
|
+
popover.setAttribute("open", "");
|
|
458
|
+
const menuRect = popover.getBoundingClientRect();
|
|
459
|
+
let x = e.clientX,
|
|
460
|
+
y = e.clientY;
|
|
461
|
+
if (x + menuRect.width > window.innerWidth) x = window.innerWidth - menuRect.width - 4;
|
|
462
|
+
if (y + menuRect.height > window.innerHeight) y = window.innerHeight - menuRect.height - 4;
|
|
463
|
+
popover.style.left = `${x}px`;
|
|
464
|
+
popover.style.top = `${y}px`;
|
|
463
465
|
}
|
|
464
466
|
|
|
465
467
|
// ─── File CRUD ────────────────────────────────────────────────────────────────
|
package/src/panels/editors.js
CHANGED
|
@@ -57,6 +57,8 @@ export function renderFunctionEditor() {
|
|
|
57
57
|
|
|
58
58
|
litRender(nothing, canvasWrap);
|
|
59
59
|
canvasWrap.style.padding = "0";
|
|
60
|
+
canvasWrap.style.flexDirection = "column";
|
|
61
|
+
canvasWrap.style.alignItems = "stretch";
|
|
60
62
|
|
|
61
63
|
// Toolbar breadcrumb handles context display — re-render it
|
|
62
64
|
renderOnly("toolbar");
|
|
@@ -65,12 +67,14 @@ export function renderFunctionEditor() {
|
|
|
65
67
|
/** @type {HTMLDivElement | null} */
|
|
66
68
|
let editorContainer = null;
|
|
67
69
|
litRender(
|
|
68
|
-
html`<div
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
html`<div class="source-wrap">
|
|
71
|
+
<div
|
|
72
|
+
class="source-editor"
|
|
73
|
+
${ref((el) => {
|
|
74
|
+
if (el) editorContainer = /** @type {HTMLDivElement} */ (el);
|
|
75
|
+
})}
|
|
76
|
+
></div>
|
|
77
|
+
</div>`,
|
|
74
78
|
canvasWrap,
|
|
75
79
|
);
|
|
76
80
|
|
package/src/panels/git-panel.js
CHANGED
|
@@ -7,6 +7,7 @@ import { html, nothing } from "lit-html";
|
|
|
7
7
|
import { live } from "lit-html/directives/live.js";
|
|
8
8
|
import { getPlatform } from "../platform.js";
|
|
9
9
|
import { updateUi, renderOnly } from "../store.js";
|
|
10
|
+
import { activeTab } from "../workspace/workspace.js";
|
|
10
11
|
import { view } from "../view.js";
|
|
11
12
|
|
|
12
13
|
async function refreshGitStatus() {
|
|
@@ -70,7 +71,8 @@ export function renderGitPanel(S, ctx) {
|
|
|
70
71
|
const totalChanges = status?.files?.length || 0;
|
|
71
72
|
|
|
72
73
|
const doCommit = async () => {
|
|
73
|
-
const
|
|
74
|
+
const tab = activeTab.value;
|
|
75
|
+
const msg = tab?.session.ui.gitCommitMessage?.trim();
|
|
74
76
|
if (!msg) return;
|
|
75
77
|
updateUi("gitCommitMessage", "");
|
|
76
78
|
await gitAction("gitCommit", msg);
|
|
@@ -145,11 +147,7 @@ export function renderGitPanel(S, ctx) {
|
|
|
145
147
|
}
|
|
146
148
|
}}
|
|
147
149
|
></sp-textfield>
|
|
148
|
-
<sp-action-button
|
|
149
|
-
class="git-commit-btn"
|
|
150
|
-
@click=${doCommit}
|
|
151
|
-
?disabled=${!S.ui.gitCommitMessage?.trim() || loading}
|
|
152
|
-
>
|
|
150
|
+
<sp-action-button class="git-commit-btn" @click=${doCommit} ?disabled=${loading}>
|
|
153
151
|
<sp-icon-checkmark slot="icon" size="xs"></sp-icon-checkmark>
|
|
154
152
|
Commit
|
|
155
153
|
</sp-action-button>
|
|
@@ -162,29 +160,33 @@ export function renderGitPanel(S, ctx) {
|
|
|
162
160
|
const dir = parts.join("/");
|
|
163
161
|
|
|
164
162
|
const onFileClick = async () => {
|
|
165
|
-
// Only support diff for modified/added files
|
|
166
163
|
if (file.status !== "M" && file.status !== "A") return;
|
|
167
164
|
if (!file.path.endsWith(".md") && !file.path.endsWith(".json")) return;
|
|
168
165
|
|
|
169
166
|
try {
|
|
170
167
|
const plat = getPlatform();
|
|
171
168
|
updateUi("gitLoading", true);
|
|
172
|
-
const originalContent = await plat.gitShow({ path: file.path, ref: "HEAD" });
|
|
173
169
|
|
|
174
|
-
|
|
175
|
-
|
|
170
|
+
const [originalContent, currentContent] = await Promise.all([
|
|
171
|
+
file.status === "A"
|
|
172
|
+
? Promise.resolve("")
|
|
173
|
+
: plat.gitShow({ path: file.path, ref: "HEAD" }),
|
|
174
|
+
plat.readFile(file.path),
|
|
175
|
+
]);
|
|
176
176
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
updateUi("gitDiffState", {
|
|
177
|
+
const isMarkdown = file.path.endsWith(".md");
|
|
178
|
+
const diffState = {
|
|
180
179
|
filePath: file.path,
|
|
181
180
|
originalContent,
|
|
181
|
+
currentContent,
|
|
182
182
|
isMarkdown,
|
|
183
183
|
fileStatus: file.status,
|
|
184
|
-
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
updateUi("gitDiffState", diffState);
|
|
185
187
|
|
|
186
|
-
// Trigger diff mode via context
|
|
187
188
|
if (ctx?.setCanvasMode) {
|
|
189
|
+
if (ctx.setGitDiffState) ctx.setGitDiffState(diffState);
|
|
188
190
|
ctx.setCanvasMode("git-diff");
|
|
189
191
|
}
|
|
190
192
|
} catch (/** @type {any} */ e) {
|
package/src/panels/left-panel.js
CHANGED
|
@@ -40,6 +40,7 @@ import { selectStylebookTag, stylebookMeta } from "./stylebook-panel.js";
|
|
|
40
40
|
* registerElementsDnD: () => void;
|
|
41
41
|
* registerComponentsDnD: () => void;
|
|
42
42
|
* setupTreeKeyboard: (tree: HTMLElement) => void;
|
|
43
|
+
* setGitDiffState: (state: any) => void;
|
|
43
44
|
* }} LeftPanelCtx
|
|
44
45
|
*/
|
|
45
46
|
|
|
@@ -69,6 +70,9 @@ export function mount(ctx) {
|
|
|
69
70
|
void tab.doc.mode;
|
|
70
71
|
void tab.session.selection;
|
|
71
72
|
void tab.session.ui.settingsTab;
|
|
73
|
+
void tab.session.ui.gitStatus;
|
|
74
|
+
void tab.session.ui.gitLoading;
|
|
75
|
+
void tab.session.ui.gitError;
|
|
72
76
|
render();
|
|
73
77
|
});
|
|
74
78
|
});
|
|
@@ -81,12 +81,14 @@ export function registerPanelEvents(panel) {
|
|
|
81
81
|
document.elementsFromPoint(e.clientX, e.clientY),
|
|
82
82
|
);
|
|
83
83
|
|
|
84
|
+
if (!tab) return;
|
|
85
|
+
|
|
84
86
|
for (const el of elements) {
|
|
85
87
|
if (canvas.contains(el) && el !== canvas) {
|
|
86
88
|
// Layout element clicked — show layout info instead of selecting in page doc
|
|
87
89
|
if (layoutElements.has(el)) {
|
|
88
90
|
view.layoutSelection = { el, layoutPath: activeLayoutPath };
|
|
89
|
-
|
|
91
|
+
tab.session.selection = null;
|
|
90
92
|
renderOnly("rightPanel");
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
@@ -94,35 +96,35 @@ export function registerPanelEvents(panel) {
|
|
|
94
96
|
|
|
95
97
|
const originalPath = elToPath.get(el);
|
|
96
98
|
if (originalPath) {
|
|
97
|
-
let path = bubbleInlinePath(tab
|
|
99
|
+
let path = bubbleInlinePath(tab.doc.document, originalPath);
|
|
98
100
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
99
101
|
|
|
100
102
|
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
101
103
|
|
|
102
104
|
if (
|
|
103
|
-
pathsEqual(path, tab
|
|
105
|
+
pathsEqual(path, tab.session.selection) &&
|
|
104
106
|
isEditableBlock(resolvedEl) &&
|
|
105
|
-
(canvasMode === "edit" || tab
|
|
107
|
+
(canvasMode === "edit" || tab.doc.mode === "content")
|
|
106
108
|
) {
|
|
107
|
-
|
|
109
|
+
tab.session.ui.activeMedia = newMedia;
|
|
108
110
|
_ctx.enterInlineEdit(resolvedEl, path);
|
|
109
111
|
return;
|
|
110
112
|
}
|
|
111
113
|
|
|
112
|
-
if (canvasMode === "design" && tab
|
|
114
|
+
if (canvasMode === "design" && tab.doc.mode !== "content") {
|
|
113
115
|
updateUi("pendingInlineEdit", { path, mediaName });
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
tab.session.ui.activeMedia = newMedia;
|
|
117
|
+
tab.session.selection = path;
|
|
116
118
|
return;
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
tab.session.ui.activeMedia = newMedia;
|
|
122
|
+
tab.session.selection = path;
|
|
121
123
|
return;
|
|
122
124
|
}
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
|
-
|
|
127
|
+
tab.session.selection = null;
|
|
126
128
|
},
|
|
127
129
|
opts,
|
|
128
130
|
);
|
|
@@ -149,16 +151,18 @@ export function registerPanelEvents(panel) {
|
|
|
149
151
|
document.elementsFromPoint(e.clientX, e.clientY),
|
|
150
152
|
);
|
|
151
153
|
|
|
154
|
+
if (!tab) return;
|
|
155
|
+
|
|
152
156
|
for (const el of elements) {
|
|
153
157
|
if (canvas.contains(el) && el !== canvas) {
|
|
154
158
|
const originalPath = elToPath.get(el);
|
|
155
159
|
if (originalPath) {
|
|
156
|
-
const path = bubbleInlinePath(tab
|
|
160
|
+
const path = bubbleInlinePath(tab.doc.document, originalPath);
|
|
157
161
|
const resolvedEl = path === originalPath ? el : findCanvasElement(path, canvas) || el;
|
|
158
162
|
if (isEditableBlock(resolvedEl)) {
|
|
159
163
|
const newMedia = mediaName === "base" ? null : (mediaName ?? null);
|
|
160
|
-
|
|
161
|
-
|
|
164
|
+
tab.session.ui.activeMedia = newMedia;
|
|
165
|
+
tab.session.selection = path;
|
|
162
166
|
_ctx.enterInlineEdit(resolvedEl, path);
|
|
163
167
|
return;
|
|
164
168
|
}
|
|
@@ -217,18 +221,19 @@ export function registerPanelEvents(panel) {
|
|
|
217
221
|
return;
|
|
218
222
|
}
|
|
219
223
|
const tab = activeTab.value;
|
|
224
|
+
if (!tab) return;
|
|
220
225
|
const el = withPanelPointerEvents(() => document.elementFromPoint(e.clientX, e.clientY));
|
|
221
226
|
if (el && canvas.contains(el) && el !== canvas) {
|
|
222
227
|
let path = elToPath.get(el);
|
|
223
228
|
if (path) {
|
|
224
|
-
path = bubbleInlinePath(tab
|
|
225
|
-
if (!pathsEqual(path, tab
|
|
226
|
-
|
|
229
|
+
path = bubbleInlinePath(tab.doc.document, path);
|
|
230
|
+
if (!pathsEqual(path, tab.session.hover)) {
|
|
231
|
+
tab.session.hover = path;
|
|
227
232
|
renderOnly("overlays");
|
|
228
233
|
}
|
|
229
234
|
}
|
|
230
|
-
} else if (tab
|
|
231
|
-
|
|
235
|
+
} else if (tab.session.hover) {
|
|
236
|
+
tab.session.hover = null;
|
|
232
237
|
renderOnly("overlays");
|
|
233
238
|
}
|
|
234
239
|
},
|
|
@@ -222,16 +222,16 @@ function renderFrontmatterOnlyPanel() {
|
|
|
222
222
|
fields.push({ field, entry: fieldSchema, value: fm[field] });
|
|
223
223
|
}
|
|
224
224
|
for (const [field, value] of Object.entries(fm)) {
|
|
225
|
-
if (
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
}
|
|
225
|
+
if (schemaProps[field] || field.startsWith("$")) continue;
|
|
226
|
+
fields.push({
|
|
227
|
+
field,
|
|
228
|
+
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
229
|
+
value,
|
|
230
|
+
});
|
|
232
231
|
}
|
|
233
232
|
} else {
|
|
234
233
|
for (const [field, value] of Object.entries(fm)) {
|
|
234
|
+
if (field.startsWith("$")) continue;
|
|
235
235
|
fields.push({
|
|
236
236
|
field,
|
|
237
237
|
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
@@ -1656,16 +1656,16 @@ export function renderPropertiesPanelTemplate(ctx) {
|
|
|
1656
1656
|
fields.push({ field, entry: fieldSchema, value: fm[field] });
|
|
1657
1657
|
}
|
|
1658
1658
|
for (const [field, value] of Object.entries(fm)) {
|
|
1659
|
-
if (
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
}
|
|
1659
|
+
if (schemaProps[field] || field.startsWith("$")) continue;
|
|
1660
|
+
fields.push({
|
|
1661
|
+
field,
|
|
1662
|
+
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|
|
1663
|
+
value,
|
|
1664
|
+
});
|
|
1666
1665
|
}
|
|
1667
1666
|
} else {
|
|
1668
1667
|
for (const [field, value] of Object.entries(fm)) {
|
|
1668
|
+
if (field.startsWith("$")) continue;
|
|
1669
1669
|
fields.push({
|
|
1670
1670
|
field,
|
|
1671
1671
|
entry: { type: typeof value === "boolean" ? "boolean" : "string" },
|