@hyebook/vue3-adapter 0.2.0 → 0.2.2

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.
@@ -9,12 +9,22 @@ export interface EditorWorkbenchFeatures {
9
9
  export interface EditorWorkbenchOptions {
10
10
  initialDoc?: EbookDoc;
11
11
  features?: Partial<EditorWorkbenchFeatures>;
12
+ previewAnnotations?: EditorWorkbenchPreviewAnnotationOptions;
12
13
  title?: string;
13
14
  pageWidth?: number;
14
15
  upload?: EditorWorkbenchUploadOptions;
15
16
  importDocx?: EditorWorkbenchDocxImportOptions;
16
17
  onDocumentChange?: (doc: EbookDoc) => void;
17
18
  }
19
+ export interface EditorWorkbenchPreviewAnnotationActions {
20
+ canDelete?: boolean;
21
+ }
22
+ export interface EditorWorkbenchPreviewAnnotationOptions {
23
+ showAnnotationList?: boolean;
24
+ enablePreviewSelectionToolbar?: boolean;
25
+ annotationListDefaultTab?: "highlights" | "notes";
26
+ annotationActions?: EditorWorkbenchPreviewAnnotationActions;
27
+ }
18
28
  export interface EditorWorkbenchDocxImportOptions {
19
29
  maxSizeBytes?: number;
20
30
  }
@@ -1 +1 @@
1
- {"version":3,"file":"editor-workbench.d.ts","sourceRoot":"","sources":["../../../../../core/src/workbench/editor-workbench.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EAKR,kBAAkB,EAWnB,MAAM,gBAAgB,CAAC;AA8IxB,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,4BAA4B,CAAC;IACtC,UAAU,CAAC,EAAE,gCAAgC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,gCAAgC;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,wBAAyB,SAAQ,2BAA2B;CAAG;AAEhF,MAAM,WAAW,uBAAwB,SAAQ,2BAA2B;IAC1E,SAAS,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,uBAAuB,KAC7B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,wBAAwB,KAC9B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,QAAQ,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC9B,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;IACjD,sBAAsB,EAAE,CACtB,WAAW,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,KAC/C,kBAAkB,CAAC;IACxB,WAAW,EAAE,MAAM,uBAAuB,CAAC;IAC3C,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,IAAI,CAAC;CACnE;AAED,eAAO,MAAM,iCAAiC,EAAE,uBAM/C,CAAC;AAEF,KAAK,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAC;AA6PjC,wBAAgB,8BAA8B,IAAI,QAAQ,CA6GzD;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,WAAW,EACtB,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAohIvB"}
1
+ {"version":3,"file":"editor-workbench.d.ts","sourceRoot":"","sources":["../../../../../core/src/workbench/editor-workbench.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,QAAQ,EAKR,kBAAkB,EAWnB,MAAM,gBAAgB,CAAC;AA2KxB,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,UAAU,CAAC,EAAE,QAAQ,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAC5C,kBAAkB,CAAC,EAAE,uCAAuC,CAAC;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,4BAA4B,CAAC;IACtC,UAAU,CAAC,EAAE,gCAAgC,CAAC;IAC9C,gBAAgB,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,uCAAuC;IACtD,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,uCAAuC;IACtD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,6BAA6B,CAAC,EAAE,OAAO,CAAC;IACxC,wBAAwB,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC;IAClD,iBAAiB,CAAC,EAAE,uCAAuC,CAAC;CAC7D;AAED,MAAM,WAAW,gCAAgC;IAC/C,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,OAAO,CAAC;AAEtD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,qBAAqB,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,2BAA2B;IAC1C,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3D;AAED,MAAM,WAAW,wBAAyB,SAAQ,2BAA2B;CAAG;AAEhF,MAAM,WAAW,uBAAwB,SAAQ,2BAA2B;IAC1E,SAAS,EAAE,qBAAqB,CAAC;CAClC;AAED,MAAM,WAAW,4BAA4B;IAC3C,UAAU,CAAC,EAAE,CACX,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,uBAAuB,KAC7B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,wBAAwB,KAC9B,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,WAAW,EAAE,MAAM,QAAQ,CAAC;IAC5B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,EAAE,MAAM,MAAM,CAAC;IAC3B,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,OAAO,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC9B,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;IACjD,sBAAsB,EAAE,CACtB,WAAW,EAAE,kBAAkB,GAAG,IAAI,GAAG,SAAS,KAC/C,kBAAkB,CAAC;IACxB,WAAW,EAAE,MAAM,uBAAuB,CAAC;IAC3C,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,uBAAuB,CAAC,KAAK,IAAI,CAAC;CACnE;AAED,eAAO,MAAM,iCAAiC,EAAE,uBAM/C,CAAC;AAEF,KAAK,IAAI,GAAG,QAAQ,GAAG,SAAS,CAAC;AAoSjC,wBAAgB,8BAA8B,IAAI,QAAQ,CA6GzD;AAED,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,WAAW,EACtB,OAAO,GAAE,sBAA2B,GACnC,qBAAqB,CAs9JvB"}
@@ -22,8 +22,10 @@ const WORKBENCH_CSS = `
22
22
  .hyewb-field-icon{position:absolute;left:10px;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;color:#64748b;pointer-events:none;z-index:1}
23
23
  .hyewb-field-icon svg{width:14px;height:14px;stroke-width:2}
24
24
  .hyewb-field-with-icon .hyewb-select,.hyewb-field-with-icon .hyewb-number{padding-left:30px}
25
- .hyewb-shell{border:1px dashed #b8c3d1;border-radius:10px;background:#eef4ff;padding:10px;overflow-x:auto}
26
- .hyewb-canvas{position:relative;background:#fff;border:1px solid #d9e3ee;border-radius:8px;min-height:420px;padding:20px 24px;box-shadow:0 6px 16px rgba(15,23,42,.08);margin:0 auto}
25
+ .hyewb-shell{border:1px dashed #b8c3d1;border-radius:10px;background:#eef4ff;padding:10px;overflow-x:auto;overflow-y:auto;overscroll-behavior:auto;-webkit-overflow-scrolling:touch}
26
+ .hyewb-preview-layout{display:grid;grid-template-columns:minmax(0,1fr);gap:12px;align-items:start}
27
+ .hyewb-preview-layout.with-annotation-panel{grid-template-columns:minmax(0,1fr) 300px}
28
+ .hyewb-canvas{position:relative;background:#fff;border:1px solid #d9e3ee;border-radius:8px;min-height:420px;padding:20px 24px;box-shadow:0 6px 16px rgba(15,23,42,.08);margin:0 auto;max-width:100%;box-sizing:border-box}
27
29
  .hyewb-editor{min-height:360px;outline:none;line-height:1.7;font-size:16px;color:#1e293b}
28
30
  .hyewb-float{position:fixed;z-index:9999;display:none;align-items:center;gap:6px;padding:6px 8px;border-radius:10px;border:1px solid #0f172a;background:rgba(15,23,42,.96);box-shadow:0 10px 24px rgba(15,23,42,.22)}
29
31
  .hyewb-float.show{display:inline-flex}
@@ -81,18 +83,20 @@ const WORKBENCH_CSS = `
81
83
  .hyewb-table-picker-row{display:flex;align-items:center;gap:8px}
82
84
  .hyewb-table-picker-confirm{border:1px solid #0b7285;background:#0b7285;color:#fff;border-radius:8px;height:32px;padding:0 10px;cursor:pointer}
83
85
  .hyewb-table-tools{display:none !important}
84
- .hyewb-table-tools.show{display:none !important}
86
+ .hyewb-table-tools.show{display:flex !important}
85
87
  .hyewb-table-tools .hyewb-btn{width:30px;height:30px;border-color:#334155;background:#1e293b;color:#f8fafc}
86
88
  .hyewb-table-tools .hyewb-number{width:68px;height:30px;padding:4px 6px;border-color:#334155;background:#1e293b;color:#f8fafc}
87
89
  .hyewb-table-tools label{font-size:12px;color:#e2e8f0;display:inline-flex;align-items:center;gap:4px}
88
- .hyewb-table-row-delete{position:fixed;display:none;z-index:10026;width:30px;height:30px;border:1px solid #ef4444;background:#fff;color:#dc2626;border-radius:999px;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 8px 18px rgba(15,23,42,.2);padding:0}
90
+ .hyewb-table-row-delete{position:fixed;display:none;z-index:10026;width:20px;height:20px;border:1px solid #ef4444;background:#fff;color:#dc2626;border-radius:999px;align-items:center;justify-content:center;cursor:pointer;box-shadow:0 8px 18px rgba(15,23,42,.2);padding:0}
89
91
  .hyewb-table-row-delete.show{display:inline-flex}
90
92
  .hyewb-table-row-delete::before{content:"";position:absolute;left:-14px;top:-8px;width:20px;height:44px}
91
- .hyewb-table-handle{position:fixed;display:none;z-index:10030;width:24px;height:24px;border-radius:999px;border:1px solid #0284c7;background:#ffffff;color:#0369a1;align-items:center;justify-content:center;cursor:grab;box-shadow:0 8px 18px rgba(15,23,42,.2);padding:0}
93
+ .hyewb-table-handle{position:fixed;display:none;z-index:10030;width:16px;height:16px;border-radius:999px;border:1px solid #0284c7;background:#ffffff;color:#0369a1;align-items:center;justify-content:center;cursor:grab;box-shadow:0 8px 18px rgba(15,23,42,.2);padding:0}
92
94
  .hyewb-table-handle.show{display:inline-flex}
93
95
  .hyewb-table-handle:active{cursor:grabbing}
94
96
  .hyewb-table-handle.scale{cursor:nwse-resize}
95
97
  .hyewb-table-handle.scale:active{cursor:nwse-resize}
98
+ .hyewb-table-handle .hyewb-icon,.hyewb-table-row-delete .hyewb-icon{display:inline-flex;align-items:center;justify-content:center}
99
+ .hyewb-table-handle svg,.hyewb-table-row-delete svg{width:8px;height:8px;stroke-width:2.25;flex:0 0 auto}
96
100
  .hyewb-table-edge-layer{position:fixed;display:none;z-index:10028;pointer-events:none}
97
101
  .hyewb-table-edge-layer.show{display:block}
98
102
  .hyewb-table-edge-hit{position:absolute;display:flex;align-items:center;justify-content:center;border:1px solid #cbd5e1;background:#f8fafc;color:#0369a1;border-radius:0px;cursor:grab;pointer-events:auto;line-height:1;font-size:12px;padding:0}
@@ -100,7 +104,7 @@ const WORKBENCH_CSS = `
100
104
  .hyewb-table-edge-hit.active{border-color:#0284c7;background:#e0f2fe;color:#0c4a6e}
101
105
  .hyewb-table-edge-layer.col .hyewb-table-edge-hit{height:14px}
102
106
  .hyewb-table-edge-layer.row .hyewb-table-edge-hit{width:14px}
103
- .hyewb-table-gap-insert{position:fixed;display:none;z-index:10029;width:20px;height:20px;border-radius:999px;border:1px solid #0284c7;background:#ffffff;color:#0369a1;cursor:pointer;align-items:center;justify-content:center;padding:0;font-size:14px;line-height:1;box-shadow:0 8px 18px rgba(15,23,42,.2)}
107
+ .hyewb-table-gap-insert{position:fixed;display:none;z-index:10029;width:18px;height:18px;border-radius:999px;border:1px solid #0284c7;background:#ffffff;color:#0369a1;cursor:pointer;align-items:center;justify-content:center;padding:0;font-size:14px;line-height:1;box-shadow:0 8px 18px rgba(15,23,42,.2)}
104
108
  .hyewb-table-gap-insert.show{display:inline-flex}
105
109
  .hyewb-table-drop-indicator{position:fixed;display:none;z-index:10024;background:#0284c7;pointer-events:none;border-radius:999px}
106
110
  .hyewb-table-drop-indicator.show{display:block}
@@ -137,6 +141,31 @@ const WORKBENCH_CSS = `
137
141
  .hyewb-upload-progress{height:8px;background:#e2e8f0;border-radius:999px;overflow:hidden}
138
142
  .hyewb-upload-progress > span{display:block;height:100%;width:0;background:#0b7285;transition:width .18s ease}
139
143
  .hyewb-upload-error{min-height:18px;font-size:12px;color:#b91c1c;margin:0}
144
+ .hyewb-preview-highlight-mark{background:var(--hyewb-highlight-color,#fff59d);color:inherit;padding:0 .08em;border-radius:.2em}
145
+ .hyewb-preview-note-mark{background:rgba(14,116,144,.14);border-bottom:2px solid #0e7490;color:inherit;padding:0 .04em;border-radius:.2em}
146
+ .hyewb-preview-annotation-panel{display:none;align-content:start;gap:10px;border:1px solid #d8e0ea;border-radius:10px;background:#ffffff;padding:12px;max-height:100%;overflow:auto}
147
+ .hyewb-preview-annotation-title{margin:0;font-size:14px}
148
+ .hyewb-preview-annotation-tabs{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}
149
+ .hyewb-preview-annotation-tab{width:auto;min-width:0;height:32px;padding:0 12px;font-size:13px;border:1px solid #cbd5e1;border-radius:8px;background:#fff;color:#0f172a;cursor:pointer}
150
+ .hyewb-preview-annotation-tab.active{color:#ffffff;background:#0b7285;border-color:#0b7285}
151
+ .hyewb-preview-annotation-list{margin:0;padding:0;list-style:none;display:grid;gap:8px}
152
+ .hyewb-preview-annotation-item{width:100%;text-align:left;display:grid;gap:4px;padding:8px 36px 8px 8px;border-radius:8px;border:1px solid #dce5ef;background:#f8fafc;position:relative;cursor:pointer}
153
+ .hyewb-preview-annotation-item:hover{background:#eef6ff;border-color:#b9d7f4}
154
+ .hyewb-preview-annotation-item-title{font-size:12px;color:#0f766e}
155
+ .hyewb-preview-annotation-item-body{font-size:13px;color:#1e293b;line-height:1.5}
156
+ .hyewb-preview-annotation-delete{position:absolute;right:8px;top:8px;border-radius:8px;border:1px solid #f1b3b3;background:#fff5f5;color:#b42318;width:24px;height:24px;display:inline-flex;align-items:center;justify-content:center;opacity:0;pointer-events:none;transition:opacity .15s ease;cursor:pointer;padding:0}
157
+ .hyewb-preview-annotation-item:hover .hyewb-preview-annotation-delete{opacity:1;pointer-events:auto}
158
+ .hyewb-preview-annotation-delete svg{width:14px;height:14px}
159
+ .hyewb-preview-annotation-empty{margin:0;color:#64748b;font-size:13px}
160
+ .hyewb-preview-selection-toolbar{position:fixed;z-index:10060;display:none;align-items:center;gap:8px;padding:6px 8px;border-radius:10px;border:1px solid #0f172a;background:rgba(15,23,42,.96);box-shadow:0 10px 24px rgba(15,23,42,.22);transform:translate(-50%,-100%)}
161
+ .hyewb-preview-selection-toolbar.show{display:inline-flex}
162
+ .hyewb-preview-selection-btn{width:32px;height:32px;padding:0;display:inline-flex;align-items:center;justify-content:center;border:1px solid #334155;background:#1e293b;color:#f8fafc;border-radius:8px;cursor:pointer}
163
+ .hyewb-preview-selection-btn:hover{background:#334155}
164
+ .hyewb-preview-selection-btn svg{width:16px;height:16px}
165
+ .hyewb-preview-annotation-flash{animation:hyewb-preview-annotation-pulse .9s ease}
166
+ @keyframes hyewb-preview-annotation-pulse{0%{box-shadow:0 0 0 0 rgba(14,116,144,.4)}100%{box-shadow:0 0 0 12px rgba(14,116,144,0)}}
167
+ .hyewb-root:fullscreen .hyewb-editor,.hyewb-root:fullscreen .hyewb-preview{min-height:1000px}
168
+ .hyewb-root:-webkit-full-screen .hyewb-editor,.hyewb-root:-webkit-full-screen .hyewb-preview{min-height:1000px}
140
169
  `;
141
170
  export const DEFAULT_EDITOR_WORKBENCH_FEATURES = {
142
171
  textToolbar: true,
@@ -155,19 +184,25 @@ const WORKBENCH_ICON_PATHS = {
155
184
  bold: '<path d="M7 4h6a3 3 0 0 1 0 6H7z"/><path d="M7 10h7a3 3 0 0 1 0 6H7z"/>',
156
185
  "chevron-left": '<polyline points="15 18 9 12 15 6"/>',
157
186
  "chevron-right": '<polyline points="9 18 15 12 9 6"/>',
187
+ "fullscreen-enter": '<polyline points="9 3 3 3 3 9"/><line x1="3" y1="3" x2="10" y2="10"/><polyline points="15 3 21 3 21 9"/><line x1="14" y1="10" x2="21" y2="3"/><polyline points="3 15 3 21 9 21"/><line x1="3" y1="21" x2="10" y2="14"/><polyline points="15 21 21 21 21 15"/><line x1="14" y1="14" x2="21" y2="21"/>',
188
+ "fullscreen-exit": '<polyline points="10 14 10 21 3 21"/><line x1="10" y1="14" x2="3" y2="21"/><polyline points="14 10 21 10 21 3"/><line x1="14" y1="10" x2="21" y2="3"/><polyline points="3 9 3 3 9 3"/><line x1="3" y1="3" x2="10" y2="10"/><polyline points="21 15 21 21 15 21"/><line x1="21" y1="21" x2="14" y2="14"/>',
189
+ highlighter: '<path d="M15 2l7 7-8.5 8.5-7-7z"/><path d="M12.5 11.5L2 22l6-1.5 6-6"/><line x1="13" y1="4" x2="20" y2="11"/>',
158
190
  "file-up": '<path d="M14 2H8a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><line x1="12" y1="18" x2="12" y2="12"/><polyline points="9.5 14.5 12 12 14.5 14.5"/>',
159
191
  image: '<rect x="3" y="5" width="18" height="14" rx="2"/><circle cx="9" cy="10" r="1.5"/><path d="M6 17l4-4 3 3 3-2 2 3"/>',
160
192
  italic: '<line x1="10" y1="4" x2="16" y2="4"/><line x1="8" y1="20" x2="14" y2="20"/><line x1="14" y1="4" x2="10" y2="20"/>',
161
193
  "indent-increase": '<line x1="4" y1="6" x2="14" y2="6"/><line x1="4" y1="10" x2="10" y2="10"/><line x1="4" y1="14" x2="14" y2="14"/><line x1="4" y1="18" x2="10" y2="18"/><polyline points="15 9 19 12 15 15"/>',
162
194
  move: '<polyline points="8 4 12 1 16 4"/><line x1="12" y1="1" x2="12" y2="23"/><polyline points="8 20 12 23 16 20"/><polyline points="4 8 1 12 4 16"/><line x1="1" y1="12" x2="23" y2="12"/><polyline points="20 8 23 12 20 16"/>',
195
+ "notebook-pen": '<path d="M4 5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2z"/><line x1="8" y1="7" x2="14" y2="7"/><line x1="8" y1="11" x2="14" y2="11"/><path d="M14.5 16.5l4-4 2 2-4 4-2.5.5z"/>',
163
196
  "paint-bucket": '<path d="M7 10l6-6 7 7-6 6z"/><path d="M12 5l7 7"/><path d="M5 19a2 2 0 1 0 4 0c0-1-1-2-2-3-1 1-2 2-2 3z"/>',
164
197
  palette: '<path d="M12 3a9 9 0 1 0 0 18h1.5a2.5 2.5 0 0 0 0-5h-1a2 2 0 0 1 0-4H16a5 5 0 0 0-4-9z"/><circle cx="7.5" cy="10" r="1"/><circle cx="10" cy="7.5" r="1"/><circle cx="14" cy="7.5" r="1"/>',
165
198
  pilcrow: '<path d="M9 4h7v16"/><path d="M12 4v16"/><path d="M9 4a4 4 0 0 0 0 8h3"/>',
199
+ "resize-se": '<polyline points="11 13 13 13 13 11"/><line x1="10" y1="14" x2="14" y2="10"/><polyline points="15 19 19 19 19 15"/><line x1="14" y1="20" x2="20" y2="14"/>',
166
200
  "square-plus": '<rect x="3" y="3" width="18" height="18" rx="2"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/>',
167
201
  strikethrough: '<path d="M16 5a4 4 0 0 0-8 0c0 2.2 2 3.2 4 4"/><path d="M8 19a4 4 0 0 0 8 0c0-2.2-2-3.2-4-4"/><line x1="4" y1="12" x2="20" y2="12"/>',
168
- subscript: '<path d="M4 6l6 8 6-8"/><line x1="16" y1="20" x2="20" y2="20"/><line x1="18" y1="18" x2="18" y2="22"/>',
169
- superscript: '<path d="M4 18l6-8 6 8"/><line x1="16" y1="4" x2="20" y2="4"/><line x1="18" y1="2" x2="18" y2="6"/>',
202
+ subscript: '<line x1="4" y1="8" x2="10" y2="16"/><line x1="10" y1="8" x2="4" y2="16"/><path d="M14 15c0-1.1.9-2 2-2s2 .9 2 2c0 .7-.3 1.2-.9 1.7L14 20h4"/>',
203
+ superscript: '<line x1="4" y1="8" x2="10" y2="16"/><line x1="10" y1="8" x2="4" y2="16"/><path d="M14 6c0-1.1.9-2 2-2s2 .9 2 2c0 .7-.3 1.2-.9 1.7L14 11h4"/>',
170
204
  table: '<rect x="3" y="5" width="18" height="14" rx="1"/><line x1="3" y1="10" x2="21" y2="10"/><line x1="9" y1="5" x2="9" y2="19"/><line x1="15" y1="5" x2="15" y2="19"/>',
205
+ "trash-2": '<polyline points="3 6 5 6 21 6"/><path d="M8 6V4a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v2"/><path d="M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/>',
171
206
  underline: '<path d="M6 4v6a6 6 0 0 0 12 0V4"/><line x1="4" y1="20" x2="20" y2="20"/>',
172
207
  video: '<rect x="3" y="6" width="14" height="12" rx="2"/><polygon points="10 10 10 14 13 12"/><path d="M17 10l4-2v8l-4-2z"/>',
173
208
  x: '<line x1="6" y1="6" x2="18" y2="18"/><line x1="18" y1="6" x2="6" y2="18"/>',
@@ -191,6 +226,7 @@ const WORKBENCH_BUTTONS = {
191
226
  alignCenter: { icon: "align-center", ariaLabel: "居中" },
192
227
  alignRight: { icon: "align-right", ariaLabel: "右对齐" },
193
228
  alignJustify: { icon: "align-justify", ariaLabel: "两端对齐" },
229
+ fullscreen: { icon: "fullscreen-enter", ariaLabel: "全屏" },
194
230
  close: { icon: "x", ariaLabel: "关闭" },
195
231
  };
196
232
  const COLOR_SWATCHES = [
@@ -265,6 +301,17 @@ const RESIZE_HANDLES = [
265
301
  "sw",
266
302
  "w",
267
303
  ];
304
+ const DEFAULT_FLOW_BLOCK_WIDTH = 620;
305
+ function resolvePreviewAnnotationOptions(options) {
306
+ return {
307
+ showAnnotationList: options?.showAnnotationList ?? true,
308
+ enablePreviewSelectionToolbar: options?.enablePreviewSelectionToolbar ?? true,
309
+ annotationListDefaultTab: options?.annotationListDefaultTab === "notes" ? "notes" : "highlights",
310
+ annotationActions: {
311
+ canDelete: options?.annotationActions?.canDelete ?? true,
312
+ },
313
+ };
314
+ }
268
315
  export function createDefaultWorkbenchDocument() {
269
316
  const now = new Date().toISOString();
270
317
  return {
@@ -385,6 +432,7 @@ export function mountEditorWorkbench(container, options = {}) {
385
432
  doc: editor.getDocument(),
386
433
  features: resolveFeatures(options.features),
387
434
  };
435
+ const previewAnnotationOptions = resolvePreviewAnnotationOptions(options.previewAnnotations);
388
436
  const paperWidth = resolvePaperWidth(options.pageWidth, state.doc.settings.page.width);
389
437
  const root = document.createElement("section");
390
438
  root.className = "hyewb-root";
@@ -393,6 +441,7 @@ export function mountEditorWorkbench(container, options = {}) {
393
441
  const title = document.createElement("div");
394
442
  title.className = "hyewb-title";
395
443
  title.textContent = options.title ?? "hy-ebook Editor Workbench";
444
+ const fullscreenBtn = createButton("fullscreen");
396
445
  header.append(title);
397
446
  const toolbar = document.createElement("div");
398
447
  toolbar.className = "hyewb-row";
@@ -611,7 +660,7 @@ export function mountEditorWorkbench(container, options = {}) {
611
660
  tableScaleHandle.className = "hyewb-table-handle scale";
612
661
  tableScaleHandle.title = "按比例缩放表格";
613
662
  tableScaleHandle.setAttribute("aria-label", "按比例缩放表格");
614
- tableScaleHandle.append(createIconPlaceholder("table"));
663
+ tableScaleHandle.append(createIconPlaceholder("resize-se"));
615
664
  const tableColEdgeLayer = document.createElement("div");
616
665
  tableColEdgeLayer.className = "hyewb-table-edge-layer col";
617
666
  const tableRowEdgeLayer = document.createElement("div");
@@ -662,7 +711,10 @@ export function mountEditorWorkbench(container, options = {}) {
662
711
  const addPageBtn = createButton("addPage");
663
712
  const pageInfo = document.createElement("span");
664
713
  pageInfo.className = "hyewb-status";
665
- pageRow.append(prevPageBtn, nextPageBtn, addPageBtn, pageInfo);
714
+ fullscreenBtn.style.marginLeft = "auto";
715
+ pageRow.append(prevPageBtn, nextPageBtn, addPageBtn, pageInfo, fullscreenBtn);
716
+ const previewLayout = document.createElement("div");
717
+ previewLayout.className = "hyewb-preview-layout";
666
718
  const shell = document.createElement("div");
667
719
  shell.className = "hyewb-shell";
668
720
  const canvas = document.createElement("div");
@@ -687,6 +739,43 @@ export function mountEditorWorkbench(container, options = {}) {
687
739
  mediaList.className = "hyewb-media-list";
688
740
  canvas.append(editorArea, previewArea, mediaStage, inlineResizeOverlay, mediaList);
689
741
  shell.append(canvas);
742
+ const annotationPanel = document.createElement("aside");
743
+ annotationPanel.className = "hyewb-preview-annotation-panel";
744
+ const annotationTitle = document.createElement("h3");
745
+ annotationTitle.className = "hyewb-preview-annotation-title";
746
+ annotationTitle.textContent = "预览标注";
747
+ const annotationTabs = document.createElement("div");
748
+ annotationTabs.className = "hyewb-preview-annotation-tabs";
749
+ const annotationHighlightTab = document.createElement("button");
750
+ annotationHighlightTab.type = "button";
751
+ annotationHighlightTab.className = "hyewb-preview-annotation-tab";
752
+ annotationHighlightTab.textContent = "高亮";
753
+ const annotationNoteTab = document.createElement("button");
754
+ annotationNoteTab.type = "button";
755
+ annotationNoteTab.className = "hyewb-preview-annotation-tab";
756
+ annotationNoteTab.textContent = "笔记";
757
+ annotationTabs.append(annotationHighlightTab, annotationNoteTab);
758
+ const annotationList = document.createElement("ul");
759
+ annotationList.className = "hyewb-preview-annotation-list";
760
+ const annotationEmpty = document.createElement("p");
761
+ annotationEmpty.className = "hyewb-preview-annotation-empty";
762
+ annotationPanel.append(annotationTitle, annotationTabs, annotationList, annotationEmpty);
763
+ previewLayout.append(shell, annotationPanel);
764
+ const previewSelectionToolbar = document.createElement("div");
765
+ previewSelectionToolbar.className = "hyewb-preview-selection-toolbar";
766
+ const previewHighlightBtn = document.createElement("button");
767
+ previewHighlightBtn.type = "button";
768
+ previewHighlightBtn.className = "hyewb-preview-selection-btn";
769
+ previewHighlightBtn.title = "创建高亮";
770
+ previewHighlightBtn.setAttribute("aria-label", "创建高亮");
771
+ previewHighlightBtn.append(createIconPlaceholder("highlighter"));
772
+ const previewNoteBtn = document.createElement("button");
773
+ previewNoteBtn.type = "button";
774
+ previewNoteBtn.className = "hyewb-preview-selection-btn";
775
+ previewNoteBtn.title = "创建笔记";
776
+ previewNoteBtn.setAttribute("aria-label", "创建笔记");
777
+ previewNoteBtn.append(createIconPlaceholder("notebook-pen"));
778
+ previewSelectionToolbar.append(previewHighlightBtn, previewNoteBtn);
690
779
  const floatingToolbar = document.createElement("div");
691
780
  floatingToolbar.className = "hyewb-float";
692
781
  const floatBoldBtn = createButton("bold");
@@ -761,7 +850,7 @@ export function mountEditorWorkbench(container, options = {}) {
761
850
  const colorGrid = document.createElement("div");
762
851
  colorGrid.className = "hyewb-color-grid";
763
852
  colorPalette.append(colorGrid);
764
- root.append(header, toolbar, pageRow, shell, status, colorPalette, tablePicker, tableTools, tableRowDeleteBtn, tableMoveHandle, tableScaleHandle, tableColEdgeLayer, tableRowEdgeLayer, tableRowGapInsertBtn, tableColGapInsertBtn, tableDropIndicator, tableContextMenu, uploadBackdrop, docxInput);
853
+ root.append(header, toolbar, pageRow, previewLayout, status, colorPalette, tablePicker, tableTools, tableRowDeleteBtn, tableMoveHandle, tableScaleHandle, tableColEdgeLayer, tableRowEdgeLayer, tableRowGapInsertBtn, tableColGapInsertBtn, tableDropIndicator, tableContextMenu, uploadBackdrop, docxInput, previewSelectionToolbar);
765
854
  container.innerHTML = "";
766
855
  container.append(root, floatingToolbar);
767
856
  renderWorkbenchIcons();
@@ -787,6 +876,8 @@ export function mountEditorWorkbench(container, options = {}) {
787
876
  let tableContextTarget = null;
788
877
  let selectedTableRowIndex = null;
789
878
  let selectedTableColIndex = null;
879
+ let annotationActiveTab = previewAnnotationOptions.annotationListDefaultTab;
880
+ let previewSelectionDraft = null;
790
881
  const isTableGapInsertTarget = (node) => {
791
882
  if (!node) {
792
883
  return false;
@@ -802,6 +893,77 @@ export function mountEditorWorkbench(container, options = {}) {
802
893
  speedMbps: undefined,
803
894
  error: undefined,
804
895
  };
896
+ const setButtonIcon = (button, iconName) => {
897
+ button.innerHTML = "";
898
+ button.append(createIconPlaceholder(iconName));
899
+ };
900
+ const getFullscreenElement = () => {
901
+ const webkitDocument = document;
902
+ return (document.fullscreenElement ||
903
+ webkitDocument.webkitFullscreenElement ||
904
+ null);
905
+ };
906
+ const isWorkbenchFullscreen = () => {
907
+ return getFullscreenElement() === root;
908
+ };
909
+ const updateFullscreenButtonState = () => {
910
+ const isFullscreen = isWorkbenchFullscreen();
911
+ setButtonActive(fullscreenBtn, isFullscreen);
912
+ setButtonIcon(fullscreenBtn, isFullscreen ? "fullscreen-exit" : "fullscreen-enter");
913
+ const label = isFullscreen ? "退出全屏" : "全屏";
914
+ fullscreenBtn.title = label;
915
+ fullscreenBtn.setAttribute("aria-label", label);
916
+ };
917
+ const updateShellViewportHeight = () => {
918
+ const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
919
+ const top = root.getBoundingClientRect().top;
920
+ const bottomPadding = isWorkbenchFullscreen() ? 16 : 24;
921
+ const nextHeight = Math.max(320, Math.floor(viewportHeight - top - bottomPadding));
922
+ shell.style.maxHeight = `${nextHeight}px`;
923
+ };
924
+ const requestWorkbenchFullscreen = async () => {
925
+ const target = root;
926
+ if (typeof target.requestFullscreen === "function") {
927
+ await target.requestFullscreen();
928
+ return;
929
+ }
930
+ if (typeof target.webkitRequestFullscreen === "function") {
931
+ target.webkitRequestFullscreen();
932
+ return;
933
+ }
934
+ throw new Error("当前环境不支持全屏");
935
+ };
936
+ const exitWorkbenchFullscreen = async () => {
937
+ const webkitDocument = document;
938
+ if (typeof document.exitFullscreen === "function") {
939
+ await document.exitFullscreen();
940
+ return;
941
+ }
942
+ if (typeof webkitDocument.webkitExitFullscreen === "function") {
943
+ webkitDocument.webkitExitFullscreen();
944
+ return;
945
+ }
946
+ if (typeof webkitDocument.webkitCancelFullScreen === "function") {
947
+ webkitDocument.webkitCancelFullScreen();
948
+ }
949
+ };
950
+ const toggleWorkbenchFullscreen = async () => {
951
+ try {
952
+ if (isWorkbenchFullscreen()) {
953
+ await exitWorkbenchFullscreen();
954
+ }
955
+ else {
956
+ await requestWorkbenchFullscreen();
957
+ }
958
+ }
959
+ catch {
960
+ status.textContent = "当前浏览器不支持全屏";
961
+ }
962
+ finally {
963
+ updateFullscreenButtonState();
964
+ updateShellViewportHeight();
965
+ }
966
+ };
805
967
  const formatUploadProgress = (percent) => {
806
968
  const safe = Number.isFinite(percent) ? percent : 0;
807
969
  const clamped = Math.max(0, Math.min(100, safe));
@@ -947,7 +1109,7 @@ export function mountEditorWorkbench(container, options = {}) {
947
1109
  const mammothNamespace = await import("mammoth");
948
1110
  const mammothModule = resolveMammothModule(mammothNamespace);
949
1111
  const arrayBuffer = await file.arrayBuffer();
950
- const xmlPreferredFlowBlocks = await parseDocxFlowBlocksFromXml(arrayBuffer);
1112
+ const xmlPreferredFlowBlocks = await parseDocxFlowBlocksFromXml(arrayBuffer, resolveDocxImportMaxFlowBlockWidth(state.doc.settings.page, paperWidth));
951
1113
  if (xmlPreferredFlowBlocks?.length) {
952
1114
  syncEditorToDoc();
953
1115
  const nextDoc = clone(state.doc);
@@ -1050,6 +1212,7 @@ export function mountEditorWorkbench(container, options = {}) {
1050
1212
  syncEditorToDoc();
1051
1213
  state.mode = nextMode;
1052
1214
  hideFloatingToolbar();
1215
+ hidePreviewSelectionToolbar();
1053
1216
  render();
1054
1217
  };
1055
1218
  const loadPreviewAnnotations = () => {
@@ -1062,6 +1225,423 @@ export function mountEditorWorkbench(container, options = {}) {
1062
1225
  updateStateDoc(nextDoc);
1063
1226
  return clone(normalized);
1064
1227
  };
1228
+ const getCurrentPageId = () => {
1229
+ return state.doc.pages[state.pageIndex]?.id || `page-${state.pageIndex}`;
1230
+ };
1231
+ const normalizeOffsetRange = (start, end) => {
1232
+ const safeStart = Math.max(0, Math.floor(start || 0));
1233
+ const safeEnd = Math.max(0, Math.floor(end || 0));
1234
+ if (safeStart === safeEnd) {
1235
+ return null;
1236
+ }
1237
+ return safeStart < safeEnd
1238
+ ? { start: safeStart, end: safeEnd }
1239
+ : { start: safeEnd, end: safeStart };
1240
+ };
1241
+ const getNodeTextLength = (rootNode) => {
1242
+ if (!rootNode) {
1243
+ return 0;
1244
+ }
1245
+ const walker = document.createTreeWalker(rootNode, NodeFilter.SHOW_TEXT);
1246
+ let size = 0;
1247
+ while (walker.nextNode()) {
1248
+ size += walker.currentNode.textContent?.length || 0;
1249
+ }
1250
+ return size;
1251
+ };
1252
+ const getRangeTextLength = (range) => {
1253
+ if (!range) {
1254
+ return 0;
1255
+ }
1256
+ return getNodeTextLength(range.cloneContents());
1257
+ };
1258
+ const getSelectionOffsets = (containerNode, selection) => {
1259
+ if (!selection.rangeCount) {
1260
+ return null;
1261
+ }
1262
+ const range = selection.getRangeAt(0);
1263
+ if (!containerNode.contains(range.startContainer) ||
1264
+ !containerNode.contains(range.endContainer)) {
1265
+ return null;
1266
+ }
1267
+ const beforeStart = range.cloneRange();
1268
+ beforeStart.selectNodeContents(containerNode);
1269
+ beforeStart.setEnd(range.startContainer, range.startOffset);
1270
+ const beforeEnd = range.cloneRange();
1271
+ beforeEnd.selectNodeContents(containerNode);
1272
+ beforeEnd.setEnd(range.endContainer, range.endOffset);
1273
+ return normalizeOffsetRange(getRangeTextLength(beforeStart), getRangeTextLength(beforeEnd));
1274
+ };
1275
+ const createRangeFromOffsets = (containerNode, start, end) => {
1276
+ const normalized = normalizeOffsetRange(start, end);
1277
+ if (!normalized) {
1278
+ return null;
1279
+ }
1280
+ const range = document.createRange();
1281
+ const walker = document.createTreeWalker(containerNode, NodeFilter.SHOW_TEXT);
1282
+ let cursor = 0;
1283
+ let startNode = null;
1284
+ let endNode = null;
1285
+ let startOffset = 0;
1286
+ let endOffset = 0;
1287
+ let lastTextNode = null;
1288
+ while (walker.nextNode()) {
1289
+ const node = walker.currentNode;
1290
+ const length = node.textContent?.length || 0;
1291
+ lastTextNode = node;
1292
+ const nextCursor = cursor + length;
1293
+ if (!startNode &&
1294
+ normalized.start >= cursor &&
1295
+ normalized.start < nextCursor) {
1296
+ startNode = node;
1297
+ startOffset = normalized.start - cursor;
1298
+ }
1299
+ if (!endNode && normalized.end > cursor && normalized.end <= nextCursor) {
1300
+ endNode = node;
1301
+ endOffset = normalized.end - cursor;
1302
+ break;
1303
+ }
1304
+ cursor = nextCursor;
1305
+ }
1306
+ if (!startNode && lastTextNode && normalized.start === cursor) {
1307
+ startNode = lastTextNode;
1308
+ startOffset = lastTextNode.textContent?.length || 0;
1309
+ }
1310
+ if (!endNode && lastTextNode && normalized.end === cursor) {
1311
+ endNode = lastTextNode;
1312
+ endOffset = lastTextNode.textContent?.length || 0;
1313
+ }
1314
+ if (!startNode || !endNode) {
1315
+ return null;
1316
+ }
1317
+ range.setStart(startNode, startOffset);
1318
+ range.setEnd(endNode, endOffset);
1319
+ return range;
1320
+ };
1321
+ const unwrapPreviewMarks = (containerNode, kinds) => {
1322
+ const selector = kinds
1323
+ .map((kind) => `mark[data-preview-annotation='${kind}']`)
1324
+ .join(",");
1325
+ if (!selector) {
1326
+ return;
1327
+ }
1328
+ const marks = Array.from(containerNode.querySelectorAll(selector));
1329
+ marks.forEach((mark) => {
1330
+ const parent = mark.parentNode;
1331
+ if (!parent) {
1332
+ return;
1333
+ }
1334
+ while (mark.firstChild) {
1335
+ parent.insertBefore(mark.firstChild, mark);
1336
+ }
1337
+ parent.removeChild(mark);
1338
+ });
1339
+ };
1340
+ const applyHighlightMarks = (containerNode, highlights) => {
1341
+ unwrapPreviewMarks(containerNode, ["highlight"]);
1342
+ [...highlights]
1343
+ .filter((item) => Number.isFinite(item.start) && Number.isFinite(item.end))
1344
+ .sort((a, b) => b.start - a.start)
1345
+ .forEach((item) => {
1346
+ const range = createRangeFromOffsets(containerNode, item.start, item.end);
1347
+ if (!range || range.collapsed) {
1348
+ return;
1349
+ }
1350
+ const mark = document.createElement("mark");
1351
+ mark.dataset.previewAnnotation = "highlight";
1352
+ mark.className = "hyewb-preview-highlight-mark";
1353
+ mark.style.setProperty("--hyewb-highlight-color", item.color || "#fff59d");
1354
+ try {
1355
+ range.surroundContents(mark);
1356
+ }
1357
+ catch {
1358
+ const fragment = range.extractContents();
1359
+ mark.append(fragment);
1360
+ range.insertNode(mark);
1361
+ }
1362
+ });
1363
+ };
1364
+ const applyNoteMarks = (containerNode, notes) => {
1365
+ unwrapPreviewMarks(containerNode, ["note"]);
1366
+ [...notes]
1367
+ .filter((item) => Number.isFinite(item.start) && Number.isFinite(item.end))
1368
+ .sort((a, b) => b.start - a.start)
1369
+ .forEach((item) => {
1370
+ const range = createRangeFromOffsets(containerNode, item.start, item.end);
1371
+ if (!range || range.collapsed) {
1372
+ return;
1373
+ }
1374
+ const mark = document.createElement("mark");
1375
+ mark.dataset.previewAnnotation = "note";
1376
+ mark.dataset.previewNoteId = item.id || "";
1377
+ mark.className = "hyewb-preview-note-mark";
1378
+ try {
1379
+ range.surroundContents(mark);
1380
+ }
1381
+ catch {
1382
+ const fragment = range.extractContents();
1383
+ mark.append(fragment);
1384
+ range.insertNode(mark);
1385
+ }
1386
+ });
1387
+ };
1388
+ const mergeHighlightRecords = (records, draft) => {
1389
+ const normalized = normalizeOffsetRange(draft.start, draft.end);
1390
+ if (!normalized || !draft.pageId || !draft.blockId) {
1391
+ return records;
1392
+ }
1393
+ const targetColor = draft.color || "#fff59d";
1394
+ const sameBucket = [];
1395
+ const rest = [];
1396
+ records.forEach((item) => {
1397
+ if (item.pageId === draft.pageId &&
1398
+ item.blockId === draft.blockId &&
1399
+ (item.color || "#fff59d") === targetColor) {
1400
+ sameBucket.push(item);
1401
+ }
1402
+ else {
1403
+ rest.push(item);
1404
+ }
1405
+ });
1406
+ let mergedStart = normalized.start;
1407
+ let mergedEnd = normalized.end;
1408
+ const kept = [];
1409
+ sameBucket.forEach((item) => {
1410
+ const overlap = !(item.end < mergedStart || item.start > mergedEnd);
1411
+ if (overlap) {
1412
+ mergedStart = Math.min(mergedStart, item.start);
1413
+ mergedEnd = Math.max(mergedEnd, item.end);
1414
+ }
1415
+ else {
1416
+ kept.push(item);
1417
+ }
1418
+ });
1419
+ return [
1420
+ ...rest,
1421
+ ...kept,
1422
+ {
1423
+ ...draft,
1424
+ start: mergedStart,
1425
+ end: mergedEnd,
1426
+ },
1427
+ ];
1428
+ };
1429
+ const mergeNoteRecords = (records, draft) => {
1430
+ const index = records.findIndex((item) => {
1431
+ return (item.pageId === draft.pageId &&
1432
+ item.start === draft.start &&
1433
+ item.end === draft.end);
1434
+ });
1435
+ if (index === -1) {
1436
+ return [...records, draft];
1437
+ }
1438
+ const next = [...records];
1439
+ const existing = next[index];
1440
+ if (!existing) {
1441
+ return [...records, draft];
1442
+ }
1443
+ next[index] = {
1444
+ ...existing,
1445
+ ...draft,
1446
+ id: existing.id || draft.id,
1447
+ updatedAt: new Date().toISOString(),
1448
+ };
1449
+ return next;
1450
+ };
1451
+ const summarizePreviewText = (text, fallback, maxLength = 52) => {
1452
+ const raw = String(text || "")
1453
+ .replace(/\s+/g, " ")
1454
+ .trim();
1455
+ if (!raw) {
1456
+ return fallback;
1457
+ }
1458
+ return raw.length > maxLength ? `${raw.slice(0, maxLength)}...` : raw;
1459
+ };
1460
+ const flashPreviewAnnotationTarget = (node) => {
1461
+ if (!node || !node.classList) {
1462
+ return;
1463
+ }
1464
+ node.classList.remove("hyewb-preview-annotation-flash");
1465
+ void node.offsetWidth;
1466
+ node.classList.add("hyewb-preview-annotation-flash");
1467
+ window.setTimeout(() => {
1468
+ node.classList.remove("hyewb-preview-annotation-flash");
1469
+ }, 900);
1470
+ };
1471
+ const focusPreviewAnnotationRecord = (record, kind) => {
1472
+ let target = null;
1473
+ if (kind === "note" && "id" in record && record.id) {
1474
+ const noteId = String(record.id);
1475
+ target = previewArea.querySelector(`mark[data-preview-annotation='note'][data-preview-note-id='${noteId.replace(/["\\]/g, "\\$&")}']`);
1476
+ }
1477
+ if (!target) {
1478
+ const range = createRangeFromOffsets(previewArea, record.start, record.end);
1479
+ if (range && !range.collapsed) {
1480
+ const node = range.startContainer;
1481
+ target =
1482
+ node.nodeType === Node.ELEMENT_NODE
1483
+ ? node
1484
+ : node.parentElement;
1485
+ }
1486
+ }
1487
+ if (!target ||
1488
+ typeof target.scrollIntoView !== "function") {
1489
+ return;
1490
+ }
1491
+ target.scrollIntoView({
1492
+ behavior: "smooth",
1493
+ block: "center",
1494
+ inline: "nearest",
1495
+ });
1496
+ flashPreviewAnnotationTarget(target);
1497
+ };
1498
+ const getPreviewAnnotationsByPage = () => {
1499
+ const pageId = getCurrentPageId();
1500
+ const annotations = normalizePreviewAnnotations(state.doc.meta.previewAnnotations);
1501
+ return {
1502
+ annotations,
1503
+ pageId,
1504
+ highlights: annotations.highlights.filter((item) => item.pageId === pageId),
1505
+ notes: annotations.notes.filter((item) => item.pageId === pageId),
1506
+ };
1507
+ };
1508
+ const renderAnnotationPanel = () => {
1509
+ const shouldShowPanel = state.mode === "preview" &&
1510
+ state.features.preview &&
1511
+ previewAnnotationOptions.showAnnotationList;
1512
+ annotationPanel.style.display = shouldShowPanel ? "grid" : "none";
1513
+ previewLayout.classList.toggle("with-annotation-panel", shouldShowPanel);
1514
+ if (!shouldShowPanel) {
1515
+ return;
1516
+ }
1517
+ const { highlights, notes } = getPreviewAnnotationsByPage();
1518
+ annotationHighlightTab.classList.toggle("active", annotationActiveTab === "highlights");
1519
+ annotationNoteTab.classList.toggle("active", annotationActiveTab === "notes");
1520
+ const activeList = annotationActiveTab === "highlights" ? highlights : notes;
1521
+ annotationList.innerHTML = "";
1522
+ if (!activeList.length) {
1523
+ annotationEmpty.style.display = "block";
1524
+ annotationEmpty.textContent =
1525
+ annotationActiveTab === "highlights"
1526
+ ? "当前页暂无高亮"
1527
+ : "当前页暂无笔记";
1528
+ return;
1529
+ }
1530
+ annotationEmpty.style.display = "none";
1531
+ activeList.forEach((record, index) => {
1532
+ const item = document.createElement("li");
1533
+ const button = document.createElement("button");
1534
+ button.type = "button";
1535
+ button.className = "hyewb-preview-annotation-item";
1536
+ const heading = document.createElement("span");
1537
+ heading.className = "hyewb-preview-annotation-item-title";
1538
+ heading.textContent =
1539
+ annotationActiveTab === "highlights"
1540
+ ? `高亮 ${index + 1}`
1541
+ : `笔记 ${index + 1}`;
1542
+ const body = document.createElement("span");
1543
+ body.className = "hyewb-preview-annotation-item-body";
1544
+ const fallbackRange = `[${record.start}, ${record.end}]`;
1545
+ body.textContent =
1546
+ annotationActiveTab === "highlights"
1547
+ ? summarizePreviewText(record.selectedText, fallbackRange)
1548
+ : summarizePreviewText(record.text, "未命名笔记");
1549
+ button.append(heading, body);
1550
+ button.addEventListener("click", () => {
1551
+ focusPreviewAnnotationRecord(record, annotationActiveTab === "highlights" ? "highlight" : "note");
1552
+ });
1553
+ if (previewAnnotationOptions.annotationActions.canDelete) {
1554
+ const deleteButton = document.createElement("button");
1555
+ deleteButton.type = "button";
1556
+ deleteButton.className = "hyewb-preview-annotation-delete";
1557
+ deleteButton.title = "删除标注";
1558
+ deleteButton.setAttribute("aria-label", "删除标注");
1559
+ deleteButton.append(createIconPlaceholder("trash-2"));
1560
+ deleteButton.addEventListener("click", (event) => {
1561
+ event.preventDefault();
1562
+ event.stopPropagation();
1563
+ if (!window.confirm("确定删除这条标注吗?")) {
1564
+ return;
1565
+ }
1566
+ const current = normalizePreviewAnnotations(state.doc.meta.previewAnnotations);
1567
+ if (annotationActiveTab === "highlights") {
1568
+ current.highlights = current.highlights.filter((item) => {
1569
+ if ("id" in record && record.id && item.id) {
1570
+ return String(item.id) !== String(record.id);
1571
+ }
1572
+ return !(item.pageId === record.pageId &&
1573
+ item.start === record.start &&
1574
+ item.end === record.end);
1575
+ });
1576
+ }
1577
+ else {
1578
+ current.notes = current.notes.filter((item) => {
1579
+ if ("id" in record && record.id && item.id) {
1580
+ return String(item.id) !== String(record.id);
1581
+ }
1582
+ return !(item.pageId === record.pageId &&
1583
+ item.start === record.start &&
1584
+ item.end === record.end);
1585
+ });
1586
+ }
1587
+ savePreviewAnnotations(current);
1588
+ });
1589
+ button.append(deleteButton);
1590
+ }
1591
+ item.append(button);
1592
+ annotationList.append(item);
1593
+ });
1594
+ };
1595
+ const applyPreviewAnnotationsToView = () => {
1596
+ if (state.mode !== "preview") {
1597
+ previewSelectionToolbar.classList.remove("show");
1598
+ return;
1599
+ }
1600
+ const { highlights, notes } = getPreviewAnnotationsByPage();
1601
+ applyHighlightMarks(previewArea, highlights);
1602
+ applyNoteMarks(previewArea, notes);
1603
+ };
1604
+ const hidePreviewSelectionToolbar = () => {
1605
+ previewSelectionToolbar.classList.remove("show");
1606
+ previewSelectionDraft = null;
1607
+ };
1608
+ const syncPreviewSelectionToolbar = () => {
1609
+ if (!previewAnnotationOptions.enablePreviewSelectionToolbar ||
1610
+ state.mode !== "preview") {
1611
+ hidePreviewSelectionToolbar();
1612
+ return;
1613
+ }
1614
+ const selection = window.getSelection();
1615
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
1616
+ hidePreviewSelectionToolbar();
1617
+ return;
1618
+ }
1619
+ const range = selection.getRangeAt(0);
1620
+ if (!previewArea.contains(range.startContainer) ||
1621
+ !previewArea.contains(range.endContainer)) {
1622
+ hidePreviewSelectionToolbar();
1623
+ return;
1624
+ }
1625
+ const offsets = getSelectionOffsets(previewArea, selection);
1626
+ if (!offsets) {
1627
+ hidePreviewSelectionToolbar();
1628
+ return;
1629
+ }
1630
+ const rect = range.getBoundingClientRect();
1631
+ if (!rect.width && !rect.height) {
1632
+ hidePreviewSelectionToolbar();
1633
+ return;
1634
+ }
1635
+ previewSelectionDraft = {
1636
+ start: offsets.start,
1637
+ end: offsets.end,
1638
+ pageId: getCurrentPageId(),
1639
+ text: selection.toString().trim(),
1640
+ };
1641
+ previewSelectionToolbar.style.left = `${Math.round(rect.left + rect.width / 2)}px`;
1642
+ previewSelectionToolbar.style.top = `${Math.round(rect.top - 8)}px`;
1643
+ previewSelectionToolbar.classList.add("show");
1644
+ };
1065
1645
  const hideFloatingToolbar = () => {
1066
1646
  floatingToolbar.classList.remove("show");
1067
1647
  lastSelection = null;
@@ -1220,11 +1800,11 @@ export function mountEditorWorkbench(container, options = {}) {
1220
1800
  }
1221
1801
  cancelTableToolsHide();
1222
1802
  const tableRect = activeTableElement.getBoundingClientRect();
1223
- tableMoveHandle.style.left = `${Math.round(tableRect.left - 18)}px`;
1224
- tableMoveHandle.style.top = `${Math.round(tableRect.top - 18)}px`;
1803
+ tableMoveHandle.style.left = `${Math.round(tableRect.left - 14)}px`;
1804
+ tableMoveHandle.style.top = `${Math.round(tableRect.top - 14)}px`;
1225
1805
  tableMoveHandle.classList.add("show");
1226
- tableScaleHandle.style.left = `${Math.round(tableRect.right - 12)}px`;
1227
- tableScaleHandle.style.top = `${Math.round(tableRect.bottom - 12)}px`;
1806
+ tableScaleHandle.style.left = `${Math.round(tableRect.right - 10)}px`;
1807
+ tableScaleHandle.style.top = `${Math.round(tableRect.bottom - 10)}px`;
1228
1808
  tableScaleHandle.classList.add("show");
1229
1809
  tableColEdgeLayer.style.left = `${Math.round(tableRect.left)}px`;
1230
1810
  tableColEdgeLayer.style.top = `${Math.round(tableRect.top - 12)}px`;
@@ -1240,7 +1820,7 @@ export function mountEditorWorkbench(container, options = {}) {
1240
1820
  if (activeTableRowElement) {
1241
1821
  const rowRect = activeTableRowElement.getBoundingClientRect();
1242
1822
  tableRowDeleteBtn.style.left = `${Math.round(rowRect.right - 2)}px`;
1243
- tableRowDeleteBtn.style.top = `${Math.round(rowRect.top + rowRect.height / 2 - 14)}px`;
1823
+ tableRowDeleteBtn.style.top = `${Math.round(rowRect.top + rowRect.height / 2 - 10)}px`;
1244
1824
  tableRowDeleteBtn.classList.add("show");
1245
1825
  }
1246
1826
  else {
@@ -1587,8 +2167,8 @@ export function mountEditorWorkbench(container, options = {}) {
1587
2167
  return best;
1588
2168
  };
1589
2169
  const updateGapInsertButton = (hover) => {
1590
- const rowGapOffset = { x: -3, y: -10 };
1591
- const colGapOffset = { x: -10, y: -3 };
2170
+ const rowGapOffset = { x: -1, y: -9 };
2171
+ const colGapOffset = { x: -9, y: -1 };
1592
2172
  tableGapHover = hover;
1593
2173
  if (!hover || state.mode !== "editor") {
1594
2174
  tableRowGapInsertBtn.classList.remove("show");
@@ -1847,6 +2427,119 @@ export function mountEditorWorkbench(container, options = {}) {
1847
2427
  rememberedParagraphIndex = index;
1848
2428
  }
1849
2429
  };
2430
+ const normalizeVerticalAlignValue = (value) => {
2431
+ return String(value || "")
2432
+ .trim()
2433
+ .toLowerCase();
2434
+ };
2435
+ const detectScriptMarkFromElement = (element) => {
2436
+ let cursor = element;
2437
+ while (cursor && cursor !== editorArea) {
2438
+ const tagName = cursor.tagName.toLowerCase();
2439
+ if (tagName === "sup") {
2440
+ return "superscript";
2441
+ }
2442
+ if (tagName === "sub") {
2443
+ return "subscript";
2444
+ }
2445
+ const inlineAlign = normalizeVerticalAlignValue(cursor.style?.verticalAlign);
2446
+ if (inlineAlign === "super") {
2447
+ return "superscript";
2448
+ }
2449
+ if (inlineAlign === "sub") {
2450
+ return "subscript";
2451
+ }
2452
+ cursor = cursor.parentElement;
2453
+ }
2454
+ return null;
2455
+ };
2456
+ const doesRangeIntersectNode = (range, node) => {
2457
+ try {
2458
+ return range.intersectsNode(node);
2459
+ }
2460
+ catch {
2461
+ return false;
2462
+ }
2463
+ };
2464
+ const resolveScriptStateFromRange = (range) => {
2465
+ if (!range) {
2466
+ return { superscript: false, subscript: false };
2467
+ }
2468
+ const detectFromContainer = (container) => {
2469
+ const element = container.nodeType === Node.ELEMENT_NODE
2470
+ ? container
2471
+ : container.parentElement;
2472
+ return detectScriptMarkFromElement(element);
2473
+ };
2474
+ const fromStart = detectFromContainer(range.startContainer);
2475
+ const fromEnd = detectFromContainer(range.endContainer);
2476
+ const mark = fromStart || fromEnd;
2477
+ return {
2478
+ superscript: mark === "superscript",
2479
+ subscript: mark === "subscript",
2480
+ };
2481
+ };
2482
+ const getActiveEditorRange = () => {
2483
+ const selection = window.getSelection();
2484
+ if (selection &&
2485
+ selection.rangeCount > 0 &&
2486
+ editorArea.contains(selection.getRangeAt(0).startContainer) &&
2487
+ editorArea.contains(selection.getRangeAt(0).endContainer)) {
2488
+ return selection.getRangeAt(0);
2489
+ }
2490
+ return rememberedRange;
2491
+ };
2492
+ const clearScriptMarkInRange = (range, command) => {
2493
+ const selector = command === "superscript"
2494
+ ? 'sup,span[style*="vertical-align: super"],span[style*="vertical-align:super"]'
2495
+ : 'sub,span[style*="vertical-align: sub"],span[style*="vertical-align:sub"]';
2496
+ const rootNode = range.commonAncestorContainer.nodeType === Node.ELEMENT_NODE
2497
+ ? range.commonAncestorContainer
2498
+ : range.commonAncestorContainer.parentElement;
2499
+ if (!rootNode) {
2500
+ return;
2501
+ }
2502
+ const candidateSet = new Set();
2503
+ const addIfMatches = (element) => {
2504
+ let cursor = element;
2505
+ while (cursor && cursor !== editorArea) {
2506
+ if (cursor.matches(selector) && doesRangeIntersectNode(range, cursor)) {
2507
+ candidateSet.add(cursor);
2508
+ }
2509
+ cursor = cursor.parentElement;
2510
+ }
2511
+ };
2512
+ addIfMatches(range.startContainer.nodeType === Node.ELEMENT_NODE
2513
+ ? range.startContainer
2514
+ : range.startContainer.parentElement);
2515
+ addIfMatches(range.endContainer.nodeType === Node.ELEMENT_NODE
2516
+ ? range.endContainer
2517
+ : range.endContainer.parentElement);
2518
+ rootNode.querySelectorAll(selector).forEach((node) => {
2519
+ if (doesRangeIntersectNode(range, node)) {
2520
+ candidateSet.add(node);
2521
+ }
2522
+ });
2523
+ Array.from(candidateSet).forEach((element) => {
2524
+ const tagName = element.tagName.toLowerCase();
2525
+ if (tagName === "sup" || tagName === "sub") {
2526
+ const parent = element.parentNode;
2527
+ if (!parent) {
2528
+ return;
2529
+ }
2530
+ while (element.firstChild) {
2531
+ parent.insertBefore(element.firstChild, element);
2532
+ }
2533
+ parent.removeChild(element);
2534
+ return;
2535
+ }
2536
+ element.style.removeProperty("vertical-align");
2537
+ element.style.removeProperty("top");
2538
+ if (!element.getAttribute("style")?.trim()) {
2539
+ element.removeAttribute("style");
2540
+ }
2541
+ });
2542
+ };
1850
2543
  const syncToolbarState = () => {
1851
2544
  if (!state.features.textToolbar || state.mode !== "editor") {
1852
2545
  return;
@@ -1874,11 +2567,14 @@ export function mountEditorWorkbench(container, options = {}) {
1874
2567
  const strikeActive = canReflect
1875
2568
  ? safeQueryCommandState("strikeThrough")
1876
2569
  : false;
2570
+ const scriptState = canReflect
2571
+ ? resolveScriptStateFromRange(getActiveEditorRange())
2572
+ : { superscript: false, subscript: false };
1877
2573
  const superscriptActive = canReflect
1878
- ? safeQueryCommandState("superscript")
2574
+ ? safeQueryCommandState("superscript") || scriptState.superscript
1879
2575
  : false;
1880
2576
  const subscriptActive = canReflect
1881
- ? safeQueryCommandState("subscript")
2577
+ ? safeQueryCommandState("subscript") || scriptState.subscript
1882
2578
  : false;
1883
2579
  [boldBtn, floatBoldBtn].forEach((btn) => setButtonActive(btn, boldActive));
1884
2580
  [italicBtn, floatItalicBtn].forEach((btn) => setButtonActive(btn, italicActive));
@@ -2071,6 +2767,26 @@ export function mountEditorWorkbench(container, options = {}) {
2071
2767
  syncToolbarState();
2072
2768
  updateFloatingToolbarBySelection();
2073
2769
  };
2770
+ const toggleScriptMark = (command, activeMessage) => {
2771
+ withSelectionCommand(() => {
2772
+ const opposite = command === "superscript" ? "subscript" : "superscript";
2773
+ const activeRange = getActiveEditorRange();
2774
+ const currentScriptState = resolveScriptStateFromRange(activeRange);
2775
+ const wasActive = command === "superscript"
2776
+ ? currentScriptState.superscript
2777
+ : currentScriptState.subscript;
2778
+ if (activeRange) {
2779
+ clearScriptMarkInRange(activeRange, opposite);
2780
+ }
2781
+ if (wasActive) {
2782
+ if (activeRange) {
2783
+ clearScriptMarkInRange(activeRange, command);
2784
+ }
2785
+ return;
2786
+ }
2787
+ document.execCommand(command, false);
2788
+ }, activeMessage);
2789
+ };
2074
2790
  const applyColorFromPalette = (color) => {
2075
2791
  if (!activeColorTarget) {
2076
2792
  return;
@@ -2626,7 +3342,14 @@ export function mountEditorWorkbench(container, options = {}) {
2626
3342
  selectInlineImage(null);
2627
3343
  rememberedParagraphIndex = null;
2628
3344
  }
2629
- canvas.style.width = `${paperWidth}px`;
3345
+ if (state.mode === "preview") {
3346
+ canvas.style.width = "100%";
3347
+ canvas.style.maxWidth = `${paperWidth}px`;
3348
+ }
3349
+ else {
3350
+ canvas.style.width = `${paperWidth}px`;
3351
+ canvas.style.maxWidth = "100%";
3352
+ }
2630
3353
  previewArea.innerHTML = html;
2631
3354
  previewArea.classList.toggle("show", state.mode === "preview");
2632
3355
  editorArea.style.display = state.mode === "editor" ? "block" : "none";
@@ -2719,6 +3442,9 @@ export function mountEditorWorkbench(container, options = {}) {
2719
3442
  });
2720
3443
  updateInlineResizeOverlay();
2721
3444
  syncToolbarState();
3445
+ applyPreviewAnnotationsToView();
3446
+ renderAnnotationPanel();
3447
+ syncPreviewSelectionToolbar();
2722
3448
  };
2723
3449
  boldBtn.addEventListener("click", () => {
2724
3450
  withSelectionCommand(() => {
@@ -2741,14 +3467,10 @@ export function mountEditorWorkbench(container, options = {}) {
2741
3467
  }, "已应用删除线");
2742
3468
  });
2743
3469
  superscriptBtn.addEventListener("click", () => {
2744
- withSelectionCommand(() => {
2745
- document.execCommand("superscript", false);
2746
- }, "已应用上角标");
3470
+ toggleScriptMark("superscript", "已切换上角标");
2747
3471
  });
2748
3472
  subscriptBtn.addEventListener("click", () => {
2749
- withSelectionCommand(() => {
2750
- document.execCommand("subscript", false);
2751
- }, "已应用下角标");
3473
+ toggleScriptMark("subscript", "已切换下角标");
2752
3474
  });
2753
3475
  headingSelect.addEventListener("change", () => {
2754
3476
  const value = headingSelect.value === "h1" ||
@@ -2823,6 +3545,12 @@ export function mountEditorWorkbench(container, options = {}) {
2823
3545
  }, 140);
2824
3546
  };
2825
3547
  [
3548
+ boldBtn,
3549
+ italicBtn,
3550
+ underlineBtn,
3551
+ strikeBtn,
3552
+ superscriptBtn,
3553
+ subscriptBtn,
2826
3554
  floatBoldBtn,
2827
3555
  floatItalicBtn,
2828
3556
  floatUnderlineBtn,
@@ -2886,14 +3614,10 @@ export function mountEditorWorkbench(container, options = {}) {
2886
3614
  }, "已应用删除线");
2887
3615
  });
2888
3616
  floatSuperscriptBtn.addEventListener("click", () => {
2889
- withSelectionCommand(() => {
2890
- document.execCommand("superscript", false);
2891
- }, "已应用上角标");
3617
+ toggleScriptMark("superscript", "已切换上角标");
2892
3618
  });
2893
3619
  floatSubscriptBtn.addEventListener("click", () => {
2894
- withSelectionCommand(() => {
2895
- document.execCommand("subscript", false);
2896
- }, "已应用下角标");
3620
+ toggleScriptMark("subscript", "已切换下角标");
2897
3621
  });
2898
3622
  floatFontSizeSelect.addEventListener("change", () => {
2899
3623
  applyFontSize(floatFontSizeSelect.value);
@@ -2911,6 +3635,59 @@ export function mountEditorWorkbench(container, options = {}) {
2911
3635
  floatBgColorInput.addEventListener("click", () => {
2912
3636
  openColorPalette(floatBgColorInput, "background");
2913
3637
  });
3638
+ annotationHighlightTab.addEventListener("click", () => {
3639
+ annotationActiveTab = "highlights";
3640
+ renderAnnotationPanel();
3641
+ });
3642
+ annotationNoteTab.addEventListener("click", () => {
3643
+ annotationActiveTab = "notes";
3644
+ renderAnnotationPanel();
3645
+ });
3646
+ previewHighlightBtn.addEventListener("click", () => {
3647
+ if (!previewSelectionDraft) {
3648
+ return;
3649
+ }
3650
+ const current = normalizePreviewAnnotations(state.doc.meta.previewAnnotations);
3651
+ current.highlights = mergeHighlightRecords(current.highlights, {
3652
+ id: `hl-${Date.now()}-${Math.round(Math.random() * 10000)}`,
3653
+ pageId: previewSelectionDraft.pageId,
3654
+ blockId: "preview-flow-text",
3655
+ start: previewSelectionDraft.start,
3656
+ end: previewSelectionDraft.end,
3657
+ color: "#fff59d",
3658
+ selectedText: previewSelectionDraft.text,
3659
+ createdAt: new Date().toISOString(),
3660
+ });
3661
+ annotationActiveTab = "highlights";
3662
+ savePreviewAnnotations(current);
3663
+ window.getSelection()?.removeAllRanges();
3664
+ hidePreviewSelectionToolbar();
3665
+ });
3666
+ previewNoteBtn.addEventListener("click", () => {
3667
+ if (!previewSelectionDraft) {
3668
+ return;
3669
+ }
3670
+ const text = window.prompt("请输入笔记内容", previewSelectionDraft.text || "") || "";
3671
+ const noteText = text.trim();
3672
+ if (!noteText) {
3673
+ return;
3674
+ }
3675
+ const current = normalizePreviewAnnotations(state.doc.meta.previewAnnotations);
3676
+ current.notes = mergeNoteRecords(current.notes, {
3677
+ id: `note-${Date.now()}-${Math.round(Math.random() * 10000)}`,
3678
+ pageId: previewSelectionDraft.pageId,
3679
+ start: previewSelectionDraft.start,
3680
+ end: previewSelectionDraft.end,
3681
+ text: noteText,
3682
+ selectedText: previewSelectionDraft.text,
3683
+ color: "#0e7490",
3684
+ createdAt: new Date().toISOString(),
3685
+ });
3686
+ annotationActiveTab = "notes";
3687
+ savePreviewAnnotations(current);
3688
+ window.getSelection()?.removeAllRanges();
3689
+ hidePreviewSelectionToolbar();
3690
+ });
2914
3691
  floatLeftBtn.addEventListener("click", () => updateAlign("left"));
2915
3692
  floatCenterBtn.addEventListener("click", () => updateAlign("center"));
2916
3693
  floatRightBtn.addEventListener("click", () => updateAlign("right"));
@@ -3537,12 +4314,14 @@ export function mountEditorWorkbench(container, options = {}) {
3537
4314
  rememberActiveParagraphIndex();
3538
4315
  updateInlineResizeOverlay();
3539
4316
  updateFloatingToolbarBySelection();
4317
+ syncToolbarState();
3540
4318
  syncFocusedTableControls();
3541
4319
  });
3542
4320
  editorArea.addEventListener("keyup", () => {
3543
4321
  rememberActiveParagraphIndex();
3544
4322
  updateInlineResizeOverlay();
3545
4323
  updateFloatingToolbarBySelection();
4324
+ syncToolbarState();
3546
4325
  syncFocusedTableControls();
3547
4326
  });
3548
4327
  editorArea.addEventListener("blur", () => {
@@ -3563,20 +4342,25 @@ export function mountEditorWorkbench(container, options = {}) {
3563
4342
  updateInlineResizeOverlay();
3564
4343
  syncToolbarState();
3565
4344
  syncFocusedTableControls();
4345
+ syncPreviewSelectionToolbar();
3566
4346
  };
3567
4347
  const handleWindowResize = () => {
4348
+ updateShellViewportHeight();
3568
4349
  updateInlineResizeOverlay();
3569
4350
  syncTableToolsPosition();
3570
4351
  syncFocusedTableControls();
4352
+ syncPreviewSelectionToolbar();
3571
4353
  if (tablePicker.classList.contains("show")) {
3572
4354
  showTablePicker();
3573
4355
  }
3574
4356
  };
3575
4357
  const handleWindowScroll = () => {
4358
+ updateShellViewportHeight();
3576
4359
  updateInlineResizeOverlay();
3577
4360
  updateFloatingToolbarBySelection();
3578
4361
  syncTableToolsPosition();
3579
4362
  syncFocusedTableControls();
4363
+ syncPreviewSelectionToolbar();
3580
4364
  if (tablePicker.classList.contains("show")) {
3581
4365
  showTablePicker();
3582
4366
  }
@@ -3592,6 +4376,18 @@ export function mountEditorWorkbench(container, options = {}) {
3592
4376
  };
3593
4377
  window.addEventListener("resize", handleWindowResize);
3594
4378
  window.addEventListener("scroll", handleWindowScroll, true);
4379
+ const handleFullscreenChange = () => {
4380
+ window.requestAnimationFrame(() => {
4381
+ updateFullscreenButtonState();
4382
+ updateShellViewportHeight();
4383
+ updateInlineResizeOverlay();
4384
+ syncTableToolsPosition();
4385
+ syncFocusedTableControls();
4386
+ syncPreviewSelectionToolbar();
4387
+ });
4388
+ };
4389
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
4390
+ document.addEventListener("webkitfullscreenchange", handleFullscreenChange);
3595
4391
  document.addEventListener("selectionchange", handleSelectionChange);
3596
4392
  const handleDocumentPointerDownForPalette = (event) => {
3597
4393
  const clickTarget = event.target;
@@ -3663,6 +4459,9 @@ export function mountEditorWorkbench(container, options = {}) {
3663
4459
  };
3664
4460
  document.addEventListener("pointerdown", handleDocumentPointerDownForPalette);
3665
4461
  document.addEventListener("keydown", handleDocumentKeyDown);
4462
+ fullscreenBtn.addEventListener("click", () => {
4463
+ void toggleWorkbenchFullscreen();
4464
+ });
3666
4465
  tableAddBtn.addEventListener("mousedown", preserveSelectionForInputControl);
3667
4466
  videoAddBtn.addEventListener("mousedown", preserveSelectionForInputControl);
3668
4467
  videoAddBtn.addEventListener("click", () => {
@@ -3742,6 +4541,8 @@ export function mountEditorWorkbench(container, options = {}) {
3742
4541
  nextPageBtn.addEventListener("click", () => jumpPage(1));
3743
4542
  addPageBtn.addEventListener("click", addPage);
3744
4543
  render();
4544
+ updateFullscreenButtonState();
4545
+ updateShellViewportHeight();
3745
4546
  syncToolbarState();
3746
4547
  emitDocumentChange();
3747
4548
  return {
@@ -3755,6 +4556,8 @@ export function mountEditorWorkbench(container, options = {}) {
3755
4556
  window.removeEventListener("scroll", handleWindowScroll, true);
3756
4557
  document.removeEventListener("pointerdown", handleDocumentPointerDownForPalette);
3757
4558
  document.removeEventListener("keydown", handleDocumentKeyDown);
4559
+ document.removeEventListener("fullscreenchange", handleFullscreenChange);
4560
+ document.removeEventListener("webkitfullscreenchange", handleFullscreenChange);
3758
4561
  container.innerHTML = "";
3759
4562
  },
3760
4563
  getDocument: () => clone(state.doc),
@@ -4593,7 +5396,7 @@ function resolveMammothModule(namespace) {
4593
5396
  const candidate = namespace?.default ?? namespace;
4594
5397
  return candidate;
4595
5398
  }
4596
- async function parseDocxFlowBlocksFromXml(arrayBuffer) {
5399
+ async function parseDocxFlowBlocksFromXml(arrayBuffer, maxFlowBlockWidth) {
4597
5400
  try {
4598
5401
  const archive = await loadDocxArchive(arrayBuffer);
4599
5402
  if (!archive) {
@@ -4609,7 +5412,7 @@ async function parseDocxFlowBlocksFromXml(arrayBuffer) {
4609
5412
  const themeXml = await archive
4610
5413
  .file("word/theme/theme1.xml")
4611
5414
  ?.async("string");
4612
- return parseDocxBodyToFlowBlocks(documentXml, stylesXml, themeXml);
5415
+ return parseDocxBodyToFlowBlocks(documentXml, stylesXml, themeXml, maxFlowBlockWidth);
4613
5416
  }
4614
5417
  catch {
4615
5418
  return undefined;
@@ -4623,7 +5426,7 @@ async function loadDocxArchive(arrayBuffer) {
4623
5426
  }
4624
5427
  return zipRuntime.loadAsync(arrayBuffer);
4625
5428
  }
4626
- function parseDocxBodyToFlowBlocks(documentXml, stylesXml, themeXml) {
5429
+ function parseDocxBodyToFlowBlocks(documentXml, stylesXml, themeXml, maxFlowBlockWidth) {
4627
5430
  const parser = new DOMParser();
4628
5431
  const documentDoc = parser.parseFromString(documentXml, "application/xml");
4629
5432
  const body = findDescendantByLocalName(documentDoc.documentElement, "body");
@@ -4642,7 +5445,7 @@ function parseDocxBodyToFlowBlocks(documentXml, stylesXml, themeXml) {
4642
5445
  continue;
4643
5446
  }
4644
5447
  if (name === "tbl") {
4645
- blocks.push(parseDocxTableNode(child, index, styleContext));
5448
+ blocks.push(parseDocxTableNode(child, index, styleContext, maxFlowBlockWidth));
4646
5449
  index += 1;
4647
5450
  }
4648
5451
  }
@@ -4974,11 +5777,11 @@ function mergeTextMarks(...sources) {
4974
5777
  }
4975
5778
  return merged;
4976
5779
  }
4977
- function parseDocxTableNode(tableNode, index, styleContext) {
5780
+ function parseDocxTableNode(tableNode, index, styleContext, maxFlowBlockWidth) {
4978
5781
  const tableProps = childXmlNodeByLocalName(tableNode, "tblPr");
4979
5782
  const tableStyleId = xmlAttribute(childXmlNodeByLocalName(tableProps, "tblStyle"), "val");
4980
5783
  const tableTextStyle = resolveDocxStyle(styleContext, tableStyleId, "table");
4981
- const tableStyle = resolveDocxTableBlockStyle(tableProps, styleContext.themeColorMap);
5784
+ const tableStyleInfo = resolveDocxTableBlockStyle(tableProps, styleContext.themeColorMap, maxFlowBlockWidth);
4982
5785
  const tableBorders = resolveDocxTableBorderSet(tableProps, styleContext.themeColorMap);
4983
5786
  const rowNodes = Array.from(tableNode.children).filter((node) => {
4984
5787
  return xmlLocalName(node) === "tr";
@@ -5070,12 +5873,15 @@ function parseDocxTableNode(tableNode, index, styleContext) {
5070
5873
  cells,
5071
5874
  };
5072
5875
  });
5876
+ const tableWidth = tableStyleInfo.widthPx ??
5877
+ resolveFlowBlockWidthWithinBounds(maxFlowBlockWidth, DEFAULT_FLOW_BLOCK_WIDTH) ??
5878
+ DEFAULT_FLOW_BLOCK_WIDTH;
5073
5879
  return {
5074
5880
  id: `table-${Date.now()}-${index}`,
5075
5881
  type: "table",
5076
- rect: { x: 80, y: 80 + index * 24, width: 620, height: 120 },
5882
+ rect: { x: 80, y: 80 + index * 24, width: tableWidth, height: 120 },
5077
5883
  zIndex: index + 1,
5078
- ...(tableStyle ? { style: tableStyle } : {}),
5884
+ ...(tableStyleInfo.style ? { style: tableStyleInfo.style } : {}),
5079
5885
  rows: rows.length
5080
5886
  ? rows
5081
5887
  : [
@@ -5110,9 +5916,9 @@ function resolvePrimaryTextStyleFromParagraphs(paragraphs) {
5110
5916
  }
5111
5917
  return undefined;
5112
5918
  }
5113
- function resolveDocxTableBlockStyle(tableProps, themeColorMap) {
5919
+ function resolveDocxTableBlockStyle(tableProps, themeColorMap, maxFlowBlockWidth) {
5114
5920
  const borders = childXmlNodeByLocalName(tableProps, "tblBorders");
5115
- const tableWidth = resolveDocxTableWidthPx(tableProps);
5921
+ const tableWidth = resolveFlowBlockWidthWithinBounds(maxFlowBlockWidth, resolveDocxTableWidthPx(tableProps));
5116
5922
  const styleTokens = ["border-collapse:collapse"];
5117
5923
  const borderSides = [
5118
5924
  "top",
@@ -5132,7 +5938,36 @@ function resolveDocxTableBlockStyle(tableProps, themeColorMap) {
5132
5938
  if (tableWidth !== undefined) {
5133
5939
  styleTokens.push(`width:${tableWidth}px`);
5134
5940
  }
5135
- return styleTokens.join(";");
5941
+ return {
5942
+ style: styleTokens.join(";"),
5943
+ widthPx: tableWidth,
5944
+ };
5945
+ }
5946
+ function resolveFlowBlockWidthWithinBounds(maxWidth, preferredWidth) {
5947
+ const safeMax = Number.isFinite(maxWidth) && Number(maxWidth) > 0
5948
+ ? Math.max(1, Math.round(Number(maxWidth)))
5949
+ : undefined;
5950
+ const safePreferred = Number.isFinite(preferredWidth) && Number(preferredWidth) > 0
5951
+ ? Math.max(1, Math.round(Number(preferredWidth)))
5952
+ : undefined;
5953
+ if (safeMax === undefined) {
5954
+ return safePreferred;
5955
+ }
5956
+ if (safePreferred === undefined) {
5957
+ return safeMax;
5958
+ }
5959
+ return Math.min(safePreferred, safeMax);
5960
+ }
5961
+ function resolveDocxImportMaxFlowBlockWidth(page, fallbackPaperWidth) {
5962
+ const pageWidth = resolvePaperWidth(page.width, fallbackPaperWidth);
5963
+ const marginLeft = Number.isFinite(page.marginLeft)
5964
+ ? Math.max(0, page.marginLeft)
5965
+ : 0;
5966
+ const marginRight = Number.isFinite(page.marginRight)
5967
+ ? Math.max(0, page.marginRight)
5968
+ : 0;
5969
+ const availableWidth = Math.max(240, pageWidth - marginLeft - marginRight);
5970
+ return Math.min(DEFAULT_FLOW_BLOCK_WIDTH, availableWidth);
5136
5971
  }
5137
5972
  function resolveDocxTableCellStyle(cellProps, themeColorMap, rowHeight, tableBorders, gridPos) {
5138
5973
  if (!cellProps) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyebook/vue3-adapter",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Vue3 adapter for hy-ebook core",
5
5
  "repository": {
6
6
  "type": "git",
@@ -17,7 +17,7 @@
17
17
  "dist"
18
18
  ],
19
19
  "dependencies": {
20
- "@hyebook/core": "^0.2.0"
20
+ "@hyebook/core": "^0.2.2"
21
21
  },
22
22
  "scripts": {
23
23
  "build": "tsc -p tsconfig.json",