@8btc/mditor 0.0.17 → 0.0.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@8btc/mditor",
3
- "version": "0.0.17",
3
+ "version": "0.0.19",
4
4
  "description": "mditor, Md编辑器",
5
5
  "author": "wujie-vditor",
6
6
  "jsdelivr": "dist/index.min.js",
package/src/index.ts CHANGED
@@ -228,6 +228,13 @@ class Vditor extends VditorMethod {
228
228
  if (!Array.isArray(numbers)) {
229
229
  return;
230
230
  }
231
+
232
+ // 当 highlightTimer 为 0 时,不处理高亮逻辑
233
+ const highlightTimer = this.vditor.options.lineNumber?.highlightTimer;
234
+ if (highlightTimer === 0) {
235
+ return;
236
+ }
237
+
231
238
  const valid = numbers
232
239
  .filter((n) => typeof n === "number" && isFinite(n))
233
240
  .map((n) => String(Math.floor(n)));
@@ -244,7 +251,7 @@ class Vditor extends VditorMethod {
244
251
  }
245
252
  const duration = Math.max(
246
253
  0,
247
- Number(this.vditor.options.lineNumber?.highlightTimer) || 1500
254
+ Number(highlightTimer) || 1500
248
255
  );
249
256
  const apply = () => {
250
257
  nodeList.forEach((el) => {
@@ -37,6 +37,9 @@ const mergeOptions = (options?: IPreviewOptions) => {
37
37
  markdown: Constants.MARKDOWN_OPTIONS,
38
38
  math: Constants.MATH_OPTIONS,
39
39
  mode: "light",
40
+ link: {
41
+ isOpen: true,
42
+ },
40
43
  speech: {
41
44
  enable: false,
42
45
  },
@@ -102,6 +105,14 @@ export const md2html = (mdText: string, options?: IPreviewOptions) => {
102
105
  });
103
106
  };
104
107
 
108
+ /**
109
+ * 预览区域渲染入口
110
+ * - 负责将 Markdown 转为 HTML 并对各类增强特性进行初始化
111
+ * - 支持目录点击定位与链接点击行为配置(link.click / link.isOpen)
112
+ * @param previewElement 预览容器元素
113
+ * @param markdown Markdown 文本内容
114
+ * @param options 预览配置项,支持 link 点击行为自定义
115
+ */
105
116
  export const previewRender = async (
106
117
  previewElement: HTMLDivElement,
107
118
  markdown: string,
@@ -196,6 +207,7 @@ export const previewRender = async (
196
207
  if (mergedOptions.lazyLoadImage) {
197
208
  lazyLoadImageRender(previewElement);
198
209
  }
210
+
199
211
  previewElement.addEventListener(
200
212
  "click",
201
213
  (event: MouseEvent & { target: HTMLElement }) => {
@@ -212,6 +224,15 @@ export const previewRender = async (
212
224
  }
213
225
  return;
214
226
  }
227
+ if (event.target.tagName === "A") {
228
+ if (mergedOptions.link?.click) {
229
+ mergedOptions.link.click(event.target);
230
+ } else if (mergedOptions.link?.isOpen) {
231
+ window.open(event.target.getAttribute("href"));
232
+ }
233
+ event.preventDefault();
234
+ return;
235
+ }
215
236
  }
216
237
  );
217
238
  };
@@ -12,7 +12,10 @@ import { SMILESRender } from "../markdown/SMILESRender";
12
12
  import { markmapRender } from "../markdown/markmapRender";
13
13
  import { mindmapRender } from "../markdown/mindmapRender";
14
14
  import { plantumlRender } from "../markdown/plantumlRender";
15
- import { hasClosestByClassName, hasClosestByMatchTag } from "../util/hasClosest";
15
+ import {
16
+ hasClosestByClassName,
17
+ hasClosestByMatchTag,
18
+ } from "../util/hasClosest";
16
19
  import { setSelectionFocus } from "../util/selection";
17
20
  import { selectionRender } from "../markdown/selectionRender";
18
21
  import { previewImage } from "./image";
@@ -64,21 +64,31 @@ class WYSIWYG {
64
64
  divElement.classList.add("vditor--linenumber");
65
65
  }
66
66
 
67
+ // 构建选择浮窗的操作按钮HTML
68
+ const aiButtonHtml = vditor.options.ai?.enable
69
+ ? '<button type="button" data-action="ai" aria-label="AI编辑" class="vditor-selection-popover__btn">✨ AI编辑</button>'
70
+ : '';
71
+
72
+ // 构建AI输入区域HTML
73
+ const aiInputHtml = vditor.options.ai?.enable
74
+ ? `<div class="vditor-selection-popover__input">
75
+ <textarea placeholder="请输入您想修改的内容"></textarea>
76
+ <button class="vditor-selection-popover__send">
77
+ <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>
78
+ </button>
79
+ </div>`
80
+ : '';
81
+
67
82
  divElement.innerHTML = `<pre class="vditor-reset" placeholder="${vditor.options.placeholder}"
68
83
  contenteditable="true" spellcheck="false"></pre>
69
84
  <div class="vditor-panel vditor-panel--none"></div>
70
85
  <div class="vditor-selection-popover">
71
86
  <div class="vditor-selection-popover__actions">
72
- <button type="button" data-action="ai" aria-label="AI编辑" class="vditor-selection-popover__btn">✨ AI编辑</button>
87
+ ${aiButtonHtml}
73
88
  <button type="button" data-action="cut" aria-label="剪切" class="vditor-selection-popover__btn">剪切</button>
74
89
  <button type="button" data-action="copy" aria-label="复制" class="vditor-selection-popover__btn">复制</button>
75
90
  </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>
91
+ ${aiInputHtml}
82
92
  </div>`;
83
93
 
84
94
  this.element = divElement.firstElementChild as HTMLPreElement;
@@ -94,14 +104,14 @@ class WYSIWYG {
94
104
 
95
105
  this.bindEvent(vditor);
96
106
 
97
- // 绑定发送按钮事件
98
- if (this.popoverSendBtn) {
107
+ // 绑定发送按钮事件(仅在AI启用时)
108
+ if (this.popoverSendBtn && vditor.options.ai?.enable) {
99
109
  this.popoverSendBtn.setAttribute("disabled", "disabled");
100
110
 
101
111
  this.popoverSendBtn.onclick = (event) => {
102
112
  event.stopPropagation();
103
- if (vditor.options.ai) {
104
- vditor.options.ai({
113
+ if (vditor.options.ai?.callback) {
114
+ vditor.options.ai.callback({
105
115
  value: this.popoverInput.value,
106
116
  content: this.selectionContent,
107
117
  lines: this.selectionLines,
@@ -109,35 +119,36 @@ class WYSIWYG {
109
119
  }
110
120
  this.hideSelectionPopover();
111
121
  };
112
- // 阻止点击 popover 内任何区域时导致编辑器失去焦点
113
- // 但不影响 textarea 获取焦点和按钮点击
114
- this.selectPopover.addEventListener("mousedown", (event) => {
115
- const target = event.target as HTMLElement;
116
- // 如果点击的是 textarea 或 button,允许默认行为
117
- if (
118
- target.tagName === "TEXTAREA" ||
119
- target.tagName === "BUTTON"
120
- ) {
121
- return;
122
- }
123
- // 其他情况阻止默认行为,防止编辑器失去焦点
124
- event.preventDefault();
125
- event.stopPropagation();
126
- });
122
+ }
127
123
 
128
- // 输入验证
129
- if (this.popoverInput) {
130
- this.popoverInput.addEventListener("input", () => {
131
- if (this.popoverInput.value.trim().length > 0) {
132
- this.popoverSendBtn.removeAttribute("disabled");
133
- } else {
134
- this.popoverSendBtn.setAttribute(
135
- "disabled",
136
- "disabled"
137
- );
138
- }
139
- });
124
+ // 阻止点击 popover 内任何区域时导致编辑器失去焦点
125
+ // 但不影响 textarea 获取焦点和按钮点击
126
+ this.selectPopover.addEventListener("mousedown", (event) => {
127
+ const target = event.target as HTMLElement;
128
+ // 如果点击的是 textarea 或 button,允许默认行为
129
+ if (
130
+ target.tagName === "TEXTAREA" ||
131
+ target.tagName === "BUTTON"
132
+ ) {
133
+ return;
140
134
  }
135
+ // 其他情况阻止默认行为,防止编辑器失去焦点
136
+ event.preventDefault();
137
+ event.stopPropagation();
138
+ });
139
+
140
+ // 输入验证(仅在AI启用时)
141
+ if (this.popoverInput && vditor.options.ai?.enable) {
142
+ this.popoverInput.addEventListener("input", () => {
143
+ if (this.popoverInput.value.trim().length > 0) {
144
+ this.popoverSendBtn.removeAttribute("disabled");
145
+ } else {
146
+ this.popoverSendBtn.setAttribute(
147
+ "disabled",
148
+ "disabled"
149
+ );
150
+ }
151
+ });
141
152
  }
142
153
 
143
154
  focusEvent(vditor, this.element);
@@ -149,42 +160,32 @@ class WYSIWYG {
149
160
  copyEvent(vditor, this.element, this.copy);
150
161
  cutEvent(vditor, this.element, this.copy);
151
162
 
152
- // 选择浮窗按钮事件绑定
153
- // const aiBtn = this.selectPopover.querySelector(
154
- // '[data-action="ai"]'
155
- // ) as HTMLButtonElement | null;
156
- // if (aiBtn) {
157
- // /**
158
- // * AI编辑按钮占位事件
159
- // * - 当前仅输出日志,不执行编辑逻辑
160
- // */
161
- // aiBtn.onclick = () => {
162
- // console.log("[Selection AI Edit] clicked");
163
- // };
164
- // }
165
- const copyBtn = this.selectPopover.querySelector(
166
- '[data-action="copy"]'
167
- ) as HTMLButtonElement | null;
168
- if (copyBtn) {
169
- /**
170
- * 复制按钮点击处理
171
- * - 优先使用 Clipboard API;降级为 execCommand('copy')
172
- */
173
- copyBtn.onclick = () => {
174
- this.copySelection(vditor);
175
- };
176
- }
177
- const cutBtn = this.selectPopover.querySelector(
178
- '[data-action="cut"]'
179
- ) as HTMLButtonElement | null;
180
- if (cutBtn) {
181
- /**
182
- * 剪切按钮点击处理
183
- * - 将选区复制到剪贴板后删除,并接入撤销栈
184
- */
185
- cutBtn.onclick = () => {
186
- this.cutSelection(vditor);
187
- };
163
+ // 选择浮窗按钮事件绑定(仅在AI启用时)
164
+ if (vditor.options.ai?.enable) {
165
+ const copyBtn = this.selectPopover.querySelector(
166
+ '[data-action="copy"]'
167
+ ) as HTMLButtonElement | null;
168
+ if (copyBtn) {
169
+ /**
170
+ * 复制按钮点击处理
171
+ * - 优先使用 Clipboard API;降级为 execCommand('copy')
172
+ */
173
+ copyBtn.onclick = () => {
174
+ this.copySelection(vditor);
175
+ };
176
+ }
177
+ const cutBtn = this.selectPopover.querySelector(
178
+ '[data-action="cut"]'
179
+ ) as HTMLButtonElement | null;
180
+ if (cutBtn) {
181
+ /**
182
+ * 剪切按钮点击处理
183
+ * - 将选区复制到剪贴板后删除,并接入撤销栈
184
+ */
185
+ cutBtn.onclick = () => {
186
+ this.cutSelection(vditor);
187
+ };
188
+ }
188
189
  }
189
190
 
190
191
  // 评论按钮仅在启用时注册(避免选择浮窗结构变化导致报错)
@@ -374,8 +375,14 @@ class WYSIWYG {
374
375
  /**
375
376
  * 显示选择浮窗(定位于选区右上)
376
377
  * - 当未选中文本或选区不在编辑器内时不显示
378
+ * - 当AI功能未启用时不显示
377
379
  */
378
380
  public showSelectionPopover() {
381
+ // 如果AI功能未启用,不显示选择浮窗
382
+ if (!this.vditor.options.ai?.enable) {
383
+ return;
384
+ }
385
+
379
386
  if (getSelection().rangeCount === 0) {
380
387
  return;
381
388
  }