x-star-editor 0.2.4 → 0.2.6

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.
@@ -97,10 +97,14 @@ var stateReducer = function stateReducer(_ref, action) {
97
97
  }
98
98
  case 'code':
99
99
  {
100
- var language = action.payload.language;
100
+ var _action$payload2 = action.payload,
101
+ lang = _action$payload2.lang,
102
+ value = _action$payload2.value,
103
+ showLineNumbers = _action$payload2.showLineNumbers;
104
+ var _startOffset = lineEndOffset + lang.length + (showLineNumbers ? 21 : 5);
101
105
  return {
102
- sourceCode: "".concat(sourceCode.slice(0, lineEndOffset), "\n```").concat(language, " showLineNumbers\n\n```\n").concat(lineAfter),
103
- selection: createSelection(lineEndOffset + language.length + 21)
106
+ sourceCode: "".concat(sourceCode.slice(0, lineEndOffset), "\n```").concat(lang).concat(showLineNumbers ? ' showLineNumbers' : '', "\n").concat(value, "\n```\n").concat(lineAfter),
107
+ selection: createSelection(_startOffset, _startOffset + value.length)
104
108
  };
105
109
  }
106
110
  case 'delete':
@@ -139,9 +143,9 @@ var stateReducer = function stateReducer(_ref, action) {
139
143
  });
140
144
  });
