@blocklet/editor 2.2.37 → 2.2.39

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.
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import { ElementTransformer, Transformer } from '@lexical/markdown';
9
+ export declare const HR: ElementTransformer;
10
+ export declare const TABLE: ElementTransformer;
11
+ export declare const CUSTOM_TRANSFORMERS: Array<Transformer>;
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ *
7
+ */
8
+ import { $convertFromMarkdownString, $convertToMarkdownString, CHECK_LIST, ELEMENT_TRANSFORMERS, TEXT_FORMAT_TRANSFORMERS, TEXT_MATCH_TRANSFORMERS, TRANSFORMERS, } from '@lexical/markdown';
9
+ import { $createHorizontalRuleNode, $isHorizontalRuleNode, HorizontalRuleNode, } from '@lexical/react/LexicalHorizontalRuleNode';
10
+ import { $createTableCellNode, $createTableNode, $createTableRowNode, $isTableCellNode, $isTableNode, $isTableRowNode, TableCellHeaderStates, TableCellNode, TableNode, TableRowNode, } from '@lexical/table';
11
+ import { $isParagraphNode, $isTextNode } from 'lexical';
12
+ export const HR = {
13
+ dependencies: [HorizontalRuleNode],
14
+ export: (node) => {
15
+ return $isHorizontalRuleNode(node) ? '***' : null;
16
+ },
17
+ regExp: /^(---|\*\*\*|___)\s?$/,
18
+ replace: (parentNode, _1, _2, isImport) => {
19
+ const line = $createHorizontalRuleNode();
20
+ // TODO: Get rid of isImport flag
21
+ if (isImport || parentNode.getNextSibling() != null) {
22
+ parentNode.replace(line);
23
+ }
24
+ else {
25
+ parentNode.insertBefore(line);
26
+ }
27
+ line.selectNext();
28
+ },
29
+ type: 'element',
30
+ };
31
+ // Very primitive table setup
32
+ const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/;
33
+ const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-*:? ?)+\|\s?$/;
34
+ export const TABLE = {
35
+ dependencies: [TableNode, TableRowNode, TableCellNode],
36
+ export: (node) => {
37
+ if (!$isTableNode(node)) {
38
+ return null;
39
+ }
40
+ const output = [];
41
+ for (const row of node.getChildren()) {
42
+ const rowOutput = [];
43
+ if (!$isTableRowNode(row)) {
44
+ // eslint-disable-next-line no-continue
45
+ continue;
46
+ }
47
+ let isHeaderRow = false;
48
+ for (const cell of row.getChildren()) {
49
+ // It's TableCellNode so it's just to make flow happy
50
+ if ($isTableCellNode(cell)) {
51
+ rowOutput.push($convertToMarkdownString(CUSTOM_TRANSFORMERS, cell).replace(/\n/g, '\\n').trim());
52
+ if (cell.__headerState === TableCellHeaderStates.ROW) {
53
+ isHeaderRow = true;
54
+ }
55
+ }
56
+ }
57
+ output.push(`| ${rowOutput.join(' | ')} |`);
58
+ if (isHeaderRow) {
59
+ output.push(`| ${rowOutput.map((_) => '---').join(' | ')} |`);
60
+ }
61
+ }
62
+ return output.join('\n');
63
+ },
64
+ regExp: TABLE_ROW_REG_EXP,
65
+ replace: (parentNode, _1, match) => {
66
+ // Header row
67
+ if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {
68
+ const table = parentNode.getPreviousSibling();
69
+ if (!table || !$isTableNode(table)) {
70
+ return;
71
+ }
72
+ const rows = table.getChildren();
73
+ const lastRow = rows[rows.length - 1];
74
+ if (!lastRow || !$isTableRowNode(lastRow)) {
75
+ return;
76
+ }
77
+ // Add header state to row cells
78
+ lastRow.getChildren().forEach((cell) => {
79
+ if (!$isTableCellNode(cell)) {
80
+ return;
81
+ }
82
+ cell.setHeaderStyles(TableCellHeaderStates.ROW);
83
+ });
84
+ // Remove line
85
+ parentNode.remove();
86
+ return;
87
+ }
88
+ const matchCells = mapToTableCells(match[0]);
89
+ if (matchCells == null) {
90
+ return;
91
+ }
92
+ const rows = [matchCells];
93
+ let sibling = parentNode.getPreviousSibling();
94
+ let maxCells = matchCells.length;
95
+ while (sibling) {
96
+ if (!$isParagraphNode(sibling)) {
97
+ break;
98
+ }
99
+ if (sibling.getChildrenSize() !== 1) {
100
+ break;
101
+ }
102
+ const firstChild = sibling.getFirstChild();
103
+ if (!$isTextNode(firstChild)) {
104
+ break;
105
+ }
106
+ const cells = mapToTableCells(firstChild.getTextContent());
107
+ if (cells == null) {
108
+ break;
109
+ }
110
+ maxCells = Math.max(maxCells, cells.length);
111
+ rows.unshift(cells);
112
+ const previousSibling = sibling.getPreviousSibling();
113
+ sibling.remove();
114
+ sibling = previousSibling;
115
+ }
116
+ const table = $createTableNode();
117
+ for (const cells of rows) {
118
+ const tableRow = $createTableRowNode();
119
+ table.append(tableRow);
120
+ for (let i = 0; i < maxCells; i++) {
121
+ tableRow.append(i < cells.length ? cells[i] : $createTableCell(''));
122
+ }
123
+ }
124
+ const previousSibling = parentNode.getPreviousSibling();
125
+ if ($isTableNode(previousSibling) && getTableColumnsSize(previousSibling) === maxCells) {
126
+ previousSibling.append(...table.getChildren());
127
+ parentNode.remove();
128
+ }
129
+ else {
130
+ parentNode.replace(table);
131
+ }
132
+ table.selectEnd();
133
+ },
134
+ type: 'element',
135
+ };
136
+ function getTableColumnsSize(table) {
137
+ const row = table.getFirstChild();
138
+ return $isTableRowNode(row) ? row.getChildrenSize() : 0;
139
+ }
140
+ const $createTableCell = (textContent) => {
141
+ textContent = textContent.replace(/\\n/g, '\n');
142
+ const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
143
+ $convertFromMarkdownString(textContent, CUSTOM_TRANSFORMERS, cell);
144
+ return cell;
145
+ };
146
+ const mapToTableCells = (textContent) => {
147
+ const match = textContent.match(TABLE_ROW_REG_EXP);
148
+ if (!match || !match[1]) {
149
+ return null;
150
+ }
151
+ return match[1].split('|').map((text) => $createTableCell(text));
152
+ };
153
+ export const CUSTOM_TRANSFORMERS = [
154
+ ...TRANSFORMERS,
155
+ TABLE,
156
+ HR,
157
+ CHECK_LIST,
158
+ ...ELEMENT_TRANSFORMERS,
159
+ ...TEXT_FORMAT_TRANSFORMERS,
160
+ ...TEXT_MATCH_TRANSFORMERS,
161
+ ];
@@ -1,9 +1,11 @@
1
1
  import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
