x-star-editor 0.2.6 → 0.3.1

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.
@@ -1,5 +1,5 @@
1
1
  import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper";
2
- import { useRef } from 'react';
2
+ import { useEffect, useRef } from 'react';
3
3
  import { editorRender } from "./markdown";
4
4
 
5
5
  /**
@@ -53,49 +53,6 @@ export var useContainer = function useContainer(ref) {
53
53
  return node instanceof HTMLElement && node.dataset.ignore !== undefined;
54
54
  };
55
55
 
56
- /**
57
- * 将 DOM 树规范化
58
- *
59
- * 检查每个行节点的末尾是否为零宽空格,如果不是,则删除行中的零宽空格并在末尾添加零宽空格
60
- */
61
- var normalize = function normalize() {
62
- if (!ref.current) {
63
- return;
64
- }
65
- var traverse = function traverse(node, depth) {
66
- if (isIgnore(node)) {
67
- return;
68
- }
69
- if (depth === 2) {
70
- var _node$lastChild;
71
- // 检查行节点的末尾
72
- if (((_node$lastChild = node.lastChild) === null || _node$lastChild === void 0 ? void 0 : _node$lastChild.nodeType) === Node.TEXT_NODE) {
73
- var _node$lastChild$nodeV;
74
- if ((_node$lastChild$nodeV = node.lastChild.nodeValue) !== null && _node$lastChild$nodeV !== void 0 && _node$lastChild$nodeV.endsWith("\u200B")) {
75
- return;
76
- }
77
- }
78
- } else if (depth > 2) {
79
- // 删除行中的零宽空格
80
- if (node.nodeType === Node.TEXT_NODE) {
81
- var _node$nodeValue;
82
- if ((_node$nodeValue = node.nodeValue) !== null && _node$nodeValue !== void 0 && _node$nodeValue.includes("\u200B")) {
83
- node.nodeValue = node.nodeValue.replace(/\u200B/g, '');
84
- }
85
- return;
86
- }
87
- }
88
- for (var i = 0; i < node.childNodes.length; i++) {
89
- traverse(node.childNodes[i], depth + 1);
90
- }
91
- if (depth === 2) {
92
- // 添加零宽空格
93
- node.appendChild(document.createTextNode("\u200B"));
94
- }
95
- };
96
- traverse(ref.current, 0);
97
- };
98
-
99
56
  /**
100
57
  * 获取容器文本
101
58
  *
@@ -152,14 +109,9 @@ export var useContainer = function useContainer(ref) {
152
109
  for (; oldChild && newChild;) {
153
110
  var oldNext = oldChild.nextSibling;
154
111
  var newNext = newChild.nextSibling;
155
- if (oldChild.nodeName !== newChild.nodeName) {
156
- // 节点名称不同,直接替换
112
+ if (oldChild.nodeName !== newChild.nodeName || oldChild.nodeType === Node.TEXT_NODE) {
113
+ // 节点名称不同,或节点均为文本类型,直接替换
157
114
  oldChild.replaceWith(newChild);
158
- } else if (oldChild.nodeType === Node.TEXT_NODE) {
159
- // 节点均为文本类型,更新文本内容
160
- if (oldChild.nodeValue !== newChild.nodeValue) {
161
- oldChild.nodeValue = newChild.nodeValue;
162
- }
163
115
  } else {
164
116
  // 节点名称相同,更新属性和子节点
165
117
  if (oldChild instanceof HTMLElement && newChild instanceof HTMLElement) {
@@ -219,64 +171,62 @@ export var useContainer = function useContainer(ref) {
219
171
  };
220
172
 
221
173
  /**
222
- * 获取容器选区
174
+ * 根据节点和节点内偏移量获取容器内偏移量
223
175
  *
224
- * Selection API 获取选中的 DOM 节点和节点内偏移量,在 DOM 树上遍历获取对应的容器内偏移量
225
- *
226
- * @returns 容器选区
176
+ * @param root 容器根节点
177
+ * @param targetNode 节点
178
+ * @param targetOffset 节点内偏移量
179
+ * @returns 容器内偏移量
227
180
  */