141
145
  for (var _i3 = 0; _i3 < _values.length; _i3++) {
142
- var value = _values[_i3];
143
- if (value) {
144
- _lines[_i3] = _lines[_i3].replace(new RegExp("^ {0,3}#{".concat(value, "} +")), '');
146
+ var _value = _values[_i3];
147
+ if (_value) {
148
+ _lines[_i3] = _lines[_i3].replace(new RegExp("^ {0,3}#{".concat(_value, "} +")), '');
145
149
  }
146
150
  }
147
151
  if (_values.some(function (value) {
@@ -160,10 +164,10 @@ var stateReducer = function stateReducer(_ref, action) {
160
164
  case 'image':
161
165
  case 'link':
162
166
  {
163
- var _action$payload2 = action.payload,
164
- _type = _action$payload2.type,
165
- url = _action$payload2.url,
166
- description = _action$payload2.description;
167
+ var _action$payload3 = action.payload,
168
+ _type = _action$payload3.type,
169
+ url = _action$payload3.url,
170
+ description = _action$payload3.description;
167
171
  return {
168
172
  sourceCode: "".concat(before).concat(_type === 'link' ? '' : '!', "[").concat(description, "](").concat(url, ")").concat(after),
169
173
  selection: createSelection(startOffset + url.length + description.length + 4 + (_type === 'link' ? 0 : 1))
@@ -192,9 +196,9 @@ var stateReducer = function stateReducer(_ref, action) {
192
196
  }
193
197
  case 'set':
194
198
  {
195
- var _action$payload3 = action.payload,
196
- _sourceCode = _action$payload3.sourceCode,
197
- selection = _action$payload3.selection;
199
+ var _action$payload4 = action.payload,
200
+ _sourceCode = _action$payload4.sourceCode,
201
+ selection = _action$payload4.selection;
198
202
  return {
199
203
  sourceCode: _sourceCode.replace(/\r\n?/g, '\n'),
200
204
  selection: selection
@@ -1,4 +1,5 @@
1
1
  /// <reference types="react" />
2
+ import type { Root as HastRoot } from 'hast';
2
3
  import { defaultSchema } from 'rehype-sanitize';
3
4
  import type { ViewerOptions } from '../x-star-md-viewer';
4
5
  /**
@@ -8,21 +9,13 @@ import type { ViewerOptions } from '../x-star-md-viewer';
8
9
  * @returns DOM 树
9
10
  */
10
11
  export declare const editorRender: (sourceCode: string) => HTMLDivElement;
11
- /**
12
- * 将 Mdast 树转成 Hast 树的处理器
13
- */
14
- declare const toHastProcessor: import("unified").FrozenProcessor<import("mdast").Root, import("hast").Root, import("hast").Root, void>;
15
- /**
16
- * Hast 根节点
17
- */
18
- export type HastRoot = Awaited<ReturnType<(typeof toHastProcessor)['run']>>;
19
12
  /**
20
13
  * 将 Markdown 文本转成 Hast 树
21
14
  *
22
15
  * @param sourceCode Markdown 文本
23
16
  * @returns Hast 树
24
17
  */
25
- export declare const preViewerRender: (sourceCode: string) => Promise<import("hast").Root>;
18
+ export declare const preViewerRender: (sourceCode: string) => Promise<HastRoot>;
26
19
  /**
27
20
  * 过滤模式
28
21
  */
@@ -35,7 +28,7 @@ export declare const getDefaultSchema: () => Schema;
35
28
  * @param schema 过滤模式
36
29
  * @returns 新的 Hast 树
37
30
  */
38
- export declare const viewerRender: (root: HastRoot, schema: Schema) => import("hast").Root;
31
+ export declare const viewerRender: (root: HastRoot, schema: Schema) => HastRoot;
39
32
  /**
40
33
  * 将 Hast 树映射到 React 虚拟 DOM 树
41
34
  *
@@ -65,4 +58,3 @@ export declare const toMarkdown: (sourceCode: string) => string;
65
58
  * @returns 纯文本
66
59
  */
67
60
  export declare const toText: (sourceCode: string) => string;
68
- export {};
@@ -5,12 +5,13 @@ import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
5
5
  import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper";
6
6
  import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
7
7
  import { toJsxRuntime } from 'hast-util-to-jsx-runtime';
8
+ import { toText as hastToText } from 'hast-util-to-text';
8
9
  import { Fragment, jsx, jsxs } from 'react/jsx-runtime';
9
10
  import rehypeKatex from 'rehype-katex';
10
11
  import rehypeMermaid from 'rehype-mermaidjs';
11
12
  import rehypePrism from 'rehype-prism-plus';
12
13
  import rehypeRaw from 'rehype-raw';
13
- import rehypeRemark from 'rehype-remark';
14
+ import rehypeRemark, { defaultHandlers } from 'rehype-remark';
14
15
  import rehypeSanitize, { defaultSchema } from 'rehype-sanitize';
15
16
  import rehypeStringify from 'rehype-stringify';
16
17
  import remarkBreaks from 'remark-breaks';
@@ -22,16 +23,15 @@ import remarkStringify from 'remark-stringify';
22
23
  import strip from 'strip-markdown';
23
24
  import { unified } from 'unified';
24
25
  import { prefix } from "./global";
26
+ import rehypeLine from "./rehype/rehype-line";
27
+ import rehypeMath, { isMathNode } from "./rehype/rehype-math";
28
+ import rehypeRawPositions from "./rehype/rehype-raw-positions";
25
29
 
26
30
  /**
27
31
  * 将 Markdown 文本解析为 Mdast 树的处理器
28
32
  */
29
33
  var toMdastProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkMath).freeze();
30
34
 
31
- /**
32
- * Mdast 根节点
33
- */
34
-
35
35
  /**
36
36
  * 带缓存的解析函数
37
37
  *
@@ -46,10 +46,6 @@ var cachedParse = function () {
46
46
  };
47
47
  }();
48
48
 
49
- /**
50
- * Mdast 节点
51
- */
52
-
53
49
  /**
54
50
  * 将 Markdown 文本映射到 DOM 树,第一层为 `<div>` 块元素,第二层为 `<div>` 行元素,之后均为 `<span>` 元素
55
51
  *
@@ -231,6 +227,7 @@ export var editorRender = function editorRender(sourceCode) {
231
227
  // 如果栈大小为 1,说明为块级元素终点
232
228
  stack[0].element.append("\u200B");
233
229
  pushElement(0);
230
+ // 记录行号,用于同步滚动
234
231
  block.dataset.line = "".concat((_node$position3 = node.position) === null || _node$position3 === void 0 ? void 0 : _node$position3.start.line);
235
232
  pushBlock();
236
233
  scanOffset++;
@@ -278,11 +275,7 @@ var toHastProcessor = unified().use(remarkBreaks).use(remarkRehype, {
278
275
  strategy: 'img-svg'
279
276
  }).use(rehypePrism, {
280
277
  ignoreMissing: true
281
- }).use(rehypeKatex).freeze();
282
-
283
- /**
284
- * Hast 根节点
285
- */
278
+ }).freeze();
286
279
 
287
280
  /**
288
281
  * 将 Markdown 文本转成 Hast 树
@@ -341,25 +334,7 @@ export var getDefaultSchema = function getDefaultSchema() {
341
334
  * @returns 新的 Hast 树
342
335
  */
343
336
  export var viewerRender = function viewerRender(root, schema) {
344
- return unified().use(rehypeRaw).use(rehypeSanitize, schema).use(function () {
345
- return function (root) {
346
- var _iterator2 = _createForOfIteratorHelper(root.children),
347
- _step2;
348
- try {
349
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
350
- var node = _step2.value;
351
- if ('properties' in node && node.properties) {
352
- var _node$position4;
353
- node.properties['data-line'] = (_node$position4 = node.position) === null || _node$position4 === void 0 ? void 0 : _node$position4.start.line;
354
- }
355
- }
356
- } catch (err) {
357
- _iterator2.e(err);
358
- } finally {
359
- _iterator2.f();
360
- }
361
- };
362
- }).runSync(root);
337
+ return unified().use(rehypeRawPositions).use(rehypeRaw).use(rehypeMath).use(rehypeSanitize, schema).use(rehypeLine).runSync(root);
363
338
  };
364
339
 
365
340
  /**
@@ -370,25 +345,35 @@ export var viewerRender = function viewerRender(root, schema) {
370
345
  * @returns React 虚拟 DOM 树
371
346
  */
372
347
  export var postViewerRender = function postViewerRender(root, options) {
373
- return toJsxRuntime(root, {
348
+ var components = _objectSpread(_objectSpread({}, options.customHTMLElements), {}, {
349
+ custom: function custom(props) {
350
+ var _options$customBlocks;
351
+ return jsx((_options$customBlocks = options.customBlocks[props.meta]) !== null && _options$customBlocks !== void 0 ? _options$customBlocks : 'div', {
352
+ children: props.value
353
+ });
354
+ }
355
+ });
356
+ return toJsxRuntime(unified().use(rehypeKatex).runSync(root), {
374
357
  Fragment: Fragment,
375
358
  jsx: jsx,
376
359
  jsxs: jsxs,
377
- components: _objectSpread(_objectSpread({}, options.customHTMLElements), {}, {
378
- custom: function custom(_ref2) {
379
- var _options$customBlocks;
380
- var meta = _ref2.meta,
381
- value = _ref2.value;
382
- return jsx((_options$customBlocks = options.customBlocks[meta]) !== null && _options$customBlocks !== void 0 ? _options$customBlocks : 'div', {
383
- children: value
384
- });
385
- }
386
- })
360
+ components: components
387
361
  });
388
362
  };
389
- var toHTMLProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkRehype, {
390
- allowDangerousHtml: true
391
- }).use(rehypeRaw).use(rehypeStringify).freeze();
363
+ var toHTMLProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkMath).use(remarkRehype, {
364
+ allowDangerousHtml: true,
365
+ handlers: {
366
+ math: function math(h, node) {
367
+ return (
368
+ // 如果 Math 节点的 `meta` 属性不为空,则视为自定义块
369
+ node.meta ? h(node.position, 'custom', {
370
+ meta: node.meta,
371
+ value: node.value
372
+ }) : h(node, 'div')
373
+ );
374
+ }
375
+ }
376
+ }).use(rehypeRawPositions).use(rehypeRaw).use(rehypeMath).use(rehypeStringify).freeze();
392
377
 
393
378
  /**
394
379
  * 将 Markdown 文本转成 HTML 文本
@@ -400,8 +385,21 @@ export var toHTML = function toHTML(sourceCode) {
400
385
  return toHTMLProcessor.processSync(sourceCode).toString();
401
386
  };
402
387
  var toMarkdownProcessor = unified().use(rehypeRemark, {
403
- newlines: true
404
- }).use(remarkGfm).use(remarkStringify).freeze();
388
+ newlines: true,
389
+ handlers: {
390
+ div: function div(h, node) {
391
+ return isMathNode(node) ? h(node, 'math', hastToText(node)) : defaultHandlers.div(h, node);
392
+ },
393
+ span: function span(h, node) {
394
+ return isMathNode(node) ? h(node, 'inlineMath', hastToText(node)) : defaultHandlers.span(h, node);
395
+ },
396
+ custom: function custom(h, node) {
397
+ return h(node, 'math', {
398
+ meta: node.properties.meta
399
+ }, node.properties.value);
400
+ }
401
+ }
402
+ }).use(remarkMath).use(remarkGfm).use(remarkStringify).freeze();
405
403
 
406
404
  /**
407
405
  * 将带有 HTML 的 Markdown 文本转成 Markdown 文本
@@ -412,7 +410,14 @@ var toMarkdownProcessor = unified().use(rehypeRemark, {
412
410
  export var toMarkdown = function toMarkdown(sourceCode) {
413
411
  return toMarkdownProcessor.stringify(toMarkdownProcessor.runSync(toHTMLProcessor.runSync(toHTMLProcessor.parse(sourceCode))));
414
412
  };
415
- var toTextProcessor = unified().use(remarkParse).use(remarkGfm).use(strip).use(remarkStringify).freeze();
413
+ var toTextProcessor = unified().use(remarkParse).use(remarkGfm).use(remarkMath).use(strip, {
414
+ remove: ['math', ['inlineMath', function (node) {
415
+ return {
416
+ type: 'text',
417
+ value: node.value
418
+ };
419
+ }]]
420
+ }).use(remarkStringify).freeze();
416
421
 
417
422
  /**
418
423
  * 将 Markdown 文本转成纯文本
@@ -0,0 +1,6 @@
1
+ import type { Root as HastRoot } from 'hast';
2
+ /**
3
+ * 为第一层 Hast 节点添加行号属性
4
+ */
5
+ declare const rehypeLine: () => (root: HastRoot) => void;
6
+ export default rehypeLine;
@@ -0,0 +1,17 @@
1
+ import { SKIP, visit } from 'unist-util-visit';
2
+
3
+ /**
4
+ * 为第一层 Hast 节点添加行号属性
5
+ */
6
+ var rehypeLine = function rehypeLine() {
7
+ return function (root) {
8
+ return visit(root, 'element', function (node) {
9
+ var _node$properties, _node$position;
10
+ (_node$properties = node.properties) !== null && _node$properties !== void 0 ? _node$properties : node.properties = {};
11
+ // 记录行号,用于同步滚动
12
+ node.properties['data-line'] = (_node$position = node.position) === null || _node$position === void 0 ? void 0 : _node$position.start.line;
13
+ return SKIP;
14
+ });
15
+ };
16
+ };
17
+ export default rehypeLine;
@@ -0,0 +1,13 @@
1
+ import type { RootContent as HastNode, Root as HastRoot } from 'hast';
2
+ /**
3
+ * 判断一个 Hast 节点是否属于行内公式或公式块
4
+ *
5
+ * @param node Hast 节点
6
+ * @returns 是否属于行内公式或公式块
7
+ */
8
+ export declare const isMathNode: (node: HastRoot | HastNode) => boolean | undefined;
9
+ /**
10
+ * 解析属于 Raw 节点的文本中的数学公式
11
+ */
12
+ declare const rehypeMath: () => (root: HastRoot) => void;
13
+ export default rehypeMath;
@@ -0,0 +1,175 @@
1
+ import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
2
+ import { EXIT, SKIP, visit } from 'unist-util-visit';
3
+
4
+ /**
5
+ * 判断一个 Hast 节点是否属于行内代码或代码块
6
+ *
7
+ * @param node Hast 节点
8
+ * @returns 是否属于行内代码或代码块
9
+ */
10
+ var isCodeNode = function isCodeNode(node) {
11
+ return node.type === 'element' && node.tagName === 'code';
12
+ };
13
+
14
+ /**
15
+ * 判断一个 Hast 节点是否属于行内公式或公式块
16
+ *
17
+ * @param node Hast 节点
18
+ * @returns 是否属于行内公式或公式块
19
+ */
20
+ export var isMathNode = function isMathNode(node) {
21
+ var _node$properties, _node$properties2;
22
+ return node.type === 'element' && Array.isArray((_node$properties = node.properties) === null || _node$properties === void 0 ? void 0 : _node$properties.className) && ((_node$properties2 = node.properties) === null || _node$properties2 === void 0 ? void 0 : _node$properties2.className.includes('math'));
23
+ };
24
+
25
+ /**
26
+ * 解析文本中的公式
27
+ *
28
+ * @param text 文本
29
+ * @returns Hast 节点
30
+ */
31
+ var parseMath = function parseMath(text) {
32
+ var result = [];
33
+
34
+ /**
35
+ * 扫描文本
36
+ */
37
+ var scan = '';
38
+
39
+ /**
40
+ * 是否转义
41
+ */
42
+ var escape = false;
43
+
44
+ /**
45
+ * 连续 `$` 的数量
46
+ */
47
+ var level = 0;
48
+
49
+ /**
50
+ * 是否在行内公式中
51
+ */
52
+ var inline = false;
53
+
54
+ /**
55
+ * 是否在公式块中
56
+ */
57
+ var block = false;
58
+ var push = function push() {
59
+ if (level === 1 && !block) {
60
+ result.push(inline ? {
61
+ type: 'element',
62
+ tagName: 'span',
63
+ properties: {
64
+ className: ['math', 'math-inline']
65
+ },
66
+ children: [{
67
+ type: 'text',
68
+ value: scan
69
+ }]
70
+ } : {
71
+ type: 'text',
72
+ value: scan
73
+ });
74
+ scan = '';
75
+ inline = !inline;
76
+ } else if (level === 2 && !inline) {
77
+ result.push(block ? {
78
+ type: 'element',
79
+ tagName: 'div',
80
+ properties: {
81
+ className: ['math', 'math-display']
82
+ },
83
+ children: [{
84
+ type: 'text',
85
+ value: scan
86
+ }]
87
+ } : {
88
+ type: 'text',
89
+ value: scan
90
+ });
91
+ scan = '';
92
+ block = !block;
93
+ } else if (level) {
94
+ scan += '$'.repeat(level);
95
+ }
96
+ level = 0;
97
+ };
98
+ for (var i = 0; i <= text.length; i++) {
99
+ var _text$i;
100
+ var c = (_text$i = text[i]) !== null && _text$i !== void 0 ? _text$i : '';
101
+ if (escape) {
102
+ if (c === '\\') {
103
+ scan += c;
104
+ } else {
105
+ scan += c === '$' ? c : "\\".concat(c);
106
+ escape = false;
107
+ }
108
+ } else if (c === '$') {
109
+ level++;
110
+ } else {
111
+ push();
112
+ if (c === '\\') {
113
+ escape = true;
114
+ } else {
115
+ scan += c;
116
+ }
117
+ }
118
+ }
119
+ if (scan) {
120
+ result.push({
121
+ type: 'text',
122
+ value: scan
123
+ });
124
+ }
125
+ return result;
126
+ };
127
+
128
+ /**
129
+ * 解析属于 Raw 节点的文本中的数学公式
130
+ */
131
+ var rehypeMath = function rehypeMath() {
132
+ return function (root) {
133
+ var rawPositions = [];
134
+ var i = 0;
135
+
136
+ // 获取 rehype-raw-positions 记录的 Raw 节点的位置信息
137
+ visit(root, {
138
+ tagName: 'raw-positions'
139
+ }, function (node, index, parent) {
140
+ var _node$properties3;
141
+ if (typeof ((_node$properties3 = node.properties) === null || _node$properties3 === void 0 ? void 0 : _node$properties3.value) === 'string') {
142
+ rawPositions = node.properties.value.split(' ').map(function (offset) {
143
+ return parseInt(offset);
144
+ });
145
+ }
146
+ if (index !== null && parent) {
147
+ parent.children.splice(index);
148
+ }
149
+ return EXIT;
150
+ }, true);
151
+ if (rawPositions.length < 2) {
152
+ return;
153
+ }
154
+ visit(root, function (node, index, parent) {
155
+ if (isCodeNode(node) || isMathNode(node)) {
156
+ return SKIP;
157
+ }
158
+ if (node.type === 'text' && index !== null && parent) {
159
+ var _node$position;
160
+ var startOffset = (_node$position = node.position) === null || _node$position === void 0 ? void 0 : _node$position.start.offset;
161
+ if (startOffset === undefined) {
162
+ return;
163
+ }
164
+ for (; i < rawPositions.length - 1 && rawPositions[i + 1] <= startOffset; i += 2);
165
+ if (i < rawPositions.length - 1 && rawPositions[i] <= startOffset) {
166
+ var _parent$children;
167
+ var result = parseMath(node.value);
168
+ (_parent$children = parent.children).splice.apply(_parent$children, [index, 1].concat(_toConsumableArray(result)));
169
+ return index + result.length;
170
+ }
171
+ }
172
+ });
173
+ };
174
+ };
175
+ export default rehypeMath;
@@ -0,0 +1,6 @@
1
+ import type { Root as HastRoot } from 'hast';
2
+ /**
3
+ * 保存 Raw 节点的位置信息
4
+ */
5
+ declare const rehypeRawPositions: () => (root: HastRoot) => void;
6
+ export default rehypeRawPositions;
@@ -0,0 +1,30 @@
1
+ import { visit } from 'unist-util-visit';
2
+
3
+ /**
4
+ * 保存 Raw 节点的位置信息
5
+ */
6
+ var rehypeRawPositions = function rehypeRawPositions() {
7
+ return function (root) {
8
+ var rawPositions = [];
9
+ visit(root, 'raw', function (node) {
10
+ var _node$position, _node$position2;
11
+ var startOffset = (_node$position = node.position) === null || _node$position === void 0 ? void 0 : _node$position.start.offset;
12
+ var endOffset = (_node$position2 = node.position) === null || _node$position2 === void 0 ? void 0 : _node$position2.end.offset;
13
+ if (startOffset === undefined || endOffset === undefined) {
14
+ return;
15
+ }
16
+ rawPositions.push(startOffset, endOffset);
17
+ });
18
+
19
+ // 记录在新的 Hast 节点上,防止被 rehype-raw 删除
20
+ root.children.push({
21
+ type: 'element',
22
+ tagName: 'raw-positions',
23
+ properties: {
24
+ value: rawPositions.join(' ')
25
+ },
26
+ children: []
27
+ });
28
+ };
29
+ };
30
+ export default rehypeRawPositions;
@@ -238,12 +238,9 @@ var XStarMdEditor = /*#__PURE__*/React.forwardRef(function (_ref, ref) {
238
238
  onInsertFileLatest.current = onInsertFile;
239
239
  var options = useMemo(function () {
240
240
  return composeHandlers(plugins)({
241
- toolbarItemMap: getDefaultToolbarItemMap(locale, function () {
241
+ toolbarItemMap: getDefaultToolbarItemMap(locale, function (file, options) {
242
242
  var _onInsertFileLatest$c;
243
- for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
244
- args[_key] = arguments[_key];
245
- }
246
- return (_onInsertFileLatest$c = onInsertFileLatest.current) === null || _onInsertFileLatest$c === void 0 ? void 0 : _onInsertFileLatest$c.call.apply(_onInsertFileLatest$c, [onInsertFileLatest].concat(args));
243
+ return (_onInsertFileLatest$c = onInsertFileLatest.current) === null || _onInsertFileLatest$c === void 0 ? void 0 : _onInsertFileLatest$c.call(onInsertFileLatest, file, options);
247
244
  }),
248
245
  toolbarItems: getDefaultToolbarItems(),
249
246
  keyboardEventHandlers: getDefaultKeyboardEventHandlers()