2
2
  import { COMMAND_PRIORITY_CRITICAL, PASTE_COMMAND, $getSelection, $isRangeSelection, $createParagraphNode, } from 'lexical';
3
3
  import { useEffect } from 'react';
4
- import { $convertFromMarkdownString, TRANSFORMERS } from '@lexical/markdown';
4
+ import { $convertFromMarkdownString } from '@lexical/markdown';
5
5
  import isEmpty from 'lodash/isEmpty';
6
6
  import { lexer } from 'marked';
7
+ import { isValidUrl } from '../utils';
8
+ import { CUSTOM_TRANSFORMERS } from './CustomTransformers';
7
9
  function isHtml(str) {
8
10
  const doc = new DOMParser().parseFromString(str, 'text/html');
9
11
  return Array.from(doc.body.childNodes).some((node) => node.nodeType === 1);
@@ -18,6 +20,9 @@ export function isMarkdownText(text) {
18
20
  if (isHtml(text)) {
19
21
  return false;
20
22
  }
23
+ if (isValidUrl(text)) {
24
+ return false;
25
+ }
21
26
  function containsNonTextTokens(tokens) {
22
27
  return tokens.some((token) => {
23
28
  if (token.type !== 'text' && token.type !== 'paragraph') {
@@ -32,13 +37,22 @@ export function isMarkdownText(text) {
32
37
  const tokens = lexer(text);
33
38
  return containsNonTextTokens(tokens);
34
39
  }
40
+ function getMarkdownText(e) {
41
+ const markdownText = e.clipboardData?.types
42
+ .map((type) => {
43
+ const data = e.clipboardData?.getData(type);
44
+ return data;
45
+ })
46
+ .find((data) => data && isMarkdownText(data));
47
+ return markdownText;
48
+ }
35
49
  export default function PasteMarkdownPlugin() {
36
50
  const [editor] = useLexicalComposerContext();
37
51
  useEffect(() => {
38
52
  const unregister = editor.registerCommand(PASTE_COMMAND, (e) => {
39
53
  if (e instanceof ClipboardEvent) {
40
- const markdownText = e.clipboardData?.getData('text/html') || e.clipboardData?.getData('text/plain');
41
- if (markdownText && isMarkdownText(markdownText)) {
54
+ const markdownText = getMarkdownText(e);
55
+ if (markdownText) {
42
56
  editor.update(() => {
43
57
  // 获取当前选区
44
58
  const selection = $getSelection();
@@ -74,7 +88,7 @@ export default function PasteMarkdownPlugin() {
74
88
  selection.insertNodes([containerNode]);
75
89
  }
76
90
  // 将Markdown内容转换并插入到D区域中
77
- $convertFromMarkdownString(markdownText, TRANSFORMERS, containerNode);
91
+ $convertFromMarkdownString(markdownText, CUSTOM_TRANSFORMERS, containerNode);
78
92
  });
79
93
  return true;
80
94
  }
@@ -23,4 +23,7 @@ describe('isMarkdownText', () => {
23
23
  it('should return false if text is not valid Markdown', () => {
24
24
  expect(isMarkdownText('{ not: markdown }')).toBe(false);
25
25
  });
26
+ it('should return false if text is valid url', () => {
27
+ expect(isMarkdownText('https://team.arcblock.io/comment/discussions/a51d0ae6-22d9-48b6-93e8-72628e08ff24/#f8b75cae-cdcc-4bcb-b9c0-fde32218b55a')).toBe(false);
28
+ });
26
29
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/editor",
3
- "version": "2.2.37",
3
+ "version": "2.2.39",
4
4
  "main": "lib/index.js",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -62,13 +62,14 @@
62
62
  "markerjs2": "^2.32.2",
63
63
  "medium-zoom": "^1.1.0",
64
64
  "path-parser": "^6.1.0",
65
+ "split-pane-react": "^0.1.3",
65
66
  "react-player": "^2.16.0",
66
67
  "react-popper": "^2.3.0",
67
68
  "ufo": "^1.5.4",
68
69
  "url-join": "^4.0.1",
69
70
  "zustand": "^4.5.5",
70
71
  "marked": "^15.0.8",
71
- "@blocklet/pdf": "^2.2.37"
72
+ "@blocklet/pdf": "^2.2.39"
72
73
  },
73
74
  "devDependencies": {
74
75
  "@babel/core": "^7.25.2",