@ptengine/inline-editor 0.1.5 → 0.1.7

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.
Files changed (52) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/InlineEditor.d.ts +15 -1
  3. package/dist/InlineEditor.js +226 -52
  4. package/dist/canvas-ui/tree/TreePanel.d.ts +2 -1
  5. package/dist/canvas-ui/tree/TreePanel.js +79 -39
  6. package/dist/chrome/EditorChrome.d.ts +2 -0
  7. package/dist/chrome/EditorChrome.js +1 -1
  8. package/dist/chrome/VersionMenu.d.ts +3 -1
  9. package/dist/chrome/VersionMenu.js +24 -12
  10. package/dist/chrome/topbarPrimitives.d.ts +5 -52
  11. package/dist/chrome/topbarPrimitives.js +13 -31
  12. package/dist/dialogs/ClickActionDialog.js +4 -5
  13. package/dist/dialogs/CodeField.d.ts +4 -0
  14. package/dist/dialogs/CodeField.js +10 -0
  15. package/dist/dialogs/HtmlEditDialog.js +3 -2
  16. package/dist/dialogs/ImageEditDialog.js +53 -15
  17. package/dist/dialogs/ImageSettingDialog.js +3 -3
  18. package/dist/dialogs/InsertElementDialog.js +10 -6
  19. package/dist/dialogs/MediaPreview.d.ts +5 -0
  20. package/dist/dialogs/MediaPreview.js +34 -0
  21. package/dist/dialogs/ReplaceVideoDialog.js +8 -4
  22. package/dist/index.d.ts +1 -0
  23. package/dist/kernel/ai/aiInlineApply.d.ts +4 -3
  24. package/dist/kernel/blocks/BlockOverlay.js +1 -1
  25. package/dist/kernel/blocks/resolveBlocks.d.ts +16 -0
  26. package/dist/kernel/blocks/resolveBlocks.js +39 -6
  27. package/dist/kernel/changelist/ChangeListPanel.js +11 -9
  28. package/dist/kernel/changelist/useChangeList.d.ts +5 -0
  29. package/dist/kernel/changelist/useChangeList.js +85 -10
  30. package/dist/kernel/selection/SelectionOverlay.js +1 -1
  31. package/dist/kernel/selection/SelectionToolbar.js +26 -8
  32. package/dist/kernel/selection/buildRootShelf.d.ts +56 -2
  33. package/dist/kernel/selection/buildRootShelf.js +189 -41
  34. package/dist/kernel/selection/elementAiContext.d.ts +11 -3
  35. package/dist/kernel/selection/resolveAnchors.d.ts +17 -0
  36. package/dist/kernel/selection/resolveAnchors.js +59 -11
  37. package/dist/locales.d.ts +201 -48
  38. package/dist/locales.js +13 -9
  39. package/dist/panels/EditorStatus.d.ts +5 -0
  40. package/dist/panels/EditorStatus.js +4 -0
  41. package/dist/panels/MovePositionControls.d.ts +6 -0
  42. package/dist/panels/MovePositionControls.js +35 -0
  43. package/dist/panels/ScreenshotCard.d.ts +7 -0
  44. package/dist/panels/ScreenshotCard.js +57 -0
  45. package/dist/panels/StylePanel.d.ts +9 -1
  46. package/dist/panels/StylePanel.js +108 -12
  47. package/dist/reviewLocales.d.ts +149 -0
  48. package/dist/reviewLocales.js +143 -0
  49. package/dist/tree/LayersPanel.js +4 -2
  50. package/dist/useInlineEditor.d.ts +1 -0
  51. package/dist/useInlineEditor.js +40 -1
  52. package/package.json +2 -2
@@ -1,6 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { EditorStatus } from './panels/EditorStatus.js';
3
+ import { MovePositionControls } from './panels/MovePositionControls.js';
4
+ import { ScreenshotCard } from './panels/ScreenshotCard.js';
2
5
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
6
  import { useTranslation } from 'react-i18next';
7
+ import { createPortal } from 'react-dom';
4
8
  import { CANVAS_UI_SCOPE } from './canvas-ui/index.js';
5
9
  import { EditorChrome } from './chrome/EditorChrome.js';
6
10
  import { CanvasFrame, TARGET_VIEWPORT_WIDTH, computeZoom } from './kernel/CanvasFrame.js';
@@ -13,17 +17,17 @@ import { ChangeListPanel } from './kernel/changelist/ChangeListPanel.js';
13
17
  import { ChangeListPopover } from './kernel/changelist/ChangeListPopover.js';
14
18
  import { frameRectToProductRect, measureFrameContext } from './kernel/selection/geometry.js';
15
19
  import { buildElementAiContext, extractPageLinks, linksFragment, ROOT_SHELF_LIMIT, SHELF_TREE_MAX_DEPTH, SHELF_TREE_MAX_NODES } from './kernel/selection/elementAiContext.js';
16
- import { getAncestorChain, getDomTree, getElementCapabilities, getElementImages, getOuterHtml, resolveCanvasSelector } from './kernel/canvasCommands.js';
20
+ import { getAncestorChain, getDomTree, getElementCapabilities, getElementImages, getOuterHtml, resolveCanvasSelector, queryElements } from './kernel/canvasCommands.js';
17
21
  import { useAiSelectionContext } from './kernel/selection/useAiSelectionContext.js';
18
22
  import { buildDescendantShelf } from './kernel/selection/buildDescendantShelf.js';
19
- import { buildRootShelf } from './kernel/selection/buildRootShelf.js';
23
+ import { ROOT_SHELF_TREE_PARAMS, buildRootShelf } from './kernel/selection/buildRootShelf.js';
20
24
  import { SHELF_LAYOUT_FIELDS } from './kernel/selection/rowLayout.js';
21
25
  import { backgroundImageAncestorFor, blockContainerFor, resolveInlinePlanAnchors, withImageRows } from './kernel/selection/resolveAnchors.js';
22
26
  import { elementGeometryForAi } from './kernel/selection/elementGeometryForAi.js';
23
27
  import { applyAiChanges } from './kernel/ai/aiInlineApply.js';
24
28
  import { APPLY_CHECK_SETTLE_MS } from './kernel/ai/postApplyCheck.js';
25
29
  import { deleteElement, editHtml, editStyle, insertElement, replaceImage, replaceVideo, setClickAction, setImageAlt } from './kernel/changelist/useChangeList.js';
