vuewrite 0.0.8 → 0.0.9
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/vuewrite.d.ts +27 -0
- package/dist/vuewrite.js +117 -10
- package/package.json +1 -1
package/dist/vuewrite.d.ts
CHANGED
|
@@ -34,6 +34,12 @@ export declare type Block = {
|
|
|
34
34
|
|
|
35
35
|
export declare type Decorator = (style: Style) => HTMLAttributes | undefined;
|
|
36
36
|
|
|
37
|
+
declare type HistoryAction = {
|
|
38
|
+
type: "insertText" | "setText";
|
|
39
|
+
blocks: Block[];
|
|
40
|
+
selection: TextEditorSelection;
|
|
41
|
+
};
|
|
42
|
+
|
|
37
43
|
export declare type Style = {
|
|
38
44
|
start: number;
|
|
39
45
|
end: number;
|
|
@@ -49,9 +55,11 @@ text: string;
|
|
|
49
55
|
styles?: Style[] | undefined;
|
|
50
56
|
type?: string | undefined;
|
|
51
57
|
}[] | undefined;
|
|
58
|
+
parser?: TextParser | undefined;
|
|
52
59
|
styles?: Style[] | undefined;
|
|
53
60
|
autofocus?: boolean | undefined;
|
|
54
61
|
autoselect?: boolean | undefined;
|
|
62
|
+
preventMultiline?: boolean | undefined;
|
|
55
63
|
}>, {
|
|
56
64
|
currentStyles: ComputedRef<Map<string, Style>>;
|
|
57
65
|
currentBlock: ComputedRef< {
|
|
@@ -86,6 +94,7 @@ insertBlock: (blockData: Partial<Block>) => void;
|
|
|
86
94
|
addNewLine: () => void;
|
|
87
95
|
removeNewLine: () => void;
|
|
88
96
|
selectAll: () => void;
|
|
97
|
+
pushHistory: (type: "insertText" | "setText") => void;
|
|
89
98
|
getClientRects: (selection: TextEditorSelection) => DOMRectList;
|
|
90
99
|
}, unknown, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {
|
|
91
100
|
keydown: (...args: any[]) => void;
|
|
@@ -99,9 +108,11 @@ text: string;
|
|
|
99
108
|
styles?: Style[] | undefined;
|
|
100
109
|
type?: string | undefined;
|
|
101
110
|
}[] | undefined;
|
|
111
|
+
parser?: TextParser | undefined;
|
|
102
112
|
styles?: Style[] | undefined;
|
|
103
113
|
autofocus?: boolean | undefined;
|
|
104
114
|
autoselect?: boolean | undefined;
|
|
115
|
+
preventMultiline?: boolean | undefined;
|
|
105
116
|
}>>> & {
|
|
106
117
|
onKeydown?: ((...args: any[]) => any) | undefined;
|
|
107
118
|
"onUpdate:modelValue"?: ((...args: any[]) => any) | undefined;
|
|
@@ -110,9 +121,22 @@ onKeydown?: ((...args: any[]) => any) | undefined;
|
|
|
110
121
|
placeholder?(_: {}): any;
|
|
111
122
|
}>;
|
|
112
123
|
|
|
124
|
+
declare class TextEditorHistory {
|
|
125
|
+
actions: HistoryAction[];
|
|
126
|
+
currentCursor: number;
|
|
127
|
+
private store;
|
|
128
|
+
constructor(store: TextEditorStore);
|
|
129
|
+
tickStarted: boolean;
|
|
130
|
+
push(type: HistoryAction["type"]): void;
|
|
131
|
+
private applyAction;
|
|
132
|
+
undo(): void;
|
|
133
|
+
redo(): void;
|
|
134
|
+
}
|
|
135
|
+
|
|
113
136
|
export declare type TextEditorRef = Pick<TextEditorStore, "currentStyles" | "currentBlock" | "isCollapsed" | "selection" | "toggleStyle" | "applyStyle" | "removeStyle" | "insertText" | "insertBlock" | "addNewLine" | "removeNewLine" | "selectAll"> & {
|
|
114
137
|
isFocused: boolean;
|
|
115
138
|
getClientRects: (selection: TextEditorSelection) => DOMRectList;
|
|
139
|
+
pushHistory: TextEditorHistory["push"];
|
|
116
140
|
};
|
|
117
141
|
|
|
118
142
|
declare type TextEditorSelection = {
|
|
@@ -127,6 +151,7 @@ declare type TextEditorSelection = {
|
|
|
127
151
|
};
|
|
128
152
|
|
|
129
153
|
declare class TextEditorStore {
|
|
154
|
+
history: TextEditorHistory;
|
|
130
155
|
blocks: {
|
|
131
156
|
id: string;
|
|
132
157
|
text: string;
|
|
@@ -204,4 +229,6 @@ declare class TextEditorStore {
|
|
|
204
229
|
selectAll(): void;
|
|
205
230
|
}
|
|
206
231
|
|
|
232
|
+
declare type TextParser = (text: string) => Style[];
|
|
233
|
+
|
|
207
234
|
export { }
|
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, reactive, computed, ref, defineComponent, getCurrentInstance, openBlock, createBlock, resolveDynamicComponent, createElementBlock, normalizeProps, mergeProps, h,
|
|
7
|
+
import { getCurrentScope, onScopeDispose, unref, watch, nextTick, reactive, computed, ref, defineComponent, getCurrentInstance, openBlock, createBlock, resolveDynamicComponent, createElementBlock, normalizeProps, mergeProps, h, useSlots, onMounted, Fragment, renderList, renderSlot, createCommentVNode } from "vue";
|
|
8
8
|
function tryOnScopeDispose(fn) {
|
|
9
9
|
if (getCurrentScope()) {
|
|
10
10
|
onScopeDispose(fn);
|
|
@@ -140,8 +140,85 @@ const isEqual = (a, b) => {
|
|
|
140
140
|
}
|
|
141
141
|
return true;
|
|
142
142
|
};
|
|
143
|
+
class TextEditorHistory {
|
|
144
|
+
constructor(store) {
|
|
145
|
+
__publicField(this, "actions", []);
|
|
146
|
+
__publicField(this, "currentCursor", 0);
|
|
147
|
+
__publicField(this, "store");
|
|
148
|
+
__publicField(this, "tickStarted", false);
|
|
149
|
+
this.store = store;
|
|
150
|
+
window.showHistory = () => console.log(this.actions);
|
|
151
|
+
}
|
|
152
|
+
push(type) {
|
|
153
|
+
var _a;
|
|
154
|
+
if (this.currentCursor + 1 < this.actions.length) {
|
|
155
|
+
this.actions.length = this.currentCursor + 1;
|
|
156
|
+
}
|
|
157
|
+
const lastAction = this.actions.length > 0 && this.currentCursor === this.actions.length - 1 ? this.actions[this.actions.length - 1] : null;
|
|
158
|
+
const selection = JSON.parse(JSON.stringify(this.store.selection));
|
|
159
|
+
if (type === "insertText") {
|
|
160
|
+
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) {
|
|
162
|
+
lastAction.blocks = [currentBlock];
|
|
163
|
+
lastAction.selection = selection;
|
|
164
|
+
} 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 });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
this.currentCursor = this.actions.length - 1;
|
|
177
|
+
if (!this.tickStarted && type === "setText") {
|
|
178
|
+
this.tickStarted = true;
|
|
179
|
+
nextTick(() => {
|
|
180
|
+
this.tickStarted = false;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
applyAction(action) {
|
|
185
|
+
if (action.type === "setText") {
|
|
186
|
+
for (let i = 0; i < action.blocks.length; i++) {
|
|
187
|
+
if (i >= this.store.blocks.length) {
|
|
188
|
+
this.store.blocks.push({ ...action.blocks[i] });
|
|
189
|
+
} else {
|
|
190
|
+
Object.assign(this.store.blocks[i], action.blocks[i]);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (this.store.blocks.length > action.blocks.length) {
|
|
194
|
+
this.store.blocks.length = action.blocks.length;
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
for (let _block of action.blocks) {
|
|
198
|
+
const block = this.store.blocks.find((block2) => block2.id === _block.id);
|
|
199
|
+
if (block) {
|
|
200
|
+
block.text = _block.text;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
Object.assign(this.store.selection, action.selection);
|
|
205
|
+
}
|
|
206
|
+
undo() {
|
|
207
|
+
if (this.currentCursor === 0)
|
|
208
|
+
return;
|
|
209
|
+
this.currentCursor--;
|
|
210
|
+
this.applyAction(this.actions[this.currentCursor]);
|
|
211
|
+
}
|
|
212
|
+
redo() {
|
|
213
|
+
if (this.currentCursor >= this.actions.length - 1)
|
|
214
|
+
return;
|
|
215
|
+
this.currentCursor++;
|
|
216
|
+
this.applyAction(this.actions[this.currentCursor]);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
143
219
|
class TextEditorStore {
|
|
144
220
|
constructor() {
|
|
221
|
+
__publicField(this, "history", new TextEditorHistory(this));
|
|
145
222
|
__publicField(this, "blocks", reactive([{ id: uid(), text: "", styles: [] }]));
|
|
146
223
|
__publicField(this, "selection", reactive({
|
|
147
224
|
anchor: { blockId: this.blocks[0].id, offset: 0 },
|
|
@@ -224,6 +301,7 @@ class TextEditorStore {
|
|
|
224
301
|
this.selection.focus.blockId = this.blocks[blockIndex - 1].id;
|
|
225
302
|
this.selection.anchor.offset = this.blocks[blockIndex - 1].text.length;
|
|
226
303
|
this.selection.focus.offset = this.blocks[blockIndex - 1].text.length;
|
|
304
|
+
this.history.push("setText");
|
|
227
305
|
}
|
|
228
306
|
onInput(_e) {
|
|
229
307
|
const ev = _e;
|
|
@@ -266,6 +344,7 @@ class TextEditorStore {
|
|
|
266
344
|
}
|
|
267
345
|
if (ev.inputType === "insertText") {
|
|
268
346
|
this.insertText(ev.data);
|
|
347
|
+
this.history.push("insertText");
|
|
269
348
|
}
|
|
270
349
|
}
|
|
271
350
|
addNewLine() {
|
|
@@ -279,13 +358,15 @@ class TextEditorStore {
|
|
|
279
358
|
this.blocks.splice(index + 1, 0, block);
|
|
280
359
|
this.selection.anchor = { blockId: block.id, offset: 0 };
|
|
281
360
|
this.selection.focus = { blockId: block.id, offset: 0 };
|
|
361
|
+
this.history.push("setText");
|
|
282
362
|
}
|
|
283
363
|
insertText(data) {
|
|
284
364
|
const block = this.currentBlock;
|
|
285
365
|
if (!block)
|
|
286
366
|
return;
|
|
287
|
-
|
|
288
|
-
|
|
367
|
+
const text = data.replace(/\r/g, "");
|
|
368
|
+
block.text = block.text.slice(0, this.selection.focus.offset) + text + block.text.slice(this.selection.focus.offset);
|
|
369
|
+
this.moveOffset(clamp(this.selection.focus.offset + text.length, 0, block.text.length));
|
|
289
370
|
}
|
|
290
371
|
insertBlock(blockData) {
|
|
291
372
|
if (!this.currentBlock)
|
|
@@ -303,6 +384,7 @@ class TextEditorStore {
|
|
|
303
384
|
if (blockData.editable === false && this.currentBlock === this.blocks[this.blocks.length - 1]) {
|
|
304
385
|
this.addNewLine();
|
|
305
386
|
}
|
|
387
|
+
this.history.push("setText");
|
|
306
388
|
}
|
|
307
389
|
get startAndEnd() {
|
|
308
390
|
const a = this.blocks.findIndex((block) => block.id === this.selection.anchor.blockId);
|
|
@@ -447,7 +529,8 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
447
529
|
props: {
|
|
448
530
|
block: {},
|
|
449
531
|
slots: {},
|
|
450
|
-
decorator: { type: Function }
|
|
532
|
+
decorator: { type: Function },
|
|
533
|
+
parser: {}
|
|
451
534
|
},
|
|
452
535
|
emits: ["postrender"],
|
|
453
536
|
setup(__props, { emit: __emit }) {
|
|
@@ -546,6 +629,12 @@ const _sfc_main$1 = /* @__PURE__ */ defineComponent({
|
|
|
546
629
|
markers.push([style.start, style]);
|
|
547
630
|
markers.push([style.end, style]);
|
|
548
631
|
}
|
|
632
|
+
if (props.parser) {
|
|
633
|
+
for (let style of props.parser(text)) {
|
|
634
|
+
markers.push([style.start, style]);
|
|
635
|
+
markers.push([style.end, style]);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
549
638
|
markers.sort((a, b) => a[0] - b[0]);
|
|
550
639
|
let currentIndex = 0;
|
|
551
640
|
const activeStyles = /* @__PURE__ */ new Set();
|
|
@@ -588,9 +677,11 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
588
677
|
decorator: { type: Function },
|
|
589
678
|
single: { type: Boolean },
|
|
590
679
|
modelValue: {},
|
|
680
|
+
parser: { type: Function },
|
|
591
681
|
styles: {},
|
|
592
682
|
autofocus: { type: Boolean },
|
|
593
|
-
autoselect: { type: Boolean }
|
|
683
|
+
autoselect: { type: Boolean },
|
|
684
|
+
preventMultiline: { type: Boolean }
|
|
594
685
|
},
|
|
595
686
|
emits: ["keydown", "update:modelValue", "update:styles"],
|
|
596
687
|
setup(__props, { expose: __expose, emit: __emit }) {
|
|
@@ -649,7 +740,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
649
740
|
return;
|
|
650
741
|
if (e.code === "Enter") {
|
|
651
742
|
e.preventDefault();
|
|
652
|
-
if (e.shiftKey || props.single) {
|
|
743
|
+
if (!props.preventMultiline && (e.shiftKey || props.single)) {
|
|
653
744
|
store.insertText("\n");
|
|
654
745
|
} else {
|
|
655
746
|
store.addNewLine();
|
|
@@ -660,24 +751,34 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
660
751
|
e.preventDefault();
|
|
661
752
|
}
|
|
662
753
|
}
|
|
754
|
+
if ((e.ctrlKey || e.metaKey) && e.code === "KeyZ" && !e.shiftKey) {
|
|
755
|
+
e.preventDefault();
|
|
756
|
+
store.history.undo();
|
|
757
|
+
}
|
|
758
|
+
if ((e.ctrlKey || e.metaKey) && (e.code === "KeyY" || e.shiftKey && e.code === "KeyZ")) {
|
|
759
|
+
e.preventDefault();
|
|
760
|
+
store.history.redo();
|
|
761
|
+
}
|
|
663
762
|
};
|
|
664
763
|
let cachedSelection = {};
|
|
665
764
|
useEventListener(document, "selectionchange", () => {
|
|
666
765
|
const sel = window.getSelection();
|
|
667
766
|
const anchor = findParent(sel.anchorNode, (el) => el.hasAttribute("data-vw-block-id") && el.parentElement === textEditorRef.value);
|
|
668
767
|
if (anchor) {
|
|
669
|
-
const
|
|
768
|
+
const isNonEditable = anchor.getAttribute("contenteditable") === "false";
|
|
769
|
+
const offset = isNonEditable || anchor === sel.focusNode ? 0 : calcOffsetToNode(anchor, sel.anchorNode) + sel.anchorOffset;
|
|
670
770
|
store.selection.anchor = { blockId: anchor.getAttribute("data-vw-block-id"), offset };
|
|
671
771
|
}
|
|
672
772
|
const focus = findParent(sel.focusNode, (el) => el.hasAttribute("data-vw-block-id") && el.parentElement === textEditorRef.value);
|
|
673
773
|
if (focus) {
|
|
674
|
-
const
|
|
774
|
+
const isNonEditable = focus.getAttribute("contenteditable") === "false";
|
|
775
|
+
const offset = isNonEditable || focus === sel.focusNode ? 0 : calcOffsetToNode(focus, sel.focusNode) + sel.focusOffset;
|
|
675
776
|
store.selection.focus = { blockId: focus.getAttribute("data-vw-block-id"), offset };
|
|
676
777
|
}
|
|
677
778
|
if (store.isFocused.value !== (!!focus || !!anchor)) {
|
|
678
779
|
store.isFocused.value = !!focus || !!anchor;
|
|
679
780
|
}
|
|
680
|
-
if (
|
|
781
|
+
if (anchor && focus) {
|
|
681
782
|
cachedSelection = JSON.parse(JSON.stringify(store.selection));
|
|
682
783
|
}
|
|
683
784
|
});
|
|
@@ -732,15 +833,18 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
732
833
|
(_b = textEditorRef.value) == null ? void 0 : _b.focus();
|
|
733
834
|
store.selectAll();
|
|
734
835
|
}
|
|
836
|
+
store.history.push("setText");
|
|
735
837
|
});
|
|
736
838
|
const onCopy = (e) => {
|
|
737
839
|
e.preventDefault();
|
|
738
840
|
navigator.clipboard.writeText(store.selectedText);
|
|
841
|
+
store.history.push("setText");
|
|
739
842
|
};
|
|
740
843
|
const onCut = (e) => {
|
|
741
844
|
e.preventDefault();
|
|
742
845
|
navigator.clipboard.writeText(store.selectedText);
|
|
743
846
|
store.deleteSelected();
|
|
847
|
+
store.history.push("setText");
|
|
744
848
|
};
|
|
745
849
|
const onPaste = (e) => {
|
|
746
850
|
var _a;
|
|
@@ -749,6 +853,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
749
853
|
if (!text)
|
|
750
854
|
return;
|
|
751
855
|
store.insertText(text);
|
|
856
|
+
store.history.push("setText");
|
|
752
857
|
};
|
|
753
858
|
const getClientRects = (selection) => {
|
|
754
859
|
const anchor = getNode(selection.anchor.blockId);
|
|
@@ -775,6 +880,7 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
775
880
|
addNewLine: store.addNewLine.bind(store),
|
|
776
881
|
removeNewLine: store.removeNewLine.bind(store),
|
|
777
882
|
selectAll: store.selectAll.bind(store),
|
|
883
|
+
pushHistory: store.history.push.bind(store.history),
|
|
778
884
|
getClientRects
|
|
779
885
|
});
|
|
780
886
|
return (_ctx, _cache) => {
|
|
@@ -795,8 +901,9 @@ const _sfc_main = /* @__PURE__ */ defineComponent({
|
|
|
795
901
|
block,
|
|
796
902
|
slots: unref(slots),
|
|
797
903
|
decorator: props.decorator,
|
|
904
|
+
parser: props.parser,
|
|
798
905
|
onPostrender: onPostRender
|
|
799
|
-
}, null, 8, ["block", "slots", "decorator"]);
|
|
906
|
+
}, null, 8, ["block", "slots", "decorator", "parser"]);
|
|
800
907
|
}), 128)),
|
|
801
908
|
unref(store).blocks.length === 1 && unref(store).blocks[0].text === "" && !unref(store).blocks[0].type ? renderSlot(_ctx.$slots, "placeholder", { key: 0 }) : createCommentVNode("", true)
|
|
802
909
|
], 544);
|