vuewrite 0.0.19 → 0.0.21-a
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/.vscode/extensions.json +3 -3
- package/README.md +67 -67
- package/dist/vuewrite.d.ts +16 -2
- package/dist/vuewrite.js +171 -68
- package/package.json +39 -38
package/.vscode/extensions.json
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
{
|
|
2
|
-
"recommendations": ["Vue.volar"]
|
|
3
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"recommendations": ["Vue.volar"]
|
|
3
|
+
}
|
package/README.md
CHANGED
|
@@ -1,68 +1,68 @@
|
|
|
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
|
-
|
|
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
68
|
```
|
package/dist/vuewrite.d.ts
CHANGED
|
@@ -40,11 +40,16 @@ export declare type Decorator = (style: Style) => HTMLAttributes & {
|
|
|
40
40
|
} | undefined;
|
|
41
41
|
|
|
42
42
|
declare type HistoryAction = {
|
|
43
|
-
type:
|
|
43
|
+
type: string;
|
|
44
44
|
blocks: Block[];
|
|
45
|
+
fullUpdate: boolean;
|
|
45
46
|
selection: TextEditorSelection;
|
|
46
47
|
};
|
|
47
48
|
|
|
49
|
+
declare type Renderer = (block: Block) => HTMLAttributes & {
|
|
50
|
+
tag?: string;
|
|
51
|
+
} | undefined;
|
|
52
|
+
|
|
48
53
|
export declare type Style = {
|
|
49
54
|
start: number;
|
|
50
55
|
end: number;
|
|
@@ -54,6 +59,7 @@ export declare type Style = {
|
|
|
54
59
|
|
|
55
60
|
export declare const TextEditor: __VLS_WithTemplateSlots<DefineComponent<__VLS_TypePropsToRuntimeProps<{
|
|
56
61
|
decorator?: Decorator | undefined;
|
|
62
|
+
renderer?: Renderer | undefined;
|
|
57
63
|
single?: boolean | undefined;
|
|
58
64
|
modelValue?: string | {
|
|
59
65
|
text: string;
|
|
@@ -99,7 +105,7 @@ insertBlock: (blockData: Partial<Block>) => void;
|
|
|
99
105
|
addNewLine: () => void;
|
|
100
106
|
removeNewLine: () => void;
|
|
101
107
|
selectAll: () => void;
|
|
102
|
-
pushHistory: (type:
|
|
108
|
+
pushHistory: (type: string) => void;
|
|
103
109
|
getClientRects: (selection: TextEditorSelection) => DOMRectList;
|
|
104
110
|
}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
|
|
105
111
|
keydown: (...args: any[]) => void;
|
|
@@ -107,6 +113,7 @@ keydown: (...args: any[]) => void;
|
|
|
107
113
|
"update:styles": (...args: any[]) => void;
|
|
108
114
|
}, string, PublicProps, Readonly<ExtractPropTypes<__VLS_TypePropsToRuntimeProps<{
|
|
109
115
|
decorator?: Decorator | undefined;
|
|
116
|
+
renderer?: Renderer | undefined;
|
|
110
117
|
single?: boolean | undefined;
|
|
111
118
|
modelValue?: string | {
|
|
112
119
|
text: string;
|
|
@@ -129,8 +136,12 @@ onKeydown?: ((...args: any[]) => any) | undefined;
|
|
|
129
136
|
declare class TextEditorHistory {
|
|
130
137
|
actions: HistoryAction[];
|
|
131
138
|
currentCursor: number;
|
|
139
|
+
private blocksIds;
|
|
140
|
+
private cachedBlocksJson;
|
|
132
141
|
private store;
|
|
133
142
|
constructor(store: TextEditorStore);
|
|
143
|
+
private cacheBlockIds;
|
|
144
|
+
private blockIdsChanged;
|
|
134
145
|
tickStarted: boolean;
|
|
135
146
|
push(type: HistoryAction["type"]): void;
|
|
136
147
|
private applyAction;
|
|
@@ -242,6 +253,7 @@ declare class TextEditorStore {
|
|
|
242
253
|
export declare const TextEditorView: DefineComponent<Readonly<{
|
|
243
254
|
styles?: any;
|
|
244
255
|
decorator?: any;
|
|
256
|
+
renderer?: any;
|
|
245
257
|
parser?: any;
|
|
246
258
|
modelValue?: any;
|
|
247
259
|
}>, () => VNode<RendererNode, RendererElement, {
|
|
@@ -249,11 +261,13 @@ modelValue?: any;
|
|
|
249
261
|
}>, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, string, PublicProps, Readonly<ExtractPropTypes<Readonly<{
|
|
250
262
|
styles?: any;
|
|
251
263
|
decorator?: any;
|
|
264
|
+
renderer?: any;
|
|
252
265
|
parser?: any;
|
|
253
266
|
modelValue?: any;
|
|
254
267
|
}>>>, {
|
|
255
268
|
readonly styles?: any;
|
|
256
269
|
readonly decorator?: any;
|
|
270
|
+
readonly renderer?: any;
|
|
257
271
|
readonly parser?: any;
|
|
258
272
|
readonly modelValue?: any;
|
|
259
273
|
}, {}>;
|
package/dist/vuewrite.js
CHANGED
|
@@ -4,7 +4,7 @@ var __publicField = (obj, key, value) => {
|
|
|
4
4
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
|
-
import { getCurrentScope, onScopeDispose, unref, watch,
|
|
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
8
|
function tryOnScopeDispose(fn) {
|
|
9
9
|
if (getCurrentScope()) {
|
|
10
10
|
onScopeDispose(fn);
|
|
@@ -144,50 +144,57 @@ class TextEditorHistory {
|
|
|
144
144
|
constructor(store) {
|
|
145
145
|
__publicField(this, "actions", []);
|
|
146
146
|
__publicField(this, "currentCursor", 0);
|
|
147
|
+
__publicField(this, "blocksIds", "");
|
|
148
|
+
__publicField(this, "cachedBlocksJson", "");
|
|
147
149
|
__publicField(this, "store");
|
|
148
150
|
__publicField(this, "tickStarted", false);
|
|
149
151
|
this.store = store;
|
|
150
152
|
window.showHistory = () => console.log(this.actions);
|
|
151
153
|
}
|
|
154
|
+
cacheBlockIds() {
|
|
155
|
+
this.blocksIds = this.store.blocks.map((item) => item.id).join(",");
|
|
156
|
+
}
|
|
157
|
+
blockIdsChanged() {
|
|
158
|
+
return this.blocksIds !== this.store.blocks.map((item) => item.id).join(",");
|
|
159
|
+
}
|
|
152
160
|
push(type) {
|
|
153
161
|
var _a;
|
|
154
162
|
if (this.currentCursor + 1 < this.actions.length) {
|
|
155
163
|
this.actions.length = this.currentCursor + 1;
|
|
156
164
|
}
|
|
157
|
-
const lastAction = this.actions.length > 0
|
|
165
|
+
const lastAction = this.actions.length > 0 ? this.actions[this.actions.length - 1] : null;
|
|
158
166
|
const selection = JSON.parse(JSON.stringify(this.store.selection));
|
|
159
|
-
|
|
167
|
+
const fullUpdate = type === "setText" || type === "addNewLine" || this.blockIdsChanged();
|
|
168
|
+
const blocksJson = JSON.stringify(this.store.blocks);
|
|
169
|
+
if (!fullUpdate && this.store.currentBlock) {
|
|
160
170
|
const currentBlock = JSON.parse(JSON.stringify(this.store.currentBlock));
|
|
161
|
-
if (lastAction && lastAction.type === type && ((_a = lastAction.blocks[0]) == null ? void 0 : _a.id) === currentBlock.id) {
|
|
171
|
+
if (lastAction && lastAction.type === type && ((_a = lastAction.blocks[0]) == null ? void 0 : _a.id) === currentBlock.id && (lastAction.type === "insertText" || lastAction.type === "deleteContentBackward" || lastAction.type === "deleteContentForward")) {
|
|
162
172
|
lastAction.blocks = [currentBlock];
|
|
163
173
|
lastAction.selection = selection;
|
|
164
174
|
} else {
|
|
165
|
-
this.actions.push({ type, blocks: [currentBlock], selection });
|
|
166
|
-
}
|
|
167
|
-
} else if (type === "setText") {
|
|
168
|
-
const blocks = JSON.parse(JSON.stringify(this.store.blocks));
|
|
169
|
-
if (this.tickStarted && lastAction) {
|
|
170
|
-
lastAction.blocks = blocks;
|
|
171
|
-
lastAction.selection = selection;
|
|
172
|
-
} else {
|
|
173
|
-
this.actions.push({ type: "setText", blocks, selection });
|
|
175
|
+
this.actions.push({ type, blocks: [currentBlock], selection, fullUpdate });
|
|
174
176
|
}
|
|
177
|
+
} else {
|
|
178
|
+
this.actions.push({ type, blocks: JSON.parse(blocksJson), selection, fullUpdate });
|
|
175
179
|
}
|
|
176
180
|
this.currentCursor = this.actions.length - 1;
|
|
177
|
-
if (
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
181
|
+
if (fullUpdate) {
|
|
182
|
+
if (lastAction && !lastAction.fullUpdate) {
|
|
183
|
+
lastAction.fullUpdate = true;
|
|
184
|
+
lastAction.blocks = JSON.parse(this.cachedBlocksJson);
|
|
185
|
+
}
|
|
186
|
+
this.cacheBlockIds();
|
|
182
187
|
}
|
|
188
|
+
this.cachedBlocksJson = blocksJson;
|
|
183
189
|
}
|
|
184
190
|
applyAction(action) {
|
|
185
|
-
if (action.
|
|
191
|
+
if (action.fullUpdate) {
|
|
186
192
|
for (let i = 0; i < action.blocks.length; i++) {
|
|
187
193
|
if (i >= this.store.blocks.length) {
|
|
188
194
|
this.store.blocks.push({ ...action.blocks[i] });
|
|
189
195
|
} else {
|
|
190
196
|
Object.assign(this.store.blocks[i], action.blocks[i]);
|
|
197
|
+
this.store.blocks[i].type = action.blocks[i].type;
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
200
|
if (this.store.blocks.length > action.blocks.length) {
|
|
@@ -198,10 +205,13 @@ class TextEditorHistory {
|
|
|
198
205
|
const block = this.store.blocks.find((block2) => block2.id === _block.id);
|
|
199
206
|
if (block) {
|
|
200
207
|
block.text = _block.text;
|
|
208
|
+
block.styles = _block.styles;
|
|
209
|
+
block.type = _block.type;
|
|
201
210
|
}
|
|
202
211
|
}
|
|
203
212
|
}
|
|
204
213
|
Object.assign(this.store.selection, action.selection);
|
|
214
|
+
this.cacheBlockIds();
|
|
205
215
|
}
|
|
206
216
|
undo() {
|
|
207
217
|
if (this.currentCursor === 0)
|
|
@@ -301,7 +311,6 @@ class TextEditorStore {
|
|
|
301
311
|
this.concatBlocks(this.blocks[blockIndex - 1], this.blocks[blockIndex]);
|
|
302
312
|
this.blocks.splice(blockIndex, 1);
|
|
303
313
|
}
|
|
304
|
-
this.history.push("setText");
|
|
305
314
|
}
|
|
306
315
|
onInput(_e) {
|
|
307
316
|
const ev = _e;
|
|
@@ -327,6 +336,7 @@ class TextEditorStore {
|
|
|
327
336
|
block.text = block.text.slice(0, offset) + block.text.slice(this.selection.focus.offset);
|
|
328
337
|
this.moveOffset(offset);
|
|
329
338
|
}
|
|
339
|
+
this.history.push("deleteContentBackward");
|
|
330
340
|
}
|
|
331
341
|
if (ev.inputType === "deleteContentForward") {
|
|
332
342
|
const blockIndex = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
|
|
@@ -334,12 +344,13 @@ class TextEditorStore {
|
|
|
334
344
|
const nextBlock = this.blocks[blockIndex + 1];
|
|
335
345
|
if (nextBlock) {
|
|
336
346
|
this.blocks.splice(blockIndex + 1, 1);
|
|
337
|
-
this.concatBlocks(this.blocks[blockIndex
|
|
347
|
+
this.concatBlocks(this.blocks[blockIndex], nextBlock);
|
|
338
348
|
}
|
|
339
349
|
} else {
|
|
340
350
|
block.text = block.text.slice(0, this.selection.focus.offset) + block.text.slice(this.selection.focus.offset + 1);
|
|
341
351
|
this.moveStyles(block, this.selection.focus.offset + 1, 1);
|
|
342
352
|
}
|
|
353
|
+
this.history.push("deleteContentForward");
|
|
343
354
|
}
|
|
344
355
|
}
|
|
345
356
|
if (ev.inputType === "insertText") {
|
|
@@ -352,6 +363,7 @@ class TextEditorStore {
|
|
|
352
363
|
const index2 = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
|
|
353
364
|
const block2 = { id: uid(), text: "", styles: [] };
|
|
354
365
|
this.blocks.splice(index2, 0, block2);
|
|
366
|
+
this.history.push("addNewLine");
|
|
355
367
|
return;
|
|
356
368
|
}
|
|
357
369
|
this.deleteSelected();
|
|
@@ -364,7 +376,7 @@ class TextEditorStore {
|
|
|
364
376
|
this.blocks.splice(index + 1, 0, block);
|
|
365
377
|
this.selection.anchor = { blockId: block.id, offset: 0 };
|
|
366
378
|
this.selection.focus = { blockId: block.id, offset: 0 };
|
|
367
|
-
this.history.push("
|
|
379
|
+
this.history.push("addNewLine");
|
|
368
380
|
}
|
|
369
381
|
addNewLineAfter() {
|
|
370
382
|
const index = this.blocks.findIndex((item) => item.id === this.selection.anchor.blockId);
|
|
@@ -448,6 +460,8 @@ class TextEditorStore {
|
|
|
448
460
|
return this._currentStyles.value;
|
|
449
461
|
}
|
|
450
462
|
applyStyle(_style, meta) {
|
|
463
|
+
if (this.isCollapsed)
|
|
464
|
+
return;
|
|
451
465
|
const [start, end, startIndex, endIndex] = this.startAndEnd;
|
|
452
466
|
for (let i = startIndex; i <= endIndex; i++) {
|
|
453
467
|
const block = this.blocks[i];
|
|
@@ -481,6 +495,7 @@ class TextEditorStore {
|
|
|
481
495
|
block.styles.sort((a, b) => a.start - b.start);
|
|
482
496
|
}
|
|
483
497
|
}
|
|
498
|
+
this.history.push("applyStyle");
|
|
484
499
|
}
|
|
485
500
|
removeStyleAt(block, _start, _end, _style) {
|
|
486
501
|
const removeAll = typeof _style !== "string";
|
|
@@ -511,6 +526,7 @@ class TextEditorStore {
|
|
|
511
526
|
const _end = i === endIndex ? end.offset : block.text.length;
|
|
512
527
|
this.removeStyleAt(block, _start, _end, _style);
|
|
513
528
|
}
|
|
529
|
+
this.history.push("applyStyle");
|
|
514
530
|
}
|
|
515
531
|
removeAllStyles() {
|
|
516
532
|
const [start, end, startIndex, endIndex] = this.startAndEnd;
|
|
@@ -541,7 +557,7 @@ class TextEditorStore {
|
|
|
541
557
|
let uidCounter = 0;
|
|
542
558
|
const uid = () => (uidCounter++).toString();
|
|
543
559
|
const _sfc_main$2 = defineComponent({
|
|
544
|
-
props: ["block", "slots", "static", "decorator", "parser"],
|
|
560
|
+
props: ["block", "slots", "static", "decorator", "renderer", "parser"],
|
|
545
561
|
emits: ["postrender"],
|
|
546
562
|
setup(props, { emit }) {
|
|
547
563
|
const slot = computed(() => {
|
|
@@ -549,9 +565,6 @@ const _sfc_main$2 = defineComponent({
|
|
|
549
565
|
return props.slots["default"] ?? null;
|
|
550
566
|
return props.slots[props.block.type] ?? null;
|
|
551
567
|
});
|
|
552
|
-
const blockProps = {
|
|
553
|
-
"data-vw-block-id": props.block.id
|
|
554
|
-
};
|
|
555
568
|
const instance = getCurrentInstance();
|
|
556
569
|
const getRef = () => {
|
|
557
570
|
if (!instance)
|
|
@@ -675,25 +688,144 @@ const _sfc_main$2 = defineComponent({
|
|
|
675
688
|
elementTag = tag;
|
|
676
689
|
}
|
|
677
690
|
}
|
|
678
|
-
if (Object.keys(attrs).length === 0)
|
|
691
|
+
if (Object.keys(attrs).length === 0 && elementTag === "span")
|
|
679
692
|
return text;
|
|
680
693
|
return h(elementTag, attrs, text);
|
|
681
694
|
};
|
|
682
695
|
return () => {
|
|
696
|
+
let elementTag = "div";
|
|
697
|
+
const blockProps = {
|
|
698
|
+
"data-vw-block-id": props.block.id
|
|
699
|
+
};
|
|
700
|
+
const additionalProps = props.renderer && props.renderer(props.block);
|
|
701
|
+
if (additionalProps) {
|
|
702
|
+
if (additionalProps.tag) {
|
|
703
|
+
elementTag = additionalProps.tag;
|
|
704
|
+
delete additionalProps.tag;
|
|
705
|
+
}
|
|
706
|
+
Object.assign(blockProps, additionalProps);
|
|
707
|
+
}
|
|
683
708
|
if (slot.value) {
|
|
684
709
|
const component = slot.value({ content, props: blockProps, block: props.block });
|
|
685
710
|
if (Array.isArray(component) && component.length === 1)
|
|
686
711
|
return component[0];
|
|
687
712
|
return component;
|
|
688
713
|
}
|
|
689
|
-
return h(
|
|
714
|
+
return h(elementTag, blockProps, content());
|
|
690
715
|
};
|
|
691
716
|
}
|
|
692
717
|
});
|
|
718
|
+
const createClipboardEvents = (store, props) => {
|
|
719
|
+
const getSelected = () => {
|
|
720
|
+
const [start, end, startIndex, endIndex] = store.startAndEnd;
|
|
721
|
+
if (startIndex === endIndex) {
|
|
722
|
+
const text2 = store.blocks[startIndex].text.slice(start.offset, end.offset);
|
|
723
|
+
return [
|
|
724
|
+
new ClipboardItem({
|
|
725
|
+
"text/plain": new Blob([text2], { type: "text/plain" })
|
|
726
|
+
})
|
|
727
|
+
];
|
|
728
|
+
}
|
|
729
|
+
const startText = store.blocks[startIndex].text.slice(start.offset);
|
|
730
|
+
const endText = store.blocks[endIndex].text.slice(0, end.offset);
|
|
731
|
+
const arr = [
|
|
732
|
+
{ type: store.blocks[startIndex].type, text: startText },
|
|
733
|
+
...store.blocks.slice(startIndex + 1, endIndex),
|
|
734
|
+
{ type: store.blocks[endIndex].type, text: endText }
|
|
735
|
+
];
|
|
736
|
+
const html = arr.map((item) => `<div>${item.text}</div>`).join("\n");
|
|
737
|
+
const text = arr.map((item) => item.text).join("\n");
|
|
738
|
+
return [
|
|
739
|
+
new ClipboardItem({
|
|
740
|
+
"text/html": new Blob([html], { type: "text/html" }),
|
|
741
|
+
"text/plain": new Blob([text], { type: "text/plain" })
|
|
742
|
+
})
|
|
743
|
+
];
|
|
744
|
+
};
|
|
745
|
+
const onCopy = (e) => {
|
|
746
|
+
if (e.defaultPrevented)
|
|
747
|
+
return;
|
|
748
|
+
e.preventDefault();
|
|
749
|
+
navigator.clipboard.write(getSelected());
|
|
750
|
+
store.history.push("setText");
|
|
751
|
+
};
|
|
752
|
+
const onCut = (e) => {
|
|
753
|
+
if (e.defaultPrevented)
|
|
754
|
+
return;
|
|
755
|
+
e.preventDefault();
|
|
756
|
+
navigator.clipboard.write(getSelected());
|
|
757
|
+
store.deleteSelected();
|
|
758
|
+
store.history.push("setText");
|
|
759
|
+
};
|
|
760
|
+
const insertText = (text) => {
|
|
761
|
+
if (props.preventMultiline) {
|
|
762
|
+
const blocks = text.split("\n");
|
|
763
|
+
store.insertText(blocks[0]);
|
|
764
|
+
for (let i = 1; i < blocks.length; i++) {
|
|
765
|
+
store.addNewLine();
|
|
766
|
+
store.insertText(blocks[i]);
|
|
767
|
+
}
|
|
768
|
+
} else {
|
|
769
|
+
store.insertText(text);
|
|
770
|
+
}
|
|
771
|
+
};
|
|
772
|
+
const parseHtml = (node) => {
|
|
773
|
+
var _a;
|
|
774
|
+
let isTextNode = false;
|
|
775
|
+
for (let _node of node.childNodes) {
|
|
776
|
+
if (_node.nodeType === Node.TEXT_NODE && ((_a = _node.textContent) == null ? void 0 : _a.trim()) || _node.nodeType == Node.ELEMENT_NODE && _node.tagName === "SPAN") {
|
|
777
|
+
isTextNode = true;
|
|
778
|
+
break;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (isTextNode) {
|
|
782
|
+
const text = node.textContent;
|
|
783
|
+
if (!text)
|
|
784
|
+
return;
|
|
785
|
+
insertText(text);
|
|
786
|
+
store.addNewLine();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
for (let child of node.children) {
|
|
790
|
+
if (child.tagName === "DIV") {
|
|
791
|
+
parseHtml(child);
|
|
792
|
+
} else {
|
|
793
|
+
insertText(child.textContent ?? "");
|
|
794
|
+
store.addNewLine();
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
const parser = new DOMParser();
|
|
799
|
+
const onPaste = (e) => {
|
|
800
|
+
var _a, _b;
|
|
801
|
+
if (e.defaultPrevented)
|
|
802
|
+
return;
|
|
803
|
+
e.preventDefault();
|
|
804
|
+
const html = (_a = e.clipboardData) == null ? void 0 : _a.getData("text/html");
|
|
805
|
+
if (html) {
|
|
806
|
+
store.deleteSelected();
|
|
807
|
+
const dom = parser.parseFromString(html, "text/html");
|
|
808
|
+
parseHtml(dom.body);
|
|
809
|
+
} else {
|
|
810
|
+
const text = (_b = e.clipboardData) == null ? void 0 : _b.getData("text");
|
|
811
|
+
if (!text)
|
|
812
|
+
return;
|
|
813
|
+
store.deleteSelected();
|
|
814
|
+
insertText(text);
|
|
815
|
+
}
|
|
816
|
+
store.history.push("setText");
|
|
817
|
+
};
|
|
818
|
+
return {
|
|
819
|
+
onCopy,
|
|
820
|
+
onCut,
|
|
821
|
+
onPaste
|
|
822
|
+
};
|
|
823
|
+
};
|
|
693
824
|
const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
694
825
|
__name: "TextEditor",
|
|
695
826
|
props: {
|
|
696
827
|
decorator: { type: Function },
|
|
828
|
+
renderer: { type: Function },
|
|
697
829
|
single: { type: Boolean },
|
|
698
830
|
modelValue: {},
|
|
699
831
|
parser: { type: Function },
|
|
@@ -856,41 +988,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
856
988
|
}
|
|
857
989
|
store.history.push("setText");
|
|
858
990
|
});
|
|
859
|
-
const onCopy = (
|
|
860
|
-
if (e.defaultPrevented)
|
|
861
|
-
return;
|
|
862
|
-
e.preventDefault();
|
|
863
|
-
navigator.clipboard.writeText(store.selectedText);
|
|
864
|
-
store.history.push("setText");
|
|
865
|
-
};
|
|
866
|
-
const onCut = (e) => {
|
|
867
|
-
if (e.defaultPrevented)
|
|
868
|
-
return;
|
|
869
|
-
e.preventDefault();
|
|
870
|
-
navigator.clipboard.writeText(store.selectedText);
|
|
871
|
-
store.deleteSelected();
|
|
872
|
-
store.history.push("setText");
|
|
873
|
-
};
|
|
874
|
-
const onPaste = (e) => {
|
|
875
|
-
var _a;
|
|
876
|
-
if (e.defaultPrevented)
|
|
877
|
-
return;
|
|
878
|
-
e.preventDefault();
|
|
879
|
-
const text = (_a = e.clipboardData) == null ? void 0 : _a.getData("Text");
|
|
880
|
-
if (!text)
|
|
881
|
-
return;
|
|
882
|
-
if (props.preventMultiline) {
|
|
883
|
-
const blocks = text.split("\n");
|
|
884
|
-
store.insertText(blocks[0]);
|
|
885
|
-
for (let i = 1; i < blocks.length; i++) {
|
|
886
|
-
store.addNewLine();
|
|
887
|
-
store.insertText(blocks[i]);
|
|
888
|
-
}
|
|
889
|
-
} else {
|
|
890
|
-
store.insertText(text);
|
|
891
|
-
}
|
|
892
|
-
store.history.push("setText");
|
|
893
|
-
};
|
|
991
|
+
const { onCut, onCopy, onPaste } = createClipboardEvents(store, props);
|
|
894
992
|
const getClientRects = (selection) => {
|
|
895
993
|
const anchor = getNode(selection.anchor.blockId);
|
|
896
994
|
const focus = getNode(selection.focus.blockId);
|
|
@@ -927,19 +1025,23 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
927
1025
|
onBeforeinput: _cache[0] || (_cache[0] = //@ts-ignore
|
|
928
1026
|
(...args) => unref(store).onInput && unref(store).onInput(...args)),
|
|
929
1027
|
onKeydown: onKeyDown,
|
|
930
|
-
onCopy
|
|
931
|
-
|
|
932
|
-
|
|
1028
|
+
onCopy: _cache[1] || (_cache[1] = //@ts-ignore
|
|
1029
|
+
(...args) => unref(onCopy) && unref(onCopy)(...args)),
|
|
1030
|
+
onPaste: _cache[2] || (_cache[2] = //@ts-ignore
|
|
1031
|
+
(...args) => unref(onPaste) && unref(onPaste)(...args)),
|
|
1032
|
+
onCut: _cache[3] || (_cache[3] = //@ts-ignore
|
|
1033
|
+
(...args) => unref(onCut) && unref(onCut)(...args))
|
|
933
1034
|
}, [
|
|
934
1035
|
(openBlock(true), createElementBlock(Fragment, null, renderList(unref(store).blocks, (block) => {
|
|
935
1036
|
return openBlock(), createBlock(_sfc_main$2, {
|
|
936
1037
|
key: block.id,
|
|
937
1038
|
block,
|
|
938
1039
|
slots: unref(slots),
|
|
1040
|
+
renderer: props.renderer,
|
|
939
1041
|
decorator: props.decorator,
|
|
940
1042
|
parser: props.parser,
|
|
941
1043
|
onPostrender: onPostRender
|
|
942
|
-
}, null, 8, ["block", "slots", "decorator", "parser"]);
|
|
1044
|
+
}, null, 8, ["block", "slots", "renderer", "decorator", "parser"]);
|
|
943
1045
|
}), 128)),
|
|
944
1046
|
unref(store).blocks.length === 1 && unref(store).blocks[0].text === "" && !unref(store).blocks[0].type ? renderSlot(_ctx.$slots, "placeholder", { key: 0 }) : createCommentVNode("", true)
|
|
945
1047
|
], 544);
|
|
@@ -947,7 +1049,7 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
947
1049
|
}
|
|
948
1050
|
});
|
|
949
1051
|
const _sfc_main = defineComponent({
|
|
950
|
-
props: ["modelValue", "decorator", "parser", "styles"],
|
|
1052
|
+
props: ["modelValue", "decorator", "parser", "styles", "renderer"],
|
|
951
1053
|
setup(props, { slots }) {
|
|
952
1054
|
const blocks = computed(() => {
|
|
953
1055
|
if (Array.isArray(props.modelValue)) {
|
|
@@ -960,6 +1062,7 @@ const _sfc_main = defineComponent({
|
|
|
960
1062
|
block,
|
|
961
1063
|
slots,
|
|
962
1064
|
decorator: props.decorator,
|
|
1065
|
+
renderer: props.renderer,
|
|
963
1066
|
parser: props.parser,
|
|
964
1067
|
static: true
|
|
965
1068
|
})));
|
package/package.json
CHANGED
|
@@ -1,38 +1,39 @@
|
|
|
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
|
-
"scripts": {
|
|
16
|
-
"dev": "vite",
|
|
17
|
-
"build": "vue-tsc && vite build",
|
|
18
|
-
"build:app": "vue-tsc && vite build -c vite-app.config.ts",
|
|
19
|
-
"preview": "vite preview"
|
|
20
|
-
},
|
|
21
|
-
"dependencies": {
|
|
22
|
-
"vue": "^3"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"@
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
"vite
|
|
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.21-a",
|
|
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
|
+
"scripts": {
|
|
16
|
+
"dev": "vite",
|
|
17
|
+
"build": "vue-tsc && vite build",
|
|
18
|
+
"build:app": "vue-tsc && vite build -c vite-app.config.ts",
|
|
19
|
+
"preview": "vite preview"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"vue": "^3",
|
|
23
|
+
"vuewrite": "^0.0.19"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@vitejs/plugin-vue": "^5.0.4",
|
|
27
|
+
"@vueuse/core": "^10.10.0",
|
|
28
|
+
"color2k": "^2.0.3",
|
|
29
|
+
"sass": "^1.77.2",
|
|
30
|
+
"typescript": "^5.2.2",
|
|
31
|
+
"vite": "^5.2.0",
|
|
32
|
+
"vite-plugin-dts": "^3.9.1",
|
|
33
|
+
"vue-tsc": "^2.0.6",
|
|
34
|
+
"vuesix": "^1.0.12"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist"
|
|
38
|
+
]
|
|
39
|
+
}
|