@blocklet/editor 2.1.40 → 2.1.42

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,23 +1,21 @@
1
1
  import { $getSelection, $isRangeSelection } from 'lexical';
2
- import { $replaceTranslations, $extractTranslationBlocksFromSelection, defaultTranslateAPI, $preview, $insertTranslations, processPlaceholders, restorePlaceholders, } from './utils';
2
+ import { $replaceTranslations, $extractTranslationBlocksFromSelection, defaultTranslateAPI, $preview, $insertTranslations, } from './utils';
3
3
  // const isTranslationBlockWithSelection = (o: any): o is TranslationBlockWithSelection => !!o.point;
4
4
  export const translateSelection = async (editor, options) => {
5
5
  const selection = editor.getEditorState().read($getSelection);
6
6
  if (!$isRangeSelection(selection) || selection.isCollapsed()) {
7
7
  return null;
8
8
  }
9
- const translationBlocks = editor.getEditorState().read(() => $extractTranslationBlocksFromSelection(selection));
9
+ const translationBlocks = editor
10
+ .getEditorState()
11
+ .read(() => $extractTranslationBlocksFromSelection(editor, selection));
10
12
  const { sourceLanguage, targetLanguage } = options;
11
13
  const translateAPI = options.translateAPI || defaultTranslateAPI;
12
14
  if (translationBlocks && translationBlocks.length > 0) {
13
15
  const sourceTexts = translationBlocks.reduce((acc, cur) => ({ ...acc, [cur.key]: cur.text }), {});
14
- const sourceTextsWithPlaceholder = { ...sourceTexts };
15
- Object.entries(sourceTextsWithPlaceholder).forEach(([key, text]) => {
16
- sourceTextsWithPlaceholder[key] = processPlaceholders(text);
17
- });
18
16
  const targetTexts = {};
19
17
  await translateAPI({ texts: sourceTexts, sourceLanguage, targetLanguage }, ({ data, progress }) => {
20
- targetTexts[data.key] = restorePlaceholders(sourceTexts[data.key], data.text);
18
+ targetTexts[data.key] = data.text;
21
19
  if (options.onProgress && progress) {
22
20
  options.onProgress(progress);
23
21
  }
@@ -33,7 +31,7 @@ export const translateSelection = async (editor, options) => {
33
31
  },
34
32
  replace: () => {
35
33
  editor.update(() => {
36
- $replaceTranslations(translations);
34
+ $replaceTranslations(editor, translations);
37
35
  });
38
36
  },
39
37
  insertBelow: () => {
@@ -1,26 +1,25 @@
1
- import { $extractTranslationBlocks, $preview, $replaceTranslations, defaultTranslateAPI, processPlaceholders, restorePlaceholders, } from './utils';
1
+ import { $extractTranslationBlocks, $preview, $replaceTranslations, defaultTranslateAPI } from './utils';
2
2
  export const translateFullContent = async (editor, options) => {
3
3
  const { sourceLanguage, targetLanguage, signal } = options;
4
4
  const translateAPI = options.translateAPI || defaultTranslateAPI;
5
- const translationBlocks = editor.getEditorState().read(() => $extractTranslationBlocks());
5
+ let translationBlocks = [];
6
+ editor.update(() => {
7
+ translationBlocks = $extractTranslationBlocks(editor);
8
+ }, { discrete: true });
6
9
  // eslint-disable-next-line no-console
7
10
  console.log('translationBlocks: ', translationBlocks);
8
11
  if (translationBlocks) {
9
12
  const sourceTexts = translationBlocks.reduce((acc, cur) => ({ ...acc, [cur.key]: cur.text }), {});
10
- const sourceTextsWithPlaceholder = { ...sourceTexts };
11
- Object.entries(sourceTextsWithPlaceholder).forEach(([key, text]) => {
12
- sourceTextsWithPlaceholder[key] = processPlaceholders(text);
13
- });
14
13
  const targetTexts = {};
15
14
  const errorsMap = {};
16
- await translateAPI({ texts: sourceTextsWithPlaceholder, sourceLanguage, targetLanguage }, ({ data, progress, error }) => {
15
+ await translateAPI({ texts: sourceTexts, sourceLanguage, targetLanguage }, ({ data, progress, error }) => {
17
16
  const sourceText = sourceTexts[data.key];
18
17
  if (error) {
19
18
  targetTexts[data.key] = sourceText;
20
19
  errorsMap[data.key] = error;
21
20
  }
22
21
  else {
23
- targetTexts[data.key] = restorePlaceholders(sourceText, data.text);
22
+ targetTexts[data.key] = data.text;
24
23
  }
25
24
  if (options.onProgress && progress) {
26
25
  options.onProgress(progress);
@@ -47,7 +46,7 @@ export const translateFullContent = async (editor, options) => {
47
46
  const autoReplace = options.autoReplace !== false;
48
47
  if (autoReplace && !signal?.aborted) {
49
48
  editor.update(() => {
50
- $replaceTranslations(translations);
49
+ $replaceTranslations(editor, translations);
51
50
  });
52
51
  }
53
52
  return {
@@ -58,7 +57,7 @@ export const translateFullContent = async (editor, options) => {
58
57
  },
59
58
  replace: (e) => {
60
59
  (e || editor).update(() => {
61
- $replaceTranslations(translations);
60
+ $replaceTranslations(editor, translations);
62
61
  });
63
62
  },
64
63
  insertBelow: () => { },
@@ -1,4 +1,4 @@
1
- import { ElementNode, LexicalNode, RangeSelection } from 'lexical';
1
+ import { ElementNode, LexicalNode, RangeSelection, LexicalEditor } from 'lexical';
2
2
  import type { PointType } from 'lexical/LexicalSelection';
3
3
  import type { TranslationBlock, NonTextNodeMatch, TranslateAPI, TranslationBlockWithSelection } from './types';
4
4
  export declare const defaultTranslateAPI: TranslateAPI;
@@ -9,7 +9,7 @@ export declare const insertNodesAfter: (node: LexicalNode, nodesToInsert: Lexica
9
9
  * - $isTextNode => true
10
10
  * - format 不是 code
11
11
  */
12
- export declare const $isValidTextNode: (node: LexicalNode) => boolean;
12
+ export declare const $isValidTextNode: (node: LexicalNode) => string | false;
13
13
  /**
14
14
  * 判断一个 top level node 是否含有效的 TextNode 的 child
15
15
  */
@@ -30,7 +30,7 @@ export declare const $extractTranslationNodes: (topLevelNodes: ElementNode[]) =>
30
30
  * - quote node
31
31
  */
32
32
  export declare const $extractTextContentFromTranslationNode: (translationNode: ElementNode, selectedNodes?: LexicalNode[]) => string;
33
- export declare const $extractTranslationBlocks: (translationNodes?: ElementNode[]) => TranslationBlock[];
33
+ export declare const $extractTranslationBlocks: (editor: LexicalEditor, translationNodes?: ElementNode[]) => TranslationBlock[];
34
34
  /**
35
35
  * 处理带有 `###key###` 的文本, 将普通文本转成 TextNode, 将 `###key###` 替换成 key 对应的 Node
36
36
  * @param {string} text - 如: "a ###1### b ###20### c"
@@ -44,8 +44,8 @@ export declare const rearrangeNodes: (text: string, matches: NonTextNodeMatch[])
44
44
  * 比如: "a ###1### b ###20### c" => [{key: 1, index: 2, length: 7}, {key: 20, index: 12, length: 8}]
45
45
  */
46
46
  export declare const matchNonTextNodes: (text: string) => NonTextNodeMatch[];
47
- export declare const $replaceTranslation: (translationBlock: TranslationBlock) => LexicalNode | ElementNode | import("@lexical/link").LinkNode | null;
48
- export declare const $replaceTranslations: (translationBlocks: TranslationBlock[]) => (LexicalNode | ElementNode | import("@lexical/link").LinkNode | null)[];
47
+ export declare const $replaceTranslation: (translationBlock: TranslationBlock, editor: LexicalEditor) => LexicalNode | ElementNode | null;
48
+ export declare const $replaceTranslations: (editor: LexicalEditor, translationBlocks: TranslationBlock[]) => (LexicalNode | ElementNode | null)[];
49
49
  export declare const $insertTranslations: (translationBlocks: TranslationBlock[], selection: RangeSelection) => void;
50
50
  export declare const $preview: (translationBlocks: TranslationBlock[]) => string;
51
51
  /**
@@ -53,6 +53,4 @@ export declare const $preview: (translationBlocks: TranslationBlock[]) => string
53
53
  */
54
54
  export declare const $extractTranslationBlockWithSelection: (translationNode: ElementNode, point: PointType, backward?: boolean) => TranslationBlockWithSelection | null;
55
55
  export declare const getTopLevelNodesFromSelection: (selection: RangeSelection) => ElementNode[];
56
- export declare const $extractTranslationBlocksFromSelection: (selection: RangeSelection) => TranslationBlockWithSelection[];
57
- export declare const processPlaceholders: (text: string, placeholder?: string) => string;
58
- export declare const restorePlaceholders: (original: string, replacedText: string, placeholder?: string) => string;
56
+ export declare const $extractTranslationBlocksFromSelection: (editor: LexicalEditor, selection: RangeSelection) => TranslationBlockWithSelection[];
@@ -1,15 +1,14 @@
1
- import { $isParagraphNode, $isTextNode, $createTextNode, $getNodeByKey, $isElementNode, $copyNode, } from 'lexical';
1
+ import { $isParagraphNode, $isTextNode, $createTextNode, $getNodeByKey, $isElementNode, $copyNode, $createNodeSelection, } from 'lexical';
2
2
  import { $isHeadingNode, $isQuoteNode } from '@lexical/rich-text';
3
3
  import { $isListNode, $isListItemNode } from '@lexical/list';
4
4
  import { $dfs, $findMatchingParent } from '@lexical/utils';
5
5
  import { fetchEventSource } from '@microsoft/fetch-event-source';
6
- import { $createLinkNode, $isLinkNode } from '@lexical/link';
7
6
  import { joinURL } from 'ufo';
8
7
  import { getCSRFToken } from '@blocklet/js-sdk';
8
+ import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html';
9
9
  import { blockletExists, getBlockletMountPointInfo, isValidUrl } from '../utils';
10
10
  import { $getDepth } from '../../lexical-utils';
11
11
  const REGEX_INVALID_SEQUENCE = /^[#$%^&*()_+\-=\\[\]{};':"\\|,.<>/?]+$/;
12
- const PLACEHOLDER = '[[PLACEHOLDER]]';
13
12
  export const defaultTranslateAPI = async ({ texts, sourceLanguage, targetLanguage }, onData) => {
14
13
  if (!blockletExists('did-comments')) {
15
14
  throw new Error('Discuss Kit component must be installed.');
@@ -85,7 +84,7 @@ export const insertNodesAfter = (node, nodesToInsert) => {
85
84
  * - format 不是 code
86
85
  */
87
86
  export const $isValidTextNode = (node) => {
88
- return $isTextNode(node) && !node.hasFormat('code');
87
+ return $isTextNode(node) && node.getTextContent().trim();
89
88
  };
90
89
  /**
91
90
  * 判断一个 top level node 是否含有效的 TextNode 的 child
@@ -143,24 +142,25 @@ export const $extractTextContentFromTranslationNode = (translationNode, selected
143
142
  });
144
143
  return text;
145
144
  };
145
+ const $nodeToHtml = (editor, node) => {
146
+ const selection = $createNodeSelection();
147
+ const children = node.getChildren();
148
+ // 剔除嵌套 list 节点
149
+ children
150
+ .filter((x) => !$isListNode(x))
151
+ .forEach((child) => $dfs(child).forEach((x) => selection.add(x.node.getKey())));
152
+ const html = $generateHtmlFromNodes(editor, selection);
153
+ return html;
154
+ };
146
155
  // translation nodes => translation blocks (若未传入 nodes, 默认为 editor 内所有的顶层结点)
147
- export const $extractTranslationBlocks = (translationNodes) => {
156
+ export const $extractTranslationBlocks = (editor, translationNodes) => {
148
157
  const _translationNodes = translationNodes ||
149
158
  $extractTranslationNodes($dfs()
150
159
  .map((x) => x.node)
151
160
  .filter((x) => $isElementNode(x)));
152
- const keys = new Set(_translationNodes.map((x) => x.getKey()));
153
- // 某些类型的特殊结点 (如 link node, nested list item), 也需要翻译
154
- $dfs().forEach(({ node }) => {
155
- if ($isElementNode(node) && !keys.has(node.getKey())) {
156
- if ($isLinkNode(node) || $isListItemNode(node)) {
157
- _translationNodes.push(node);
158
- }
159
- }
160
- });
161
161
  return _translationNodes
162
162
  .map((node) => {
163
- const text = $extractTextContentFromTranslationNode(node);
163
+ const text = $nodeToHtml(editor, node);
164
164
  return { key: node.getKey(), text };
165
165
  })
166
166
  .filter((item) => {
@@ -214,29 +214,36 @@ export const matchNonTextNodes = (text) => {
214
214
  }
215
215
  return matches;
216
216
  };
217
- export const $replaceTranslation = (translationBlock) => {
217
+ function $htmlToNodes(editor, text) {
218
+ try {
219
+ const dom = new DOMParser().parseFromString(text, 'text/html');
220
+ return $generateNodesFromDOM(editor, dom);
221
+ }
222
+ catch (e) {
223
+ console.error(e, text);
224
+ return [$createTextNode(text)];
225
+ }
226
+ }
227
+ export const $replaceTranslation = (translationBlock, editor) => {
218
228
  try {
219
229
  const { text, key } = translationBlock;
220
230
  const blockNode = $getNodeByKey(key);
221
- /**
222
- * ===== 特殊结点处理 =====
223
- */
224
- if ($isLinkNode(blockNode)) {
225
- const linkNode = $createLinkNode(blockNode.getURL(), { title: blockNode.getTitle() });
226
- linkNode.append($createTextNode(text));
227
- blockNode.replace(linkNode);
228
- return linkNode;
229
- }
230
- /**
231
- * ===== 非特殊结点 =====
232
- */
233
- const matches = matchNonTextNodes(text);
234
- const newNodes = rearrangeNodes(text, matches);
235
231
  if ($isElementNode(blockNode)) {
236
232
  // 清除所有 children
237
233
  blockNode.getChildren().forEach((child) => child.remove());
238
- // 将新生成的 nodes 插入
239
- blockNode.append(...newNodes);
234
+ const nodes = $htmlToNodes(editor, text);
235
+ if (nodes.length === 1) {
236
+ const node = nodes[0];
237
+ if ($isElementNode(node)) {
238
+ blockNode.append(...node.getChildren());
239
+ }
240
+ else {
241
+ blockNode.append(node);
242
+ }
243
+ }
244
+ else {
245
+ blockNode.append(...nodes);
246
+ }
240
247
  }
241
248
  return blockNode;
242
249
  }
@@ -245,8 +252,8 @@ export const $replaceTranslation = (translationBlock) => {
245
252
  return null;
246
253
  }
247
254
  };
248
- export const $replaceTranslations = (translationBlocks) => {
249
- return translationBlocks.map($replaceTranslation);
255
+ export const $replaceTranslations = (editor, translationBlocks) => {
256
+ return translationBlocks.map((item) => $replaceTranslation(item, editor));
250
257
  };
251
258
  export const $insertTranslations = (translationBlocks, selection) => {
252
259
  const nodes = translationBlocks
@@ -270,15 +277,6 @@ export const $insertTranslations = (translationBlocks, selection) => {
270
277
  insertNodesAfter(topLevelNodeOfInsertPoint, nodes);
271
278
  }
272
279
  };
273
- // const $getTextContentFromNodes = (nodes: LexicalNode[]) => {
274
- // let textContent = '';
275
- // nodes.forEach((node) => {
276
- // if ($isElementNode(node)) {
277
- // textContent += node.getTextContent();
278
- // }
279
- // });
280
- // return textContent;
281
- // };
282
280
  export const $preview = (translationBlocks) => {
283
281
  const result = translationBlocks.map((item) => {
284
282
  const { text = '', key } = item;
@@ -303,7 +301,6 @@ export const $preview = (translationBlocks) => {
303
301
  });
304
302
  return result.join('\n');
305
303
  };
306
- // export const stripInvalidNodes = (nodes: LexicalNode[]) => {};
307
304
  /**
308
305
  * TODO: 剥除未选中的部分
309
306
  */
@@ -351,7 +348,7 @@ export const getTopLevelNodesFromSelection = (selection) => {
351
348
  };
352
349
  // TODO: 选中非首个 list item 进行翻译时, firstTranslationNode 为首个 list item
353
350
  // TODO: topLevelNode 内的简单选择, 比如只选择了一个段落或 list item 中的一个词语
354
- export const $extractTranslationBlocksFromSelection = (selection) => {
351
+ export const $extractTranslationBlocksFromSelection = (editor, selection) => {
355
352
  const topLevelNodes = getTopLevelNodesFromSelection(selection);
356
353
  const translationNodes = $extractTranslationNodes(topLevelNodes);
357
354
  const startPoint = selection.isBackward() ? selection.focus : selection.anchor;
@@ -374,23 +371,7 @@ export const $extractTranslationBlocksFromSelection = (selection) => {
374
371
  const lastNode = translationNodes[translationNodes.length - 1];
375
372
  const lastTranslationBlock = $extractTranslationBlockWithSelection(lastNode, endPoint, true);
376
373
  // 去掉首尾 node
377
- const blocks = $extractTranslationBlocks(translationNodes.slice(1, -1));
374
+ const blocks = $extractTranslationBlocks(editor, translationNodes.slice(1, -1));
378
375
  // console.log({ firstTranslationBlock, blocks, lastTranslationBlock });
379
376
  return [firstTranslationBlock, ...blocks, lastTranslationBlock].filter((item) => !!item?.text);
380
377
  };
381
- export const processPlaceholders = (text, placeholder = PLACEHOLDER) => {
382
- const matches = text.match(/###\d+###/g);
383
- if (matches) {
384
- return text.replaceAll(/###\d+###/g, placeholder);
385
- }
386
- return text;
387
- };
388
- export const restorePlaceholders = (original, replacedText, placeholder = PLACEHOLDER) => {
389
- const matches = original.match(/###\d+###/g);
390
- if (matches) {
391
- return matches.reduce((acc, cur) => {
392
- return acc.replace(placeholder, cur);
393
- }, replacedText);
394
- }
395
- return replacedText;
396
- };
@@ -19,7 +19,7 @@ export declare const restoreTranslation: (editor: LexicalEditor) => void;
19
19
  * - $isTextNode => true
20
20
  * - format 不是 code
21
21
  */
22
- export declare const $isValidTextNode: (node: LexicalNode) => boolean;
22
+ export declare const $isValidTextNode: (node: LexicalNode) => string | false;
23
23
  /**
24
24
  * 判断一个 top level node 是否含有效的 TextNode 的 child
25
25
  */
@@ -103,7 +103,7 @@ export const restoreTranslation = (editor) => {
103
103
  * - format 不是 code
104
104
  */
105
105
  export const $isValidTextNode = (node) => {
106
- return $isTextNode(node) && !node.hasFormat('code');
106
+ return $isTextNode(node) && node.getTextContent().trim();
107
107
  };
108
108
  /**
109
109
  * 判断一个 top level node 是否含有效的 TextNode 的 child
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/editor",
3
- "version": "2.1.40",
3
+ "version": "2.1.42",
4
4
  "main": "lib/index.js",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -65,7 +65,7 @@
65
65
  "ufo": "^1.5.4",
66
66
  "url-join": "^4.0.1",
67
67
  "zustand": "^4.5.5",
68
- "@blocklet/pdf": "^2.1.40"
68
+ "@blocklet/pdf": "^2.1.42"
69
69
  },
70
70
  "devDependencies": {
71
71
  "@babel/core": "^7.25.2",