26
- import { Dialog, DialogContent, DialogHeader, DialogTitle, ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@ptengine/design-components';
30
+ import { Button, Dialog, DialogContent, DialogHeader, DialogTitle, ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@ptengine/design-components';
27
31
  import { pasteModes } from './kernel/selection/toolbarButtons.js';
28
32
  import { ClickActionDialog } from './dialogs/ClickActionDialog.js';
29
33
  import { ImageEditDialog } from './dialogs/ImageEditDialog.js';
@@ -32,6 +36,7 @@ import { ImageSettingDialog } from './dialogs/ImageSettingDialog.js';
32
36
  import { InsertElementDialog } from './dialogs/InsertElementDialog.js';
33
37
  import { ReplaceVideoDialog } from './dialogs/ReplaceVideoDialog.js';
34
38
  import { StylePanel } from './panels/StylePanel.js';
39
+ import { STYLE_SECTIONS } from './panels/styleFields.js';
35
40
  import { HtmlEditDialog } from './dialogs/HtmlEditDialog.js';
36
41
  import { setCode } from './kernel/canvasCommands.js';
37
42
  import { describeCanvasError } from './kernel/canvasErrors.js';
@@ -51,13 +56,15 @@ function imageTargetOf(caps) {
51
56
  return branch === 'background' ? 'background' : undefined;
52
57
  }
53
58
  const AI_ELEMENT_IMAGE_LIMIT = 4;
54
- export function InlineEditor({ url, device, onDeviceChange, title, versionLabel = 'V1', chrome = 'full', onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedLinkUrls, viewportWidthOverride, blockOverlay }) {
59
+ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel = 'V1', chrome = 'full', onExit, onSave, libraryComponents, libraryLoading, onAddVersion, onRenameVersion, removableVersionIds, onRemoveVersion, onPreview, onToggleAi, aiOpen, uploadImage, initialChanges, onHydrated, onAskAi, onPageContextReady, anchors, pageBlocks, onEditorReady, onSessionReady, onAiContextChange, onAiApplyReady, onAiInsertedRemoved, onAiInsertedRestored, trackAiSelection, renderNodeIcon, versions, activeVersionId, versionChanges, onVersionChange, onVersionLeave, onVersionEnter, resolveChangeId, allowedLinkUrls, viewportWidthOverride, blockOverlay }) {
55
60
  const { t } = useTranslation();
56
61
  const canvasAreaRef = useRef(null);
57
62
  const canvasIframeRef = useRef(null);
58
63
  const insertedRemovedRef = useRef(null);
59
64
  const insertedRestoredRef = useRef(null);
65
+ const historyShortcutRef = useRef(() => { });
60
66
  const editor = useInlineEditor({
67
+ onHistoryShortcut: direction => historyShortcutRef.current(direction),
61
68
  onInsertedRemoved: selectors => insertedRemovedRef.current?.(selectors),
62
69
  onInsertedRestored: selectors => insertedRestoredRef.current?.(selectors),
63
70
  url,
@@ -73,11 +80,11 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
73
80
  const [commandError, setCommandError] = useState(null);
74
81
  const [aiApplyNote, setAiApplyNote] = useState(null);
75
82
  const [shot, setShot] = useState(null);
76
- const [shotUrl, setShotUrl] = useState(null);
77
83
  const [panelWidth, setPanelWidth] = useState(0);
78
84
  const [showChangeList, setShowChangeList] = useState(false);
79
85
  const [stylePanelOpen, setStylePanelOpen] = useState(false);
80
86
  const [htmlDialog, setHtmlDialog] = useState({ open: false, seed: '' });
87
+ const [currentStyles, setCurrentStyles] = useState({});
81
88
  const [imageDialog, setImageDialog] = useState({ open: false, seed: '' });
82
89
  const [imageSettingDialog, setImageSettingDialog] = useState({ open: false, seed: '' });
83
90
  const [videoDialog, setVideoDialog] = useState({ open: false, seed: '' });
@@ -94,6 +101,25 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
94
101
  return '';
95
102
  }
96
103
  }, [url]);
104
+ useEffect(() => {
105
+ if (!stylePanelOpen || !selection || !frameProps?.sessionId)
106
+ return;
107
+ let cancelled = false;
108
+ void queryElements(frameProps.sessionId, [selection.selector], {
109
+ fields: STYLE_SECTIONS.flatMap(section => section.fields.map(field => `computed:${field.property}`))
110
+ })
111
+ .then(result => {
112
+ if (!cancelled)
113
+ setCurrentStyles(result.results[0]?.matches[0]?.computed ?? {});
114
+ })
115
+ .catch(error => {
116
+ if (!cancelled)
117
+ setCommandError(describeCanvasError(error));
118
+ });
119
+ return () => {
120
+ cancelled = true;
121
+ };
122
+ }, [stylePanelOpen, selection?.selector, frameProps?.sessionId, changeList.list]);
97
123
  const changeListRef = useRef(changeList);
98
124
  changeListRef.current = changeList;
99
125
  const versionChangesRef = useRef(versionChanges);
@@ -170,6 +196,27 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
170
196
  }
171
197
  void run().catch(e => setCommandError(describeCanvasError(e)));
172
198
  }, [t]);
199
+ historyShortcutRef.current = direction => {
200
+ if (!domReady || textEditing || switchingRef.current)
201
+ return;
202
+ uiWrite(() => enqueue(() => direction === 'undo' ? changeListRef.current.undo() : changeListRef.current.redo()));
203
+ };
204
+ useEffect(() => {
205
+ const onKey = (event) => {
206
+ if (!(event.metaKey || event.ctrlKey) || event.altKey)
207
+ return;
208
+ const target = event.target;
209
+ if (target?.closest('input, textarea, select, [contenteditable="true"], [role="textbox"]'))
210
+ return;
211
+ const key = event.key.toLowerCase();
212
+ if (key !== 'z' && !(key === 'y' && event.ctrlKey))
213
+ return;
214
+ event.preventDefault();
215
+ historyShortcutRef.current(event.shiftKey || key === 'y' ? 'redo' : 'undo');
216
+ };
217
+ document.addEventListener('keydown', onKey);
218
+ return () => document.removeEventListener('keydown', onKey);
219
+ }, []);
173
220
  useEffect(() => {
174
221
  if (!hasVersions || !activeVersionId)
175
222
  return;
@@ -200,18 +247,43 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
200
247
  }, [sessionIdForAssets]);
201
248
  const hydratedSessionRef = useRef(null);
