@8btc/mditor 0.0.7 → 0.0.9

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.
@@ -21,7 +21,6 @@ import {
21
21
  } from "../util/hasClosest";
22
22
  import { hasClosestByHeadings } from "../util/hasClosestByHeadings";
23
23
  import {
24
- getCursorPosition,
25
24
  getEditorRange,
26
25
  getSelectPosition,
27
26
  setRangeByWbr,
@@ -45,36 +44,93 @@ class WYSIWYG {
45
44
  public element: HTMLPreElement;
46
45
  public popover: HTMLDivElement;
47
46
  public selectPopover: HTMLDivElement;
47
+ public popoverInput: HTMLTextAreaElement;
48
+ public popoverSendBtn: HTMLButtonElement;
49
+ public selectionContent: string;
50
+ public selectionLines: [number, number];
48
51
  public afterRenderTimeoutId: number;
49
52
  public hlToolbarTimeoutId: number;
50
53
  public preventInput: boolean;
51
54
  public composingLock = false;
52
55
  public commentIds: string[] = [];
56
+ private vditor: IVditor;
53
57
  private scrollListener: () => void;
54
58
 
55
59
  constructor(vditor: IVditor) {
60
+ this.vditor = vditor;
56
61
  const divElement = document.createElement("div");
57
62
  divElement.className = "vditor-wysiwyg";
58
- if (vditor.options.lineNumber) {
63
+ if (vditor.options.lineNumber?.enable) {
59
64
  divElement.classList.add("vditor--linenumber");
60
65
  }
61
66
 
62
67
  divElement.innerHTML = `<pre class="vditor-reset" placeholder="${vditor.options.placeholder}"
63
68
  contenteditable="true" spellcheck="false"></pre>
64
69
  <div class="vditor-panel vditor-panel--none"></div>
65
- <div class="vditor-panel vditor-panel--none">
66
- <button type="button" aria-label="${window.VditorI18n.comment}" class="vditor-icon vditor-tooltipped vditor-tooltipped__n">
67
- <svg><use xlink:href="#vditor-icon-comment"></use></svg>
68
- </button>
70
+ <div class="vditor-selection-popover">
71
+ <div class="vditor-selection-popover__actions">
72
+ <button type="button" data-action="ai" aria-label="AI编辑" class="vditor-selection-popover__btn" disabled>✨ AI编辑</button>
73
+ <button type="button" data-action="cut" aria-label="剪切" class="vditor-selection-popover__btn">剪切</button>
74
+ <button type="button" data-action="copy" aria-label="复制" class="vditor-selection-popover__btn">复制</button>
75
+ </div>
76
+ <div class="vditor-selection-popover__input">
77
+ <textarea placeholder="请输入您想修改的内容"></textarea>
78
+ <button class="vditor-selection-popover__send">
79
+ <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sparkles-icon lucide-sparkles"><path d="M11.017 2.814a1 1 0 0 1 1.966 0l1.051 5.558a2 2 0 0 0 1.594 1.594l5.558 1.051a1 1 0 0 1 0 1.966l-5.558 1.051a2 2 0 0 0-1.594 1.594l-1.051 5.558a1 1 0 0 1-1.966 0l-1.051-5.558a2 2 0 0 0-1.594-1.594l-5.558-1.051a1 1 0 0 1 0-1.966l5.558-1.051a2 2 0 0 0 1.594-1.594z"/><path d="M20 2v4"/><path d="M22 4h-4"/><circle cx="4" cy="20" r="2"/></svg>
80
+ </button>
81
+ </div>
69
82
  </div>`;
70
83
 
71
84
  this.element = divElement.firstElementChild as HTMLPreElement;
72
85
  this.popover = divElement.firstElementChild
73
86
  .nextElementSibling as HTMLDivElement;
74
87
  this.selectPopover = divElement.lastElementChild as HTMLDivElement;
88
+ this.popoverInput = this.selectPopover.querySelector(
89
+ "textarea"
90
+ ) as HTMLTextAreaElement;
91
+ this.popoverSendBtn = this.selectPopover.querySelector(
92
+ ".vditor-selection-popover__send"
93
+ ) as HTMLButtonElement;
75
94
 
76
95
  this.bindEvent(vditor);
77
96
 
97
+ // 绑定发送按钮事件
98
+ if (this.popoverSendBtn) {
99
+ this.popoverSendBtn.setAttribute("disabled", "disabled");
100
+
101
+ this.popoverSendBtn.onclick = (event) => {
102
+ event.stopPropagation();
103
+ if (vditor.options.ai) {
104
+ vditor.options.ai({
105
+ value: this.popoverInput.value,
106
+ content: this.selectionContent,
107
+ lines: this.selectionLines,
108
+ });
109
+ }
110
+ this.hideSelectionPopover();
111
+ };
112
+ // 阻止点击输入区域时清除选区
113
+ this.selectPopover
114
+ .querySelector(".vditor-selection-popover__input")
115
+ ?.addEventListener("mousedown", (event) => {
116
+ event.stopPropagation();
117
+ });
118
+
119
+ // 输入验证
120
+ if (this.popoverInput) {
121
+ this.popoverInput.addEventListener("input", () => {
122
+ if (this.popoverInput.value.trim().length > 0) {
123
+ this.popoverSendBtn.removeAttribute("disabled");
124
+ } else {
125
+ this.popoverSendBtn.setAttribute(
126
+ "disabled",
127
+ "disabled"
128
+ );
129
+ }
130
+ });
131
+ }
132
+ }
133
+
78
134
  focusEvent(vditor, this.element);
79
135
  dblclickEvent(vditor, this.element);
80
136
  blurEvent(vditor, this.element);
@@ -84,121 +140,177 @@ class WYSIWYG {
84
140
  copyEvent(vditor, this.element, this.copy);
85
141
  cutEvent(vditor, this.element, this.copy);
86
142
 
143
+ // 选择浮窗按钮事件绑定
144
+ const aiBtn = this.selectPopover.querySelector(
145
+ '[data-action="ai"]'
146
+ ) as HTMLButtonElement | null;
147
+ if (aiBtn) {
148
+ /**
149
+ * AI编辑按钮占位事件
150
+ * - 当前仅输出日志,不执行编辑逻辑
151
+ */
152
+ aiBtn.onclick = () => {
153
+ console.log("[Selection AI Edit] clicked");
154
+ };
155
+ }
156
+ const copyBtn = this.selectPopover.querySelector(
157
+ '[data-action="copy"]'
158
+ ) as HTMLButtonElement | null;
159
+ if (copyBtn) {
160
+ /**
161
+ * 复制按钮点击处理
162
+ * - 优先使用 Clipboard API;降级为 execCommand('copy')
163
+ */
164
+ copyBtn.onclick = () => {
165
+ this.copySelection(vditor);
166
+ };
167
+ }
168
+ const cutBtn = this.selectPopover.querySelector(
169
+ '[data-action="cut"]'
170
+ ) as HTMLButtonElement | null;
171
+ if (cutBtn) {
172
+ /**
173
+ * 剪切按钮点击处理
174
+ * - 将选区复制到剪贴板后删除,并接入撤销栈
175
+ */
176
+ cutBtn.onclick = () => {
177
+ this.cutSelection(vditor);
178
+ };
179
+ }
180
+
181
+ // 评论按钮仅在启用时注册(避免选择浮窗结构变化导致报错)
87
182
  if (vditor.options.comment.enable) {
88
- this.selectPopover.querySelector("button").onclick = () => {
89
- const id = Lute.NewNodeID();
90
- const range = getSelection().getRangeAt(0);
91
- const rangeClone = range.cloneRange();
92
- const contents = range.extractContents();
93
- let blockStartElement: HTMLElement;
94
- let blockEndElement: HTMLElement;
95
- let removeStart = false;
96
- let removeEnd = false;
97
- contents.childNodes.forEach(
98
- (item: HTMLElement, index: number) => {
99
- let wrap = false;
100
- if (item.nodeType === 3) {
101
- wrap = true;
102
- } else if (!item.classList.contains("vditor-comment")) {
103
- wrap = true;
104
- } else if (item.classList.contains("vditor-comment")) {
105
- item.setAttribute(
106
- "data-cmtids",
107
- item.getAttribute("data-cmtids") + " " + id
108
- );
109
- }
110
- if (wrap) {
111
- if (
112
- item.nodeType !== 3 &&
113
- item.getAttribute("data-block") === "0" &&
114
- index === 0 &&
115
- rangeClone.startOffset > 0
116
- ) {
117
- item.innerHTML = `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
118
- blockStartElement = item;
183
+ const commentBtn = this.selectPopover.querySelector(
184
+ '[data-action="comment"]'
185
+ ) as HTMLButtonElement | null;
186
+ if (commentBtn) {
187
+ commentBtn.onclick = () => {
188
+ const id = Lute.NewNodeID();
189
+ const range = getSelection().getRangeAt(0);
190
+ const rangeClone = range.cloneRange();
191
+ const contents = range.extractContents();
192
+ let blockStartElement: HTMLElement;
193
+ let blockEndElement: HTMLElement;
194
+ let removeStart = false;
195
+ let removeEnd = false;
196
+ contents.childNodes.forEach(
197
+ (item: HTMLElement, index: number) => {
198
+ let wrap = false;
199
+ if (item.nodeType === 3) {
200
+ wrap = true;
119
201
  } else if (
120
- item.nodeType !== 3 &&
121
- item.getAttribute("data-block") === "0" &&
122
- index === contents.childNodes.length - 1 &&
123
- rangeClone.endOffset <
124
- rangeClone.endContainer.textContent.length
202
+ !item.classList.contains("vditor-comment")
125
203
  ) {
126
- item.innerHTML = `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
127
- blockEndElement = item;
204
+ wrap = true;
128
205
  } else if (
129
- item.nodeType !== 3 &&
130
- item.getAttribute("data-block") === "0"
206
+ item.classList.contains("vditor-comment")
131
207
  ) {
132
- if (index === 0) {
133
- removeStart = true;
208
+ item.setAttribute(
209
+ "data-cmtids",
210
+ item.getAttribute("data-cmtids") + " " + id
211
+ );
212
+ }
213
+ if (wrap) {
214
+ if (
215
+ item.nodeType !== 3 &&
216
+ item.getAttribute("data-block") === "0" &&
217
+ index === 0 &&
218
+ rangeClone.startOffset > 0
219
+ ) {
220
+ item.innerHTML = `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
221
+ blockStartElement = item;
134
222
  } else if (
135
- index ===
136
- contents.childNodes.length - 1
223
+ item.nodeType !== 3 &&
224
+ item.getAttribute("data-block") === "0" &&
225
+ index === contents.childNodes.length - 1 &&
226
+ rangeClone.endOffset <
227
+ rangeClone.endContainer.textContent
228
+ .length
137
229
  ) {
138
- removeEnd = true;
230
+ item.innerHTML = `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
231
+ blockEndElement = item;
232
+ } else if (
233
+ item.nodeType !== 3 &&
234
+ item.getAttribute("data-block") === "0"
235
+ ) {
236
+ if (index === 0) {
237
+ removeStart = true;
238
+ } else if (
239
+ index ===
240
+ contents.childNodes.length - 1
241
+ ) {
242
+ removeEnd = true;
243
+ }
244
+ item.innerHTML = `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
245
+ } else {
246
+ const commentElement =
247
+ document.createElement("span");
248
+ commentElement.classList.add(
249
+ "vditor-comment"
250
+ );
251
+ commentElement.setAttribute(
252
+ "data-cmtids",
253
+ id
254
+ );
255
+ item.parentNode.insertBefore(
256
+ commentElement,
257
+ item
258
+ );
259
+ commentElement.appendChild(item);
139
260
  }
140
- item.innerHTML = `<span class="vditor-comment" data-cmtids="${id}">${item.innerHTML}</span>`;
141
- } else {
142
- const commentElement =
143
- document.createElement("span");
144
- commentElement.classList.add("vditor-comment");
145
- commentElement.setAttribute("data-cmtids", id);
146
- item.parentNode.insertBefore(
147
- commentElement,
148
- item
149
- );
150
- commentElement.appendChild(item);
151
261
  }
152
262
  }
263
+ );
264
+ const startElement = hasClosestBlock(
265
+ rangeClone.startContainer
266
+ );
267
+ if (startElement) {
268
+ if (blockStartElement) {
269
+ startElement.insertAdjacentHTML(
270
+ "beforeend",
271
+ blockStartElement.innerHTML
272
+ );
273
+ blockStartElement.remove();
274
+ } else if (
275
+ startElement.textContent
276
+ .trim()
277
+ .replace(Constants.ZWSP, "") === "" &&
278
+ removeStart
279
+ ) {
280
+ startElement.remove();
281
+ }
153
282
  }
154
- );
155
- const startElement = hasClosestBlock(rangeClone.startContainer);
156
- if (startElement) {
157
- if (blockStartElement) {
158
- startElement.insertAdjacentHTML(
159
- "beforeend",
160
- blockStartElement.innerHTML
161
- );
162
- blockStartElement.remove();
163
- } else if (
164
- startElement.textContent
165
- .trim()
166
- .replace(Constants.ZWSP, "") === "" &&
167
- removeStart
168
- ) {
169
- startElement.remove();
170
- }
171
- }
172
- const endElement = hasClosestBlock(rangeClone.endContainer);
173
- if (endElement) {
174
- if (blockEndElement) {
175
- endElement.insertAdjacentHTML(
176
- "afterbegin",
177
- blockEndElement.innerHTML
178
- );
179
- blockEndElement.remove();
180
- } else if (
181
- endElement.textContent
182
- .trim()
183
- .replace(Constants.ZWSP, "") === "" &&
184
- removeEnd
185
- ) {
186
- endElement.remove();
283
+ const endElement = hasClosestBlock(rangeClone.endContainer);
284
+ if (endElement) {
285
+ if (blockEndElement) {
286
+ endElement.insertAdjacentHTML(
287
+ "afterbegin",
288
+ blockEndElement.innerHTML
289
+ );
290
+ blockEndElement.remove();
291
+ } else if (
292
+ endElement.textContent
293
+ .trim()
294
+ .replace(Constants.ZWSP, "") === "" &&
295
+ removeEnd
296
+ ) {
297
+ endElement.remove();
298
+ }
187
299
  }
188
- }
189
- range.insertNode(contents);
190
- vditor.options.comment.add(
191
- id,
192
- range.toString(),
193
- this.getComments(vditor, true)
194
- );
195
- afterRenderEvent(vditor, {
196
- enableAddUndoStack: true,
197
- enableHint: false,
198
- enableInput: false,
199
- });
200
- this.hideComment();
201
- };
300
+ range.insertNode(contents);
301
+ vditor.options.comment.add(
302
+ id,
303
+ range.toString(),
304
+ this.getComments(vditor, true)
305
+ );
306
+ afterRenderEvent(vditor, {
307
+ enableAddUndoStack: true,
308
+ enableHint: false,
309
+ enableInput: false,
310
+ });
311
+ this.hideSelectionPopover();
312
+ };
313
+ }
202
314
  }
203
315
  }
204
316
 
@@ -250,16 +362,141 @@ class WYSIWYG {
250
362
  }
251
363
  }
252
364
 
365
+ /**
366
+ * 显示选择浮窗(定位于选区右上)
367
+ * - 当未选中文本或选区不在编辑器内时不显示
368
+ */
369
+ public showSelectionPopover() {
370
+ if (getSelection().rangeCount === 0) {
371
+ return;
372
+ }
373
+ const range = getSelection().getRangeAt(0);
374
+ if (range.toString().trim() === "") {
375
+ return;
376
+ }
377
+
378
+ // 捕获选区内容(在选区丢失前)
379
+ const fragment = range.cloneContents();
380
+ const tempContainer = document.createElement("div");
381
+ tempContainer.appendChild(fragment);
382
+ this.selectionContent = this.vditor.lute
383
+ .VditorDOM2Md(tempContainer.innerHTML)
384
+ .trim();
385
+ if (!this.selectionContent) {
386
+ this.selectionContent = range.toString().trim();
387
+ }
388
+
389
+ // 捕获行号
390
+ const getBlockIndex = (node: Node, editor: HTMLElement): number => {
391
+ let target = node;
392
+ let directChild: Element | null = null;
393
+
394
+ // 向上遍历查找行号或直接子元素
395
+ while (target && target.parentElement !== editor) {
396
+ if (
397
+ target instanceof Element &&
398
+ target.hasAttribute("data-linenumber")
399
+ ) {
400
+ return parseInt(target.getAttribute("data-linenumber"), 10);
401
+ }
402
+ target = target.parentNode;
403
+ if (target && target.parentElement === editor) {
404
+ directChild = target as Element;
405
+ }
406
+ }
407
+
408
+ // 检查直接子元素
409
+ if (directChild && directChild.hasAttribute("data-linenumber")) {
410
+ return parseInt(
411
+ directChild.getAttribute("data-linenumber"),
412
+ 10
413
+ );
414
+ }
415
+
416
+ // 回退到使用子元素索引
417
+ if (directChild) {
418
+ return Array.from(editor.children).indexOf(directChild) + 1;
419
+ }
420
+
421
+ return 0;
422
+ };
423
+
424
+ const startLine = getBlockIndex(range.startContainer, this.element);
425
+ const endLine = getBlockIndex(range.endContainer, this.element);
426
+ this.selectionLines = [startLine, endLine];
427
+
428
+ const editorRect = this.element.getBoundingClientRect();
429
+ const rect = range.getBoundingClientRect();
430
+
431
+ // 计算浮窗尺寸
432
+ this.selectPopover.style.display = "flex";
433
+ this.selectPopover.style.opacity = "0";
434
+ const popoverHeight = this.selectPopover.offsetHeight;
435
+ const popoverWidth = this.selectPopover.offsetWidth;
436
+
437
+ // 理想位置:选区上方居中
438
+ let top = rect.top - editorRect.top - popoverHeight - 8;
439
+ let left =
440
+ rect.left - editorRect.left + rect.width / 2 - popoverWidth / 2;
441
+
442
+ // 边界检查:顶部超出则翻转到底部
443
+ if (top < 0) {
444
+ top = rect.bottom - editorRect.top + 8;
445
+ }
446
+
447
+ // 边界检查:左右限制
448
+ const maxLeft = this.element.clientWidth - popoverWidth;
449
+ left = Math.max(0, Math.min(left, maxLeft));
450
+
451
+ this.selectPopover.style.top = `${top}px`;
452
+ this.selectPopover.style.left = `${left}px`;
453
+
454
+ // 强制重排以触发过渡动画
455
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
456
+ this.selectPopover.offsetHeight;
457
+
458
+ this.selectPopover.style.opacity = "1";
459
+ this.selectPopover.style.transform = "scale(1)";
460
+ this.popover.setAttribute("data-top", top.toString());
461
+ this.selectPopover.setAttribute("data-top", top.toString());
462
+ }
463
+
464
+ /**
465
+ * 隐藏选择浮窗(淡出)
466
+ */
467
+ public hideSelectionPopover() {
468
+ if (this.selectPopover.style.display !== "flex") {
469
+ return;
470
+ }
471
+ this.selectPopover.style.opacity = "0";
472
+ this.selectPopover.style.transform = "scale(0.95)";
473
+ window.setTimeout(() => {
474
+ this.selectPopover.setAttribute("style", "display:none");
475
+ // Reset state
476
+ if (this.popoverInput) {
477
+ this.popoverInput.value = "";
478
+ }
479
+ if (this.popoverSendBtn) {
480
+ this.popoverSendBtn.classList.remove(
481
+ "vditor-selection-popover__send--loading"
482
+ );
483
+ this.popoverSendBtn.setAttribute("disabled", "disabled");
484
+ }
485
+ }, 150);
486
+ }
487
+
488
+ /**
489
+ * 兼容旧接口:显示评论面板(映射为选择浮窗)
490
+ */
253
491
  public showComment() {
254
- const position = getCursorPosition(this.element);
255
- this.selectPopover.setAttribute(
256
- "style",
257
- `left:${position.left}px;display:block;top:${Math.max(-8, position.top - 21)}px`
258
- );
492
+ this.showSelectionPopover();
259
493
  }
260
494
 
495
+ /**
496
+ * 兼容旧接口:隐藏评论面板(映射为选择浮窗隐藏)
497
+ */
261
498
  public hideComment() {
262
- this.selectPopover.setAttribute("style", "display:none");
499
+ this.hideSelectionPopover();
263
500
  }
264
501
 
265
502
  public unbindListener() {
@@ -344,9 +581,162 @@ class WYSIWYG {
344
581
  "text/plain",
345
582
  vditor.lute.VditorDOM2Md(tempElement.innerHTML).trim()
346
583
  );
347
- event.clipboardData.setData("text/html", "");
584
+ event.clipboardData.setData("text/html", tempElement.innerHTML);
348
585
  }
349
586
 
587
+ /**
588
+ * 构建选区剪贴板内容(纯文本 + 富文本)
589
+ */
590
+ private buildClipboardPayload(vditor: IVditor) {
591
+ const range = getSelection().getRangeAt(0);
592
+ const codeElement = hasClosestByMatchTag(range.startContainer, "CODE");
593
+ const codeEndElement = hasClosestByMatchTag(range.endContainer, "CODE");
594
+ if (
595
+ codeElement &&
596
+ codeEndElement &&
597
+ codeEndElement.isSameNode(codeElement)
598
+ ) {
599
+ const codeText =
600
+ codeElement.parentElement.tagName === "PRE"
601
+ ? range.toString()
602
+ : "`" + range.toString() + "`";
603
+ return { plainText: codeText, html: "" };
604
+ }
605
+ const aElement = hasClosestByMatchTag(range.startContainer, "A");
606
+ const aEndElement = hasClosestByMatchTag(range.endContainer, "A");
607
+ if (aElement && aEndElement && aEndElement.isSameNode(aElement)) {
608
+ let aTitle = aElement.getAttribute("title") || "";
609
+ if (aTitle) {
610
+ aTitle = ` "${aTitle}"`;
611
+ }
612
+ return {
613
+ plainText: `[${range.toString()}](${aElement.getAttribute("href")}${aTitle})`,
614
+ html: "",
615
+ };
616
+ }
617
+ const startPreview = hasClosestByClassName(
618
+ range.startContainer,
619
+ "vditor-wysiwyg__preview"
620
+ ) as HTMLElement;
621
+ const endPreview = hasClosestByClassName(
622
+ range.endContainer,
623
+ "vditor-wysiwyg__preview"
624
+ ) as HTMLElement;
625
+ const isMathPreview = (el: HTMLElement) => {
626
+ const first = el.firstElementChild as HTMLElement | null;
627
+ return !!first && first.classList.contains("language-math");
628
+ };
629
+ if (
630
+ startPreview &&
631
+ endPreview &&
632
+ startPreview.isSameNode(endPreview) &&
633
+ isMathPreview(startPreview)
634
+ ) {
635
+ return { plainText: range.toString(), html: "" };
636
+ }
637
+ const tempElement = document.createElement("div");
638
+ tempElement.appendChild(range.cloneContents());
639
+ return {
640
+ plainText: vditor.lute.VditorDOM2Md(tempElement.innerHTML).trim(),
641
+ html: tempElement.innerHTML,
642
+ };
643
+ }
644
+
645
+ /**
646
+ * 复制选区(按钮触发)
647
+ * - Clipboard API 优先,降级为 execCommand('copy') 调用原有 copy 逻辑
648
+ */
649
+ private copySelection(vditor: IVditor) {
650
+ const range = getSelection().getRangeAt(0);
651
+ if (range.toString().trim() === "") {
652
+ return;
653
+ }
654
+ const payload = this.buildClipboardPayload(vditor);
655
+ const writeClipboard = async () => {
656
+ // Safari 兼容:ClipboardItem 可能不可用
657
+ const ClipboardItemCtor = (window as any)["ClipboardItem"];
658
+ const navClipboard = (navigator as any).clipboard as
659
+ | { write?(items: unknown[]): Promise<void> }
660
+ | undefined;
661
+ if (
662
+ navClipboard?.write &&
663
+ typeof ClipboardItemCtor !== "undefined"
664
+ ) {
665
+ try {
666
+ const item = new ClipboardItemCtor({
667
+ "text/plain": new Blob([payload.plainText], {
668
+ type: "text/plain",
669
+ }),
670
+ "text/html": new Blob([payload.html], {
671
+ type: "text/html",
672
+ }),
673
+ });
674
+ await navClipboard.write([item]);
675
+ return true;
676
+ } catch {
677
+ return false;
678
+ }
679
+ }
680
+ return false;
681
+ };
682
+ writeClipboard().then((ok) => {
683
+ if (!ok) {
684
+ document.execCommand("copy");
685
+ }
686
+ });
687
+ this.hideSelectionPopover();
688
+ }
689
+
690
+ /**
691
+ * 剪切选区(按钮触发)
692
+ * - 复制到剪贴板后删除选区,并加入撤销栈
693
+ */
694
+ private cutSelection(vditor: IVditor) {
695
+ const range = getSelection().getRangeAt(0);
696
+ if (range.toString().trim() === "") {
697
+ return;
698
+ }
699
+ const payload = this.buildClipboardPayload(vditor);
700
+ const writeClipboard = async () => {
701
+ const ClipboardItemCtor = (window as any)["ClipboardItem"];
702
+ const navClipboard = (navigator as any).clipboard as
703
+ | { write?(items: unknown[]): Promise<void> }
704
+ | undefined;
705
+ if (
706
+ navClipboard?.write &&
707
+ typeof ClipboardItemCtor !== "undefined"
708
+ ) {
709
+ try {
710
+ const item = new ClipboardItemCtor({
711
+ "text/plain": new Blob([payload.plainText], {
712
+ type: "text/plain",
713
+ }),
714
+ "text/html": new Blob([payload.html], {
715
+ type: "text/html",
716
+ }),
717
+ });
718
+ await navClipboard.write([item]);
719
+ return true;
720
+ } catch {
721
+ return false;
722
+ }
723
+ }
724
+ return false;
725
+ };
726
+ writeClipboard().then((ok) => {
727
+ if (!ok) {
728
+ document.execCommand("cut");
729
+ } else {
730
+ document.execCommand("delete");
731
+ }
732
+ afterRenderEvent(vditor, {
733
+ enableAddUndoStack: true,
734
+ enableHint: false,
735
+ enableInput: true,
736
+ });
737
+ });
738
+ this.hideSelectionPopover();
739
+ }
350
740
  private bindEvent(vditor: IVditor) {
351
741
  this.unbindListener();
352
742
  window.addEventListener(
@@ -355,7 +745,7 @@ class WYSIWYG {
355
745
  hidePanel(vditor, ["hint"]);
356
746
  if (
357
747
  this.popover.style.display !== "block" ||
358
- this.selectPopover.style.display !== "block"
748
+ this.selectPopover.style.display !== "flex"
359
749
  ) {
360
750
  return;
361
751
  }
@@ -376,7 +766,7 @@ class WYSIWYG {
376
766
  if (this.popover.style.display === "block") {
377
767
  this.popover.style.top = popoverTop;
378
768
  }
379
- if (this.selectPopover.style.display === "block") {
769
+ if (this.selectPopover.style.display === "flex") {
380
770
  this.selectPopover.style.top = popoverTop;
381
771
  }
382
772
  }
@@ -392,7 +782,7 @@ class WYSIWYG {
392
782
  if (this.popover.style.display === "block") {
393
783
  this.popover.style.top = popoverTop1;
394
784
  }
395
- if (this.selectPopover.style.display === "block") {
785
+ if (this.selectPopover.style.display === "flex") {
396
786
  this.selectPopover.style.top = popoverTop1;
397
787
  }
398
788
  })