@hyebook/vue3-adapter 2.3.9 → 2.3.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { addPageCommand } from "../editor/commands";
2
2
  import { EditorEngine } from "../editor/engine";
3
3
  const WORKBENCH_STYLE_ID = "hy-ebook-workbench-style";
4
- const WORKBENCH_STYLE_VERSION = "0.3.0";
4
+ const WORKBENCH_STYLE_VERSION = "0.3.1";
5
5
  const WORKBENCH_CSS = `
6
6
  .hyewb-root{font-family:"Noto Sans SC","PingFang SC","Microsoft YaHei",sans-serif;color:#0f172a;background:#f8fbff;border:1px solid #d8e0ea;border-radius:12px;padding:12px;display:grid;gap:10px;position:relative;--hyewb-top-shield-height:0px}
7
7
  .hyewb-root::before{content:"";position:absolute;left:0;right:0;top:0;height:var(--hyewb-top-shield-height);background:#f8fbff;z-index:10030;pointer-events:none}
@@ -138,6 +138,15 @@ const WORKBENCH_CSS = `
138
138
  .hyewb-media-handle[data-handle="w"]{left:-6px;top:calc(50% - 5px);cursor:ew-resize}
139
139
  .hyewb-media-list{margin:0;padding-left:18px;color:#334155;font-size:13px;display:grid;gap:4px}
140
140
  .hyewb-status{font-size:12px;color:#475569}
141
+ .hyewb-load-warning{position:absolute;top:12px;right:12px;z-index:10090;max-width:min(420px,calc(100% - 24px));display:none;border:1px solid #f5c2c7;background:#fff5f5;color:#7f1d1d;border-radius:10px;box-shadow:0 10px 24px rgba(15,23,42,.12);padding:8px 10px}
142
+ .hyewb-load-warning.show{display:block}
143
+ .hyewb-load-warning-header{display:flex;align-items:center;gap:8px}
144
+ .hyewb-load-warning-title{font-size:12px;font-weight:600;line-height:1.4}
145
+ .hyewb-load-warning-actions{margin-left:auto;display:inline-flex;align-items:center;gap:6px}
146
+ .hyewb-load-warning-btn{border:1px solid #f1b3b3;background:#fff;color:#7f1d1d;border-radius:6px;height:24px;padding:0 8px;font-size:12px;line-height:1;cursor:pointer}
147
+ .hyewb-load-warning-close{width:24px;padding:0}
148
+ .hyewb-load-warning-message{margin:8px 0 0;font-size:12px;line-height:1.5;color:#7f1d1d;white-space:pre-wrap;word-break:break-word;display:none;max-height:180px;overflow:auto}
149
+ .hyewb-load-warning.expanded .hyewb-load-warning-message{display:block}
141
150
  .hyewb-upload-backdrop{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(15,23,42,.45);z-index:10100;padding:16px}
142
151
  .hyewb-upload-backdrop.show{display:flex}
143
152
  .hyewb-upload-dialog{position:relative;width:min(520px,100%);background:#fff;border:1px solid #cbd5e1;border-radius:12px;padding:14px;display:grid;gap:10px;box-shadow:0 16px 34px rgba(15,23,42,.28)}
@@ -332,6 +341,15 @@ function resolvePreviewAnnotationOptions(options) {
332
341
  },
333
342
  };
334
343
  }
344
+ function resolveExtensionActionPlacement(placement) {
345
+ return placement === "toolbar" ? "toolbar" : "header";
346
+ }
347
+ function resolveExtensionActionVisibility(visibility) {
348
+ if (visibility === "always" || visibility === "fullscreen") {
349
+ return visibility;
350
+ }
351
+ return "editor";
352
+ }
335
353
  export function createDefaultWorkbenchDocument() {
336
354
  const now = new Date().toISOString();
337
355
  return {
@@ -369,54 +387,19 @@ export function createDefaultWorkbenchDocument() {
369
387
  {
370
388
  id: "text-1",
371
389
  type: "text",
372
- rect: { x: 80, y: 80, width: 620, height: 240 },
390
+ rect: { x: 80, y: 80, width: 620, height: 120 },
373
391
  zIndex: 1,
374
392
  content: {
375
393
  paragraphs: [
376
394
  {
377
395
  id: "p-1",
378
- type: "h1",
379
- align: "left",
380
- spacingBefore: 12,
381
- spacingAfter: 12,
382
- runs: [
383
- {
384
- text: "欢迎使用业务系统编辑器",
385
- marks: { bold: true, fontSize: 30 },
386
- },
387
- ],
388
- },
389
- {
390
- id: "p-2",
391
396
  type: "p",
392
397
  align: "left",
393
- lineHeight: 1.6,
394
- spacingBefore: 12,
395
- spacingAfter: 12,
396
- indent: 2,
397
- inlineElements: [
398
- {
399
- id: "inline-image-1",
400
- type: "image",
401
- src: "https://picsum.photos/id/1025/1200/800",
402
- alt: "Classroom",
403
- width: 320,
404
- },
405
- ],
398
+ spacingBefore: 0,
399
+ spacingAfter: 0,
406
400
  runs: [
407
- {
408
- text: "该页面默认包含样式和基础编辑能力。你可以在初始化阶段通过 features 开关关闭不需要的模块。\n",
409
- },
410
401
  {
411
402
  text: "",
412
- inlineRef: "inline-image-1",
413
- },
414
- {
415
- text: "\n图片后依然可以继续输入文字。",
416
- marks: {
417
- color: "#0f766e",
418
- backgroundColor: "#ecfeff",
419
- },
420
403
  },
421
404
  ],
422
405
  },
@@ -430,8 +413,16 @@ export function createDefaultWorkbenchDocument() {
430
413
  }
431
414
  export function mountEditorWorkbench(container, options = {}) {
432
415
  ensureWorkbenchStyles();
433
- const initialDoc = clone(options.initialDoc ?? createDefaultWorkbenchDocument());
434
- const editor = new EditorEngine(initialDoc);
416
+ let initialLoadErrorMessage = null;
417
+ let editor;
418
+ try {
419
+ const initialDoc = clone(options.initialDoc ?? createDefaultWorkbenchDocument());
420
+ editor = new EditorEngine(initialDoc);
421
+ }
422
+ catch (error) {
423
+ initialLoadErrorMessage = resolveErrorMessage(error, "initialDoc 校验失败");
424
+ editor = new EditorEngine(clone(createDefaultWorkbenchDocument()));
425
+ }
435
426
  const state = {
436
427
  mode: "editor",
437
428
  pageIndex: 0,
@@ -443,13 +434,19 @@ export function mountEditorWorkbench(container, options = {}) {
443
434
  const root = document.createElement("section");
444
435
  root.className = "hyewb-root";
445
436
  const extensionActionConfigs = Array.isArray(options.extension?.actions)
446
- ? options.extension.actions.filter((action) => {
437
+ ? options.extension.actions
438
+ .filter((action) => {
447
439
  return (!!action &&
448
440
  typeof action.id === "string" &&
449
441
  action.id.trim().length > 0 &&
450
442
  typeof action.text === "string" &&
451
443
  action.text.trim().length > 0);
452
444
  })
445
+ .map((action) => ({
446
+ ...action,
447
+ placement: resolveExtensionActionPlacement(action.placement),
448
+ visibility: resolveExtensionActionVisibility(action.visibility),
449
+ }))
453
450
  : [];
454
451
  const header = document.createElement("header");
455
452
  header.className = "hyewb-header hyewb-top-control";
@@ -459,32 +456,6 @@ export function mountEditorWorkbench(container, options = {}) {
459
456
  const headerActions = document.createElement("div");
460
457
  headerActions.className = "hyewb-header-actions";
461
458
  const fullscreenBtn = createButton("fullscreen");
462
- const extensionActionButtons = extensionActionConfigs.map((action) => {
463
- const button = document.createElement("button");
464
- button.type = "button";
465
- button.className = "hyewb-btn hyewb-btn-text hyewb-header-action-btn";
466
- const text = action.text.trim();
467
- button.textContent = text;
468
- button.title = text;
469
- button.setAttribute("aria-label", text);
470
- button.style.display = "none";
471
- const color = typeof action.color === "string" ? action.color.trim() : "";
472
- if (color) {
473
- button.style.backgroundColor = color;
474
- button.style.borderColor = color;
475
- }
476
- const textColor = typeof action.textColor === "string" ? action.textColor.trim() : "";
477
- if (textColor) {
478
- button.style.color = textColor;
479
- }
480
- const borderColor = typeof action.borderColor === "string" ? action.borderColor.trim() : "";
481
- if (borderColor) {
482
- button.style.borderColor = borderColor;
483
- }
484
- headerActions.append(button);
485
- return { action, button };
486
- });
487
- headerActions.append(fullscreenBtn);
488
459
  header.append(title, headerActions);
489
460
  const toolbar = document.createElement("div");
490
461
  toolbar.className = "hyewb-row hyewb-top-control";
@@ -607,6 +578,40 @@ export function mountEditorWorkbench(container, options = {}) {
607
578
  const tableAddBtn = createButton("insertTable");
608
579
  const importDocxBtn = createButton("importDocx");
609
580
  toolbar.append(boldBtn, italicBtn, underlineBtn, strikeBtn, superscriptBtn, subscriptBtn, headingSelect, fontSizeSelect, fontFamilySelect, textColorInput, bgColorInput, alignGroup, lineHeightField, spacingField, indentField, tableAddBtn, importDocxBtn, videoAddBtn, imageUploadBtn);
581
+ const extensionActionButtons = extensionActionConfigs.map((action) => {
582
+ const button = document.createElement("button");
583
+ button.type = "button";
584
+ button.className = "hyewb-btn hyewb-btn-text";
585
+ if (action.placement === "header") {
586
+ button.classList.add("hyewb-header-action-btn");
587
+ }
588
+ const text = action.text.trim();
589
+ button.textContent = text;
590
+ button.title = text;
591
+ button.setAttribute("aria-label", text);
592
+ button.style.display = "none";
593
+ const color = typeof action.color === "string" ? action.color.trim() : "";
594
+ if (color) {
595
+ button.style.backgroundColor = color;
596
+ button.style.borderColor = color;
597
+ }
598
+ const textColor = typeof action.textColor === "string" ? action.textColor.trim() : "";
599
+ if (textColor) {
600
+ button.style.color = textColor;
601
+ }
602
+ const borderColor = typeof action.borderColor === "string" ? action.borderColor.trim() : "";
603
+ if (borderColor) {
604
+ button.style.borderColor = borderColor;
605
+ }
606
+ if (action.placement === "toolbar") {
607
+ toolbar.append(button);
608
+ }
609
+ else {
610
+ headerActions.append(button);
611
+ }
612
+ return { action, button };
613
+ });
614
+ headerActions.append(fullscreenBtn);
610
615
  const tablePicker = document.createElement("div");
611
616
  tablePicker.className = "hyewb-table-picker";
612
617
  const tablePickerHint = document.createElement("div");
@@ -883,6 +888,51 @@ export function mountEditorWorkbench(container, options = {}) {
883
888
  const status = document.createElement("p");
884
889
  status.className = "hyewb-status";
885
890
  status.textContent = "已加载默认编辑器能力";
891
+ const loadWarning = document.createElement("div");
892
+ loadWarning.className = "hyewb-load-warning";
893
+ const loadWarningHeader = document.createElement("div");
894
+ loadWarningHeader.className = "hyewb-load-warning-header";
895
+ const loadWarningTitle = document.createElement("span");
896
+ loadWarningTitle.className = "hyewb-load-warning-title";
897
+ loadWarningTitle.textContent = "加载数据异常";
898
+ const loadWarningActions = document.createElement("div");
899
+ loadWarningActions.className = "hyewb-load-warning-actions";
900
+ const loadWarningToggleBtn = document.createElement("button");
901
+ loadWarningToggleBtn.type = "button";
902
+ loadWarningToggleBtn.className = "hyewb-load-warning-btn";
903
+ const loadWarningCloseBtn = document.createElement("button");
904
+ loadWarningCloseBtn.type = "button";
905
+ loadWarningCloseBtn.className = "hyewb-load-warning-btn hyewb-load-warning-close";
906
+ loadWarningCloseBtn.textContent = "×";
907
+ loadWarningCloseBtn.title = "关闭";
908
+ loadWarningCloseBtn.setAttribute("aria-label", "关闭加载异常提示");
909
+ const loadWarningMessage = document.createElement("pre");
910
+ loadWarningMessage.className = "hyewb-load-warning-message";
911
+ const setLoadWarningExpanded = (expanded) => {
912
+ loadWarning.classList.toggle("expanded", expanded);
913
+ loadWarningToggleBtn.textContent = expanded ? "收起" : "详情";
914
+ };
915
+ const hideLoadWarning = () => {
916
+ loadWarning.classList.remove("show");
917
+ setLoadWarningExpanded(false);
918
+ };
919
+ const showLoadWarning = (message) => {
920
+ loadWarningMessage.textContent = message;
921
+ loadWarning.classList.add("show");
922
+ setLoadWarningExpanded(false);
923
+ };
924
+ const handleLoadWarningToggle = () => {
925
+ setLoadWarningExpanded(!loadWarning.classList.contains("expanded"));
926
+ };
927
+ const handleLoadWarningClose = () => {
928
+ hideLoadWarning();
929
+ };
930
+ setLoadWarningExpanded(false);
931
+ loadWarningToggleBtn.addEventListener("click", handleLoadWarningToggle);
932
+ loadWarningCloseBtn.addEventListener("click", handleLoadWarningClose);
933
+ loadWarningActions.append(loadWarningToggleBtn, loadWarningCloseBtn);
934
+ loadWarningHeader.append(loadWarningTitle, loadWarningActions);
935
+ loadWarning.append(loadWarningHeader, loadWarningMessage);
886
936
  const debugConfig = typeof options.debug === "object" && options.debug !== null
887
937
  ? options.debug
888
938
  : undefined;
@@ -1002,10 +1052,14 @@ export function mountEditorWorkbench(container, options = {}) {
1002
1052
  const colorGrid = document.createElement("div");
1003
1053
  colorGrid.className = "hyewb-color-grid";
1004
1054
  colorPalette.append(colorGrid);
1005
- root.append(header, toolbar, pageRow, previewLayout, colorPalette, tablePicker, tableTools, tableRowDeleteBtn, tableMoveHandle, tableScaleHandle, tableColEdgeLayer, tableRowEdgeLayer, tableRowGapInsertBtn, tableColGapInsertBtn, tableDropIndicator, tableContextMenu, uploadBackdrop, previewNoteBackdrop, docxInput, previewSelectionToolbar);
1055
+ root.append(header, toolbar, pageRow, previewLayout, colorPalette, tablePicker, tableTools, tableRowDeleteBtn, tableMoveHandle, tableScaleHandle, tableColEdgeLayer, tableRowEdgeLayer, tableRowGapInsertBtn, tableColGapInsertBtn, tableDropIndicator, tableContextMenu, uploadBackdrop, previewNoteBackdrop, docxInput, previewSelectionToolbar, loadWarning);
1006
1056
  container.innerHTML = "";
1007
1057
  root.append(floatingToolbar);
1008
1058
  container.append(root);
1059
+ if (initialLoadErrorMessage) {
1060
+ status.textContent = `加载数据异常:${initialLoadErrorMessage}`;
1061
+ showLoadWarning(initialLoadErrorMessage);
1062
+ }
1009
1063
  renderWorkbenchIcons();
1010
1064
  let editorRenderKey = "";
1011
1065
  let lastSelection = null;
@@ -1082,8 +1136,24 @@ export function mountEditorWorkbench(container, options = {}) {
1082
1136
  fullscreenBtn.title = ariaLabel;
1083
1137
  fullscreenBtn.setAttribute("aria-label", ariaLabel);
1084
1138
  fullscreenBtn.style.display = isFullscreen ? "inline-flex" : "none";
1085
- extensionActionButtons.forEach(({ button }) => {
1086
- button.style.display = isFullscreen ? "inline-flex" : "none";
1139
+ };
1140
+ const shouldShowExtensionAction = (action) => {
1141
+ const isFullscreen = isWorkbenchFullscreen();
1142
+ const isEditorMode = state.mode === "editor";
1143
+ const visibleByRule = action.visibility === "always"
1144
+ ? true
1145
+ : action.visibility === "fullscreen"
1146
+ ? isFullscreen
1147
+ : isEditorMode;
1148
+ const visibleByPlacement = action.placement === "toolbar"
1149
+ ? isEditorMode && state.features.textToolbar
1150
+ : true;
1151
+ return visibleByRule && visibleByPlacement;
1152
+ };
1153
+ const updateExtensionActionButtonsVisibility = () => {
1154
+ extensionActionButtons.forEach(({ action, button }) => {
1155
+ button.style.display =
1156
+ shouldShowExtensionAction(action) ? "inline-flex" : "none";
1087
1157
  });
1088
1158
  };
1089
1159
  const updateShellViewportHeight = () => {
@@ -1123,6 +1193,7 @@ export function mountEditorWorkbench(container, options = {}) {
1123
1193
  };
1124
1194
  const syncWorkbenchLayoutAfterFullscreenChange = () => {
1125
1195
  updateFullscreenButtonState();
1196
+ updateExtensionActionButtonsVisibility();
1126
1197
  updateShellViewportHeight();
1127
1198
  updateTopControlShieldHeight();
1128
1199
  updateInlineResizeOverlay();
@@ -2851,6 +2922,26 @@ export function mountEditorWorkbench(container, options = {}) {
2851
2922
  return item !== editorArea && isParagraphElement(item);
2852
2923
  });
2853
2924
  };
2925
+ const ensureEditorHasFocusableParagraph = () => {
2926
+ const paragraphs = getEditorParagraphCandidates();
2927
+ if (paragraphs.length) {
2928
+ return paragraphs[0];
2929
+ }
2930
+ const paragraph = document.createElement("p");
2931
+ paragraph.innerHTML = "<br>";
2932
+ editorArea.append(paragraph);
2933
+ return paragraph;
2934
+ };
2935
+ const isEditorSurfaceEffectivelyEmpty = () => {
2936
+ if (getEditorParagraphCandidates().length > 0) {
2937
+ return false;
2938
+ }
2939
+ const text = (editorArea.textContent || "").replace(/\u00a0/g, " ").trim();
2940
+ if (text.length > 0) {
2941
+ return false;
2942
+ }
2943
+ return !editorArea.querySelector("table,img,video,.inline-media");
2944
+ };
2854
2945
  const rememberActiveParagraphIndex = () => {
2855
2946
  const paragraph = resolveActiveParagraph(editorArea, rememberedRange);
2856
2947
  if (!paragraph) {
@@ -3437,6 +3528,14 @@ export function mountEditorWorkbench(container, options = {}) {
3437
3528
  });
3438
3529
  return container.innerHTML;
3439
3530
  };
3531
+ const normalizePastedRichHtml = (html) => {
3532
+ const stripped = stripMediaTagsFromHtml(html);
3533
+ if (!stripped.trim()) {
3534
+ return "";
3535
+ }
3536
+ const flowBlocks = parseRichHTMLToFlowBlocks(stripped);
3537
+ return flowBlocksToHTML(flowBlocks);
3538
+ };
3440
3539
  const createPasteAnchor = () => {
3441
3540
  editorArea.focus();
3442
3541
  if (!restoreSelectionRange()) {
@@ -3796,6 +3895,9 @@ export function mountEditorWorkbench(container, options = {}) {
3796
3895
  editorRenderKey = key;
3797
3896
  selectInlineImage(null);
3798
3897
  rememberedParagraphIndex = null;
3898
+ if (isEditorSurfaceEffectivelyEmpty()) {
3899
+ ensureEditorHasFocusableParagraph();
3900
+ }
3799
3901
  }
3800
3902
  if (state.mode === "preview") {
3801
3903
  canvas.style.width = "100%";
@@ -3809,8 +3911,15 @@ export function mountEditorWorkbench(container, options = {}) {
3809
3911
  previewArea.classList.toggle("show", state.mode === "preview");
3810
3912
  editorArea.style.display = state.mode === "editor" ? "block" : "none";
3811
3913
  const showEditorControls = state.mode === "editor";
3914
+ const hasVisibleHeaderExtensionAction = extensionActionButtons.some(({ action }) => {
3915
+ return action.placement === "header" && shouldShowExtensionAction(action);
3916
+ });
3812
3917
  header.style.display =
3813
- showEditorControls || isWorkbenchFullscreen() ? "flex" : "none";
3918
+ showEditorControls ||
3919
+ isWorkbenchFullscreen() ||
3920
+ hasVisibleHeaderExtensionAction
3921
+ ? "flex"
3922
+ : "none";
3814
3923
  toolbar.style.display =
3815
3924
  showEditorControls && state.features.textToolbar ? "flex" : "none";
3816
3925
  pageRow.style.display = "none";
@@ -3829,6 +3938,7 @@ export function mountEditorWorkbench(container, options = {}) {
3829
3938
  hideTablePicker();
3830
3939
  hideTableTools();
3831
3940
  }
3941
+ updateExtensionActionButtonsVisibility();
3832
3942
  updateTopControlShieldHeight();
3833
3943
  prevPageBtn.disabled = state.pageIndex <= 0;
3834
3944
  nextPageBtn.disabled = state.pageIndex >= state.doc.pages.length - 1;
@@ -4054,6 +4164,9 @@ export function mountEditorWorkbench(container, options = {}) {
4054
4164
  tableWidthInput,
4055
4165
  tableHeightInput,
4056
4166
  tableRowHeightInput,
4167
+ ...extensionActionButtons
4168
+ .filter(({ action }) => action.placement === "toolbar")
4169
+ .map(({ button }) => button),
4057
4170
  ].forEach((control) => {
4058
4171
  control.addEventListener("mousedown", preserveSelectionForInputControl);
4059
4172
  control.addEventListener("focus", preserveSelectionForInputControl);
@@ -4179,6 +4292,20 @@ export function mountEditorWorkbench(container, options = {}) {
4179
4292
  const image = target.closest("img.inline-image");
4180
4293
  if (!image || !editorArea.contains(image)) {
4181
4294
  selectInlineImage(null);
4295
+ if (target === editorArea && isEditorSurfaceEffectivelyEmpty()) {
4296
+ const paragraph = ensureEditorHasFocusableParagraph();
4297
+ editorArea.focus();
4298
+ const range = document.createRange();
4299
+ range.selectNodeContents(paragraph);
4300
+ range.collapse(false);
4301
+ const selection = window.getSelection();
4302
+ if (selection) {
4303
+ selection.removeAllRanges();
4304
+ selection.addRange(range);
4305
+ rememberedRange = range.cloneRange();
4306
+ rememberActiveParagraphIndex();
4307
+ }
4308
+ }
4182
4309
  return;
4183
4310
  }
4184
4311
  selectInlineImage(image);
@@ -4215,6 +4342,25 @@ export function mountEditorWorkbench(container, options = {}) {
4215
4342
  if (!clipboard) {
4216
4343
  return;
4217
4344
  }
4345
+ const htmlContent = clipboard.getData("text/html");
4346
+ const plainContent = clipboard.getData("text/plain");
4347
+ const normalizedRichHtml = normalizePastedRichHtml(htmlContent);
4348
+ if (normalizedRichHtml.trim()) {
4349
+ event.preventDefault();
4350
+ cacheSelectionRange();
4351
+ const pasteAnchorId = createPasteAnchor();
4352
+ if (!pasteAnchorId) {
4353
+ status.textContent = "粘贴失败:无法定位插入位置";
4354
+ return;
4355
+ }
4356
+ insertHtmlBeforePasteAnchor(pasteAnchorId, normalizedRichHtml);
4357
+ removePasteAnchor(pasteAnchorId);
4358
+ syncEditorToDoc();
4359
+ status.textContent = "已粘贴富文本内容";
4360
+ updateInlineResizeOverlay();
4361
+ syncToolbarState();
4362
+ return;
4363
+ }
4218
4364
  const mediaFiles = Array.from(clipboard.items)
4219
4365
  .map((item) => item.getAsFile())
4220
4366
  .filter((file) => {
@@ -4226,8 +4372,6 @@ export function mountEditorWorkbench(container, options = {}) {
4226
4372
  }
4227
4373
  event.preventDefault();
4228
4374
  cacheSelectionRange();
4229
- const htmlContent = clipboard.getData("text/html");
4230
- const plainContent = clipboard.getData("text/plain");
4231
4375
  const pasteAnchorId = createPasteAnchor();
4232
4376
  if (!pasteAnchorId) {
4233
4377
  status.textContent = "粘贴失败:无法定位插入位置";
@@ -5030,6 +5174,8 @@ export function mountEditorWorkbench(container, options = {}) {
5030
5174
  closeWorkbenchFullscreen();
5031
5175
  hideTablePicker();
5032
5176
  hideTableTools();
5177
+ loadWarningToggleBtn.removeEventListener("click", handleLoadWarningToggle);
5178
+ loadWarningCloseBtn.removeEventListener("click", handleLoadWarningClose);
5033
5179
  document.removeEventListener("selectionchange", handleSelectionChange);
5034
5180
  document.removeEventListener("pointermove", onMediaPointerMove);
5035
5181
  document.removeEventListener("pointerup", onMediaPointerUp);
@@ -5202,35 +5348,108 @@ function parseRichHTMLToFlowBlocks(html, defaultTextStyle) {
5202
5348
  return [createDefaultTextFlowBlock()];
5203
5349
  }
5204
5350
  const nodes = Array.from(root.childNodes).filter((node) => {
5205
- if (node.nodeType !== Node.TEXT_NODE) {
5206
- return true;
5351
+ if (node.nodeType === Node.TEXT_NODE) {
5352
+ return (node.nodeValue || "").trim().length > 0;
5353
+ }
5354
+ if (node.nodeType !== Node.ELEMENT_NODE) {
5355
+ return false;
5207
5356
  }
5208
- return (node.nodeValue || "").trim().length > 0;
5357
+ return !isIgnorableImportedElementTag(node.tagName);
5209
5358
  });
5210
5359
  if (!nodes.length) {
5211
5360
  return [createDefaultTextFlowBlock()];
5212
5361
  }
5213
5362
  const result = [];
5214
- nodes.forEach((node, index) => {
5363
+ let flowIndex = 0;
5364
+ const pushParagraphIfMeaningful = (paragraph) => {
5365
+ if (!hasMeaningfulParagraphContent(paragraph)) {
5366
+ return;
5367
+ }
5368
+ result.push(createTextFlowBlock(paragraph, flowIndex));
5369
+ flowIndex += 1;
5370
+ };
5371
+ const pushNode = (node) => {
5215
5372
  if (node.nodeType === Node.TEXT_NODE) {
5373
+ const text = node.nodeValue || "";
5374
+ if (!text.trim()) {
5375
+ return;
5376
+ }
5216
5377
  const paragraph = {
5217
- id: `p-${Date.now()}-${index}`,
5378
+ id: `p-${Date.now()}-${flowIndex}`,
5218
5379
  type: "p",
5219
5380
  align: "left",
5220
- runs: applyImportedDefaultTextStyle([{ text: node.nodeValue || "" }], defaultTextStyle),
5381
+ runs: applyImportedDefaultTextStyle([{ text }], defaultTextStyle),
5221
5382
  };
5222
- result.push(createTextFlowBlock(paragraph, index));
5383
+ pushParagraphIfMeaningful(paragraph);
5384
+ return;
5385
+ }
5386
+ if (node.nodeType !== Node.ELEMENT_NODE) {
5223
5387
  return;
5224
5388
  }
5225
5389
  const element = node;
5226
5390
  if (element.tagName === "TABLE") {
5227
- result.push(parseTableElement(element, index));
5391
+ result.push(parseTableElement(element, flowIndex));
5392
+ flowIndex += 1;
5228
5393
  return;
5229
5394
  }
5230
- result.push(createTextFlowBlock(parseElementToParagraph(element, index, defaultTextStyle), index));
5395
+ const hasNestedTable = Boolean(element.querySelector("table"));
5396
+ if (!hasNestedTable) {
5397
+ pushParagraphIfMeaningful(parseElementToParagraph(element, flowIndex, defaultTextStyle));
5398
+ return;
5399
+ }
5400
+ let textContainer = element.cloneNode(false);
5401
+ const flushTextContainer = () => {
5402
+ if (!hasMeaningfulElementContent(textContainer)) {
5403
+ textContainer = element.cloneNode(false);
5404
+ return;
5405
+ }
5406
+ const paragraph = parseElementToParagraph(textContainer, flowIndex, defaultTextStyle);
5407
+ pushParagraphIfMeaningful(paragraph);
5408
+ textContainer = element.cloneNode(false);
5409
+ };
5410
+ Array.from(element.childNodes).forEach((child) => {
5411
+ if (child.nodeType === Node.ELEMENT_NODE) {
5412
+ const childElement = child;
5413
+ if (childElement.tagName === "TABLE") {
5414
+ flushTextContainer();
5415
+ result.push(parseTableElement(childElement, flowIndex));
5416
+ flowIndex += 1;
5417
+ return;
5418
+ }
5419
+ if (childElement.querySelector("table")) {
5420
+ flushTextContainer();
5421
+ pushNode(childElement);
5422
+ return;
5423
+ }
5424
+ }
5425
+ textContainer.append(child.cloneNode(true));
5426
+ });
5427
+ flushTextContainer();
5428
+ };
5429
+ nodes.forEach((node) => {
5430
+ pushNode(node);
5231
5431
  });
5232
5432
  return result.length ? result : [createDefaultTextFlowBlock()];
5233
5433
  }
5434
+ function hasMeaningfulElementContent(element) {
5435
+ const text = (element.textContent || "").replace(/\u00a0/g, " ").trim();
5436
+ if (text.length > 0) {
5437
+ return true;
5438
+ }
5439
+ return Boolean(element.querySelector("img,video,br"));
5440
+ }
5441
+ function hasMeaningfulParagraphContent(paragraph) {
5442
+ if (Array.isArray(paragraph.inlineElements) && paragraph.inlineElements.length) {
5443
+ return true;
5444
+ }
5445
+ const runs = Array.isArray(paragraph.runs) ? paragraph.runs : [];
5446
+ return runs.some((run) => {
5447
+ if (run.inlineRef) {
5448
+ return true;
5449
+ }
5450
+ return (run.text || "").replace(/\u00a0/g, " ").trim().length > 0;
5451
+ });
5452
+ }
5234
5453
  function parseElementToParagraph(element, index, defaultTextStyle) {
5235
5454
  const parsedLineHeight = parseLineHeight(element.style.lineHeight);
5236
5455
  const parsedBefore = parsePx(element.style.marginTop);
@@ -5463,6 +5682,9 @@ function extractRuns(node, inheritedMarks, inlineElements) {
5463
5682
  return [];
5464
5683
  }
5465
5684
  const element = node;
5685
+ if (isIgnorableImportedElementTag(element.tagName)) {
5686
+ return [];
5687
+ }
5466
5688
  if (element.tagName === "BR") {
5467
5689
  const marks = cleanupMarks(inheritedMarks);
5468
5690
  return [marks ? { text: "\n", marks } : { text: "\n" }];
@@ -5853,8 +6075,34 @@ function parseLineHeight(value) {
5853
6075
  if (!value) {
5854
6076
  return undefined;
5855
6077
  }
5856
- const number = Number.parseFloat(value);
5857
- return Number.isFinite(number) ? number : undefined;
6078
+ const normalized = value.trim().toLowerCase();
6079
+ if (!normalized || normalized === "normal") {
6080
+ return undefined;
6081
+ }
6082
+ const percentMatch = normalized.match(/^(-?\d+(?:\.\d+)?)%$/);
6083
+ if (percentMatch && percentMatch[1] !== undefined) {
6084
+ const percent = Number.parseFloat(percentMatch[1]);
6085
+ if (!Number.isFinite(percent) || percent <= 0) {
6086
+ return undefined;
6087
+ }
6088
+ return Number.parseFloat((percent / 100).toFixed(4));
6089
+ }
6090
+ const unitlessMatch = normalized.match(/^-?\d+(?:\.\d+)?$/);
6091
+ if (unitlessMatch) {
6092
+ const raw = Number.parseFloat(normalized);
6093
+ if (!Number.isFinite(raw) || raw <= 0) {
6094
+ return undefined;
6095
+ }
6096
+ if (raw >= 100) {
6097
+ return Number.parseFloat((raw / 100).toFixed(4));
6098
+ }
6099
+ return raw;
6100
+ }
6101
+ const number = Number.parseFloat(normalized);
6102
+ if (!Number.isFinite(number) || number <= 0) {
6103
+ return undefined;
6104
+ }
6105
+ return number;
5858
6106
  }
5859
6107
  function parseIndentEm(value) {
5860
6108
  if (!value) {
@@ -7055,6 +7303,26 @@ function normalizeStyleAttribute(styleValue) {
7055
7303
  .filter((segment) => segment.length > 0)
7056
7304
  .join(";");
7057
7305
  }
7306
+ function isIgnorableImportedElementTag(tagName) {
7307
+ const normalized = String(tagName || "")
7308
+ .trim()
7309
+ .toUpperCase();
7310
+ if (!normalized) {
7311
+ return false;
7312
+ }
7313
+ if (normalized === "SCRIPT" ||
7314
+ normalized === "STYLE" ||
7315
+ normalized === "META" ||
7316
+ normalized === "LINK" ||
7317
+ normalized === "TITLE" ||
7318
+ normalized === "HEAD" ||
7319
+ normalized === "BASE" ||
7320
+ normalized === "XML" ||
7321
+ normalized === "O:P") {
7322
+ return true;
7323
+ }
7324
+ return normalized.startsWith("W:");
7325
+ }
7058
7326
  function normalizeStoredTableCellHTML(html) {
7059
7327
  const source = String(html || "").trim();
7060
7328
  if (!source) {
@@ -7066,7 +7334,20 @@ function normalizeStoredTableCellHTML(html) {
7066
7334
  if (!root) {
7067
7335
  return "";
7068
7336
  }
7069
- root.querySelectorAll("script,style").forEach((node) => node.remove());
7337
+ const allElements = Array.from(root.querySelectorAll("*"));
7338
+ allElements.forEach((node) => {
7339
+ if (isIgnorableImportedElementTag(node.tagName)) {
7340
+ node.remove();
7341
+ }
7342
+ });
7343
+ const commentWalker = doc.createTreeWalker(root, NodeFilter.SHOW_COMMENT);
7344
+ const comments = [];
7345
+ while (commentWalker.nextNode()) {
7346
+ comments.push(commentWalker.currentNode);
7347
+ }
7348
+ comments.forEach((commentNode) => {
7349
+ commentNode.remove();
7350
+ });
7070
7351
  root.querySelectorAll("*").forEach((node) => {
7071
7352
  Array.from(node.attributes).forEach((attr) => {
7072
7353
  if (attr.name.toLowerCase().startsWith("on")) {
@@ -7658,3 +7939,12 @@ function isRecord(value) {
7658
7939
  function clone(value) {
7659
7940
  return JSON.parse(JSON.stringify(value));
7660
7941
  }
7942
+ function resolveErrorMessage(error, fallback = "未知错误") {
7943
+ if (error instanceof Error && error.message) {
7944
+ return error.message;
7945
+ }
7946
+ if (typeof error === "string" && error.trim()) {
7947
+ return error.trim();
7948
+ }
7949
+ return fallback;
7950
+ }