@cgboiler/biz-basic 1.0.47 → 1.0.49
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/es/index.d.ts +1 -1
- package/es/index.js +1 -1
- package/es/rich-text-editor/RichTextEditor.d.ts +5 -1
- package/es/rich-text-editor/RichTextEditor.js +37 -2
- package/es/rich-text-editor/index.css +1 -1
- package/es/rich-text-editor/index.less +12 -1
- package/es/rich-text-editor/types.d.ts +15 -0
- package/es/rich-text-editor/useExtensions.d.ts +2 -7
- package/es/rich-text-editor/useExtensions.js +177 -150
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/rich-text-editor/RichTextEditor.d.ts +5 -1
- package/lib/rich-text-editor/RichTextEditor.js +37 -2
- package/lib/rich-text-editor/index.css +1 -1
- package/lib/rich-text-editor/index.less +12 -1
- package/lib/rich-text-editor/types.d.ts +15 -0
- package/lib/rich-text-editor/useExtensions.d.ts +2 -7
- package/lib/rich-text-editor/useExtensions.js +176 -149
- package/package.json +1 -1
|
@@ -6,33 +6,74 @@ import Mention from "@tiptap/extension-mention";
|
|
|
6
6
|
import { TextStyle } from "@tiptap/extension-text-style";
|
|
7
7
|
import { Color } from "@tiptap/extension-color";
|
|
8
8
|
import { Markdown } from "tiptap-markdown";
|
|
9
|
-
import { Extension, InputRule } from "@tiptap/core";
|
|
9
|
+
import { Extension, InputRule, Mark, mergeAttributes } from "@tiptap/core";
|
|
10
10
|
import HtmlBlock from "./extensions/HtmlBlock";
|
|
11
11
|
import { Placeholder } from "@tiptap/extensions";
|
|
12
12
|
function useExtensions({ props, emit }) {
|
|
13
|
-
let spentHashTagPositions = [];
|
|
14
|
-
let spentMentionPositions = [];
|
|
15
13
|
let activeEditor = null;
|
|
14
|
+
const ConsumedTrigger = Mark.create({
|
|
15
|
+
name: "consumedTrigger",
|
|
16
|
+
keepOnSplit: false,
|
|
17
|
+
// 保证在标记后继续输入时不延续标记
|
|
18
|
+
parseHTML() {
|
|
19
|
+
return [{ tag: "span[data-consumed-trigger]" }];
|
|
20
|
+
},
|
|
21
|
+
renderHTML({ HTMLAttributes }) {
|
|
22
|
+
return [
|
|
23
|
+
"span",
|
|
24
|
+
mergeAttributes(HTMLAttributes, {
|
|
25
|
+
"data-consumed-trigger": "true",
|
|
26
|
+
style: "color: inherit;"
|
|
27
|
+
}),
|
|
28
|
+
0
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
const checkConsumed = (state, range) => {
|
|
33
|
+
const { doc, schema } = state;
|
|
34
|
+
const markType = schema.marks.consumedTrigger;
|
|
35
|
+
if (!markType)
|
|
36
|
+
return false;
|
|
37
|
+
let isConsumed = false;
|
|
38
|
+
doc.nodesBetween(range.from, range.to, (node) => {
|
|
39
|
+
if (node.marks.some((m) => m.type === markType)) {
|
|
40
|
+
isConsumed = true;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return isConsumed;
|
|
44
|
+
};
|
|
45
|
+
const markAsConsumed = (editor, range, char) => {
|
|
46
|
+
if (!editor || !range || range.from === void 0)
|
|
47
|
+
return;
|
|
48
|
+
try {
|
|
49
|
+
const { doc } = editor.state;
|
|
50
|
+
const actualChar = doc.textBetween(range.from, range.from + 1);
|
|
51
|
+
if (actualChar === char) {
|
|
52
|
+
editor.chain().setMeta("addToHistory", false).setTextSelection({ from: range.from, to: range.from + 1 }).setMark("consumedTrigger").setTextSelection(editor.state.selection.to).unsetMark("consumedTrigger").run();
|
|
53
|
+
}
|
|
54
|
+
} catch (e) {
|
|
55
|
+
}
|
|
56
|
+
};
|
|
16
57
|
const HashTag = Mention.extend({
|
|
17
58
|
name: "hashTag",
|
|
18
|
-
renderHTML({ node
|
|
59
|
+
renderHTML({ node }) {
|
|
19
60
|
var _a, _b, _c, _d;
|
|
20
61
|
const id = (_b = (_a = node == null ? void 0 : node.attrs) == null ? void 0 : _a.id) != null ? _b : "";
|
|
21
62
|
const label = (_d = (_c = node == null ? void 0 : node.attrs) == null ? void 0 : _c.label) != null ? _d : "";
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
63
|
+
return [
|
|
64
|
+
"span",
|
|
65
|
+
{
|
|
66
|
+
class: "hash-tag",
|
|
67
|
+
"data-type": "hashTag",
|
|
68
|
+
"data-id": id,
|
|
69
|
+
"data-label": label,
|
|
70
|
+
"data-hash-tag": "#",
|
|
71
|
+
contenteditable: "false"
|
|
72
|
+
},
|
|
73
|
+
`#${label}`
|
|
74
|
+
];
|
|
31
75
|
}
|
|
32
76
|
}).configure({
|
|
33
|
-
HTMLAttributes: {
|
|
34
|
-
class: "hash-tag"
|
|
35
|
-
},
|
|
36
77
|
deleteTriggerWithBackspace: true,
|
|
37
78
|
suggestion: {
|
|
38
79
|
char: "#",
|
|
@@ -41,13 +82,10 @@ function useExtensions({ props, emit }) {
|
|
|
41
82
|
/**
|
|
42
83
|
* allow
|
|
43
84
|
* - 功能:判断是否允许触发建议态。
|
|
44
|
-
* -
|
|
85
|
+
* - 若该字符带有 consumedTrigger 标记,说明此前已触发过或用户选择忽略,则不再触发建议。
|
|
45
86
|
*/
|
|
46
87
|
allow: ({ state, range }) => {
|
|
47
|
-
|
|
48
|
-
return false;
|
|
49
|
-
}
|
|
50
|
-
return true;
|
|
88
|
+
return !checkConsumed(state, range);
|
|
51
89
|
},
|
|
52
90
|
render() {
|
|
53
91
|
let activeRange = null;
|
|
@@ -75,111 +113,139 @@ function useExtensions({ props, emit }) {
|
|
|
75
113
|
activeEditor = editor || activeEditor;
|
|
76
114
|
emit("hashTag-input", query != null ? query : "");
|
|
77
115
|
},
|
|
78
|
-
/**
|
|
79
|
-
* onKeyDown
|
|
80
|
-
* - 功能:在建议态期间侦测用户键盘行为并记录“非连续输入”的退出原因。
|
|
81
|
-
* - 参数说明:
|
|
82
|
-
* - event: KeyboardEvent 当前键盘事件对象。
|
|
83
|
-
* - 返回值:boolean 始终返回 false,交由默认逻辑处理(从而触发 onExit)。
|
|
84
|
-
* - 异常:无。
|
|
85
|
-
*/
|
|
86
116
|
onKeyDown({ event }) {
|
|
87
|
-
const isSpaceKey =
|
|
88
|
-
event.keyCode === 32 ||
|
|
89
|
-
event.code === "Space";
|
|
117
|
+
const isSpaceKey = [" ", "Space", "Spacebar"].includes(event.key) || // @ts-ignore
|
|
118
|
+
event.keyCode === 32 || event.code === "Space";
|
|
90
119
|
if (isSpaceKey) {
|
|
91
120
|
exitCause = "space";
|
|
92
|
-
return false;
|
|
93
121
|
}
|
|
94
122
|
return false;
|
|
95
123
|
},
|
|
96
|
-
|
|
97
|
-
* onExit
|
|
98
|
-
* - 功能:建议态结束时向外部发送退出事件,并尽可能准确地判断退出原因。
|
|
99
|
-
* - 设计考量:
|
|
100
|
-
* - 安卓设备上软键盘可能不触发标准 keydown 空格;此时通过检查当前选择位置前一个字符是否为空格来回溯判断。
|
|
101
|
-
* - 参数:无。
|
|
102
|
-
* - 返回值:无(通过 emit 派发事件)。
|
|
103
|
-
* - 异常:内部读取选择/文档时若发生异常,捕获后保持原有退出原因,不影响外部流程。
|
|
104
|
-
*/
|
|
105
|
-
onExit({ range }) {
|
|
124
|
+
onExit({ editor, range }) {
|
|
106
125
|
var _a, _b, _c, _d, _e;
|
|
107
126
|
try {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (prevChar === " ") {
|
|
114
|
-
exitCause = "space";
|
|
115
|
-
}
|
|
127
|
+
const sel = (_a = editor.state) == null ? void 0 : _a.selection;
|
|
128
|
+
const from = (_b = sel == null ? void 0 : sel.from) != null ? _b : 0;
|
|
129
|
+
const prevChar = (_e = (_d = (_c = editor.state) == null ? void 0 : _c.doc) == null ? void 0 : _d.textBetween(Math.max(from - 1, 0), from, "\0", "\0")) != null ? _e : "";
|
|
130
|
+
if (prevChar === " ") {
|
|
131
|
+
exitCause = "space";
|
|
116
132
|
}
|
|
117
133
|
} catch (e) {
|
|
118
134
|
}
|
|
119
|
-
|
|
120
|
-
spentHashTagPositions.push(range.from);
|
|
121
|
-
}
|
|
135
|
+
markAsConsumed(editor, range, "#");
|
|
122
136
|
activeRange = null;
|
|
123
137
|
activeEditor = null;
|
|
124
|
-
|
|
138
|
+
emit("hashTag-exit", {
|
|
125
139
|
reason: exitCause,
|
|
126
140
|
nonContiguous: exitCause === "space"
|
|
127
|
-
};
|
|
128
|
-
emit("hashTag-exit", payload);
|
|
141
|
+
});
|
|
129
142
|
}
|
|
130
143
|
};
|
|
131
144
|
}
|
|
132
145
|
}
|
|
133
146
|
});
|
|
134
|
-
const
|
|
135
|
-
name: "
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
147
|
+
const CustomContent = Mention.extend({
|
|
148
|
+
name: "customContent",
|
|
149
|
+
addAttributes() {
|
|
150
|
+
return {
|
|
151
|
+
id: {
|
|
152
|
+
default: null,
|
|
153
|
+
parseHTML: (element) => element.getAttribute("data-id"),
|
|
154
|
+
renderHTML: (attributes) => {
|
|
155
|
+
if (!attributes.id) {
|
|
156
|
+
return {};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
"data-id": attributes.id
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
label: {
|
|
164
|
+
default: null,
|
|
165
|
+
parseHTML: (element) => element.getAttribute("file-label"),
|
|
166
|
+
renderHTML: (attributes) => {
|
|
167
|
+
if (!attributes.label) {
|
|
168
|
+
return {};
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
"file-label": attributes.label
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
obj: {
|
|
176
|
+
default: null,
|
|
177
|
+
parseHTML: (element) => {
|
|
178
|
+
const objStr = element.getAttribute("obj");
|
|
179
|
+
return objStr ? JSON.parse(objStr) : null;
|
|
180
|
+
},
|
|
181
|
+
renderHTML: (attributes) => {
|
|
182
|
+
if (!attributes.obj) {
|
|
183
|
+
return {};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
obj: JSON.stringify(attributes.obj)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
color: {
|
|
191
|
+
default: null,
|
|
192
|
+
parseHTML: (element) => element.getAttribute("data-color"),
|
|
193
|
+
renderHTML: (attributes) => {
|
|
194
|
+
if (!attributes.color)
|
|
195
|
+
return {};
|
|
196
|
+
return { "data-color": attributes.color };
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
hoverColor: {
|
|
200
|
+
default: null,
|
|
201
|
+
parseHTML: (element) => element.getAttribute("data-hover-color"),
|
|
202
|
+
renderHTML: (attributes) => {
|
|
203
|
+
if (!attributes.hoverColor)
|
|
204
|
+
return {};
|
|
205
|
+
return { "data-hover-color": attributes.hoverColor };
|
|
206
|
+
}
|
|
167
207
|
}
|
|
168
|
-
}
|
|
169
|
-
|
|
208
|
+
};
|
|
209
|
+
},
|
|
210
|
+
renderHTML({ node }) {
|
|
211
|
+
var _a, _b, _c, _d, _e, _f;
|
|
212
|
+
const obj = (_b = (_a = node == null ? void 0 : node.attrs) == null ? void 0 : _a.obj) != null ? _b : {};
|
|
213
|
+
const label = (_d = (_c = node == null ? void 0 : node.attrs) == null ? void 0 : _c.label) != null ? _d : "";
|
|
214
|
+
const color = (_e = node == null ? void 0 : node.attrs) == null ? void 0 : _e.color;
|
|
215
|
+
const hoverColor = (_f = node == null ? void 0 : node.attrs) == null ? void 0 : _f.hoverColor;
|
|
216
|
+
const styleParts = [];
|
|
217
|
+
if (color)
|
|
218
|
+
styleParts.push(`--custom-content-color: ${color}`);
|
|
219
|
+
if (hoverColor)
|
|
220
|
+
styleParts.push(`--custom-content-hover-color: ${hoverColor}`);
|
|
221
|
+
return [
|
|
222
|
+
"span",
|
|
223
|
+
{
|
|
224
|
+
class: "custom-content",
|
|
225
|
+
"data-type": "custom-content",
|
|
226
|
+
obj: JSON.stringify(obj),
|
|
227
|
+
"file-label": label,
|
|
228
|
+
"data-color": color,
|
|
229
|
+
"data-hover-color": hoverColor,
|
|
230
|
+
style: styleParts.length ? styleParts.join(";") : void 0,
|
|
231
|
+
contenteditable: "false"
|
|
232
|
+
},
|
|
233
|
+
label
|
|
234
|
+
];
|
|
170
235
|
}
|
|
171
236
|
});
|
|
172
237
|
const extensions = [
|
|
173
238
|
StarterKit,
|
|
174
239
|
HtmlBlock,
|
|
175
|
-
|
|
240
|
+
ConsumedTrigger,
|
|
241
|
+
// Video input rule extension
|
|
176
242
|
Extension.create({
|
|
177
243
|
name: "videoInputRule",
|
|
178
244
|
addInputRules() {
|
|
179
245
|
return [
|
|
180
246
|
new InputRule({
|
|
181
247
|
find: /!video\(([^)]+)\)\s/,
|
|
182
|
-
handler: ({
|
|
248
|
+
handler: ({ range, match, commands }) => {
|
|
183
249
|
const [, url] = match;
|
|
184
250
|
if (url) {
|
|
185
251
|
const html = `<video src="${url}" autoplay="" loop="" muted="" controls="" playsinline=""></video>`;
|
|
@@ -200,7 +266,7 @@ function useExtensions({ props, emit }) {
|
|
|
200
266
|
return [
|
|
201
267
|
new InputRule({
|
|
202
268
|
find: /!audio\(([^)]+)\)\s/,
|
|
203
|
-
handler: ({
|
|
269
|
+
handler: ({ range, match, commands }) => {
|
|
204
270
|
const [, url] = match;
|
|
205
271
|
if (url) {
|
|
206
272
|
const html = `<audio src="${url}" controls="" playsinline=""></audio>`;
|
|
@@ -215,14 +281,8 @@ function useExtensions({ props, emit }) {
|
|
|
215
281
|
];
|
|
216
282
|
}
|
|
217
283
|
}),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// 如果你还想支持其他 style 属性,可以在这里配置
|
|
221
|
-
// HTMLAttributes: { class: 'my-custom-class' }
|
|
222
|
-
}),
|
|
223
|
-
Color.configure({
|
|
224
|
-
// types: ['textStyle'] 默认就是这个,一般不需要改
|
|
225
|
-
}),
|
|
284
|
+
TextStyle,
|
|
285
|
+
Color,
|
|
226
286
|
Markdown.configure({
|
|
227
287
|
html: true,
|
|
228
288
|
tightLists: true,
|
|
@@ -233,27 +293,19 @@ function useExtensions({ props, emit }) {
|
|
|
233
293
|
transformPastedText: true,
|
|
234
294
|
transformCopiedText: true
|
|
235
295
|
}),
|
|
236
|
-
// 自定义扩展来支持 Markdown 链接语法
|
|
237
296
|
Extension.create({
|
|
238
297
|
name: "markdownLinkInputRule",
|
|
239
298
|
addInputRules() {
|
|
240
299
|
return [
|
|
241
300
|
new InputRule({
|
|
242
301
|
find: /\[([^\]]+)\]\(([^\)]+)\)\s/,
|
|
243
|
-
handler: ({
|
|
302
|
+
handler: ({ range, match, commands }) => {
|
|
244
303
|
const [, text, href] = match;
|
|
245
|
-
|
|
246
|
-
const end = range.to;
|
|
247
|
-
commands.deleteRange({ from: start, to: end });
|
|
304
|
+
commands.deleteRange({ from: range.from, to: range.to });
|
|
248
305
|
commands.insertContent({
|
|
249
306
|
type: "text",
|
|
250
307
|
text,
|
|
251
|
-
marks: [
|
|
252
|
-
{
|
|
253
|
-
type: "link",
|
|
254
|
-
attrs: { href }
|
|
255
|
-
}
|
|
256
|
-
]
|
|
308
|
+
marks: [{ type: "link", attrs: { href } }]
|
|
257
309
|
});
|
|
258
310
|
}
|
|
259
311
|
})
|
|
@@ -265,9 +317,7 @@ function useExtensions({ props, emit }) {
|
|
|
265
317
|
table: { resizable: true }
|
|
266
318
|
}),
|
|
267
319
|
Placeholder.configure({
|
|
268
|
-
placeholder: () =>
|
|
269
|
-
return props.placeholder;
|
|
270
|
-
}
|
|
320
|
+
placeholder: () => props.placeholder
|
|
271
321
|
}),
|
|
272
322
|
ListKit.extend({
|
|
273
323
|
addKeyboardShortcuts() {
|
|
@@ -275,18 +325,11 @@ function useExtensions({ props, emit }) {
|
|
|
275
325
|
Tab: ({ editor }) => {
|
|
276
326
|
const { $from } = editor.state.selection;
|
|
277
327
|
const currentItem = $from.node(-1);
|
|
278
|
-
|
|
279
|
-
return true;
|
|
280
|
-
}
|
|
281
|
-
return false;
|
|
328
|
+
return currentItem.type.name === "listItem";
|
|
282
329
|
},
|
|
283
330
|
"Shift-Tab": ({ editor }) => {
|
|
284
331
|
const { $from } = editor.state.selection;
|
|
285
|
-
|
|
286
|
-
if (currentItem.type.name === "doc") {
|
|
287
|
-
return true;
|
|
288
|
-
}
|
|
289
|
-
return false;
|
|
332
|
+
return $from.node(-1).type.name === "doc";
|
|
290
333
|
}
|
|
291
334
|
};
|
|
292
335
|
}
|
|
@@ -300,32 +343,23 @@ function useExtensions({ props, emit }) {
|
|
|
300
343
|
char: "@",
|
|
301
344
|
allowedPrefixes: null,
|
|
302
345
|
allowSpaces: false,
|
|
303
|
-
/**
|
|
304
|
-
* allow
|
|
305
|
-
* - 功能:判断是否允许触发建议态。
|
|
306
|
-
* - 1. 当@ 前面一个字符是数字/英文时,不触发,防止用户是想要输入邮箱。
|
|
307
|
-
* - 2. 触发以后如果包含数字,则收起(通过返回 false 销毁建议态)。
|
|
308
|
-
*/
|
|
309
346
|
allow: ({ state, range }) => {
|
|
310
|
-
if (
|
|
347
|
+
if (checkConsumed(state, range))
|
|
311
348
|
return false;
|
|
312
|
-
}
|
|
313
349
|
const doc = state.doc;
|
|
314
350
|
const $from = doc.resolve(range.from);
|
|
315
351
|
const isAtStart = range.from === $from.start();
|
|
316
352
|
const prevChar = isAtStart ? "" : doc.textBetween(range.from - 1, range.from);
|
|
317
|
-
if (/[a-zA-Z0-9]/.test(prevChar))
|
|
353
|
+
if (/[a-zA-Z0-9]/.test(prevChar))
|
|
318
354
|
return false;
|
|
319
|
-
}
|
|
320
355
|
const query = doc.textBetween(range.from + 1, range.to);
|
|
321
|
-
if (/[0-9]/.test(query))
|
|
356
|
+
if (/[0-9]/.test(query))
|
|
322
357
|
return false;
|
|
323
|
-
}
|
|
324
358
|
return true;
|
|
325
359
|
},
|
|
326
360
|
render() {
|
|
327
361
|
return {
|
|
328
|
-
onStart({ editor,
|
|
362
|
+
onStart({ editor, command, query }) {
|
|
329
363
|
activeEditor = editor;
|
|
330
364
|
emit("mention-triggered", (data) => {
|
|
331
365
|
command({
|
|
@@ -339,23 +373,16 @@ function useExtensions({ props, emit }) {
|
|
|
339
373
|
activeEditor = editor;
|
|
340
374
|
emit("mention-input", query != null ? query : "");
|
|
341
375
|
},
|
|
342
|
-
onExit({ range }) {
|
|
343
|
-
|
|
344
|
-
spentMentionPositions.push(range.from);
|
|
345
|
-
}
|
|
376
|
+
onExit({ editor, range }) {
|
|
377
|
+
markAsConsumed(editor, range, "@");
|
|
346
378
|
emit("mention-exit");
|
|
347
379
|
}
|
|
348
380
|
};
|
|
349
381
|
}
|
|
350
382
|
}
|
|
351
383
|
}),
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
*/
|
|
355
|
-
MentionTracker,
|
|
356
|
-
HashTagTracker,
|
|
357
|
-
// HashTag 扩展:支持 # 触发、实时输入同步、完成后插入为 “#+文字”
|
|
358
|
-
HashTag
|
|
384
|
+
HashTag,
|
|
385
|
+
CustomContent
|
|
359
386
|
];
|
|
360
387
|
return extensions;
|
|
361
388
|
}
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
|
@@ -36,7 +36,7 @@ __export(stdin_exports, {
|
|
|
36
36
|
module.exports = __toCommonJS(stdin_exports);
|
|
37
37
|
var import_rich_text_editor = __toESM(require("./rich-text-editor"));
|
|
38
38
|
__reExport(stdin_exports, require("./rich-text-editor"), module.exports);
|
|
39
|
-
const version = "1.0.
|
|
39
|
+
const version = "1.0.48";
|
|
40
40
|
function install(app) {
|
|
41
41
|
const components = [
|
|
42
42
|
import_rich_text_editor.default
|
|
@@ -16,7 +16,7 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
16
16
|
type: BooleanConstructor;
|
|
17
17
|
default: boolean;
|
|
18
18
|
};
|
|
19
|
-
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("blur" | "focus" | "hashTag-triggered" | "hashTag-input" | "hashTag-exit" | "mention-triggered" | "mention-input" | "mention-exit" | "update:modelValue" | "mention-clicked" | "hashTag-clicked")[], "blur" | "focus" | "hashTag-triggered" | "hashTag-input" | "hashTag-exit" | "mention-triggered" | "mention-input" | "mention-exit" | "update:modelValue" | "mention-clicked" | "hashTag-clicked", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
19
|
+
}>, () => import("vue/jsx-runtime").JSX.Element, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, ("blur" | "focus" | "hashTag-triggered" | "hashTag-input" | "hashTag-exit" | "mention-triggered" | "mention-input" | "mention-exit" | "update:modelValue" | "mention-clicked" | "hashTag-clicked" | "customContent-triggered" | "customContent-input" | "customContent-exit" | "customContent-clicked")[], "blur" | "focus" | "hashTag-triggered" | "hashTag-input" | "hashTag-exit" | "mention-triggered" | "mention-input" | "mention-exit" | "update:modelValue" | "mention-clicked" | "hashTag-clicked" | "customContent-triggered" | "customContent-input" | "customContent-exit" | "customContent-clicked", import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
|
|
20
20
|
modelValue: {
|
|
21
21
|
type: StringConstructor;
|
|
22
22
|
default: string;
|
|
@@ -44,6 +44,10 @@ declare const _default: import("vue").DefineComponent<import("vue").ExtractPropT
|
|
|
44
44
|
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
45
45
|
"onMention-clicked"?: ((...args: any[]) => any) | undefined;
|
|
46
46
|
"onHashTag-clicked"?: ((...args: any[]) => any) | undefined;
|
|
47
|
+
"onCustomContent-triggered"?: ((...args: any[]) => any) | undefined;
|
|
48
|
+
"onCustomContent-input"?: ((...args: any[]) => any) | undefined;
|
|
49
|
+
"onCustomContent-exit"?: ((...args: any[]) => any) | undefined;
|
|
50
|
+
"onCustomContent-clicked"?: ((...args: any[]) => any) | undefined;
|
|
47
51
|
}>, {
|
|
48
52
|
modelValue: string;
|
|
49
53
|
placeholder: string;
|
|
@@ -63,7 +63,7 @@ var import_index = require("./index.css");
|
|
|
63
63
|
var stdin_default = (0, import_vue2.defineComponent)({
|
|
64
64
|
name: "RichTextEditor",
|
|
65
65
|
props: import_types.richTextEditorProps,
|
|
66
|
-
emits: ["update:modelValue", "mention-triggered", "mention-input", "mention-exit", "mention-clicked", "hashTag-triggered", "hashTag-input", "hashTag-exit", "hashTag-clicked", "blur", "focus"],
|
|
66
|
+
emits: ["update:modelValue", "mention-triggered", "mention-input", "mention-exit", "mention-clicked", "hashTag-triggered", "hashTag-input", "hashTag-exit", "hashTag-clicked", "customContent-triggered", "customContent-input", "customContent-exit", "customContent-clicked", "blur", "focus"],
|
|
67
67
|
setup(props, {
|
|
68
68
|
emit,
|
|
69
69
|
expose
|
|
@@ -131,10 +131,23 @@ var stdin_default = (0, import_vue2.defineComponent)({
|
|
|
131
131
|
}
|
|
132
132
|
});
|
|
133
133
|
};
|
|
134
|
+
const insertCustomContent = (data) => {
|
|
135
|
+
editor.value.commands.insertContent({
|
|
136
|
+
type: "customContent",
|
|
137
|
+
attrs: {
|
|
138
|
+
id: data.id || `custom-${Date.now()}`,
|
|
139
|
+
label: data.label,
|
|
140
|
+
obj: data.obj,
|
|
141
|
+
color: data.color,
|
|
142
|
+
hoverColor: data.hoverColor
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
};
|
|
134
146
|
expose({
|
|
135
147
|
getEditor: () => editor.value,
|
|
136
148
|
insertMention,
|
|
137
|
-
insertHashTag
|
|
149
|
+
insertHashTag,
|
|
150
|
+
insertCustomContent
|
|
138
151
|
});
|
|
139
152
|
(0, import_vue2.watch)(() => props.modelValue, (newValue) => {
|
|
140
153
|
var _a, _b;
|
|
@@ -217,6 +230,26 @@ var stdin_default = (0, import_vue2.defineComponent)({
|
|
|
217
230
|
return;
|
|
218
231
|
}
|
|
219
232
|
};
|
|
233
|
+
const customContentClickHandler = (event) => {
|
|
234
|
+
const target = event.target;
|
|
235
|
+
const tagEl = target.closest(".custom-content") || null;
|
|
236
|
+
if (tagEl) {
|
|
237
|
+
const objStr = tagEl.getAttribute("obj") || "{}";
|
|
238
|
+
const label = tagEl.getAttribute("file-label") || "";
|
|
239
|
+
try {
|
|
240
|
+
const obj = JSON.parse(objStr);
|
|
241
|
+
emit("customContent-clicked", {
|
|
242
|
+
obj,
|
|
243
|
+
label
|
|
244
|
+
});
|
|
245
|
+
} catch (e) {
|
|
246
|
+
console.error("Failed to parse custom content obj:", e);
|
|
247
|
+
}
|
|
248
|
+
event.stopPropagation();
|
|
249
|
+
event.preventDefault();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
};
|
|
220
253
|
const HISTORY_METADATA_KEY = "rich_text_editor_history_metadata";
|
|
221
254
|
const MAX_HISTORY_ITEMS = 10;
|
|
222
255
|
let saveTimeoutId = null;
|
|
@@ -270,6 +303,7 @@ var stdin_default = (0, import_vue2.defineComponent)({
|
|
|
270
303
|
editorElement.addEventListener("click", imageClickHandler);
|
|
271
304
|
editorElement.addEventListener("click", hashTagClickHandler);
|
|
272
305
|
editorElement.addEventListener("click", mentionClickHandler);
|
|
306
|
+
editorElement.addEventListener("click", customContentClickHandler);
|
|
273
307
|
}
|
|
274
308
|
});
|
|
275
309
|
});
|
|
@@ -283,6 +317,7 @@ var stdin_default = (0, import_vue2.defineComponent)({
|
|
|
283
317
|
editorElement.removeEventListener("click", imageClickHandler);
|
|
284
318
|
editorElement.removeEventListener("click", hashTagClickHandler);
|
|
285
319
|
editorElement.removeEventListener("click", mentionClickHandler);
|
|
320
|
+
editorElement.removeEventListener("click", customContentClickHandler);
|
|
286
321
|
}
|
|
287
322
|
(_c = editor.value) == null ? void 0 : _c.destroy();
|
|
288
323
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
body .ProseMirror{flex:1;overflow:auto;outline:none;line-height:1.6;font-size:var(--font-base);--white: #fff;--black: #2e2b29;--gray-1: rgba(61, 37, 20, .05);--gray-2: rgba(61, 37, 20, .08);--gray-3: rgba(61, 37, 20, .12);--gray-4: rgba(53, 38, 28, .3);--gray-5: rgba(28, 25, 23, .6);--green: #22c55e;--purple: #6a00f5;--purple-contrast: #5800cc;--purple-light: rgba(88, 5, 255, .05);--yellow-contrast: #facc15;--yellow: rgba(250, 204, 21, .4);--yellow-light: #fffae5;--red: #ff5c33;--red-light: #ffebe5;--shadow: 0px 12px 33px 0px rgba(0, 0, 0, .06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, .04)}body .ProseMirror :first-child{margin-top:0}body .ProseMirror video{width:100%}body .ProseMirror ol{list-style:auto}body .ProseMirror ol ol,body .ProseMirror ul{list-style:disc}body .ProseMirror ol,body .ProseMirror ul{padding-left:1.5em;margin:0 0 12px}body .ProseMirror ol li p,body .ProseMirror ul li p{margin-top:.25em;margin-bottom:.25em}body .ProseMirror li::marker{text-align:start!important}body .ProseMirror h1,body .ProseMirror h2,body .ProseMirror h3,body .ProseMirror h4,body .ProseMirror h5,body .ProseMirror h6{line-height:1.1;margin-top:2.5rem;text-wrap:pretty}body .ProseMirror h1,body .ProseMirror h2{margin-top:1rem;margin-bottom:1rem}body .ProseMirror h1{font-size:1.4rem}body .ProseMirror h2{font-size:1.2rem}body .ProseMirror h3{font-size:1.1rem}body .ProseMirror h4,body .ProseMirror h5,body .ProseMirror h6{font-size:1rem}body .ProseMirror a,body .ProseMirror .editor-link{color:var(--purple);text-decoration:underline;cursor:pointer;transition:color .2s ease}body .ProseMirror a:hover,body .ProseMirror .editor-link:hover{color:var(--purple-contrast);text-decoration:underline}body .ProseMirror code{background-color:#ffe5e8;border-radius:.4rem;color:#c02537;padding:2px 4px}body .ProseMirror pre{border-radius:.5rem;font-family:JetBrainsMono,monospace;margin:1.5rem 0;padding:.75rem 1rem}body .ProseMirror pre code{background:none;color:inherit;font-size:.8rem;padding:0}body .ProseMirror blockquote{border-left:3px solid var(--gray-3);margin:1.5rem 0;padding-left:1rem}body .ProseMirror hr{border:none;border-top:1px solid var(--gray-2);margin:2rem 0}body .ProseMirror p.is-editor-empty:first-child:before{color:var(--gray-4);content:attr(data-placeholder);float:left;height:0;pointer-events:none}body .ProseMirror .is-empty:before{color:var(--gray-4);content:attr(data-placeholder);float:left;height:0;pointer-events:none}body .ProseMirror table{border-collapse:collapse;margin:0;overflow:hidden;table-layout:fixed;width:100%}body .ProseMirror table td,body .ProseMirror table th{border:1px solid var(--gray-3);box-sizing:border-box;min-width:1em;padding:6px 8px;position:relative;vertical-align:top}body .ProseMirror table td>*,body .ProseMirror table th>*{margin-bottom:0}body .ProseMirror table th{background-color:var(--gray-1);font-weight:700;text-align:left}body .ProseMirror table .selectedCell:after{background:var(--gray-2);content:"";left:0;right:0;top:0;bottom:0;pointer-events:none;position:absolute;z-index:2}body .ProseMirror table .column-resize-handle{background-color:var(--purple);bottom:-2px;pointer-events:none;position:absolute;right:-2px;top:0;width:4px}body .ProseMirror .tableWrapper{margin:1.5rem 0;overflow-x:auto}body .ProseMirror.resize-cursor{cursor:ew-resize;cursor:col-resize}body .ProseMirror img{max-width:100%}body .ProseMirror .mention,body .ProseMirror .hash-tag{color:#c02537;padding:0 .3em}.animation-indent--right{animation:indent-right .5s cubic-bezier(.68,-.55,.27,1.55) 1 alternate-reverse}@keyframes indent-right{0%{transform:translate(0)}to{transform:translate(12px)}}
|
|
1
|
+
body .ProseMirror{flex:1;overflow:auto;outline:none;line-height:1.6;font-size:var(--font-base);--white: #fff;--black: #2e2b29;--gray-1: rgba(61, 37, 20, .05);--gray-2: rgba(61, 37, 20, .08);--gray-3: rgba(61, 37, 20, .12);--gray-4: rgba(53, 38, 28, .3);--gray-5: rgba(28, 25, 23, .6);--green: #22c55e;--purple: #6a00f5;--purple-contrast: #5800cc;--purple-light: rgba(88, 5, 255, .05);--yellow-contrast: #facc15;--yellow: rgba(250, 204, 21, .4);--yellow-light: #fffae5;--red: #ff5c33;--red-light: #ffebe5;--shadow: 0px 12px 33px 0px rgba(0, 0, 0, .06), 0px 3.618px 9.949px 0px rgba(0, 0, 0, .04)}body .ProseMirror :first-child{margin-top:0}body .ProseMirror video{width:100%}body .ProseMirror ol{list-style:auto}body .ProseMirror ol ol,body .ProseMirror ul{list-style:disc}body .ProseMirror ol,body .ProseMirror ul{padding-left:1.5em;margin:0 0 12px}body .ProseMirror ol li p,body .ProseMirror ul li p{margin-top:.25em;margin-bottom:.25em}body .ProseMirror li::marker{text-align:start!important}body .ProseMirror h1,body .ProseMirror h2,body .ProseMirror h3,body .ProseMirror h4,body .ProseMirror h5,body .ProseMirror h6{line-height:1.1;margin-top:2.5rem;text-wrap:pretty}body .ProseMirror h1,body .ProseMirror h2{margin-top:1rem;margin-bottom:1rem}body .ProseMirror h1{font-size:1.4rem}body .ProseMirror h2{font-size:1.2rem}body .ProseMirror h3{font-size:1.1rem}body .ProseMirror h4,body .ProseMirror h5,body .ProseMirror h6{font-size:1rem}body .ProseMirror a,body .ProseMirror .editor-link{color:var(--purple);text-decoration:underline;cursor:pointer;transition:color .2s ease}body .ProseMirror a:hover,body .ProseMirror .editor-link:hover{color:var(--purple-contrast);text-decoration:underline}body .ProseMirror code{background-color:#ffe5e8;border-radius:.4rem;color:#c02537;padding:2px 4px}body .ProseMirror pre{border-radius:.5rem;font-family:JetBrainsMono,monospace;margin:1.5rem 0;padding:.75rem 1rem}body .ProseMirror pre code{background:none;color:inherit;font-size:.8rem;padding:0}body .ProseMirror blockquote{border-left:3px solid var(--gray-3);margin:1.5rem 0;padding-left:1rem}body .ProseMirror hr{border:none;border-top:1px solid var(--gray-2);margin:2rem 0}body .ProseMirror p.is-editor-empty:first-child:before{color:var(--gray-4);content:attr(data-placeholder);float:left;height:0;pointer-events:none}body .ProseMirror .is-empty:before{color:var(--gray-4);content:attr(data-placeholder);float:left;height:0;pointer-events:none}body .ProseMirror table{border-collapse:collapse;margin:0;overflow:hidden;table-layout:fixed;width:100%}body .ProseMirror table td,body .ProseMirror table th{border:1px solid var(--gray-3);box-sizing:border-box;min-width:1em;padding:6px 8px;position:relative;vertical-align:top}body .ProseMirror table td>*,body .ProseMirror table th>*{margin-bottom:0}body .ProseMirror table th{background-color:var(--gray-1);font-weight:700;text-align:left}body .ProseMirror table .selectedCell:after{background:var(--gray-2);content:"";left:0;right:0;top:0;bottom:0;pointer-events:none;position:absolute;z-index:2}body .ProseMirror table .column-resize-handle{background-color:var(--purple);bottom:-2px;pointer-events:none;position:absolute;right:-2px;top:0;width:4px}body .ProseMirror .tableWrapper{margin:1.5rem 0;overflow-x:auto}body .ProseMirror.resize-cursor{cursor:ew-resize;cursor:col-resize}body .ProseMirror img{max-width:100%}body .ProseMirror .mention,body .ProseMirror .hash-tag{color:#c02537;padding:0 .3em}body .ProseMirror .custom-content{color:var(--custom-content-color, #000);padding:0 .3em;cursor:pointer;transition:color .2s ease}body .ProseMirror .custom-content:hover{color:var(--custom-content-hover-color, #c02537)}.animation-indent--right{animation:indent-right .5s cubic-bezier(.68,-.55,.27,1.55) 1 alternate-reverse}@keyframes indent-right{0%{transform:translate(0)}to{transform:translate(12px)}}
|
|
@@ -218,11 +218,22 @@ body .ProseMirror {
|
|
|
218
218
|
img {
|
|
219
219
|
max-width: 100%;
|
|
220
220
|
}
|
|
221
|
-
// @人的样式 #话题的样式
|
|
221
|
+
// @人的样式 #话题的样式 自定义内容的样式
|
|
222
222
|
.mention, .hash-tag {
|
|
223
223
|
color: #c02537;
|
|
224
224
|
padding: 0 0.3em;
|
|
225
225
|
}
|
|
226
|
+
|
|
227
|
+
.custom-content {
|
|
228
|
+
color: var(--custom-content-color, #000);
|
|
229
|
+
padding: 0 0.3em;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
transition: color 0.2s ease;
|
|
232
|
+
|
|
233
|
+
&:hover {
|
|
234
|
+
color: var(--custom-content-hover-color, #c02537);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
226
237
|
}
|
|
227
238
|
|
|
228
239
|
// animation
|
|
@@ -26,6 +26,21 @@ export interface HashTagData {
|
|
|
26
26
|
hashTagId: string;
|
|
27
27
|
name: string;
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* CustomContentData
|
|
31
|
+
* - 功能:自定义内容的数据结构。
|
|
32
|
+
* - 字段说明:
|
|
33
|
+
* - label: string 显示标签(如 )。
|
|
34
|
+
* - obj: 任意JSON对象,内容由用户自定义。
|
|
35
|
+
* - id: 可选及其他扩展字段。
|
|
36
|
+
*/
|
|
37
|
+
export interface CustomContentData {
|
|
38
|
+
label: string;
|
|
39
|
+
obj: Record<string, any>;
|
|
40
|
+
color?: string;
|
|
41
|
+
hoverColor?: string;
|
|
42
|
+
[key: string]: any;
|
|
43
|
+
}
|
|
29
44
|
/**
|
|
30
45
|
* HashTagExitEvent
|
|
31
46
|
* - 功能:在 `hashTag-exit` 事件中传递退出原因与是否为非连续输入。
|