202
249
  const sessionIdForHydrate = frameProps?.sessionId;
203
- const bootChanges = hasVersions && activeVersionId && versionChanges?.[activeVersionId]
204
- ? versionChanges[activeVersionId]
205
- : initialChanges;
250
+ const deviceSnapshot = useRef(null);
251
+ const changeDevice = (next) => {
252
+ if (next === device || switchingRef.current)
253
+ return;
254
+ switchingRef.current = true;
255
+ setSwitching(true);
256
+ void enqueue(async () => {
257
+ await changeListRef.current.whenIdle();
258
+ const live = changeListRef.current.peekList();
259
+ deviceSnapshot.current = { version: activeVersionId, changes: live.map(r => r.change) };
260
+ if (activeVersionId)
261
+ versionCallbacksRef.current.onVersionLeave?.(activeVersionId, live);
262
+ onDeviceChange(next);
263
+ })
264
+ .catch(error => setCommandError(describeCanvasError(error)))
265
+ .finally(() => {
266
+ switchingRef.current = false;
267
+ setSwitching(false);
268
+ });
269
+ };
270
+ const bootChanges = deviceSnapshot.current?.version === activeVersionId && deviceSnapshot.current
271
+ ? deviceSnapshot.current.changes
272
+ : hasVersions && activeVersionId && versionChanges?.[activeVersionId]
273
+ ? versionChanges[activeVersionId]
274
+ : initialChanges;
206
275
  useEffect(() => {
207
276
  if (!domReady || !sessionIdForHydrate)
208
277
  return;
209
278
  if (hydratedSessionRef.current === sessionIdForHydrate)
210
279
  return;
211
280
  hydratedSessionRef.current = sessionIdForHydrate;
281
+ deviceSnapshot.current = null;
212
282
  if (!bootChanges?.length)
213
283
  return;
214
284
  const versionId = hasVersions ? activeVersionId : undefined;
285
+ setSwitching(true);
286
+ switchingRef.current = true;
215
287
  void enqueue(async () => {
216
288
  const { outcome, idMap } = await changeListRef.current.replaceAll(bootChanges, prevIdsFor(versionId, bootChanges));
217
289
  if (versionId) {
@@ -220,7 +292,12 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
220
292
  versionCallbacksRef.current.onVersionEnter?.(versionId, idMap);
221
293
  }
222
294
  onHydrated?.(outcome);
223
- }).catch(e => setCommandError(describeCanvasError(e)));
295
+ })
296
+ .catch(e => setCommandError(describeCanvasError(e)))
297
+ .finally(() => {
298
+ switchingRef.current = false;
299
+ setSwitching(false);
300
+ });
224
301
  }, [domReady, sessionIdForHydrate, bootChanges]);
225
302
  const commandsRef = useRef(commands);
226
303
  commandsRef.current = commands;
@@ -349,7 +426,10 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
349
426
  tag: self.tag,
350
427
  imageTarget: imageTargetOf(selection?.capabilities),
351
428
  images: elementImages?.selector === self.selector ? elementImages.images : undefined,
352
- ancestors: (selection?.chain ?? []).map(n => n.selector).slice().reverse(),
429
+ ancestors: (selection?.chain ?? [])
430
+ .map(n => n.selector)
431
+ .slice()
432
+ .reverse(),
353
433
  outerHtml,
354
434
  descendants: descendantShelfRef.current,
355
435
  hasChildren: (selection?.capabilities?.childElementCount ?? 0) > 0,
@@ -426,6 +506,10 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
426
506
  useEffect(() => {
427
507
  anchorsRef.current = anchors;
428
508
  }, [anchors]);