228
- var getSelection = function getSelection() {
229
- var selection = window.getSelection();
230
- if (!selection || !selection.anchorNode || !selection.focusNode) {
231
- return createSelection(-1);
232
- }
233
-
234
- /**
235
- * 根据节点和节点内偏移量获取容器内偏移量
236
- *
237
- * @param targetNode 节点
238
- * @param targetOffset 节点内偏移量
239
- * @returns 容器内偏移量
240
- */
241
- var getOffset = function getOffset(targetNode, targetOffset) {
242
- if (!ref.current) {
243
- return -1;
181
+ var getOffset = function getOffset(root, targetNode, targetOffset) {
182
+ var offset = 0;
183
+ var traverse = function traverse(node) {
184
+ if (isIgnore(node)) {
185
+ return;
244
186
  }
245
- var offset = 0;
246
- var traverse = function traverse(node) {
247
- if (isIgnore(node)) {
248
- return;
249
- }
250
- if (node === targetNode) {
251
- if (node.nodeType === Node.TEXT_NODE) {
252
- if (node.nodeValue) {
253
- offset += targetOffset;
254
- if (targetOffset === node.nodeValue.length && node.nodeValue.endsWith("\u200B")) {
255
- offset--;
256
- }
257
- }
258
- } else {
259
- for (var i = 0; i < targetOffset; i++) {
260
- traverse(node.childNodes[i]);
261
- }
262
- }
263
- return true;
264
- }
187
+ if (node === targetNode) {
265
188
  if (node.nodeType === Node.TEXT_NODE) {
266
189
  if (node.nodeValue) {
267
- offset += node.nodeValue.length;
190
+ offset += targetOffset;
191
+ if (targetOffset === node.nodeValue.length && node.nodeValue.endsWith("\u200B")) {
192
+ offset--;
193
+ }
268
194
  }
269
195
  } else {
270
- for (var _i = 0; _i < node.childNodes.length; _i++) {
271
- if (traverse(node.childNodes[_i])) {
272
- return true;
273
- }
196
+ for (var i = 0; i < targetOffset; i++) {
197
+ traverse(node.childNodes[i]);
274
198
  }
275
199
  }
276
- };
277
- return traverse(ref.current) ? offset : -1;
200
+ return true;
201
+ }
202
+ if (node.nodeType === Node.TEXT_NODE) {
203
+ if (node.nodeValue) {
204
+ offset += node.nodeValue.length;
205
+ }
206
+ } else {
207
+ for (var _i = 0; _i < node.childNodes.length; _i++) {
208
+ if (traverse(node.childNodes[_i])) {
209
+ return true;
210
+ }
211
+ }
212
+ }
278
213
  };
279
- return createSelection(getOffset(selection.anchorNode, selection.anchorOffset), getOffset(selection.focusNode, selection.focusOffset));
214
+ return traverse(root) ? offset : -1;
215
+ };
216
+
217
+ /**
218
+ * 获取容器选区
219
+ *
220
+ * 用 Selection API 获取选中的 DOM 节点和节点内偏移量,在 DOM 树上遍历获取对应的容器内偏移量
221
+ *
222
+ * @returns 容器选区
223
+ */
224
+ var getSelection = function getSelection() {
225
+ var selection = window.getSelection();
226
+ if (!selection || !selection.anchorNode || !selection.focusNode || !ref.current) {
227
+ return createSelection(-1);
228
+ }
229
+ return createSelection(getOffset(ref.current, selection.anchorNode, selection.anchorOffset), getOffset(ref.current, selection.focusNode, selection.focusOffset));
280
230
  };
281
231
 
282
232
  /**
@@ -290,23 +240,18 @@ export var useContainer = function useContainer(ref) {
290
240
  var anchorOffset = _ref2.anchorOffset,
291
241
  focusOffset = _ref2.focusOffset;
292
242
  var selection = window.getSelection();
293
- if (!selection || anchorOffset === -1 || focusOffset === -1) {
243
+ if (!selection || anchorOffset === -1 || focusOffset === -1 || !ref.current) {
294
244
  return;
295
245
  }
296
246
 
297
247
  /**
298
248
  * 根据容器内偏移量获取节点和节点内偏移量
299
249
  *
250
+ * @param root 容器根节点
300
251
  * @param targetOffset 容器内偏移量
301
252
  * @returns 节点和节点内偏移量
302
253
  */
303
- var getNodeOffset = function getNodeOffset(targetOffset) {
304
- if (!ref.current) {
305
- return {
306
- node: undefined,
307
- offset: 0
308
- };
309
- }
254
+ var getNodeOffset = function getNodeOffset(root, targetOffset) {
310
255
  var offset = targetOffset;
311
256
  var traverse = function traverse(node) {
312
257
  if (isIgnore(node)) {
@@ -329,19 +274,21 @@ export var useContainer = function useContainer(ref) {
329
274
  }
330
275
  };
331
276
  return {
332
- node: traverse(ref.current),
277
+ node: traverse(root),
333
278
  offset: offset
334
279
  };
335
280
  };
336
- var anchor = getNodeOffset(anchorOffset);
337
- var focus = getNodeOffset(focusOffset);
281
+ var anchor = getNodeOffset(ref.current, anchorOffset);
282
+ var focus = getNodeOffset(ref.current, focusOffset);
338
283
  if (!anchor.node || !focus.node) {
339
284
  return;
340
285
  }
341
286
  selection.setBaseAndExtent(anchor.node, anchor.offset, focus.node, focus.offset);
342
- if (!ref.current) {
343
- return;
344
- }
287
+ ref.current.scrollIntoView({
288
+ behavior: 'instant',
289
+ block: 'nearest',
290
+ inline: 'nearest'
291
+ });
345
292
  var range = document.createRange();
346
293
  range.setStart(focus.node, focus.offset);
347
294
  range.setEnd(focus.node, focus.offset);
@@ -357,10 +304,46 @@ export var useContainer = function useContainer(ref) {
357
304
  ref.current.scrollTop += bottomOffset;
358
305
  }
359
306
  };
307
+
308
+ // 确保光标不在零宽空格后
309
+ useEffect(function () {
310
+ var listener = function listener() {
311
+ var _ref$current, _anchorNode$nodeValue, _focusNode$nodeValue;
312
+ var selection = window.getSelection();
313
+ if (!selection || !selection.anchorNode || !selection.focusNode || !((_ref$current = ref.current) !== null && _ref$current !== void 0 && _ref$current.contains(selection.anchorNode))) {
314
+ return;
315
+ }
316
+ var update = false;
317
+ var anchorNode = selection.anchorNode,
318
+ anchorOffset = selection.anchorOffset,
319
+ focusNode = selection.focusNode,
320
+ focusOffset = selection.focusOffset;
321
+ if (anchorNode.nodeType === Node.TEXT_NODE && ((_anchorNode$nodeValue = anchorNode.nodeValue) === null || _anchorNode$nodeValue === void 0 ? void 0 : _anchorNode$nodeValue[anchorOffset - 1]) === "\u200B") {
322
+ update = true;
323
+ anchorOffset--;
324
+ }
325
+ if (focusNode.nodeType === Node.TEXT_NODE && ((_focusNode$nodeValue = focusNode.nodeValue) === null || _focusNode$nodeValue === void 0 ? void 0 : _focusNode$nodeValue[focusOffset - 1]) === "\u200B") {
326
+ update = true;
327
+ focusOffset--;
328
+ }
329
+ if (update) {
330
+ selection.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
331
+ }
332
+ };
333
+ document.addEventListener('selectionchange', listener, {
334
+ capture: true,
335
+ passive: true
336
+ });
337
+ return function () {
338
+ return document.removeEventListener('selectionchange', listener, {
339
+ capture: true
340
+ });
341
+ };
342
+ }, []);
360
343
  return useRef({
361
- normalize: normalize,
362
344
  getText: getText,
363
345
  setText: setText,
346
+ getOffset: getOffset,
364
347
  getSelection: getSelection,
365
348
  setSelection: setSelection
366
349
  }).current;
@@ -1,5 +1,5 @@
1
1
  /// <reference types="react" />
2
- import type { DeleteAction, InsertAction, SelectAction, SetAction, ToggleAction, useHistory } from './history';
2
+ import type { InsertAction, SelectAction, SetAction, ToggleAction, useHistory } from './history';
3
3
  /**
4
4
  * 处理函数上下文
5
5
  */
@@ -11,11 +11,11 @@ export interface Handler<T = void> {
11
11
  (ctx: HandlerContext): T;
12
12
  }
13
13
  export declare const insertHandler: (payload: InsertAction['payload']) => Handler;
14
- export declare const deleteHandler: (payload: DeleteAction['payload']) => Handler;
15
14
  export declare const toggleHandler: (payload: ToggleAction['payload']) => Handler;
16
15
  export declare const setHandler: (payload: SetAction['payload']) => Handler;
17
16
  export declare const undoHandler: () => Handler;
18
17
  export declare const redoHandler: () => Handler;
18
+ export declare const batchHandler: () => Handler;
19
19
  export declare const selectHandler: (selection: SelectAction['selection']) => Handler;
20
20
  /**
21
21
  * 执行函数
@@ -21,52 +21,49 @@ export var insertHandler = function insertHandler(payload) {
21
21
  });
22
22
  };
23
23
  };
24
- export var deleteHandler = function deleteHandler(payload) {
24
+ export var toggleHandler = function toggleHandler(payload) {
25
25
  return function (_ref2) {
26
26
  var selection = _ref2.selection,
27
27
  dispatch = _ref2.dispatch;
28
28
  return dispatch({
29
- type: 'delete',
29
+ type: 'toggle',
30
30
  payload: payload,
31
31
  selection: selection
32
32
  });
33
33
  };
34
34
  };
35
- export var toggleHandler = function toggleHandler(payload) {
35
+ export var setHandler = function setHandler(payload) {
36
36
  return function (_ref3) {
37
37
  var selection = _ref3.selection,
38
38
  dispatch = _ref3.dispatch;
39
39
  return dispatch({
40
- type: 'toggle',
40
+ type: 'set',
41
41
  payload: payload,
42
42
  selection: selection
43
43
  });
44
44
  };
45
45
  };
46
- export var setHandler = function setHandler(payload) {
46
+ export var undoHandler = function undoHandler() {
47
47
  return function (_ref4) {
48
- var selection = _ref4.selection,
49
- dispatch = _ref4.dispatch;
48
+ var dispatch = _ref4.dispatch;
50
49
  return dispatch({
51
- type: 'set',
52
- payload: payload,
53
- selection: selection
50
+ type: 'undo'
54
51
  });
55
52
  };
56
53
  };
57
- export var undoHandler = function undoHandler() {
54
+ export var redoHandler = function redoHandler() {
58
55
  return function (_ref5) {
59
56
  var dispatch = _ref5.dispatch;
60
57
  return dispatch({
61
- type: 'undo'
58
+ type: 'redo'
62
59
  });
63
60
  };
64
61
  };
65
- export var redoHandler = function redoHandler() {
62
+ export var batchHandler = function batchHandler() {
66
63
  return function (_ref6) {
67
64
  var dispatch = _ref6.dispatch;
68
65
  return dispatch({
69
- type: 'redo'
66
+ type: 'batch'
70
67
  });
71
68
  };
72
69
  };
@@ -107,170 +104,6 @@ export var getDefaultKeyboardEventHandlers = function getDefaultKeyboardEventHan
107
104
  selection: selection
108
105
  });
109
106
  },
110
- Backspace: function Backspace(e) {
111
- e.preventDefault();
112
- dispatch({
113
- type: 'delete',
114
- payload: {
115
- direction: 'backward',
116
- boundary: 'char'
117
- },
118
- selection: selection
119
- });
120
- },
121
- '$mod+Backspace': function $modBackspace(e) {
122
- e.preventDefault();
123
- dispatch({
124
- type: 'delete',
125
- payload: {
126
- direction: 'backward',
127
- boundary: 'line'
128
- },
129
- selection: selection
130
- });
131
- },
132
- 'Shift+Backspace': function ShiftBackspace(e) {
133
- e.preventDefault();
134
- dispatch({
135
- type: 'delete',
136
- payload: {
137
- direction: 'backward',
138
- boundary: 'char'
139
- },
140
- selection: selection
141
- });
142
- },
143
- 'Meta+Backspace': function MetaBackspace(e) {
144
- return e.preventDefault();
145
- },
146
- 'Alt+Backspace': function AltBackspace(e) {
147
- return e.preventDefault();
148
- },
149
- 'Control+Backspace': function ControlBackspace(e) {
150
- return e.preventDefault();
151
- },
152
- 'Shift+Meta+Backspace': function ShiftMetaBackspace(e) {
153
- return e.preventDefault();
154
- },
155
- 'Shift+Alt+Backspace': function ShiftAltBackspace(e) {
156
- return e.preventDefault();
157
- },
158
- 'Shift+Control+Backspace': function ShiftControlBackspace(e) {
159
- return e.preventDefault();
160
- },
161
- 'Meta+Alt+Backspace': function MetaAltBackspace(e) {
162
- return e.preventDefault();
163
- },
164
- 'Meta+Control+Backspace': function MetaControlBackspace(e) {
165
- return e.preventDefault();
166
- },
167
- 'Alt+Control+Backspace': function AltControlBackspace(e) {
168
- return e.preventDefault();
169
- },
170
- 'Shift+Meta+Alt+Backspace': function ShiftMetaAltBackspace(e) {
171
- return e.preventDefault();
172
- },
173
- 'Shift+Meta+Control+Backspace': function ShiftMetaControlBackspace(e) {
174
- return e.preventDefault();
175
- },
176
- 'Shift+Alt+Control+Backspace': function ShiftAltControlBackspace(e) {
177
- return e.preventDefault();
178
- },
179
- 'Meta+Alt+Control+Backspace': function MetaAltControlBackspace(e) {
180
- return e.preventDefault();
181
- },
182
- Delete: function Delete(e) {
183
- e.preventDefault();
184
- dispatch({
185
- type: 'delete',
186
- payload: {
187
- direction: 'forward',
188
- boundary: 'char'
189
- },
190
- selection: selection
191
- });
192
- },
193
- '$mod+Delete': function $modDelete(e) {
194
- e.preventDefault();
195
- dispatch({
196
- type: 'delete',
197
- payload: {
198
- direction: 'forward',
199
- boundary: 'line'
200
- },
201
- selection: selection
202
- });
203
- },
204
- 'Shift+Delete': function ShiftDelete(e) {
205
- e.preventDefault();
206
- dispatch({
207
- type: 'delete',
208
- payload: {
209
- direction: 'forward',
210
- boundary: 'char'
211
- },
212
- selection: selection
213
- });
214
- },
215
- 'Meta+Delete': function MetaDelete(e) {
216
- return e.preventDefault();
217
- },
218
- 'Alt+Delete': function AltDelete(e) {
219
- return e.preventDefault();
220
- },
221
- 'Control+Delete': function ControlDelete(e) {
222
- return e.preventDefault();
223
- },
224
- 'Shift+Meta+Delete': function ShiftMetaDelete(e) {
225
- return e.preventDefault();
226
- },
227
- 'Shift+Alt+Delete': function ShiftAltDelete(e) {
228
- return e.preventDefault();
229
- },
230
- 'Shift+Control+Delete': function ShiftControlDelete(e) {
231
- return e.preventDefault();
232
- },
233
- 'Meta+Alt+Delete': function MetaAltDelete(e) {
234
- return e.preventDefault();
235
- },
236
- 'Meta+Control+Delete': function MetaControlDelete(e) {
237
- return e.preventDefault();
238
- },
239
- 'Alt+Control+Delete': function AltControlDelete(e) {
240
- return e.preventDefault();
241
- },
242
- 'Shift+Meta+Alt+Delete': function ShiftMetaAltDelete(e) {
243
- return e.preventDefault();
244
- },
245
- 'Shift+Meta+Control+Delete': function ShiftMetaControlDelete(e) {
246
- return e.preventDefault();
247
- },
248
- 'Shift+Alt+Control+Delete': function ShiftAltControlDelete(e) {
249
- return e.preventDefault();
250
- },
251
- 'Meta+Alt+Control+Delete': function MetaAltControlDelete(e) {
252
- return e.preventDefault();
253
- },
254
- '$mod+KeyI': function $modKeyI(e) {
255
- e.preventDefault();
256
- dispatch({
257
- type: 'toggle',
258
- payload: {
259
- type: 'emphasis'
260
- },
261
- selection: selection
262
- });
263
- },
264
- '$mod+KeyB': function $modKeyB(e) {
265
- e.preventDefault();
266
- dispatch({
267
- type: 'toggle',
268
- payload: {
269
- type: 'strong'
270
- },
271
- selection: selection
272
- });
273
- },
274
107
  '$mod+KeyZ': function $modKeyZ(e) {
275
108
  e.preventDefault();
276
109
  dispatch({
@@ -14,14 +14,6 @@ export interface InsertAction {
14
14
  };
15
15
  selection: ContainerSelection;
16
16
  }
17
- export interface DeleteAction {
18
- type: 'delete';
19
- payload?: {
20
- direction: 'backward' | 'forward';
21
- boundary: 'char' | 'line';
22
- };
23
- selection: ContainerSelection;
24
- }
25
17
  export interface ToggleAction {
26
18
  type: 'toggle';
27
19
  payload: {
@@ -46,13 +38,15 @@ export interface SetAction {
46
38
  payload: State;
47
39
  selection: ContainerSelection;
48
40
  }
49
- type StateAction = InsertAction | DeleteAction | ToggleAction | SetAction;
41
+ type StateAction = InsertAction | ToggleAction | SetAction;
50
42
  /**
51
43
  * 编辑器历史
52
44
  */
53
45
  interface History {
54
46
  states: (State & {
55
- action: StateAction;
47
+ action: StateAction & {
48
+ batch?: boolean;
49
+ };
56
50
  })[];
57
51
  index: number;
58
52
  selection: ContainerSelection;
@@ -63,15 +57,21 @@ export interface UndoAction {
63
57
  export interface RedoAction {
64
58
  type: 'redo';
65
59
  }
60
+ export interface BatchAction {
61
+ type: 'batch';
62
+ selection?: ContainerSelection;
63
+ }
66
64
  export interface SelectAction {
67
65
  type: 'select';
68
66
  selection: ContainerSelection;
69
67
  }
70
- type HistoryAction = UndoAction | RedoAction | SelectAction;
68
+ type HistoryAction = UndoAction | RedoAction | BatchAction | SelectAction;
71
69
  export declare const useHistory: (initialSourceCode: string) => {
72
70
  history: History;
73
71
  sourceCode: string;
74
72
  selection: ContainerSelection;
75
- dispatch: import("react").Dispatch<StateAction | HistoryAction>;
73
+ dispatch: import("react").Dispatch<HistoryAction | (StateAction & {
74
+ batch?: boolean | undefined;
75
+ })>;
76
76
  };
77
77
  export {};