@blocklet/editor 2.1.41 → 2.1.43
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/lib/ext/AITranslationPlugin/selection.js +6 -8
- package/lib/ext/AITranslationPlugin/translation.js +9 -10
- package/lib/ext/AITranslationPlugin/utils.d.ts +6 -8
- package/lib/ext/AITranslationPlugin/utils.js +43 -62
- package/lib/ext/InlineTranslationPlugin/utils.d.ts +1 -1
- package/lib/ext/InlineTranslationPlugin/utils.js +1 -1
- package/package.json +2 -2
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { $getSelection, $isRangeSelection } from 'lexical';
|
|
2
|
-
import { $replaceTranslations, $extractTranslationBlocksFromSelection, defaultTranslateAPI, $preview, $insertTranslations,
|
|
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
|
|
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] =
|
|
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
|
|
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
|
-
|
|
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:
|
|
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] =
|
|
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) =>
|
|
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 |
|
|
48
|
-
export declare const $replaceTranslations: (translationBlocks: TranslationBlock[]) => (LexicalNode | ElementNode |
|
|
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) &&
|
|
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 = $
|
|
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
|
-
|
|
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
|
-
|
|
239
|
-
|
|
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) =>
|
|
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) &&
|
|
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.
|
|
3
|
+
"version": "2.1.43",
|
|
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.
|
|
68
|
+
"@blocklet/pdf": "^2.1.43"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@babel/core": "^7.25.2",
|