509
+ const pageBlocksRef = useRef(pageBlocks);
510
+ useEffect(() => {
511
+ pageBlocksRef.current = pageBlocks;
512
+ }, [pageBlocks]);
429
513
  useEffect(() => {
430
514
  rootShelfRef.current = [];
431
515
  rootShelfAncestorsRef.current = new Map();
@@ -442,21 +526,14 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
442
526
  pageContextSessionRef.current = sessionIdForAiContext;
443
527
  let stale = false;
444
528
  void (async () => {
445
- const root = await getDomTree(sessionIdForAiContext, {
446
- rootSelector: 'body',
447
- maxDepth: 16,
448
- maxChildren: 40,
449
- maxNodes: 1500,
450
- visibleOnly: true,
451
- includeRect: true,
452
- fields: [...SHELF_LAYOUT_FIELDS]
453
- });
529
+ const root = await getDomTree(sessionIdForAiContext, ROOT_SHELF_TREE_PARAMS);
454
530
  const { html } = await getOuterHtml(sessionIdForAiContext, 'body');
455
531
  if (stale)
456
532
  return;
457
533
  const { shelf, ancestorsOf } = buildRootShelf(root, {
458
534
  limit: ROOT_SHELF_LIMIT,
459
- viewportWidth: geometryRef.current?.viewportWidth
535
+ viewportWidth: geometryRef.current?.viewportWidth,
536
+ blocks: pageBlocksRef.current
460
537
  });
461
538
  const planAnchors = anchorsRef.current ?? [];
462
539
  const anchorOut = planAnchors.length
@@ -480,7 +557,11 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
480
557
  : null;
481
558
  if (stale)
482
559
  return;
483
- const anchorRows = anchorOut?.shelf ?? [];
560
+ const blockIdBySelector = new Map(shelf.flatMap(r => (r.blockId ? [[r.selector, r.blockId]] : [])));
561
+ const anchorRows = (anchorOut?.shelf ?? []).map(r => {
562
+ const blockId = blockIdBySelector.get(r.selector);
563
+ return blockId && !r.blockId ? { ...r, blockId } : r;
564
+ });
484
565
  const anchorSelectors = new Set(anchorRows.map(r => r.selector));
485
566
  const mergedShelf = [
486
567
  ...anchorRows,
@@ -505,14 +586,22 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
505
586
  for (const { anchor, selector } of anchorBlocks) {
506
587
  try {
507
588
  const { html: blockHtml } = await getOuterHtml(sessionIdForAiContext, selector);
508
- fragments.push({ label: anchor.blockId ?? anchor.label, selector, html: blockHtml });
589
+ fragments.push({
590
+ label: anchor.blockId ?? anchor.label,
591
+ selector,
592
+ html: blockHtml
593
+ });
509
594
  }
510
595
  catch {
511
596
  }
512
597
  const backgroundUp = anchor.blockSelectors?.length
513
598
  ? null
514
599
  : backgroundImageAncestorFor(anchor.selector, mergedShelf, mergedAncestors);
515
- const imageRoots = Array.from(new Set([...(anchor.blockSelectors ?? []), selector, ...(backgroundUp ? [backgroundUp] : [])])).slice(0, 3);
600
+ const imageRoots = Array.from(new Set([
601
+ ...(anchor.blockSelectors ?? []),
602
+ selector,
603
+ ...(backgroundUp ? [backgroundUp] : [])
604
+ ])).slice(0, 3);
516
605
  const seenUrls = new Set(images.map(i => i.url));
517
606
  for (const root of imageRoots) {
518
607
  try {
@@ -572,7 +661,9 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
572
661
  label: anchor.label,
573
662
  selector: shotSelector
574
663
  };
575
- shots.push('url' in shot ? { ...base, url: shot.url } : { ...base, error: shot.error });
664
+ shots.push('url' in shot
665
+ ? { ...base, url: shot.url }
666
+ : { ...base, error: shot.error });
576
667
  }
577
668
  return shots;
578
669
  })
@@ -642,14 +733,23 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
642
733
  identifier: picked.selector,
643
734
  descendants: () => [
644
735
  ...descendantShelfRef.current.map(d => d.selector),
736
+ ...(pageMode ? rootShelfRef.current.map(d => d.selector) : []),
645
737
  ...aiInsertedRef.current
646
738
  ],
647
- ancestors: (selection?.chain ?? []).slice(1).map(n => n.selector)
739
+ ancestors: (selection?.chain ?? []).slice(1).map(n => n.selector),
740
+ ...(pageMode
741
+ ? {
742
+ ancestorsOf: (selector) => rootShelfAncestorsRef.current.get(selector)
743
+ }
744
+ : {})
648
745
  }
649
746
  : {
650
747
  selector: 'body',
651
748
  identifier: 'body',
652
- descendants: () => [...rootShelfRef.current.map(d => d.selector), ...aiInsertedRef.current],
749
+ descendants: () => [
750
+ ...rootShelfRef.current.map(d => d.selector),
751
+ ...aiInsertedRef.current
752
+ ],
653
753
  ancestors: [],
654
754
  ancestorsOf: (selector) => rootShelfAncestorsRef.current.get(selector)
655
755
  };
@@ -683,7 +783,11 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
683
783
  scope: opts?.grant ? { ...scope, grant: opts.grant } : scope,
684
784
  resolve: async (selector) => {
685
785
  const r = await resolveCanvasSelector(sessionIdForAiContext, selector);
686
- return { matched: r.matched, matchCount: r.matchCount, viewportRect: r.viewportRect };
786
+ return {
787
+ matched: r.matched,
788
+ matchCount: r.matchCount,
789
+ viewportRect: r.viewportRect
790
+ };
687
791
  },
688
792
  insertedSelectorsOf: (changeId) => changeListRef.current.peekList().find(r => r.changeId === changeId)
689
793
  ?.insertedSelectors ?? [],
@@ -783,11 +887,40 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
783
887
  return;
784
888
  }
785
889
  if (buttonId === 'replace-image') {
786
- setImageDialog({ open: true, seed: '' });
890
+ void Promise.all([
891
+ getOuterHtml(sessionId, self.selector),
892
+ selection?.capabilities?.hasBackgroundImage && !selection?.capabilities?.isImg
893
+ ? queryElements(sessionId, [self.selector], {
894
+ fields: ['computed:background-image']
895
+ })
896
+ : Promise.resolve(null)
897
+ ])
898
+ .then(([{ html }, facts]) => {
899
+ const node = new DOMParser().parseFromString(html, 'text/html').body
900
+ .firstElementChild;
901
+ const background = facts?.results[0]?.matches[0]?.computed?.['background-image'] ?? '';
902
+ const url = /^url\(["']?(.*?)["']?\)$/.exec(background)?.[1] ?? '';
903
+ setImageDialog({
904
+ open: true,
905
+ seed: facts ? url : (node?.getAttribute('src') ?? '')
906
+ });
907
+ })
908
+ .catch(error => setCommandError(describeCanvasError(error)));
787
909
  return;
788
910
  }
789
911
  if (buttonId === 'replace-video') {
790
- setVideoDialog({ open: true, seed: '' });
912
+ void getOuterHtml(sessionId, self.selector)
913
+ .then(({ html }) => {
914
+ const node = new DOMParser().parseFromString(html, 'text/html').body
915
+ .firstElementChild;
916
+ setVideoDialog({
917
+ open: true,
918
+ seed: node?.getAttribute('src') ??
919
+ node?.querySelector('source')?.getAttribute('src') ??
920
+ ''
921
+ });
922
+ })
923
+ .catch(error => setCommandError(describeCanvasError(error)));
791
924
  return;
792
925
  }
793
926
  if (buttonId === 'insert') {
@@ -795,11 +928,29 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
795
928
  return;
796
929
  }
797
930
  if (buttonId === 'image-setting') {
798
- setImageSettingDialog({ open: true, seed: '' });
931
+ void getOuterHtml(sessionId, self.selector)
932
+ .then(({ html }) => {
933
+ const node = new DOMParser().parseFromString(html, 'text/html').body
934
+ .firstElementChild;
935
+ setImageSettingDialog({
936
+ open: true,
937
+ seed: node?.getAttribute('alt') ?? ''
938
+ });
939
+ })
940
+ .catch(error => setCommandError(describeCanvasError(error)));
799
941
  return;
800
942
  }
801
943
  if (buttonId === 'click-action') {
802
- setActionDialog({ open: true, seed: null });
944
+ const existing = [...changeListRef.current.peekList()]
945
+ .reverse()
946
+ .find(r => r.change.type === 'changeElementAction' &&
947
+ r.change.selector === self.selector);
948
+ setActionDialog({
949
+ open: true,
950
+ seed: existing?.change.type === 'changeElementAction'
951
+ ? existing.change.action
952
+ : null
953
+ });
803
954
  return;
804
955
  }
805
956
  if (buttonId === 'edit-style') {
@@ -822,15 +973,10 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
822
973
  if (buttonId === 'screenshot') {
823
974
  setCommandError(null);
824
975
  setShot(null);
825
- setShotUrl(null);
826
976
  commands
827
977
  .captureSelectedElement(self.selector)
828
978
  .then(async (outcome) => {
829
979
  setShot(outcome);
830
- if (!uploadImage)
831
- return;
832
- const blob = await (await fetch(outcome.dataUrl)).blob();
833
- setShotUrl(await uploadImage(blob));
834
980
  })
835
981
  .catch(error => setCommandError(describeCanvasError(error)));
836
982
  return;
@@ -844,17 +990,16 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
844
990
  }
845
991
  }, [clipboard, commands, depsOf, frameProps?.sessionId, selection, t, uploadImage]);
846
992
  const self = selection?.chain[0];
847
- return (_jsxs("div", { className: `${CANVAS_UI_SCOPE} flex h-full min-h-0 flex-col bg-[#F5F7FC]`, children: [_jsx(EditorChrome, { variant: chrome, title: title, versionLabel: versionLabel, versions: versions, activeVersionId: activeVersionId, versionSwitching: switching, onVersionChange: onVersionChange, changeCount: changeList.list.length, onExit: onExit, onSave: () => {
993
+ return (_jsxs("div", { className: `${CANVAS_UI_SCOPE} flex h-full min-h-0 flex-col bg-[#F5F7FC]`, children: [_jsx(EditorChrome, { slotRight: capturing ? (_jsx("span", { role: "status", className: "text-xs text-muted-foreground", children: t('inlineCanvas.review.generating', '正在生成截图…') })) : undefined, variant: chrome, title: title, versionLabel: versionLabel, versions: versions, activeVersionId: activeVersionId, versionSwitching: switching, onVersionChange: onVersionChange, canUndo: !switching && textEditing === null && changeList.canUndo, canRedo: !switching && textEditing === null && changeList.canRedo, onUndo: () => uiWrite(() => enqueue(() => changeListRef.current.undo())), onRedo: () => uiWrite(() => enqueue(() => changeListRef.current.redo())), changeCount: changeList.list.length, onExit: onExit, onSave: () => {
848
994
  if (switchingRef.current) {
849
995
  setCommandError(t('inlineCanvas.version.switching', '正在切换版本,请稍候'));
850
996
  return;
851
997
  }
852
998
  onSave?.(editor);
853
- }, onAddVersion: onAddVersion, onRenameVersion: onRenameVersion, onPreview: onPreview, onToggleChangeList: () => setShowChangeList(v => !v), changeListOpen: showChangeList, onToggleAi: onToggleAi, aiOpen: aiOpen }), _jsxs("div", { className: "relative flex min-h-0 flex-1", children: [_jsxs(ResizablePanelGroup, { direction: "horizontal", autoSaveId: "inline-editor-panes-v2", className: "flex min-h-0 w-full flex-1", children: [showLeftPanel && (_jsxs(_Fragment, { children: [_jsx(ResizablePanel, { id: "left", order: 1, defaultSize: 18, minSize: 12, maxSize: 40, className: "flex min-h-0 flex-col", children: _jsxs("aside", { "data-testid": "inline-editor-left-panel", className: "inline-editor-left flex h-full min-h-0 flex-col bg-white", children: [!!libraryComponents && (_jsx("div", { className: "flex h-9 shrink-0 items-center gap-1 border-b\n border-[#EBECF0] px-2", children: [
999
+ }, onAddVersion: onAddVersion, onRenameVersion: onRenameVersion, removableVersionIds: removableVersionIds, onRemoveVersion: onRemoveVersion, onPreview: onPreview, onToggleChangeList: () => setShowChangeList(v => !v), changeListOpen: showChangeList, onToggleAi: onToggleAi, aiOpen: aiOpen }), _jsxs("div", { className: "relative flex min-h-0 flex-1", children: [_jsxs(ResizablePanelGroup, { direction: "horizontal", autoSaveId: "inline-editor-panes-v2", className: "flex min-h-0 w-full flex-1", children: [showLeftPanel && (_jsxs(_Fragment, { children: [_jsx(ResizablePanel, { id: "left", order: 1, defaultSize: 18, minSize: 12, maxSize: 40, className: "flex min-h-0 flex-col", children: _jsxs("aside", { "data-testid": "inline-editor-left-panel", className: "inline-editor-left flex h-full min-h-0 flex-col bg-white", children: [!!libraryComponents && (_jsx("div", { className: "flex h-9 shrink-0 items-center gap-1 border-b\n border-[#EBECF0] px-2", children: [
854
1000
  ['layers', '图层'],
855
1001
  ['assets', '组件']
856
- ]
857
- .map(([tab, label]) => (_jsx("button", { type: "button", "data-testid": `left-tab-${tab}`, "aria-pressed": leftTab === tab, onClick: () => setLeftTab(tab), className: `rounded px-2 py-1 text-[12px] ${leftTab === tab
1002
+ ].map(([tab, label]) => (_jsx("button", { type: "button", "data-testid": `left-tab-${tab}`, "aria-pressed": leftTab === tab, onClick: () => setLeftTab(tab), className: `rounded px-2 py-1 text-[12px] ${leftTab === tab
858
1003
  ? 'bg-[#F4F5F7] font-semibold text-[#091E42]'
859
1004
  : 'text-[#6B778C]'}`, children: label }, tab))) })), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: leftTab === 'assets' && libraryComponents ? (_jsx(AssetsPanel, { components: libraryComponents, loading: libraryLoading, canInsert: !!selection?.chain[0] && !switching, onInsert: component => void insertComponent(component) })) : (_jsx(LayersPanel, { tree: tree, selectedSelector: selection?.selector ?? null, onSelect: s => void selectAndReveal(s), onHover: s => void commands.hover(s), onExpand: id => void commands.expandTreeNode(id), onMove: (dragSelector, targetSelector, pos) => uiWrite(() => writes.addMove({
860
1005
  identifier: dragSelector,
@@ -862,25 +1007,54 @@ export function InlineEditor({ url, device, onDeviceChange, title, versionLabel
862
1007
  targetIdentifier: targetSelector,
863
1008
  targetSelector,
864
1009
  mode: moveModeFromDropPos(pos)
865
- })) })) })] }) }), _jsx(ResizableHandle, { withHandle: true })] })), _jsx(ResizablePanel, { id: "canvas", order: 2, minSize: 30, children: _jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col", children: [_jsx(CanvasContextBar, { url: url, device: device, onDeviceChange: onDeviceChange }), _jsx("div", { ref: canvasAreaRef, className: "relative min-h-0 flex-1 overflow-hidden p-3", children: frameProps && (_jsx(CanvasFrame, { url: frameProps.url, sessionId: frameProps.sessionId, device: frameProps.device, panelWidth: panelWidth, targetViewportWidth: targetViewportWidth[frameProps.device], onFrameLoad: () => { }, domReady: domReady, iframeRef: canvasIframeRef }, frameProps.sessionId)) })] }) })] }), !capturing && shot && (_jsxs("section", { "data-testid": "inline-editor-shot", className: "absolute bottom-4 right-4 z-30 w-[260px] rounded-lg border\n border-[#DFE1E6] bg-white p-3\n shadow-[0_8px_24px_rgba(9,30,66,0.14),0_2px_8px_rgba(9,30,66,0.08)]", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between", children: [_jsx("span", { className: "text-[13px] font-semibold text-[#091E42]", children: "\u622A\u56FE" }), _jsx("button", { type: "button", className: "text-[11px] text-[#6B778C]", onClick: () => {
866
- setShot(null);
867
- setShotUrl(null);
868
- }, children: "\u5173\u95ED" })] }), _jsx("img", { src: shot.dataUrl, alt: "", className: "max-h-40 w-full rounded border border-[#EBECF0] object-contain" }), shot.truncated && (_jsx("p", { "data-testid": "inline-editor-shot-truncated", className: "mt-1.5 text-[11px] leading-4 text-[#DE350B]", children: "\u5143\u7D20\u8D85\u51FA\u53EF\u89C6\u533A\uFF0C\u53EA\u622A\u5230\u53EF\u89C1\u90E8\u5206" })), shot.scale < 1 && (_jsxs("p", { "data-testid": "inline-editor-shot-scale", className: "mt-1.5 text-[11px] leading-4 text-[#6B778C]", children: ["\u5F53\u524D\u7A97\u53E3\u8F83\u7A84\uFF0C\u622A\u56FE\u5206\u8FA8\u7387\u7EA6\u4E3A\u539F\u56FE\u7684", ` ${Math.round(shot.scale * 100)}%`] })), shotUrl && (_jsx("a", { "data-testid": "inline-editor-shot-url", href: shotUrl, target: "_blank", rel: "noopener noreferrer", className: "mt-1.5 block truncate text-[11px] text-[#0052CC] underline", children: shotUrl }))] })), _jsx(ChangeListPopover, { open: showChangeList && !capturing, onClose: () => setShowChangeList(false), children: _jsx(ChangeListPanel, { list: changeList.list, onRevert: id => uiWrite(() => writes.revert(id)), onRevertAll: () => uiWrite(() => writes.revertAll()), lastError: changeList.lastError, onHover: s => void commands.hover(s), onSelect: s => void selectAndReveal(s) }) })] }), !capturing && (_jsx(SelectionOverlay, { geometry: geometry.latest, context: overlayContext, selectedLabel: self ? `<${self.tag}>` : undefined })), !capturing && blockOverlay && (_jsx(BlockOverlay, { blocks: blockOverlay.blocks, activeId: blockOverlay.activeId, onHover: blockOverlay.onHover, geometry: geometry.latest, context: overlayContext, scale: editor.mobileFit.scale })), !capturing && commandError && (_jsx("div", { "data-testid": "inline-editor-command-error", className: "fixed z-[2147483003] -translate-x-1/2 rounded bg-[#DE350B] px-2.5\n py-1 text-[11px] leading-4 text-white shadow", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, onClick: () => setCommandError(null), children: commandError })), !capturing && aiApplyNote && (_jsx("div", { "data-testid": "inline-editor-ai-apply-note", className: "fixed z-[2147483003] max-w-[520px] -translate-x-1/2 rounded\n bg-[#253858] px-2.5 py-1 text-[11px] leading-4 text-white shadow", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, onClick: () => setAiApplyNote(null), children: aiApplyNote })), !capturing && move && (_jsxs("div", { "data-testid": "inline-editor-move-bar", className: "fixed z-[2147483003] flex -translate-x-1/2 items-center gap-2\n rounded bg-[#091E42] px-2.5 py-1.5 text-[11px] leading-4 text-white shadow", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [move.target === null ? (_jsx(_Fragment, { children: _jsx("span", { children: move.rejected === 'same-element'
1010
+ })) })) })] }) }), _jsx(ResizableHandle, { withHandle: true })] })), _jsx(ResizablePanel, { id: "canvas", order: 2, minSize: 30, children: _jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col", children: [_jsx(CanvasContextBar, { url: url, device: device, onDeviceChange: changeDevice }), _jsxs("div", { ref: canvasAreaRef, className: "relative min-h-0 flex-1 overflow-hidden p-3", children: [editor.session.state === 'failed' && (_jsxs("div", { role: "alert", "data-testid": "inline-editor-session-failed", className: "absolute inset-3 z-20 flex flex-col items-center justify-center gap-3 rounded-lg bg-white p-6 text-center text-sm", children: [_jsx("p", { className: "font-semibold", children: t('inlineCanvas.review.pageLoadFailed', '目标页面无法加载') }), _jsx("p", { children: [
1011
+ 'NO_EXTENSION',
1012
+ 'EXTENSION_ID_MISSING',
1013
+ 'CONTRACT_MISMATCH'
1014
+ ].includes(editor.session.reason ?? '')
1015
+ ? describeCanvasError(editor.session.reason)
1016
+ : t('inlineCanvas.review.pageLoadHint', '请返回检查页面地址、网站访问权限和网络连接后重试。') }), _jsx("code", { className: "text-xs text-muted-foreground", children: editor.session.reason }), _jsx(Button, { variant: "secondary", onClick: onExit, children: t('inlineCanvas.review.backFromFailed', '返回修改') })] })), frameProps && (_jsx(CanvasFrame, { url: frameProps.url, sessionId: frameProps.sessionId, device: frameProps.device, panelWidth: panelWidth, targetViewportWidth: targetViewportWidth[frameProps.device], onFrameLoad: () => { }, domReady: domReady, iframeRef: canvasIframeRef }, frameProps.sessionId))] })] }) })] }), !capturing && shot && (_jsx(ScreenshotCard, { dataUrl: shot.dataUrl, truncated: shot.truncated, scale: shot.scale, uploadImage: uploadImage, onClose: () => {
1017
+ setShot(null);
1018
+ } }, shot.dataUrl)), _jsx(ChangeListPopover, { open: showChangeList && !capturing, onClose: () => setShowChangeList(false), children: _jsx(ChangeListPanel, { list: changeList.list, onRevert: id => uiWrite(() => writes.revert(id)), onRevertAll: () => uiWrite(() => writes.revertAll()), lastError: changeList.lastError, onHover: s => void commands.hover(s), onSelect: s => void selectAndReveal(s) }) })] }), !capturing && (_jsx(SelectionOverlay, { geometry: geometry.latest, context: overlayContext, selectedLabel: self ? `<${self.tag}>` : undefined })), !capturing && blockOverlay && (_jsx(BlockOverlay, { blocks: blockOverlay.blocks, activeId: blockOverlay.activeId, onHover: blockOverlay.onHover, geometry: geometry.latest, context: overlayContext, scale: editor.mobileFit.scale })), !capturing && commandError && (_jsxs(EditorStatus, { tone: "error", "data-testid": "inline-editor-command-error", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [_jsx("span", { children: commandError }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => setCommandError(null), children: t('inlineCanvas.chrome.close', '关闭') })] })), !capturing && aiApplyNote && (_jsxs(EditorStatus, { "data-testid": "inline-editor-ai-apply-note", style: { left: `${area.x + area.width / 2}px`, top: `${area.y + 8}px` }, children: [_jsx("span", { role: "status", children: aiApplyNote }), _jsx(Button, { size: "sm", variant: "secondary", onClick: () => setAiApplyNote(null), children: t('inlineCanvas.chrome.close', '关闭') })] })), !capturing && move && (_jsx("div", { role: "toolbar", "aria-label": t('inlineCanvas.move.title'), "data-testid": "inline-editor-move-bar", className: "fixed z-[903] -translate-x-1/2 rounded-lg border bg-popover p-3 text-xs text-popover-foreground shadow-md", style: {
1019
+ width: 'min(600px, calc(100vw - 32px))',
1020
+ left: `clamp(min(316px, 50vw), ${area.x + area.width / 2}px, max(calc(100vw - 316px), 50vw))`,
1021
+ top: `${area.y + 8}px`
1022
+ }, children: move.target === null ? (_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("span", { children: move.rejected === 'same-element'
869
1023
  ? '不能移到它自己里面,请另选一个目标'
870
- : t('inlineCanvas.move.pickTarget') }) })) : (_jsxs(_Fragment, { children: [_jsx("span", { children: t('inlineCanvas.move.position') }), ['top', 'bottom', 'before', 'after'].map(pos => (_jsx("button", { type: "button", "data-testid": `move-position-${pos}`, className: "rounded bg-white/15 px-1.5 py-0.5 hover:bg-white/25", onClick: () => void commands
871
- .confirmMove(pos)
872
- .catch(e => setCommandError(describeCanvasError(e))), children: t(`inlineCanvas.move.${pos}`) }, pos)))] })), _jsx("button", { type: "button", "data-testid": "move-cancel", className: "ml-1 text-white/70 underline", onClick: () => commands.cancelMove(), children: t('inlineCanvas.move.cancel') })] })), !capturing && textEditing !== null && (_jsx("div", { "data-testid": "inline-editor-text-editing-hint", className: "pointer-events-none fixed z-[2147483003] -translate-x-1/2 rounded\n bg-[#091E42] px-2.5 py-1 text-[11px] leading-4 text-white shadow", style: {
1024
+ : t('inlineCanvas.move.pickTarget') }), _jsx(Button, { size: "default", variant: "secondary", type: "button", "data-testid": "move-cancel", onClick: () => commands.cancelMove(), children: t('inlineCanvas.move.cancel') })] })) : (_jsx(MovePositionControls, { onConfirm: commands.confirmMove, onCancel: commands.cancelMove, onError: e => setCommandError(describeCanvasError(e)) }, move.target)) })), !capturing && textEditing !== null && (_jsx(EditorStatus, { "data-testid": "inline-editor-text-editing-hint", style: {
873
1025
  left: `${area.x + area.width / 2}px`,
874
1026
  top: `${area.y + 8}px`
875
- }, children: "\u6B63\u5728\u7F16\u8F91\u6587\u5B57 \u00B7 \u56DE\u8F66\u63D0\u4EA4 \u00B7 Esc \u53D6\u6D88" })), !capturing && !switching && (_jsx(SelectionToolbar, { selectedRect: selectedRect, area: area, capabilities: selection?.capabilities ?? null, isScrolling: geometry.isScrolling, onAction: onToolbarAction, host: { askAi: !!onAskAi } })), stylePanelOpen && !switching && selectedRect && self && (_jsx("div", { className: "fixed z-[2147483002]", style: {
876
- left: `${Math.min(selectedRect.x + selectedRect.width + 12, area.x + area.width - 332)}px`,
877
- top: `${Math.max(area.y + 8, selectedRect.y)}px`
878
- }, children: _jsx(StylePanel, { tag: self.tag, capabilities: selection?.capabilities ?? null, declaredStyles: selection?.declaredStyles, onClose: () => setStylePanelOpen(false), onApply: (property, value) => uiWrite(() => editStyle(depsOf(self), property, value)) }) })), _jsx(Dialog, { open: pasteDialog, onOpenChange: setPasteDialog, children: _jsxs(DialogContent, { className: "max-w-[480px]", "data-testid": "paste-dialog", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('inlineCanvas.clipboard.pastePosition') }) }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: (selection?.capabilities ? pasteModes(selection.capabilities) : []).map(mode => (_jsx("button", { type: "button", "data-testid": `paste-mode-${mode}`, className: "rounded border border-[#DFE1E6] px-2 py-1 text-[12px]\n text-[#344563] hover:border-[#00A92E]", onClick: () => {
1027
+ }, children: "\u6B63\u5728\u7F16\u8F91\u6587\u5B57 \u00B7 \u56DE\u8F66\u63D0\u4EA4 \u00B7 Esc \u53D6\u6D88" })), !capturing && !switching && !move && textEditing === null && (_jsx(SelectionToolbar, { selectedRect: selectedRect, area: area, capabilities: selection?.capabilities ?? null, isScrolling: geometry.isScrolling, onAction: onToolbarAction, host: { askAi: !!onAskAi } })), stylePanelOpen &&
1028
+ !switching &&
1029
+ selectedRect &&
1030
+ self &&
1031
+ createPortal(_jsx("div", { className: `${CANVAS_UI_SCOPE} fixed z-[1000]`, children: _jsx(StylePanel, { anchor: {
1032
+ x: Math.min(selectedRect.x + selectedRect.width + 12, area.x + area.width - 332),
1033
+ y: Math.max(area.y + 8, selectedRect.y)
1034
+ }, tag: self.tag, capabilities: selection?.capabilities ?? null, resettableProperties: changeList.list.flatMap(r => r.change.type === 'elementStyle' &&
1035
+ r.change.selector === self.selector
1036
+ ? [r.change.property]
1037
+ : []), declaredStyles: Object.assign({}, currentStyles, ...changeList.list
1038
+ .filter(r => r.change.type === 'elementStyle' &&
1039
+ r.change.selector === self.selector)
1040
+ .map(r => r.change.type === 'elementStyle'
1041
+ ? { [r.change.property]: r.change.value }
1042
+ : {})), onReset: property => {
1043
+ void uiWrite(async () => {
1044
+ for (const record of changeList
1045
+ .peekList()
1046
+ .filter(r => r.change.type === 'elementStyle' &&
1047
+ r.change.selector === self.selector &&
1048
+ r.change.property === property)
1049
+ .reverse())
1050
+ await writes.revert(record.changeId);
1051
+ });
1052
+ }, onClose: () => setStylePanelOpen(false), onApply: (property, value) => uiWrite(() => editStyle(depsOf(self), property, value)) }, self.selector) }), document.body), _jsx(Dialog, { open: pasteDialog, onOpenChange: setPasteDialog, children: _jsxs(DialogContent, { className: "max-w-[480px]", "data-testid": "paste-dialog", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t('inlineCanvas.clipboard.pastePosition') }) }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: (selection?.capabilities ? pasteModes(selection.capabilities) : []).map(mode => (_jsxs(Button, { variant: "secondary", type: "button", "data-testid": `paste-mode-${mode}`, onClick: () => {
879
1053
  if (!self)
880
1054
  return;
881
1055
  setPasteDialog(false);
882
1056
  void commands
883
1057
  .pasteInto(self.selector, mode)
884
1058
  .catch(e => setCommandError(describeCanvasError(e)));
885
- }, children: t(`inlineCanvas.clipboard.${mode}`) }, mode))) })] }) }), _jsx(ImageEditDialog, { open: imageDialog.open, capabilities: selection?.capabilities ?? null, seed: imageDialog.seed, uploadImage: uploadImage, onOpenChange: open => setImageDialog(prev => ({ ...prev, open })), onApply: (input) => self && uiWrite(() => replaceImage(depsOf(self), input)) }), _jsx(ReplaceVideoDialog, { open: videoDialog.open, seed: videoDialog.seed, onOpenChange: open => setVideoDialog(prev => ({ ...prev, open })), onApply: url => self && uiWrite(() => replaceVideo(depsOf(self), url)) }), _jsx(InsertElementDialog, { open: insertDialog, capabilities: selection?.capabilities ?? null, onOpenChange: setInsertDialog, onApply: input => self && uiWrite(() => insertElement(depsOf(self), input)) }), _jsx(ImageSettingDialog, { open: imageSettingDialog.open, seed: imageSettingDialog.seed, onOpenChange: open => setImageSettingDialog(prev => ({ ...prev, open })), onApply: alt => self && uiWrite(() => setImageAlt(depsOf(self), alt)) }), _jsx(ClickActionDialog, { open: actionDialog.open, seed: actionDialog.seed, onOpenChange: open => setActionDialog(prev => ({ ...prev, open })), onApply: action => self && uiWrite(() => setClickAction(depsOf(self), action)) }), _jsx(HtmlEditDialog, { open: htmlDialog.open, seed: htmlDialog.seed, onOpenChange: open => setHtmlDialog(prev => ({ ...prev, open })), onApply: html => self && uiWrite(() => editHtml(depsOf(self), html)) })] }));
1059
+ }, children: [t('inlineCanvas.clipboard.paste', '粘贴'), " \u00B7", ' ', t(`inlineCanvas.clipboard.${mode}`)] }, mode))) })] }) }), _jsx(ImageEditDialog, { open: imageDialog.open, capabilities: selection?.capabilities ?? null, seed: imageDialog.seed, uploadImage: uploadImage, onOpenChange: open => setImageDialog(prev => ({ ...prev, open })), onApply: (input) => self && uiWrite(() => replaceImage(depsOf(self), input)) }), _jsx(ReplaceVideoDialog, { open: videoDialog.open, seed: videoDialog.seed, onOpenChange: open => setVideoDialog(prev => ({ ...prev, open })), onApply: url => self && uiWrite(() => replaceVideo(depsOf(self), url)) }), _jsx(InsertElementDialog, { open: insertDialog, capabilities: selection?.capabilities ?? null, onOpenChange: setInsertDialog, onApply: input => self && uiWrite(() => insertElement(depsOf(self), input)) }), _jsx(ImageSettingDialog, { open: imageSettingDialog.open, seed: imageSettingDialog.seed, onOpenChange: open => setImageSettingDialog(prev => ({ ...prev, open })), onApply: alt => self && uiWrite(() => setImageAlt(depsOf(self), alt)) }), _jsx(ClickActionDialog, { open: actionDialog.open, seed: actionDialog.seed, onOpenChange: open => setActionDialog(prev => ({ ...prev, open })), onApply: action => self && uiWrite(() => setClickAction(depsOf(self), action)) }), _jsx(HtmlEditDialog, { open: htmlDialog.open, seed: htmlDialog.seed, onOpenChange: open => setHtmlDialog(prev => ({ ...prev, open })), onApply: html => self && uiWrite(() => editHtml(depsOf(self), html)) })] }));
886
1060
  }
@@ -26,6 +26,7 @@ export interface TreePanelNode extends TreeNode {
26
26
  truncated?: boolean;
27
27
  }
28
28
  export interface TreePanelProps {
29
+ emptyLabel?: string;
29
30
  nodes: Record<string, TreePanelNode>;
30
31
  rootId: string;
31
32
  selectedId: string | null;
@@ -44,4 +45,4 @@ export interface TreePanelProps {
44
45
  */
45
46
  onExpand?: (id: string) => void;
46
47
  }
47
- export declare function TreePanel({ nodes, rootId, selectedId, hoverId, query, onSelect, onShiftSelect, onHover, onMove, onExpand }: TreePanelProps): React.JSX.Element;
48
+ export declare function TreePanel({ emptyLabel, nodes, rootId, selectedId, hoverId, query, onSelect, onShiftSelect, onHover, onMove, onExpand }: TreePanelProps): React.JSX.Element;