vuewrite 0.0.25 → 0.0.27
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/markdown.d.ts +14 -0
- package/dist/markdown.js +358 -0
- package/dist/vuewrite.d.ts +57 -69
- package/dist/vuewrite.js +80 -220
- package/package.json +42 -38
- package/README.md +0 -68
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Block } from 'vuewrite-markdown';
|
|
2
|
+
import { blocksToMarkdown } from 'vuewrite-markdown';
|
|
3
|
+
import { markdownToBlocks } from 'vuewrite-markdown';
|
|
4
|
+
import { Style } from 'vuewrite-markdown';
|
|
5
|
+
|
|
6
|
+
export { Block }
|
|
7
|
+
|
|
8
|
+
export { blocksToMarkdown }
|
|
9
|
+
|
|
10
|
+
export { markdownToBlocks }
|
|
11
|
+
|
|
12
|
+
export { Style }
|
|
13
|
+
|
|
14
|
+
export { }
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
let _counter = 0;
|
|
2
|
+
const uid = () => (++_counter).toString();
|
|
3
|
+
function blockContentKey(block) {
|
|
4
|
+
const { id: _id, ...rest } = block;
|
|
5
|
+
return JSON.stringify(rest);
|
|
6
|
+
}
|
|
7
|
+
function lcsIndices(oldKeys, newKeys) {
|
|
8
|
+
const m = oldKeys.length;
|
|
9
|
+
const n = newKeys.length;
|
|
10
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
11
|
+
for (let i2 = 1; i2 <= m; i2++) {
|
|
12
|
+
for (let j2 = 1; j2 <= n; j2++) {
|
|
13
|
+
dp[i2][j2] = oldKeys[i2 - 1] === newKeys[j2 - 1] ? dp[i2 - 1][j2 - 1] + 1 : Math.max(dp[i2 - 1][j2], dp[i2][j2 - 1]);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
const pairs = [];
|
|
17
|
+
let i = m;
|
|
18
|
+
let j = n;
|
|
19
|
+
while (i > 0 && j > 0) {
|
|
20
|
+
if (oldKeys[i - 1] === newKeys[j - 1]) {
|
|
21
|
+
pairs.unshift([i - 1, j - 1]);
|
|
22
|
+
i--;
|
|
23
|
+
j--;
|
|
24
|
+
} else if (dp[i - 1][j] >= dp[i][j - 1]) {
|
|
25
|
+
i--;
|
|
26
|
+
} else {
|
|
27
|
+
j--;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return pairs;
|
|
31
|
+
}
|
|
32
|
+
function markdownToBlocks(markdown, previousBlocks = []) {
|
|
33
|
+
const lines = markdown.split("\n");
|
|
34
|
+
const blocks = [];
|
|
35
|
+
let i = 0;
|
|
36
|
+
while (i < lines.length) {
|
|
37
|
+
const line = lines[i];
|
|
38
|
+
const customFenceMatch = line.match(/^:::\s*(\S+)/);
|
|
39
|
+
if (customFenceMatch) {
|
|
40
|
+
const type = customFenceMatch[1];
|
|
41
|
+
const bodyLines = [];
|
|
42
|
+
i++;
|
|
43
|
+
while (i < lines.length && lines[i].trim() !== ":::") {
|
|
44
|
+
bodyLines.push(lines[i]);
|
|
45
|
+
i++;
|
|
46
|
+
}
|
|
47
|
+
const { text: text2, styles: styles2 } = parseInline(bodyLines.join("\n"));
|
|
48
|
+
const block2 = { id: uid(), text: text2, type };
|
|
49
|
+
if (styles2.length > 0) block2.styles = styles2;
|
|
50
|
+
blocks.push(block2);
|
|
51
|
+
i++;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const xmlPairedMatch = line.match(/^<(\w[\w-]*)(\s[^>]*)?>(.+)<\/\1>$/);
|
|
55
|
+
if (xmlPairedMatch) {
|
|
56
|
+
const attrs = parseAttributes(xmlPairedMatch[2] ?? "");
|
|
57
|
+
const { text: text2, styles: styles2 } = parseInline(xmlPairedMatch[3]);
|
|
58
|
+
const block2 = { id: uid(), text: text2, type: xmlPairedMatch[1], ...attrs };
|
|
59
|
+
if (styles2.length > 0) block2.styles = styles2;
|
|
60
|
+
blocks.push(block2);
|
|
61
|
+
i++;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const xmlSelfMatch = line.match(/^<(\w[\w-]*)(\s[^>]*)?\s*\/?>$/);
|
|
65
|
+
if (xmlSelfMatch) {
|
|
66
|
+
const attrs = parseAttributes(xmlSelfMatch[2] ?? "");
|
|
67
|
+
const block2 = { id: uid(), text: "", type: xmlSelfMatch[1], editable: false, ...attrs };
|
|
68
|
+
blocks.push(block2);
|
|
69
|
+
i++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (line.startsWith("```")) {
|
|
73
|
+
const codeLines = [];
|
|
74
|
+
i++;
|
|
75
|
+
while (i < lines.length && !lines[i].startsWith("```")) {
|
|
76
|
+
codeLines.push(lines[i]);
|
|
77
|
+
i++;
|
|
78
|
+
}
|
|
79
|
+
blocks.push({ id: uid(), text: codeLines.join("\n"), type: "code", editable: false });
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const headingMatch = line.match(/^(#{1,3}) (.+)$/);
|
|
84
|
+
if (headingMatch) {
|
|
85
|
+
const level = headingMatch[1].length;
|
|
86
|
+
const type = level === 1 ? "h1" : level === 2 ? "h2" : "h3";
|
|
87
|
+
const { text: text2, styles: styles2 } = parseInline(headingMatch[2]);
|
|
88
|
+
const block2 = { id: uid(), text: text2, type };
|
|
89
|
+
if (styles2.length > 0) block2.styles = styles2;
|
|
90
|
+
blocks.push(block2);
|
|
91
|
+
i++;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const ulMatch = line.match(/^[-*] (.+)$/);
|
|
95
|
+
if (ulMatch) {
|
|
96
|
+
const { text: text2, styles: styles2 } = parseInline(ulMatch[1]);
|
|
97
|
+
const block2 = { id: uid(), text: text2, type: "li" };
|
|
98
|
+
if (styles2.length > 0) block2.styles = styles2;
|
|
99
|
+
blocks.push(block2);
|
|
100
|
+
i++;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const olMatch = line.match(/^\d+\. (.+)$/);
|
|
104
|
+
if (olMatch) {
|
|
105
|
+
const { text: text2, styles: styles2 } = parseInline(olMatch[1]);
|
|
106
|
+
const block2 = { id: uid(), text: text2, type: "ol" };
|
|
107
|
+
if (styles2.length > 0) block2.styles = styles2;
|
|
108
|
+
blocks.push(block2);
|
|
109
|
+
i++;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (line === "") {
|
|
113
|
+
if (blocks.length > 0 && i < lines.length - 1) {
|
|
114
|
+
blocks.push({ id: uid(), text: "" });
|
|
115
|
+
}
|
|
116
|
+
i++;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const { text, styles } = parseInline(line);
|
|
120
|
+
const block = { id: uid(), text };
|
|
121
|
+
if (styles.length > 0) block.styles = styles;
|
|
122
|
+
blocks.push(block);
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
if (blocks.length === 0) {
|
|
126
|
+
blocks.push({ id: uid(), text: "" });
|
|
127
|
+
}
|
|
128
|
+
if (previousBlocks.length === 0) return blocks;
|
|
129
|
+
const oldKeys = previousBlocks.map(blockContentKey);
|
|
130
|
+
const newKeys = blocks.map(blockContentKey);
|
|
131
|
+
const pairs = lcsIndices(oldKeys, newKeys);
|
|
132
|
+
const oldIdByNewIndex = new Map(pairs.map(([i2, j]) => [j, previousBlocks[i2].id]));
|
|
133
|
+
return blocks.map((block, j) => {
|
|
134
|
+
const oldId = oldIdByNewIndex.get(j);
|
|
135
|
+
return oldId !== void 0 ? { ...block, id: oldId } : block;
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
function parseAttributes(attrStr) {
|
|
139
|
+
const result = {};
|
|
140
|
+
const re = /(\w[\w-]*)(?:=(?:"([^"]*)"|'([^']*)'|(\S+)))?/g;
|
|
141
|
+
let m;
|
|
142
|
+
while ((m = re.exec(attrStr)) !== null) {
|
|
143
|
+
result[m[1]] = m[2] ?? m[3] ?? m[4] ?? true;
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function parseInline(md) {
|
|
148
|
+
let plainText = "";
|
|
149
|
+
const styles = [];
|
|
150
|
+
function recurse(inner, extraStyles, linkMeta) {
|
|
151
|
+
const start = plainText.length;
|
|
152
|
+
const result = parseInline(inner);
|
|
153
|
+
plainText += result.text;
|
|
154
|
+
const end = plainText.length;
|
|
155
|
+
for (const styleName of extraStyles) {
|
|
156
|
+
styles.push({ start, end, style: styleName, ...linkMeta ? { meta: linkMeta } : {} });
|
|
157
|
+
}
|
|
158
|
+
for (const s of result.styles) {
|
|
159
|
+
styles.push({ ...s, start: start + s.start, end: start + s.end });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
let i = 0;
|
|
163
|
+
while (i < md.length) {
|
|
164
|
+
const rest = md.slice(i);
|
|
165
|
+
const boldItalicM = rest.match(/^\*\*\*([\s\S]*?)\*\*\*/);
|
|
166
|
+
if (boldItalicM) {
|
|
167
|
+
recurse(boldItalicM[1], ["bold", "italic"]);
|
|
168
|
+
i += boldItalicM[0].length;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const boldM = rest.match(/^\*\*([\s\S]*?)\*\*/);
|
|
172
|
+
if (boldM) {
|
|
173
|
+
recurse(boldM[1], ["bold"]);
|
|
174
|
+
i += boldM[0].length;
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
const underlineM = rest.match(/^__([\s\S]*?)__/);
|
|
178
|
+
if (underlineM) {
|
|
179
|
+
recurse(underlineM[1], ["underline"]);
|
|
180
|
+
i += underlineM[0].length;
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const italicAsteriskM = rest.match(/^\*([\s\S]*?)\*/);
|
|
184
|
+
if (italicAsteriskM) {
|
|
185
|
+
recurse(italicAsteriskM[1], ["italic"]);
|
|
186
|
+
i += italicAsteriskM[0].length;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const italicUnderscoreM = rest.match(/^_([\s\S]*?)_/);
|
|
190
|
+
if (italicUnderscoreM) {
|
|
191
|
+
recurse(italicUnderscoreM[1], ["italic"]);
|
|
192
|
+
i += italicUnderscoreM[0].length;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const codeM = rest.match(/^`([\s\S]*?)`/);
|
|
196
|
+
if (codeM) {
|
|
197
|
+
const start = plainText.length;
|
|
198
|
+
plainText += codeM[1];
|
|
199
|
+
styles.push({ start, end: plainText.length, style: "code" });
|
|
200
|
+
i += codeM[0].length;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
const linkM = rest.match(/^\[([\s\S]*?)\]\(([\s\S]*?)\)/);
|
|
204
|
+
if (linkM) {
|
|
205
|
+
recurse(linkM[1], ["link"], { href: linkM[2] });
|
|
206
|
+
i += linkM[0].length;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (md[i] === "\\" && i + 1 < md.length) {
|
|
210
|
+
plainText += md[i + 1];
|
|
211
|
+
i += 2;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
plainText += md[i];
|
|
215
|
+
i++;
|
|
216
|
+
}
|
|
217
|
+
return { text: plainText, styles };
|
|
218
|
+
}
|
|
219
|
+
const MARKERS = {
|
|
220
|
+
bold: { open: "**", close: "**" },
|
|
221
|
+
italic: { open: "*", close: "*" },
|
|
222
|
+
underline: { open: "__", close: "__" },
|
|
223
|
+
code: { open: "`", close: "`" }
|
|
224
|
+
};
|
|
225
|
+
const STANDARD_FIELDS = /* @__PURE__ */ new Set(["id", "text", "type", "styles", "editable"]);
|
|
226
|
+
function blocksToMarkdown(blocks) {
|
|
227
|
+
const lines = [];
|
|
228
|
+
let olCounter = 0;
|
|
229
|
+
for (const block of blocks) {
|
|
230
|
+
if (block.type === "code") {
|
|
231
|
+
lines.push("```");
|
|
232
|
+
lines.push(block.text);
|
|
233
|
+
lines.push("```");
|
|
234
|
+
olCounter = 0;
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
const extraKeys = Object.keys(block).filter((k) => !STANDARD_FIELDS.has(k));
|
|
238
|
+
if (block.type && extraKeys.length > 0) {
|
|
239
|
+
const attrStr = extraKeys.map((k) => {
|
|
240
|
+
const v = block[k];
|
|
241
|
+
return v === true ? k : `${k}="${v}"`;
|
|
242
|
+
}).join(" ");
|
|
243
|
+
const prefix = attrStr ? ` ${attrStr}` : "";
|
|
244
|
+
if (block.editable === false || !block.text) {
|
|
245
|
+
lines.push(`<${block.type}${prefix}/>`);
|
|
246
|
+
} else {
|
|
247
|
+
lines.push(`<${block.type}${prefix}>${renderInline(block.text, block.styles ?? [])}</${block.type}>`);
|
|
248
|
+
}
|
|
249
|
+
olCounter = 0;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const inline = renderInline(block.text, block.styles ?? []);
|
|
253
|
+
switch (block.type) {
|
|
254
|
+
case "h1":
|
|
255
|
+
lines.push(`# ${inline}`);
|
|
256
|
+
olCounter = 0;
|
|
257
|
+
break;
|
|
258
|
+
case "h2":
|
|
259
|
+
lines.push(`## ${inline}`);
|
|
260
|
+
olCounter = 0;
|
|
261
|
+
break;
|
|
262
|
+
case "h3":
|
|
263
|
+
lines.push(`### ${inline}`);
|
|
264
|
+
olCounter = 0;
|
|
265
|
+
break;
|
|
266
|
+
case "li":
|
|
267
|
+
lines.push(`- ${inline}`);
|
|
268
|
+
olCounter = 0;
|
|
269
|
+
break;
|
|
270
|
+
case "ol":
|
|
271
|
+
lines.push(`${++olCounter}. ${inline}`);
|
|
272
|
+
break;
|
|
273
|
+
default:
|
|
274
|
+
if (block.type) {
|
|
275
|
+
lines.push(`:::${block.type}`);
|
|
276
|
+
lines.push(inline);
|
|
277
|
+
lines.push(":::");
|
|
278
|
+
} else {
|
|
279
|
+
lines.push(inline);
|
|
280
|
+
}
|
|
281
|
+
olCounter = 0;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return lines.join("\n");
|
|
286
|
+
}
|
|
287
|
+
function escapeMarkdown(text) {
|
|
288
|
+
return text.replace(/[\\*_`\[\]]/g, "\\$&");
|
|
289
|
+
}
|
|
290
|
+
function buildSegments(text, styles) {
|
|
291
|
+
var _a;
|
|
292
|
+
const linkStyles = styles.filter((s) => s.style === "link");
|
|
293
|
+
const formatStyles = styles.filter((s) => s.style in MARKERS);
|
|
294
|
+
const bpSet = /* @__PURE__ */ new Set([0, text.length]);
|
|
295
|
+
for (const s of [...linkStyles, ...formatStyles]) {
|
|
296
|
+
bpSet.add(s.start);
|
|
297
|
+
bpSet.add(s.end);
|
|
298
|
+
}
|
|
299
|
+
const breakpoints = [...bpSet].sort((a, b) => a - b);
|
|
300
|
+
const segments = [];
|
|
301
|
+
for (let i = 0; i < breakpoints.length - 1; i++) {
|
|
302
|
+
const start = breakpoints[i];
|
|
303
|
+
const end = breakpoints[i + 1];
|
|
304
|
+
const formats = formatStyles.filter((s) => s.start <= start && s.end >= end).map((s) => s.style).sort((a, b) => MARKERS[b].open.length - MARKERS[a].open.length);
|
|
305
|
+
const link = linkStyles.find((l) => l.start <= start && l.end >= end);
|
|
306
|
+
segments.push({ start, end, formats, linkHref: (_a = link == null ? void 0 : link.meta) == null ? void 0 : _a.href });
|
|
307
|
+
}
|
|
308
|
+
return segments;
|
|
309
|
+
}
|
|
310
|
+
function renderSegments(text, segments) {
|
|
311
|
+
let result = "";
|
|
312
|
+
let activeFormats = [];
|
|
313
|
+
for (const seg of segments) {
|
|
314
|
+
const toClose = activeFormats.filter((f) => !seg.formats.includes(f));
|
|
315
|
+
if (toClose.length > 0) {
|
|
316
|
+
for (const f of [...activeFormats].reverse()) result += MARKERS[f].close;
|
|
317
|
+
for (const f of seg.formats) result += MARKERS[f].open;
|
|
318
|
+
} else {
|
|
319
|
+
for (const f of seg.formats) {
|
|
320
|
+
if (!activeFormats.includes(f)) result += MARKERS[f].open;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
activeFormats = seg.formats;
|
|
324
|
+
result += seg.formats.includes("code") ? text.slice(seg.start, seg.end) : escapeMarkdown(text.slice(seg.start, seg.end));
|
|
325
|
+
}
|
|
326
|
+
for (const f of [...activeFormats].reverse()) result += MARKERS[f].close;
|
|
327
|
+
return result;
|
|
328
|
+
}
|
|
329
|
+
function renderInline(text, styles) {
|
|
330
|
+
if (styles.length === 0) return escapeMarkdown(text);
|
|
331
|
+
const segments = buildSegments(text, styles);
|
|
332
|
+
let result = "";
|
|
333
|
+
let i = 0;
|
|
334
|
+
while (i < segments.length) {
|
|
335
|
+
const seg = segments[i];
|
|
336
|
+
if (seg.linkHref !== void 0) {
|
|
337
|
+
const href = seg.linkHref;
|
|
338
|
+
const linkSegs = [];
|
|
339
|
+
while (i < segments.length && segments[i].linkHref === href) {
|
|
340
|
+
linkSegs.push(segments[i]);
|
|
341
|
+
i++;
|
|
342
|
+
}
|
|
343
|
+
result += `[${renderSegments(text, linkSegs)}](${href})`;
|
|
344
|
+
} else {
|
|
345
|
+
const plainSegs = [];
|
|
346
|
+
while (i < segments.length && segments[i].linkHref === void 0) {
|
|
347
|
+
plainSegs.push(segments[i]);
|
|
348
|
+
i++;
|
|
349
|
+
}
|
|
350
|
+
result += renderSegments(text, plainSegs);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return result;
|
|
354
|
+
}
|
|
355
|
+
export {
|
|
356
|
+
blocksToMarkdown,
|
|
357
|
+
markdownToBlocks
|
|
358
|
+
};
|
package/dist/vuewrite.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { ComponentOptionsMixin } from 'vue';
|
|
2
|
+
import { ComponentProvideOptions } from 'vue';
|
|
2
3
|
import { ComputedRef } from 'vue';
|
|
3
4
|
import { DefineComponent } from 'vue';
|
|
4
5
|
import { ExtractPropTypes } from 'vue';
|
|
5
6
|
import { HTMLAttributes } from 'vue';
|
|
6
7
|
import { PropType } from 'vue';
|
|
7
8
|
import { PublicProps } from 'vue';
|
|
9
|
+
import { Reactive } from 'vue';
|
|
8
10
|
import { Ref } from 'vue';
|
|
9
11
|
import { RendererElement } from 'vue';
|
|
10
12
|
import { RendererNode } from 'vue';
|
|
@@ -57,22 +59,23 @@ export declare type Style = {
|
|
|
57
59
|
meta?: any;
|
|
58
60
|
};
|
|
59
61
|
|
|
60
|
-
export declare const TextEditor: __VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<{
|
|
61
|
-
decorator?: Decorator
|
|
62
|
-
renderer?: Renderer
|
|
63
|
-
single?: boolean
|
|
64
|
-
modelValue?:
|
|
62
|
+
export declare const TextEditor: __VLS_WithTemplateSlots<DefineComponent<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
|
|
63
|
+
decorator?: Decorator;
|
|
64
|
+
renderer?: Renderer;
|
|
65
|
+
single?: boolean;
|
|
66
|
+
modelValue?: {
|
|
67
|
+
id?: string;
|
|
65
68
|
text: string;
|
|
66
|
-
styles?: Style[]
|
|
67
|
-
type?: string
|
|
68
|
-
}[] |
|
|
69
|
-
parser?: TextParser
|
|
70
|
-
styles?: Style[]
|
|
71
|
-
autofocus?: boolean
|
|
72
|
-
autoselect?: boolean
|
|
73
|
-
preventMultiline?: boolean
|
|
74
|
-
htmlParser?: (
|
|
75
|
-
}
|
|
69
|
+
styles?: Style[];
|
|
70
|
+
type?: string;
|
|
71
|
+
}[] | string;
|
|
72
|
+
parser?: TextParser;
|
|
73
|
+
styles?: Style[];
|
|
74
|
+
autofocus?: boolean;
|
|
75
|
+
autoselect?: boolean;
|
|
76
|
+
preventMultiline?: boolean;
|
|
77
|
+
htmlParser?: (el: Element) => string | null | void;
|
|
78
|
+
}>>, {
|
|
76
79
|
currentStyles: ComputedRef<Map<string, Style>>;
|
|
77
80
|
currentBlock: ComputedRef< {
|
|
78
81
|
id: string;
|
|
@@ -97,8 +100,8 @@ blockId: string;
|
|
|
97
100
|
offset: number;
|
|
98
101
|
};
|
|
99
102
|
};
|
|
100
|
-
isFocused: Ref<boolean>;
|
|
101
|
-
getCurrentBlocks: () => Generator<Block
|
|
103
|
+
isFocused: Ref<boolean, boolean>;
|
|
104
|
+
getCurrentBlocks: () => Generator<Block>;
|
|
102
105
|
toggleStyle: (style: string) => void;
|
|
103
106
|
applyStyle: (_style: string, meta?: any) => void;
|
|
104
107
|
removeStyle: (_style: string) => void;
|
|
@@ -110,30 +113,31 @@ removeCurrentBlock: () => void;
|
|
|
110
113
|
selectAll: () => void;
|
|
111
114
|
pushHistory: (type: string) => void;
|
|
112
115
|
getClientRects: (selection: TextEditorSelection) => DOMRectList;
|
|
113
|
-
},
|
|
116
|
+
}, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
|
|
114
117
|
keydown: (...args: any[]) => void;
|
|
115
118
|
"update:modelValue": (...args: any[]) => void;
|
|
116
119
|
"update:styles": (...args: any[]) => void;
|
|
117
120
|
}, string, PublicProps, Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
|
|
118
|
-
decorator?: Decorator
|
|
119
|
-
renderer?: Renderer
|
|
120
|
-
single?: boolean
|
|
121
|
-
modelValue?:
|
|
121
|
+
decorator?: Decorator;
|
|
122
|
+
renderer?: Renderer;
|
|
123
|
+
single?: boolean;
|
|
124
|
+
modelValue?: {
|
|
125
|
+
id?: string;
|
|
122
126
|
text: string;
|
|
123
|
-
styles?: Style[]
|
|
124
|
-
type?: string
|
|
125
|
-
}[] |
|
|
126
|
-
parser?: TextParser
|
|
127
|
-
styles?: Style[]
|
|
128
|
-
autofocus?: boolean
|
|
129
|
-
autoselect?: boolean
|
|
130
|
-
preventMultiline?: boolean
|
|
131
|
-
htmlParser?: (
|
|
132
|
-
}>>> & {
|
|
127
|
+
styles?: Style[];
|
|
128
|
+
type?: string;
|
|
129
|
+
}[] | string;
|
|
130
|
+
parser?: TextParser;
|
|
131
|
+
styles?: Style[];
|
|
132
|
+
autofocus?: boolean;
|
|
133
|
+
autoselect?: boolean;
|
|
134
|
+
preventMultiline?: boolean;
|
|
135
|
+
htmlParser?: (el: Element) => string | null | void;
|
|
136
|
+
}>>> & Readonly<{
|
|
133
137
|
onKeydown?: ((...args: any[]) => any) | undefined;
|
|
134
138
|
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
135
139
|
"onUpdate:styles"?: ((...args: any[]) => any) | undefined;
|
|
136
|
-
}, {}, {}>, {
|
|
140
|
+
}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>, {
|
|
137
141
|
placeholder?(_: {}): any;
|
|
138
142
|
}>;
|
|
139
143
|
|
|
@@ -172,18 +176,7 @@ declare type TextEditorSelection = {
|
|
|
172
176
|
|
|
173
177
|
declare class TextEditorStore {
|
|
174
178
|
history: TextEditorHistory;
|
|
175
|
-
blocks:
|
|
176
|
-
id: string;
|
|
177
|
-
text: string;
|
|
178
|
-
type?: string | undefined;
|
|
179
|
-
styles?: {
|
|
180
|
-
start: number;
|
|
181
|
-
end: number;
|
|
182
|
-
style: string;
|
|
183
|
-
meta?: any;
|
|
184
|
-
}[] | undefined;
|
|
185
|
-
editable?: boolean | undefined;
|
|
186
|
-
}[];
|
|
179
|
+
blocks: Reactive<Block[]>;
|
|
187
180
|
selection: {
|
|
188
181
|
anchor: {
|
|
189
182
|
blockId: string;
|
|
@@ -196,7 +189,7 @@ declare class TextEditorStore {
|
|
|
196
189
|
};
|
|
197
190
|
_isCollapsed: ComputedRef<boolean>;
|
|
198
191
|
get isCollapsed(): boolean;
|
|
199
|
-
isFocused: Ref<boolean>;
|
|
192
|
+
isFocused: Ref<boolean, boolean>;
|
|
200
193
|
_currentBlock: ComputedRef< {
|
|
201
194
|
id: string;
|
|
202
195
|
text: string;
|
|
@@ -257,31 +250,26 @@ declare class TextEditorStore {
|
|
|
257
250
|
selectAll(): void;
|
|
258
251
|
}
|
|
259
252
|
|
|
260
|
-
export declare const TextEditorView: DefineComponent<
|
|
261
|
-
|
|
262
|
-
decorator?:
|
|
263
|
-
renderer?:
|
|
264
|
-
parser?:
|
|
265
|
-
|
|
266
|
-
listParser?:
|
|
267
|
-
}
|
|
253
|
+
export declare const TextEditorView: DefineComponent< {
|
|
254
|
+
modelValue: Block[] | string;
|
|
255
|
+
decorator?: Decorator | undefined;
|
|
256
|
+
renderer?: Renderer | undefined;
|
|
257
|
+
parser?: TextParser | undefined;
|
|
258
|
+
styles?: Style[] | undefined;
|
|
259
|
+
listParser?: ((block: Block) => string | undefined | void) | undefined;
|
|
260
|
+
}, () => VNode<RendererNode, RendererElement, {
|
|
268
261
|
[key: string]: any;
|
|
269
|
-
}>,
|
|
270
|
-
|
|
271
|
-
decorator?:
|
|
272
|
-
renderer?:
|
|
273
|
-
parser?:
|
|
274
|
-
|
|
275
|
-
listParser?:
|
|
276
|
-
}
|
|
277
|
-
readonly styles?: any;
|
|
278
|
-
readonly decorator?: any;
|
|
279
|
-
readonly renderer?: any;
|
|
280
|
-
readonly parser?: any;
|
|
281
|
-
readonly modelValue?: any;
|
|
282
|
-
readonly listParser?: any;
|
|
283
|
-
}, {}>;
|
|
262
|
+
}>, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<{
|
|
263
|
+
modelValue: Block[] | string;
|
|
264
|
+
decorator?: Decorator | undefined;
|
|
265
|
+
renderer?: Renderer | undefined;
|
|
266
|
+
parser?: TextParser | undefined;
|
|
267
|
+
styles?: Style[] | undefined;
|
|
268
|
+
listParser?: ((block: Block) => string | undefined | void) | undefined;
|
|
269
|
+
}> & Readonly<{}>, {}, {}, {}, {}, string, ComponentProvideOptions, true, {}, any>;
|
|
284
270
|
|
|
285
271
|
declare type TextParser = (text: string) => Style[];
|
|
286
272
|
|
|
273
|
+
export declare const uid: () => string;
|
|
274
|
+
|
|
287
275
|
export { }
|
package/dist/vuewrite.js
CHANGED
|
@@ -1,110 +1,30 @@
|
|
|
1
1
|
var __defProp = Object.defineProperty;
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
-
var __publicField = (obj, key, value) =>
|
|
4
|
-
|
|
5
|
-
return value;
|
|
6
|
-
};
|
|
7
|
-
import { getCurrentScope, onScopeDispose, unref, watch, reactive, computed, ref, defineComponent, getCurrentInstance, h, nextTick, useSlots, isProxy, toRaw, onMounted, openBlock, createElementBlock, Fragment, renderList, createBlock, renderSlot, createCommentVNode } from "vue";
|
|
8
|
-
function tryOnScopeDispose(fn) {
|
|
9
|
-
if (getCurrentScope()) {
|
|
10
|
-
onScopeDispose(fn);
|
|
11
|
-
return true;
|
|
12
|
-
}
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
function toValue(r) {
|
|
16
|
-
return typeof r === "function" ? r() : unref(r);
|
|
17
|
-
}
|
|
18
|
-
const isClient = typeof window !== "undefined" && typeof document !== "undefined";
|
|
19
|
-
typeof WorkerGlobalScope !== "undefined" && globalThis instanceof WorkerGlobalScope;
|
|
20
|
-
const toString = Object.prototype.toString;
|
|
21
|
-
const isObject = (val) => toString.call(val) === "[object Object]";
|
|
22
|
-
const noop = () => {
|
|
23
|
-
};
|
|
24
|
-
function unrefElement(elRef) {
|
|
25
|
-
var _a;
|
|
26
|
-
const plain = toValue(elRef);
|
|
27
|
-
return (_a = plain == null ? void 0 : plain.$el) != null ? _a : plain;
|
|
28
|
-
}
|
|
29
|
-
const defaultWindow = isClient ? window : void 0;
|
|
30
|
-
function useEventListener(...args) {
|
|
31
|
-
let target;
|
|
32
|
-
let events;
|
|
33
|
-
let listeners;
|
|
34
|
-
let options;
|
|
35
|
-
if (typeof args[0] === "string" || Array.isArray(args[0])) {
|
|
36
|
-
[events, listeners, options] = args;
|
|
37
|
-
target = defaultWindow;
|
|
38
|
-
} else {
|
|
39
|
-
[target, events, listeners, options] = args;
|
|
40
|
-
}
|
|
41
|
-
if (!target)
|
|
42
|
-
return noop;
|
|
43
|
-
if (!Array.isArray(events))
|
|
44
|
-
events = [events];
|
|
45
|
-
if (!Array.isArray(listeners))
|
|
46
|
-
listeners = [listeners];
|
|
47
|
-
const cleanups = [];
|
|
48
|
-
const cleanup = () => {
|
|
49
|
-
cleanups.forEach((fn) => fn());
|
|
50
|
-
cleanups.length = 0;
|
|
51
|
-
};
|
|
52
|
-
const register = (el, event, listener, options2) => {
|
|
53
|
-
el.addEventListener(event, listener, options2);
|
|
54
|
-
return () => el.removeEventListener(event, listener, options2);
|
|
55
|
-
};
|
|
56
|
-
const stopWatch = watch(
|
|
57
|
-
() => [unrefElement(target), toValue(options)],
|
|
58
|
-
([el, options2]) => {
|
|
59
|
-
cleanup();
|
|
60
|
-
if (!el)
|
|
61
|
-
return;
|
|
62
|
-
const optionsClone = isObject(options2) ? { ...options2 } : options2;
|
|
63
|
-
cleanups.push(
|
|
64
|
-
...events.flatMap((event) => {
|
|
65
|
-
return listeners.map((listener) => register(el, event, listener, optionsClone));
|
|
66
|
-
})
|
|
67
|
-
);
|
|
68
|
-
},
|
|
69
|
-
{ immediate: true, flush: "post" }
|
|
70
|
-
);
|
|
71
|
-
const stop = () => {
|
|
72
|
-
stopWatch();
|
|
73
|
-
cleanup();
|
|
74
|
-
};
|
|
75
|
-
tryOnScopeDispose(stop);
|
|
76
|
-
return stop;
|
|
77
|
-
}
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
import { reactive, computed, ref, defineComponent, getCurrentInstance, h, nextTick, useSlots, watch, isProxy, toRaw, onMounted, onUnmounted, openBlock, createElementBlock, unref, Fragment, renderList, createBlock, renderSlot, createCommentVNode } from "vue";
|
|
78
5
|
const findParent = (el, callback) => {
|
|
79
|
-
if (!el)
|
|
80
|
-
return null;
|
|
6
|
+
if (!el) return null;
|
|
81
7
|
if (el.nodeType === Node.ELEMENT_NODE) {
|
|
82
|
-
if (callback(el))
|
|
83
|
-
return el;
|
|
8
|
+
if (callback(el)) return el;
|
|
84
9
|
}
|
|
85
|
-
if (!el.parentElement)
|
|
86
|
-
return null;
|
|
10
|
+
if (!el.parentElement) return null;
|
|
87
11
|
return findParent(el.parentElement, callback);
|
|
88
12
|
};
|
|
89
13
|
const calcOffsetToNode = (parent, target) => {
|
|
90
14
|
let offset = 0;
|
|
91
15
|
for (let child of parent.childNodes) {
|
|
92
|
-
if (child.nodeName === "BR")
|
|
93
|
-
continue;
|
|
16
|
+
if (child.nodeName === "BR") continue;
|
|
94
17
|
const ch = child.nodeType === Node.TEXT_NODE ? child : child.childNodes[0];
|
|
95
|
-
if (ch === target)
|
|
96
|
-
return offset;
|
|
18
|
+
if (ch === target) return offset;
|
|
97
19
|
offset += ch.length;
|
|
98
20
|
}
|
|
99
21
|
return offset;
|
|
100
22
|
};
|
|
101
23
|
const calcNodeByOffset = (parent, offset) => {
|
|
102
24
|
let currentOffset = offset;
|
|
103
|
-
if (offset === 0)
|
|
104
|
-
return [parent, 0];
|
|
25
|
+
if (offset === 0) return [parent, 0];
|
|
105
26
|
for (let child of parent.childNodes) {
|
|
106
|
-
if (child.nodeName === "BR")
|
|
107
|
-
continue;
|
|
27
|
+
if (child.nodeName === "BR") continue;
|
|
108
28
|
const ch = child.nodeType === Node.TEXT_NODE ? child : child.childNodes[0];
|
|
109
29
|
if (currentOffset - ch.length <= 0) {
|
|
110
30
|
return [ch, currentOffset];
|
|
@@ -114,29 +34,22 @@ const calcNodeByOffset = (parent, offset) => {
|
|
|
114
34
|
return [parent, 0];
|
|
115
35
|
};
|
|
116
36
|
const clamp = (val, min, max) => {
|
|
117
|
-
if (val > max)
|
|
118
|
-
|
|
119
|
-
if (val < min)
|
|
120
|
-
return min;
|
|
37
|
+
if (val > max) return max;
|
|
38
|
+
if (val < min) return min;
|
|
121
39
|
return val;
|
|
122
40
|
};
|
|
123
41
|
const isEqual = (a, b) => {
|
|
124
|
-
if (a === b)
|
|
125
|
-
|
|
126
|
-
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null)
|
|
127
|
-
return false;
|
|
42
|
+
if (a === b) return true;
|
|
43
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
128
44
|
if (Array.isArray(a)) {
|
|
129
|
-
if (!Array.isArray(b) || a.length !== b.length)
|
|
130
|
-
return false;
|
|
45
|
+
if (!Array.isArray(b) || a.length !== b.length) return false;
|
|
131
46
|
for (let i = 0; i < a.length; i++) {
|
|
132
|
-
if (!isEqual(a[i], b[i]))
|
|
133
|
-
return false;
|
|
47
|
+
if (!isEqual(a[i], b[i])) return false;
|
|
134
48
|
}
|
|
135
49
|
return true;
|
|
136
50
|
}
|
|
137
51
|
for (let key in a) {
|
|
138
|
-
if (!isEqual(a[key], b[key]))
|
|
139
|
-
return false;
|
|
52
|
+
if (!isEqual(a[key], b[key])) return false;
|
|
140
53
|
}
|
|
141
54
|
return true;
|
|
142
55
|
};
|
|
@@ -214,14 +127,12 @@ class TextEditorHistory {
|
|
|
214
127
|
this.cacheBlockIds();
|
|
215
128
|
}
|
|
216
129
|
undo() {
|
|
217
|
-
if (this.currentCursor === 0)
|
|
218
|
-
return;
|
|
130
|
+
if (this.currentCursor === 0) return;
|
|
219
131
|
this.currentCursor--;
|
|
220
132
|
this.applyAction(this.actions[this.currentCursor]);
|
|
221
133
|
}
|
|
222
134
|
redo() {
|
|
223
|
-
if (this.currentCursor >= this.actions.length - 1)
|
|
224
|
-
return;
|
|
135
|
+
if (this.currentCursor >= this.actions.length - 1) return;
|
|
225
136
|
this.currentCursor++;
|
|
226
137
|
this.applyAction(this.actions[this.currentCursor]);
|
|
227
138
|
}
|
|
@@ -239,13 +150,11 @@ class TextEditorStore {
|
|
|
239
150
|
}));
|
|
240
151
|
__publicField(this, "isFocused", ref(false));
|
|
241
152
|
__publicField(this, "_currentBlock", computed(() => {
|
|
242
|
-
if (this.selection.anchor.blockId !== this.selection.focus.blockId)
|
|
243
|
-
return null;
|
|
153
|
+
if (this.selection.anchor.blockId !== this.selection.focus.blockId) return null;
|
|
244
154
|
return this.blocks.find((item) => item.id === this.selection.anchor.blockId) ?? null;
|
|
245
155
|
}));
|
|
246
156
|
__publicField(this, "_selectedText", computed(() => {
|
|
247
|
-
if (this.isCollapsed)
|
|
248
|
-
return "";
|
|
157
|
+
if (this.isCollapsed) return "";
|
|
249
158
|
const [start, end, startIndex, endIndex] = this.startAndEnd;
|
|
250
159
|
if (startIndex === endIndex) {
|
|
251
160
|
return this.blocks[startIndex].text.slice(start.offset, end.offset);
|
|
@@ -258,17 +167,13 @@ class TextEditorStore {
|
|
|
258
167
|
__publicField(this, "_currentStyles", computed(() => {
|
|
259
168
|
const [start, end, startIndex, endIndex] = this.startAndEnd;
|
|
260
169
|
const styles = /* @__PURE__ */ new Map();
|
|
261
|
-
if (startIndex < 0)
|
|
262
|
-
return styles;
|
|
170
|
+
if (startIndex < 0) return styles;
|
|
263
171
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
264
172
|
const blockStyles = this.blocks[i].styles;
|
|
265
|
-
if (!blockStyles)
|
|
266
|
-
continue;
|
|
173
|
+
if (!blockStyles) continue;
|
|
267
174
|
for (let style of blockStyles) {
|
|
268
|
-
if (i === startIndex && start.offset < style.start)
|
|
269
|
-
|
|
270
|
-
if (i === endIndex && end.offset > style.end)
|
|
271
|
-
continue;
|
|
175
|
+
if (i === startIndex && start.offset < style.start) continue;
|
|
176
|
+
if (i === endIndex && end.offset > style.end) continue;
|
|
272
177
|
styles.set(style.style, style);
|
|
273
178
|
}
|
|
274
179
|
}
|
|
@@ -284,8 +189,7 @@ class TextEditorStore {
|
|
|
284
189
|
*getCurrentBlocks() {
|
|
285
190
|
const blockAnchorIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
|
|
286
191
|
const focusAnchorIndex = this.blocks.findIndex((item) => item.id === this.selection.focus.blockId);
|
|
287
|
-
if (blockAnchorIndex === -1 || focusAnchorIndex === -1)
|
|
288
|
-
return;
|
|
192
|
+
if (blockAnchorIndex === -1 || focusAnchorIndex === -1) return;
|
|
289
193
|
const start = Math.min(blockAnchorIndex, focusAnchorIndex);
|
|
290
194
|
const end = Math.max(blockAnchorIndex, focusAnchorIndex);
|
|
291
195
|
for (let i = start; i <= end; i++) {
|
|
@@ -297,8 +201,7 @@ class TextEditorStore {
|
|
|
297
201
|
}
|
|
298
202
|
moveOffset(newOffset) {
|
|
299
203
|
const delta = newOffset - this.selection.anchor.offset;
|
|
300
|
-
if (delta === 0)
|
|
301
|
-
return;
|
|
204
|
+
if (delta === 0) return;
|
|
302
205
|
this.moveStyles(this.currentBlock, this.selection.focus.offset, -delta);
|
|
303
206
|
this.selection.anchor.offset = newOffset;
|
|
304
207
|
this.selection.focus.offset = newOffset;
|
|
@@ -323,8 +226,7 @@ class TextEditorStore {
|
|
|
323
226
|
}
|
|
324
227
|
removeCurrentBlock() {
|
|
325
228
|
const blockIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
|
|
326
|
-
if (blockIndex < 0)
|
|
327
|
-
return;
|
|
229
|
+
if (blockIndex < 0) return;
|
|
328
230
|
this.blocks.splice(blockIndex, 1);
|
|
329
231
|
if (this.blocks.length === 0) {
|
|
330
232
|
this.blocks.push({ id: uid(), text: "" });
|
|
@@ -338,8 +240,7 @@ class TextEditorStore {
|
|
|
338
240
|
}
|
|
339
241
|
removeNewLine() {
|
|
340
242
|
const blockIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
|
|
341
|
-
if (blockIndex < 1)
|
|
342
|
-
return;
|
|
243
|
+
if (blockIndex < 1) return;
|
|
343
244
|
if (this.blocks[blockIndex - 1].editable === false) {
|
|
344
245
|
if (this.blocks[blockIndex].text === "") {
|
|
345
246
|
this.blocks.splice(blockIndex, 1);
|
|
@@ -359,15 +260,12 @@ class TextEditorStore {
|
|
|
359
260
|
}
|
|
360
261
|
onInput(_e) {
|
|
361
262
|
const ev = _e;
|
|
362
|
-
if (ev.defaultPrevented)
|
|
363
|
-
return;
|
|
263
|
+
if (ev.defaultPrevented) return;
|
|
364
264
|
ev.preventDefault();
|
|
365
265
|
const collapsed = this.isCollapsed;
|
|
366
|
-
if (!collapsed)
|
|
367
|
-
this.deleteSelected();
|
|
266
|
+
if (!collapsed) this.deleteSelected();
|
|
368
267
|
const block = this.currentBlock;
|
|
369
|
-
if (!block)
|
|
370
|
-
return;
|
|
268
|
+
if (!block) return;
|
|
371
269
|
if ((ev.inputType === "deleteContentBackward" || ev.inputType === "deleteContentForward") && collapsed) {
|
|
372
270
|
if (ev.inputType === "deleteContentBackward") {
|
|
373
271
|
if (this.selection.anchor.offset === 0) {
|
|
@@ -416,8 +314,7 @@ class TextEditorStore {
|
|
|
416
314
|
return;
|
|
417
315
|
}
|
|
418
316
|
this.deleteSelected();
|
|
419
|
-
if (!this.currentBlock)
|
|
420
|
-
return;
|
|
317
|
+
if (!this.currentBlock) return;
|
|
421
318
|
const endText = this.currentBlock.editable === false ? "" : this.currentBlock.text.slice(this.selection.anchor.offset);
|
|
422
319
|
this.currentBlock.text = this.currentBlock.text.slice(0, this.selection.anchor.offset);
|
|
423
320
|
const block = { id: uid(), text: endText || "" };
|
|
@@ -435,15 +332,13 @@ class TextEditorStore {
|
|
|
435
332
|
}
|
|
436
333
|
insertText(data) {
|
|
437
334
|
const block = this.currentBlock;
|
|
438
|
-
if (!block)
|
|
439
|
-
return;
|
|
335
|
+
if (!block) return;
|
|
440
336
|
const text = data.replace(/\r/g, "");
|
|
441
337
|
block.text = block.text.slice(0, this.selection.focus.offset) + text + block.text.slice(this.selection.focus.offset);
|
|
442
338
|
this.moveOffset(clamp(this.selection.focus.offset + text.length, 0, block.text.length));
|
|
443
339
|
}
|
|
444
340
|
insertBlock(blockData) {
|
|
445
|
-
if (!this.currentBlock)
|
|
446
|
-
return;
|
|
341
|
+
if (!this.currentBlock) return;
|
|
447
342
|
this.deleteSelected();
|
|
448
343
|
if (this.currentBlock.text !== "") {
|
|
449
344
|
this.addNewLine();
|
|
@@ -473,14 +368,11 @@ class TextEditorStore {
|
|
|
473
368
|
}
|
|
474
369
|
}
|
|
475
370
|
moveStyles(block, offset, delta) {
|
|
476
|
-
if (!block.styles)
|
|
477
|
-
return;
|
|
371
|
+
if (!block.styles) return;
|
|
478
372
|
for (let i = 0; i < block.styles.length; i++) {
|
|
479
373
|
const style = block.styles[i];
|
|
480
|
-
if (style.start >= offset)
|
|
481
|
-
|
|
482
|
-
if (style.end >= offset)
|
|
483
|
-
style.end -= delta;
|
|
374
|
+
if (style.start >= offset) style.start -= delta;
|
|
375
|
+
if (style.end >= offset) style.end -= delta;
|
|
484
376
|
if (style.end <= style.start) {
|
|
485
377
|
block.styles.splice(i, 1);
|
|
486
378
|
i--;
|
|
@@ -488,8 +380,7 @@ class TextEditorStore {
|
|
|
488
380
|
}
|
|
489
381
|
}
|
|
490
382
|
deleteSelected() {
|
|
491
|
-
if (this.isCollapsed)
|
|
492
|
-
return;
|
|
383
|
+
if (this.isCollapsed) return;
|
|
493
384
|
const [start, end, startIndex, endIndex] = this.startAndEnd;
|
|
494
385
|
const startText = this.blocks[startIndex].text.slice(0, start.offset);
|
|
495
386
|
const endText = this.blocks[endIndex].text.slice(end.offset);
|
|
@@ -511,8 +402,7 @@ class TextEditorStore {
|
|
|
511
402
|
return this._currentStyles.value;
|
|
512
403
|
}
|
|
513
404
|
applyStyle(_style, meta) {
|
|
514
|
-
if (this.isCollapsed)
|
|
515
|
-
return;
|
|
405
|
+
if (this.isCollapsed) return;
|
|
516
406
|
const [start, end, startIndex, endIndex] = this.startAndEnd;
|
|
517
407
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
518
408
|
const block = this.blocks[i];
|
|
@@ -521,13 +411,10 @@ class TextEditorStore {
|
|
|
521
411
|
let createNewStyle = true;
|
|
522
412
|
if (block.styles) {
|
|
523
413
|
for (let style of block.styles) {
|
|
524
|
-
if (style.style !== _style)
|
|
525
|
-
|
|
526
|
-
if (style.start > _end || style.end < _start)
|
|
527
|
-
continue;
|
|
414
|
+
if (style.style !== _style) continue;
|
|
415
|
+
if (style.start > _end || style.end < _start) continue;
|
|
528
416
|
if (meta && !isEqual(meta, style.meta) && (style.start !== _start || style.end !== _end)) {
|
|
529
|
-
if (_start === style.end || _end === style.start)
|
|
530
|
-
continue;
|
|
417
|
+
if (_start === style.end || _end === style.start) continue;
|
|
531
418
|
this.removeStyleAt(block, _start, _end, _style);
|
|
532
419
|
} else {
|
|
533
420
|
style.start = Math.min(style.start, _start);
|
|
@@ -551,14 +438,11 @@ class TextEditorStore {
|
|
|
551
438
|
}
|
|
552
439
|
removeStyleAt(block, _start, _end, _style) {
|
|
553
440
|
const removeAll = typeof _style !== "string";
|
|
554
|
-
if (!block.styles)
|
|
555
|
-
return;
|
|
441
|
+
if (!block.styles) return;
|
|
556
442
|
for (let i = 0; i < block.styles.length; i++) {
|
|
557
443
|
const style = block.styles[i];
|
|
558
|
-
if (!removeAll && style.style !== _style)
|
|
559
|
-
|
|
560
|
-
if (style.start > _end || style.end < _start)
|
|
561
|
-
continue;
|
|
444
|
+
if (!removeAll && style.style !== _style) continue;
|
|
445
|
+
if (style.start > _end || style.end < _start) continue;
|
|
562
446
|
if (style.start >= _start && style.end <= _end) {
|
|
563
447
|
block.styles.splice(i, 1);
|
|
564
448
|
i--;
|
|
@@ -618,17 +502,14 @@ const _sfc_main$2 = defineComponent({
|
|
|
618
502
|
emits: ["postrender"],
|
|
619
503
|
setup(props, { emit }) {
|
|
620
504
|
const slot = computed(() => {
|
|
621
|
-
if (!props.block.type)
|
|
622
|
-
return props.slots["default"] ?? null;
|
|
505
|
+
if (!props.block.type) return props.slots["default"] ?? null;
|
|
623
506
|
return props.slots[props.block.type] ?? null;
|
|
624
507
|
});
|
|
625
508
|
const instance = getCurrentInstance();
|
|
626
509
|
const getRef = () => {
|
|
627
|
-
if (!instance)
|
|
628
|
-
return null;
|
|
510
|
+
if (!instance) return null;
|
|
629
511
|
const el = instance.vnode.el;
|
|
630
|
-
if (!el)
|
|
631
|
-
return null;
|
|
512
|
+
if (!el) return null;
|
|
632
513
|
if (el.nodeType === Node.TEXT_NODE && el.nextSibling !== null) {
|
|
633
514
|
return el.nextSibling;
|
|
634
515
|
}
|
|
@@ -637,11 +518,9 @@ const _sfc_main$2 = defineComponent({
|
|
|
637
518
|
const cacheNodes = [];
|
|
638
519
|
let cacheEl = null;
|
|
639
520
|
const cleanTree = (count) => {
|
|
640
|
-
if (props.static === true)
|
|
641
|
-
return;
|
|
521
|
+
if (props.static === true) return;
|
|
642
522
|
const el = getRef();
|
|
643
|
-
if (!el)
|
|
644
|
-
return;
|
|
523
|
+
if (!el) return;
|
|
645
524
|
if (el === cacheEl && cacheNodes.length > 0) {
|
|
646
525
|
el.prepend(cacheNodes[0]);
|
|
647
526
|
el.append(cacheNodes[1]);
|
|
@@ -653,12 +532,9 @@ const _sfc_main$2 = defineComponent({
|
|
|
653
532
|
if (el.childNodes.length > count + 2) {
|
|
654
533
|
for (let i = 0; i < el.childNodes.length; i++) {
|
|
655
534
|
const child = el.childNodes[i];
|
|
656
|
-
if (i === 0 && child.nodeType === Node.TEXT_NODE && child.textContent === "")
|
|
657
|
-
|
|
658
|
-
if (
|
|
659
|
-
continue;
|
|
660
|
-
if (child.nodeType === Node.TEXT_NODE && ((_a = child.textContent) == null ? void 0 : _a.endsWith("\n")))
|
|
661
|
-
continue;
|
|
535
|
+
if (i === 0 && child.nodeType === Node.TEXT_NODE && child.textContent === "") continue;
|
|
536
|
+
if ("__vnode" in child) continue;
|
|
537
|
+
if (child.nodeType === Node.TEXT_NODE && ((_a = child.textContent) == null ? void 0 : _a.endsWith("\n"))) continue;
|
|
662
538
|
el.removeChild(child);
|
|
663
539
|
break;
|
|
664
540
|
}
|
|
@@ -673,8 +549,7 @@ const _sfc_main$2 = defineComponent({
|
|
|
673
549
|
};
|
|
674
550
|
const content = () => {
|
|
675
551
|
const block = props.block;
|
|
676
|
-
if (block.editable === false)
|
|
677
|
-
return [];
|
|
552
|
+
if (block.editable === false) return [];
|
|
678
553
|
if (block.text.length === 0) {
|
|
679
554
|
cleanTree(1);
|
|
680
555
|
return [h("br")];
|
|
@@ -686,16 +561,14 @@ const _sfc_main$2 = defineComponent({
|
|
|
686
561
|
const markers = [];
|
|
687
562
|
if (block.styles) {
|
|
688
563
|
for (let style of block.styles) {
|
|
689
|
-
if (style.end <= style.start)
|
|
690
|
-
continue;
|
|
564
|
+
if (style.end <= style.start) continue;
|
|
691
565
|
markers.push([style.start, style]);
|
|
692
566
|
markers.push([style.end, style]);
|
|
693
567
|
}
|
|
694
568
|
}
|
|
695
569
|
if (props.parser) {
|
|
696
570
|
for (let style of props.parser(text)) {
|
|
697
|
-
if (style.end <= style.start)
|
|
698
|
-
continue;
|
|
571
|
+
if (style.end <= style.start) continue;
|
|
699
572
|
markers.push([style.start, style]);
|
|
700
573
|
markers.push([style.end, style]);
|
|
701
574
|
}
|
|
@@ -725,14 +598,12 @@ const _sfc_main$2 = defineComponent({
|
|
|
725
598
|
return blocks;
|
|
726
599
|
};
|
|
727
600
|
const renderBlockPart = (text, styles) => {
|
|
728
|
-
if (!props.decorator)
|
|
729
|
-
return text;
|
|
601
|
+
if (!props.decorator) return text;
|
|
730
602
|
let elementTag = "span";
|
|
731
603
|
const attrs = {};
|
|
732
604
|
for (let style of styles) {
|
|
733
605
|
const partProps = props.decorator(style);
|
|
734
|
-
if (!partProps)
|
|
735
|
-
continue;
|
|
606
|
+
if (!partProps) continue;
|
|
736
607
|
const { class: _class, style: _style, tag, ...otherProps } = partProps;
|
|
737
608
|
Object.assign(attrs, otherProps);
|
|
738
609
|
if (_class) {
|
|
@@ -745,8 +616,7 @@ const _sfc_main$2 = defineComponent({
|
|
|
745
616
|
elementTag = tag;
|
|
746
617
|
}
|
|
747
618
|
}
|
|
748
|
-
if (Object.keys(attrs).length === 0 && elementTag === "span")
|
|
749
|
-
return text;
|
|
619
|
+
if (Object.keys(attrs).length === 0 && elementTag === "span") return text;
|
|
750
620
|
return h(elementTag, attrs, text);
|
|
751
621
|
};
|
|
752
622
|
return () => {
|
|
@@ -764,8 +634,7 @@ const _sfc_main$2 = defineComponent({
|
|
|
764
634
|
}
|
|
765
635
|
if (slot.value) {
|
|
766
636
|
const component = slot.value({ content, props: blockProps, block: props.block });
|
|
767
|
-
if (Array.isArray(component) && component.length === 1)
|
|
768
|
-
return component[0];
|
|
637
|
+
if (Array.isArray(component) && component.length === 1) return component[0];
|
|
769
638
|
return component;
|
|
770
639
|
}
|
|
771
640
|
return h(elementTag, blockProps, content());
|
|
@@ -800,15 +669,13 @@ const createClipboardEvents = (store, props) => {
|
|
|
800
669
|
];
|
|
801
670
|
};
|
|
802
671
|
const onCopy = (e) => {
|
|
803
|
-
if (e.defaultPrevented)
|
|
804
|
-
return;
|
|
672
|
+
if (e.defaultPrevented) return;
|
|
805
673
|
e.preventDefault();
|
|
806
674
|
navigator.clipboard.write(getSelected());
|
|
807
675
|
store.history.push("setText");
|
|
808
676
|
};
|
|
809
677
|
const onCut = (e) => {
|
|
810
|
-
if (e.defaultPrevented)
|
|
811
|
-
return;
|
|
678
|
+
if (e.defaultPrevented) return;
|
|
812
679
|
e.preventDefault();
|
|
813
680
|
navigator.clipboard.write(getSelected());
|
|
814
681
|
store.deleteSelected();
|
|
@@ -840,8 +707,7 @@ const createClipboardEvents = (store, props) => {
|
|
|
840
707
|
}
|
|
841
708
|
if (isTextNode) {
|
|
842
709
|
const text = node.textContent;
|
|
843
|
-
if (!text)
|
|
844
|
-
return;
|
|
710
|
+
if (!text) return;
|
|
845
711
|
insertText(text, type ?? ((_b = props.htmlParser) == null ? void 0 : _b.call(props, node)) ?? void 0);
|
|
846
712
|
store.addNewLine();
|
|
847
713
|
return;
|
|
@@ -849,7 +715,7 @@ const createClipboardEvents = (store, props) => {
|
|
|
849
715
|
for (let child of node.children) {
|
|
850
716
|
if (child.tagName === "DIV") {
|
|
851
717
|
parseHtml(child, type);
|
|
852
|
-
} else if (child.tagName === "UL") {
|
|
718
|
+
} else if (child.tagName === "UL" || child.tagName === "OL") {
|
|
853
719
|
for (let li of child.children) {
|
|
854
720
|
parseHtml(li, ((_c = props.htmlParser) == null ? void 0 : _c.call(props, li)) ?? void 0);
|
|
855
721
|
}
|
|
@@ -862,8 +728,7 @@ const createClipboardEvents = (store, props) => {
|
|
|
862
728
|
const parser = new DOMParser();
|
|
863
729
|
const onPaste = (e) => {
|
|
864
730
|
var _a, _b;
|
|
865
|
-
if (e.defaultPrevented)
|
|
866
|
-
return;
|
|
731
|
+
if (e.defaultPrevented) return;
|
|
867
732
|
e.preventDefault();
|
|
868
733
|
const html = (_a = e.clipboardData) == null ? void 0 : _a.getData("text/html");
|
|
869
734
|
if (html) {
|
|
@@ -872,8 +737,7 @@ const createClipboardEvents = (store, props) => {
|
|
|
872
737
|
parseHtml(dom.body);
|
|
873
738
|
} else {
|
|
874
739
|
const text = (_b = e.clipboardData) == null ? void 0 : _b.getData("text");
|
|
875
|
-
if (!text)
|
|
876
|
-
return;
|
|
740
|
+
if (!text) return;
|
|
877
741
|
store.deleteSelected();
|
|
878
742
|
insertText(text);
|
|
879
743
|
}
|
|
@@ -908,10 +772,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
908
772
|
const store = new TextEditorStore();
|
|
909
773
|
let modelValue = "";
|
|
910
774
|
watch(() => props.modelValue, (newValue) => {
|
|
911
|
-
if (isProxy(newValue) && toRaw(newValue) === modelValue)
|
|
912
|
-
|
|
913
|
-
if (newValue === void 0 || newValue === null || newValue === modelValue)
|
|
914
|
-
return;
|
|
775
|
+
if (isProxy(newValue) && toRaw(newValue) === modelValue) return;
|
|
776
|
+
if (newValue === void 0 || newValue === null || newValue === modelValue) return;
|
|
915
777
|
if (!Array.isArray(newValue)) {
|
|
916
778
|
store.blocks[0].text = newValue;
|
|
917
779
|
return;
|
|
@@ -921,17 +783,15 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
921
783
|
}
|
|
922
784
|
store.blocks.length = newValue.length;
|
|
923
785
|
for (let i = 0; i < newValue.length; i++) {
|
|
924
|
-
store.blocks[i] = { ...newValue[i], id: uid() };
|
|
786
|
+
store.blocks[i] = { ...newValue[i], id: newValue[i].id ?? uid() };
|
|
925
787
|
}
|
|
926
788
|
}
|
|
927
789
|
}, { immediate: true });
|
|
928
790
|
let styles;
|
|
929
791
|
watch(() => props.styles, (newStyles) => {
|
|
930
|
-
if (!newStyles || newStyles.length === 0 || newStyles === styles)
|
|
931
|
-
return;
|
|
792
|
+
if (!newStyles || newStyles.length === 0 || newStyles === styles) return;
|
|
932
793
|
for (let i = 0; i < store.blocks.length; i++) {
|
|
933
|
-
if (newStyles.length <= i)
|
|
934
|
-
break;
|
|
794
|
+
if (newStyles.length <= i) break;
|
|
935
795
|
store.blocks[i].styles = newStyles;
|
|
936
796
|
}
|
|
937
797
|
}, { immediate: true });
|
|
@@ -948,8 +808,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
948
808
|
}, { deep: true });
|
|
949
809
|
const onKeyDown = (e) => {
|
|
950
810
|
emit("keydown", e);
|
|
951
|
-
if (e.defaultPrevented)
|
|
952
|
-
return;
|
|
811
|
+
if (e.defaultPrevented) return;
|
|
953
812
|
if (e.code === "Enter") {
|
|
954
813
|
e.preventDefault();
|
|
955
814
|
if (!props.preventMultiline && (e.shiftKey || props.single)) {
|
|
@@ -973,7 +832,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
973
832
|
}
|
|
974
833
|
};
|
|
975
834
|
let cachedSelection = {};
|
|
976
|
-
|
|
835
|
+
const onSelectionChange = () => {
|
|
977
836
|
const sel = window.getSelection();
|
|
978
837
|
const anchor = findParent(sel.anchorNode, (el) => el.hasAttribute("data-vw-block-id") && el.parentElement === textEditorRef.value);
|
|
979
838
|
if (anchor) {
|
|
@@ -993,11 +852,12 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
993
852
|
if (anchor && focus) {
|
|
994
853
|
cachedSelection = JSON.parse(JSON.stringify(store.selection));
|
|
995
854
|
}
|
|
996
|
-
}
|
|
855
|
+
};
|
|
856
|
+
onMounted(() => document.addEventListener("selectionchange", onSelectionChange));
|
|
857
|
+
onUnmounted(() => document.removeEventListener("selectionchange", onSelectionChange));
|
|
997
858
|
let postRendered = false;
|
|
998
859
|
const onPostRender = () => {
|
|
999
|
-
if (postRendered)
|
|
1000
|
-
return;
|
|
860
|
+
if (postRendered) return;
|
|
1001
861
|
cachedSelection = {};
|
|
1002
862
|
postRendered = true;
|
|
1003
863
|
applySelection();
|
|
@@ -1019,8 +879,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
1019
879
|
cachedSelection = JSON.parse(JSON.stringify(store.selection));
|
|
1020
880
|
return;
|
|
1021
881
|
}
|
|
1022
|
-
if (isEqual(store.selection, cachedSelection))
|
|
1023
|
-
return;
|
|
882
|
+
if (isEqual(store.selection, cachedSelection)) return;
|
|
1024
883
|
const anchor = getNode(store.selection.anchor.blockId);
|
|
1025
884
|
const focus = getNode(store.selection.focus.blockId);
|
|
1026
885
|
const nativeSelection = window.getSelection();
|
|
@@ -1172,5 +1031,6 @@ const _sfc_main = defineComponent({
|
|
|
1172
1031
|
});
|
|
1173
1032
|
export {
|
|
1174
1033
|
_sfc_main$1 as TextEditor,
|
|
1175
|
-
_sfc_main as TextEditorView
|
|
1034
|
+
_sfc_main as TextEditorView,
|
|
1035
|
+
uid
|
|
1176
1036
|
};
|
package/package.json
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "vuewrite",
|
|
3
|
-
"description": "Rich Text Editor based on Vue3 reactivity",
|
|
4
|
-
"private": false,
|
|
5
|
-
"version": "0.0.
|
|
6
|
-
"type": "module",
|
|
7
|
-
"license": "MIT",
|
|
8
|
-
"author": "den59k",
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "https://github.com/den59k/vuewrite.git"
|
|
12
|
-
},
|
|
13
|
-
"main": "dist/vuewrite.js",
|
|
14
|
-
"types": "dist/vuewrite.d.ts",
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"
|
|
37
|
-
|
|
38
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "vuewrite",
|
|
3
|
+
"description": "Rich Text Editor based on Vue3 reactivity",
|
|
4
|
+
"private": false,
|
|
5
|
+
"version": "0.0.27",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"author": "den59k",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "https://github.com/den59k/vuewrite.git"
|
|
12
|
+
},
|
|
13
|
+
"main": "dist/vuewrite.js",
|
|
14
|
+
"types": "dist/vuewrite.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"import": "./dist/vuewrite.js",
|
|
18
|
+
"types": "./dist/vuewrite.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./markdown": {
|
|
21
|
+
"import": "./dist/markdown.js",
|
|
22
|
+
"types": "./dist/markdown.d.ts"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "vue-tsc && vite build",
|
|
27
|
+
"dev": "vite build --watch"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"vue": "^3",
|
|
31
|
+
"vuewrite-markdown": "0.0.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
35
|
+
"typescript": "^5.2.2",
|
|
36
|
+
"vite": "^5.2.0",
|
|
37
|
+
"vite-plugin-dts": "^3.9.1",
|
|
38
|
+
"vuesix": "^1.0.12",
|
|
39
|
+
"vue-tsc": "^2.0.6"
|
|
40
|
+
},
|
|
41
|
+
"files": ["dist"]
|
|
42
|
+
}
|
package/README.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# VueWrite
|
|
2
|
-
|
|
3
|
-
VueWrite is another text editor that takes full advantage of Vue3's features.
|
|
4
|
-
It contains no pre-made styles and blocks as its main goal is complete customization and extension
|
|
5
|
-
|
|
6
|
-
## Demo
|
|
7
|
-
|
|
8
|
-
You can watch the demo [here](https://vuewrite.easix.ru)
|
|
9
|
-
|
|
10
|
-
## Quickstart
|
|
11
|
-
|
|
12
|
-
```vue
|
|
13
|
-
<template>
|
|
14
|
-
<TextEditor
|
|
15
|
-
ref="textEditorRef"
|
|
16
|
-
v-model="modelValue"
|
|
17
|
-
single
|
|
18
|
-
class="text-editor"
|
|
19
|
-
:decorator="decorator"
|
|
20
|
-
@keydown="onKeyDown"
|
|
21
|
-
/>
|
|
22
|
-
</template>
|
|
23
|
-
|
|
24
|
-
<script lang="ts">
|
|
25
|
-
import { TextEditor, TextEditorRef } from 'vuewrite'
|
|
26
|
-
|
|
27
|
-
const textEditorRef = shallowRef<TextEditorRef>()
|
|
28
|
-
const modelValue = shallowRef("")
|
|
29
|
-
|
|
30
|
-
const onKeyDown = (e: KeyboardEvent) => {
|
|
31
|
-
if (!textEditorRef.value) return
|
|
32
|
-
if ((e.ctrlKey || e.metaKey) && !e.shiftKey && !e.altKey) {
|
|
33
|
-
if (e.code === "KeyB") {
|
|
34
|
-
textEditorRef.value.toggleStyle("bold")
|
|
35
|
-
}
|
|
36
|
-
if (e.code === "KeyI") {
|
|
37
|
-
textEditorRef.value.toggleStyle("italic")
|
|
38
|
-
}
|
|
39
|
-
if (e.code === "KeyU") {
|
|
40
|
-
textEditorRef.value.toggleStyle("underline")
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const decorator = (style: Style) => {
|
|
46
|
-
if (style.style === 'bold' || style.style === "underline" || style.style === "italic") {
|
|
47
|
-
return { class: style.style }
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
</script>
|
|
52
|
-
|
|
53
|
-
<style lang="css">
|
|
54
|
-
.text-editor {
|
|
55
|
-
white-space: pre-wrap;
|
|
56
|
-
}
|
|
57
|
-
.text-editor .bold {
|
|
58
|
-
font-weight: 700
|
|
59
|
-
}
|
|
60
|
-
.text-editor .italic {
|
|
61
|
-
font-style: italic
|
|
62
|
-
}
|
|
63
|
-
.text-editor .underline {
|
|
64
|
-
text-decoration: underline
|
|
65
|
-
}
|
|
66
|
-
</style>
|
|
67
|
-
|
|
68
|
-
```
|