diff --git a/src/components/lake-editor/editor-plugin.ts b/src/components/lake-editor/editor-plugin.ts
index 8500de18..a176fa3f 100644
--- a/src/components/lake-editor/editor-plugin.ts
+++ b/src/components/lake-editor/editor-plugin.ts
@@ -175,6 +175,22 @@ export function InjectEditorPlugin({ EditorPlugin, KernelPlugin, PositionUtil, O
},
},
);
+ htmlService.registerHTMLNodeReader(
+ ['code'],
+ {
+ readNode(context: any, node: any) {
+ context.setNode({
+ id: node.attrs.id || '',
+ type: 'element',
+ name: 'code',
+ attrs: {},
+ });
+ },
+ leaveNode() {
+ // ignore empty
+ },
+ },
+ );
}
}
}
diff --git a/src/components/lake-editor/editor.tsx b/src/components/lake-editor/editor.tsx
index 97b4db32..686bf138 100644
--- a/src/components/lake-editor/editor.tsx
+++ b/src/components/lake-editor/editor.tsx
@@ -136,38 +136,22 @@ export default forwardRef
((props, ref) => {
return !url?.startsWith('https://cdn.nlark.com/yuque');
},
createUploadPromise: props.uploadImage,
+ editUI: class extends win.Doc.EditCardUI.extend(win.Doc.Plugins.Image.editImageUIAddon) {
+ init(...args: any[]) {
+ super.init(...args);
+ this.on('uploadSuccess', (data: { ocrTask: Promise}) => {
+ data.ocrTask.then(res => {
+ this.cardData.setImageInfo({ ...data, ocrLocations: res });
+ });
+ });
+ }
+ },
innerButtonWidgets: [
{
name: 'ocr',
title: 'OCR',
icon: ,
enable: (cardUI: any) => {
- cardUI.on('uploadSuccess', () => {
- setTimeout(() => {
- if (!cardUI.cardData._cardValue?.ocr?.length) {
- return;
- }
- cardUI.uiViewProxy.rerender({
- innerButtonWidgets:
- cardUI.pluginOption.innerButtonWidgets.map(
- (widget: any) => ({
- ...widget,
- execute: () => {
- widget.execute(cardUI);
- },
- enable:
- typeof widget.enable === 'function'
- ? () => {
- return widget.enable(cardUI);
- }
- : () => {
- return !!widget.enable;
- },
- }),
- ),
- });
- }, 500);
- });
return cardUI.cardData.getOcrLocations()?.length > 0;
},
execute: (cardUI: any) => {
diff --git a/src/config.ts b/src/config.ts
index fc8f7883..2a226a24 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -39,3 +39,6 @@ export const TRACERT_CONFIG = {
spmAPos: 'a385',
spmBPos: 'b65721',
};
+
+// sidePanel 的 zIndex 层级
+export const SidePanelZIndex = 2147483646;
diff --git a/src/core/ocr-manager.ts b/src/core/ocr-manager.ts
index c9499d76..cc705b9f 100644
--- a/src/core/ocr-manager.ts
+++ b/src/core/ocr-manager.ts
@@ -31,6 +31,14 @@ class OCRManager {
private ocrIframeId = 'yq-ocr-iframe-id';
private sendMessageRef: ((requestData: { action: string; data?: any }) => Promise) | undefined;
private initSidePanelPromise: Promise | undefined;
+ private ocrQueue: Array<{
+ task: () => Promise>;
+ resolve: (res: Array) => void;
+ }> = [];
+ // 当前运行数
+ private runningTasks = 0;
+ // 最大的任务运行数
+ private concurrentTasks = 5;
async init() {
if (this.initSidePanelPromise) {
@@ -78,7 +86,7 @@ class OCRManager {
return this.initSidePanelPromise;
}
- async startOCR(type: 'file' | 'blob' | 'url', content: File | Blob | string) {
+ private async executeOcr(type: 'file' | 'blob' | 'url', content: File | Blob | string) {
// 调用 ocr 时,开始 ocr 等预热
await this.init();
const enableOcrStatus = await ocrManager.isWebOcrReady();
@@ -109,6 +117,26 @@ class OCRManager {
return [];
}
+ async startOCR(type: 'file' | 'blob' | 'url', content: File | Blob | string) {
+ return await new Promise(resolve => {
+ this.ocrQueue.push({ resolve, task: () => this.executeOcr(type, content) });
+ this.dequeue();
+ });
+ }
+
+ private dequeue() {
+ if (this.runningTasks >= this.concurrentTasks || !this.ocrQueue.length) {
+ return;
+ }
+ const { task, resolve } = this.ocrQueue.shift()!;
+ this.runningTasks++;
+ task().then((res: any) => {
+ this.runningTasks--;
+ resolve(res);
+ this.dequeue();
+ });
+ }
+
async isWebOcrReady() {
const result = await this.sendMessage('isWebOcrReady');
if (!result) {
diff --git a/src/core/parseDom/plugin/code.ts b/src/core/parseDom/plugin/code.ts
index 74c5b88f..83f2ddd2 100644
--- a/src/core/parseDom/plugin/code.ts
+++ b/src/core/parseDom/plugin/code.ts
@@ -23,6 +23,14 @@ export class CodeParsePlugin extends BasePlugin {
preElements.forEach(pre => {
// 查询所有的代码块
const codeElementArray = pre.querySelectorAll('code');
+ /**
+ * 有些页面的代码块不是
+ * xxxx
+ * 对于这类不处理
+ */
+ if (!codeElementArray.length) {
+ return;
+ }
Array.from(pre.childNodes).forEach(item => {
pre.removeChild(item);
});
diff --git a/src/core/parseDom/plugin/hexoCode.ts b/src/core/parseDom/plugin/hexoCode.ts
index 8dbf07eb..c4eb79f2 100644
--- a/src/core/parseDom/plugin/hexoCode.ts
+++ b/src/core/parseDom/plugin/hexoCode.ts
@@ -11,16 +11,15 @@ export class HexoCodeParsePlugin extends BasePlugin {
}
const codeElement = code.querySelector('pre');
if (codeElement) {
- node.parentNode?.appendChild(codeElement);
+ node.parentNode?.replaceChild(codeElement, node);
}
- node.parentNode?.removeChild(node);
};
- figures.forEach(figure => {
+ Array.from(figures).forEach(figure => {
processingCodeBlock(figure);
});
if (figures.length === 0) {
const tables = cloneDom.querySelectorAll('table');
- tables.forEach(table => {
+ Array.from(tables).forEach(table => {
processingCodeBlock(table);
});
}
diff --git a/src/core/parseDom/plugin/image.ts b/src/core/parseDom/plugin/image.ts
index 44ef5213..299ac85f 100644
--- a/src/core/parseDom/plugin/image.ts
+++ b/src/core/parseDom/plugin/image.ts
@@ -1,15 +1,35 @@
import { BasePlugin } from './base';
export class ImageParsePlugin extends BasePlugin {
- public parse(cloneDom: HTMLElement): Promise | void {
+ public async parse(cloneDom: HTMLElement): Promise {
const images = cloneDom.querySelectorAll('img');
- images.forEach(image => {
- /**
- * data-src 占位图
- * currentSrc 真实渲染的图片
- * src
- */
- image.setAttribute('src', image.getAttribute('data-src') || image.currentSrc || image.src);
+ const requestArray = Array.from(images).map(image => {
+ return new Promise(async resolve => {
+ image.setAttribute('src', image.getAttribute('data-src') || image.currentSrc || image.src);
+ const isOriginImage = /^(http|https):\/\//.test(image.src);
+ if (!isOriginImage) {
+ resolve(true);
+ return;
+ }
+ try {
+ const response = await fetch(image.src);
+ if (response.status !== 200) {
+ throw new Error('Error fetching image');
+ }
+ const blob = await response.blob(); // 将响应体转换为 Blob
+ const reader = new FileReader();
+ reader.readAsDataURL(blob); // 读取 Blob 数据并编码为 Base64
+ reader.onloadend = () => {
+ // 获取 Base64 编码的数据
+ const base64data = reader.result;
+ image.src = base64data as string;
+ resolve(true);
+ };
+ } catch (e: any) {
+ resolve(true);
+ }
+ });
});
+ await Promise.all(requestArray);
}
}
diff --git a/src/core/webProxy/base.ts b/src/core/webProxy/base.ts
index a222948c..8682197e 100644
--- a/src/core/webProxy/base.ts
+++ b/src/core/webProxy/base.ts
@@ -1,11 +1,14 @@
import { getMsgId } from '@/isomorphic/util';
import type { IRequestOptions, IRequestConfig } from '@/background/core/httpClient';
import ExtensionMessage from '@/isomorphic/extensionMessage/extensionMessage';
+import Env from '@/isomorphic/env';
+import HttpClient from '@/background/core/httpClient';
import { ExtensionMessageListener } from '@/isomorphic/extensionMessage/interface';
// http 请求单独走一个通道
class HttpProxy {
private callServiceMethodCallbackFn: { [id: string]: (...rest: any[]) => void } = {};
+ private httpClient = Env.isExtensionPage ? new HttpClient() : null;
constructor() {
this.init();
}
@@ -22,6 +25,19 @@ class HttpProxy {
type?: 'abort' | 'request',
id?: string,
) {
+ if (Env.isExtensionPage) {
+ if (methodParams?.options?.isFileUpload) {
+ this.httpClient?.uploadFile(methodParams.url, methodParams.config).then(res => callback?.(res));
+ return;
+ }
+ this.httpClient
+ ?.handleRequest(methodParams?.url, methodParams?.config, {
+ ...methodParams?.options,
+ streamCallback: callback,
+ })
+ .then(res => callback?.(res));
+ return;
+ }
const callbackFnId = id ? id : this.generateCallbackFnId('', '');
if (type !== 'abort') {
this.callServiceMethodCallbackFn[callbackFnId] = (response: any) => {
diff --git a/src/injectscript/index.ts b/src/injectscript/index.ts
index 472f6a69..3a182c83 100644
--- a/src/injectscript/index.ts
+++ b/src/injectscript/index.ts
@@ -1,5 +1,5 @@
import { InjectScriptRequestKey, MessageEventRequestData } from '../isomorphic/injectScript';
-import { YuqueService } from './service';
+import { YuqueService, CommonService } from './service';
import { BaseService } from './service/base';
class InjectScriptApp {
@@ -11,6 +11,7 @@ class InjectScriptApp {
init() {
this.registerService(new YuqueService());
+ this.registerService(new CommonService());
window.addEventListener('message', async (e: MessageEvent) => {
if (e.data.key !== InjectScriptRequestKey) {
return;
diff --git a/src/injectscript/service/common.ts b/src/injectscript/service/common.ts
new file mode 100644
index 00000000..e4c7b9df
--- /dev/null
+++ b/src/injectscript/service/common.ts
@@ -0,0 +1,13 @@
+import { BaseService } from './base';
+
+export class CommonService extends BaseService {
+ public name = 'CommonService';
+ public urlRegExp = '';
+
+ enableDocumentCopy() {
+ if (typeof (window as any)?.appData?.book?.enable_document_copy === 'boolean') {
+ return (window as any).appData.book.enable_document_copy;
+ }
+ return true;
+ }
+}
diff --git a/src/injectscript/service/index.ts b/src/injectscript/service/index.ts
index 3c4d65ef..77397a32 100644
--- a/src/injectscript/service/index.ts
+++ b/src/injectscript/service/index.ts
@@ -1 +1,2 @@
export * from './yuque';
+export * from './common';
diff --git a/src/pages/inject/AreaSelector/app.tsx b/src/pages/inject/AreaSelector/app.tsx
index 23bc62c3..4c102222 100644
--- a/src/pages/inject/AreaSelector/app.tsx
+++ b/src/pages/inject/AreaSelector/app.tsx
@@ -5,6 +5,7 @@ import { useForceUpdate } from '@/hooks/useForceUpdate';
import { useEnterShortcut } from '@/hooks/useEnterShortCut';
import { parseDom } from '@/core/parseDom';
import { __i18n } from '@/isomorphic/i18n';
+import Env from '@/isomorphic/env';
import styles from './app.module.less';
type Rect = Pick;
@@ -26,6 +27,7 @@ function App(props: IAppProps) {
const onSave = useCallback(async () => {
setSaving(true);
const selections = targetListRef.current.filter(item => item) || [];
+ Env.isRunningHostPage && window._yuque_ext_app.toggleSidePanel(true);
const selectAreaElements = await parseDom.parseDom(selections);
props.onSelectAreaSuccess(selectAreaElements.join(''));
}, []);
diff --git a/src/pages/inject/content-scripts.ts b/src/pages/inject/content-scripts.ts
index 02c509c6..574defc4 100644
--- a/src/pages/inject/content-scripts.ts
+++ b/src/pages/inject/content-scripts.ts
@@ -133,6 +133,14 @@ export class App {
}
async parsePage() {
+ const enableDocumentCopy = await this.senMessageToPage({
+ serviceName: 'CommonService',
+ serviceMethod: 'enableDocumentCopy',
+ });
+ if (!enableDocumentCopy) {
+ this.showMessage({ type: 'error', text: __i18n('当前页面已开启防复制,不支持剪藏') });
+ return;
+ }
const result = await parseDom.parsePage();
return result;
}
@@ -141,6 +149,14 @@ export class App {
if (this.isOperateSelecting) {
return;
}
+ const enableDocumentCopy = await this.senMessageToPage({
+ serviceName: 'CommonService',
+ serviceMethod: 'enableDocumentCopy',
+ });
+ if (!enableDocumentCopy) {
+ this.showMessage({ type: 'error', text: __i18n('当前页面已开启防复制,不支持剪藏') });
+ return;
+ }
const { isRunningHostPage = true, formShortcut = false } = params || {};
this.isOperateSelecting = true;
isRunningHostPage && this.toggleSidePanel(false);
diff --git a/tools/dev-tools/generate-svg-map.js b/tools/dev-tools/generate-svg-map.js
index 3ba64cb2..3fd726dc 100644
--- a/tools/dev-tools/generate-svg-map.js
+++ b/tools/dev-tools/generate-svg-map.js
@@ -19,15 +19,25 @@ function nameWithoutExt(name) {
.join('.');
}
+function convertToCamelCase(str) {
+ const words = str.split('-').map(word => {
+ return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase();
+ });
+ return words.join('');
+}
+
function updateAssetsMap({
targetFilePath,
}) {
const svgMap = {};
+ const importArray = [];
walkDirSync(svgAssetPath, (filePath, name) => {
const key = nameWithoutExt(name);
if (!key) return;
- svgMap[key] = `import('@/assets/svg/${name}')`;
+ const componentName = convertToCamelCase(key);
+ svgMap[key] = componentName;
+ importArray.push(`import ${componentName} from '@/assets/svg/${name}';`);
});
const generateArray = map => {
@@ -38,6 +48,10 @@ function updateAssetsMap({
return `{\n${result}}`;
};
+ const generateImportant = array => {
+ return array.join('\n');
+ };
+
// 写入文件
fs.writeFileSync(
@@ -46,9 +60,9 @@ function updateAssetsMap({
/* eslint-disable @typescript-eslint/indent */
// 本文件为自动生成,不要手动修改
// npm run update:assets
+${generateImportant(importArray)}
export const SvgMaps = ${generateArray(svgMap)};
-
`
);