@8btc/mditor 0.0.18 → 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/dist/index.css +2 -2
- package/dist/index.js +74 -64
- package/dist/index.min.js +1 -1
- package/dist/method.js +3 -3
- package/dist/method.min.js +1 -1
- package/dist/ts/wysiwyg/index.d.ts +1 -0
- package/dist/types/index.d.ts +14 -9
- package/package.json +1 -1
- package/src/index.ts +8 -1
- package/src/ts/wysiwyg/index.ts +81 -74
package/src/ts/wysiwyg/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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
|
}
|