From c0d9e23dcfdd3d1c7447d66b4c6c581efb2f09d1 Mon Sep 17 00:00:00 2001 From: Vanessa Date: Sat, 25 Mar 2023 10:57:13 +0800 Subject: [PATCH] :art: fix https://github.com/siyuan-note/siyuan/issues/7763 --- app/appearance/langs/en_US.json | 2 +- app/appearance/langs/es_ES.json | 2 +- app/appearance/langs/fr_FR.json | 2 +- app/appearance/langs/zh_CHT.json | 2 +- app/appearance/langs/zh_CN.json | 2 +- app/src/ai/actions.ts | 218 ++++++++++++++++++++------ app/src/constants.ts | 1 + app/src/menus/workspace.ts | 4 +- app/src/protyle/util/compatibility.ts | 3 +- 9 files changed, 182 insertions(+), 54 deletions(-) diff --git a/app/appearance/langs/en_US.json b/app/appearance/langs/en_US.json index 359c142d0..a1c0fbaa8 100644 --- a/app/appearance/langs/en_US.json +++ b/app/appearance/langs/en_US.json @@ -22,7 +22,7 @@ "apiBaseURLTip": "The base address of the request, such as https://api.openai.com/v1", "skip": "Skip", "reboot": "Reboot", - "saveLayout": "Save Layout", + "save": "Save", "ai": "Artificial Intelligence", "aiContinueWrite": "Continue writing", "aiTranslate": "Translate", diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 1f22e1da8..cdafd42d7 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -22,7 +22,7 @@ "apiBaseURLTip": "La dirección base de la solicitud, como https://api.openai.com/v1", "skip": "barco", "reboot": "Reiniciar", - "saveLayout": "Guardar diseño", + "save": "Ahorrar", "ai": "Inteligencia Artificial", "aiContinueWrite": "Continuar escribiendo", "aiTranslate": "Traducir", diff --git a/app/appearance/langs/fr_FR.json b/app/appearance/langs/fr_FR.json index ce9894f08..362cbfb05 100644 --- a/app/appearance/langs/fr_FR.json +++ b/app/appearance/langs/fr_FR.json @@ -22,7 +22,7 @@ "apiBaseURLTip": "L'adresse de base de la requête, telle que https://api.openai.com/v1", "skip": "Navire", "reboot": "Redémarrer", - "saveLayout": "Enregistrer la mise en page", + "save": "Sauvegarder", "ai": "Intelligence Artificielle", "aiContinueWrite": "Continuer à écrire", "aiTranslate": "Traduire", diff --git a/app/appearance/langs/zh_CHT.json b/app/appearance/langs/zh_CHT.json index 76900450d..3ba603bc3 100644 --- a/app/appearance/langs/zh_CHT.json +++ b/app/appearance/langs/zh_CHT.json @@ -22,7 +22,7 @@ "apiBaseURLTip": "發起請求的基礎地址,如 https://api.openai.com/v1", "skip": "跳過", "reboot": "重啟", - "saveLayout": "保存佈局", + "save": "保存", "ai": "人工智能", "aiContinueWrite": "續寫", "aiTranslate": "翻譯", diff --git a/app/appearance/langs/zh_CN.json b/app/appearance/langs/zh_CN.json index c16fe989b..ee8c05096 100644 --- a/app/appearance/langs/zh_CN.json +++ b/app/appearance/langs/zh_CN.json @@ -22,7 +22,7 @@ "apiBaseURLTip": "发起请求的基础地址,如 https://api.openai.com/v1", "skip": "跳过", "reboot": "重启", - "saveLayout": "保存布局", + "save": "保存", "ai": "人工智能", "aiContinueWrite": "续写", "aiTranslate": "翻译", diff --git a/app/src/ai/actions.ts b/app/src/ai/actions.ts index 1059775cc..da9184c6a 100644 --- a/app/src/ai/actions.ts +++ b/app/src/ai/actions.ts @@ -8,8 +8,12 @@ import {getContenteditableElement} from "../protyle/wysiwyg/getBlock"; import {blockRender} from "../protyle/markdown/blockRender"; import {processRender} from "../protyle/util/processCode"; import {highlightRender} from "../protyle/markdown/highlightRender"; +import {Constants} from "../constants"; +import {setStorageVal} from "../protyle/util/compatibility"; +import {hasClosestByClassName} from "../protyle/util/hasClosest"; +import {escapeHtml} from "../util/escape"; -export const fillContent = (protyle:IProtyle, data:string, elements:Element[]) => { +export const fillContent = (protyle: IProtyle, data: string, elements: Element[]) => { setLastNodeRange(getContenteditableElement(elements[elements.length - 1]), protyle.toolbar.range); protyle.toolbar.range.collapse(true); insertHTML(data, protyle, true, true); @@ -23,11 +27,122 @@ export const AIActions = (elements: Element[], protyle: IProtyle) => { elements.forEach(item => { ids.push(item.getAttribute("data-node-id")); }); + const customMenu: IMenu[] = [{ + iconHTML: Constants.ZWSP, + label: window.siyuan.languages.aiCustomAction, + click() { + const dialog = new Dialog({ + title: window.siyuan.languages.aiCustomAction, + content: `
+
+
+ +
`, + width: isMobile() ? "80vw" : "520px", + }); + const inputElement = dialog.element.querySelector("input") as HTMLInputElement; + const btnsElement = dialog.element.querySelectorAll(".b3-button"); + dialog.bindInput(inputElement, () => { + (btnsElement[1] as HTMLButtonElement).click(); + }); + inputElement.focus(); + btnsElement[0].addEventListener("click", () => { + dialog.destroy(); + }); + btnsElement[1].addEventListener("click", () => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: inputElement.value, + }, (response) => { + dialog.destroy(); + let respContent = ""; + if (response.data && "" !== response.data) { + respContent = "\n\n" + response.data; + } + fillContent(protyle, `${inputElement.value}${respContent}`, elements); + }); + }); + } + }, { + iconHTML: Constants.ZWSP, + label: window.siyuan.languages.save, + click() { + const dialog = new Dialog({ + title: window.siyuan.languages.save, + content: `
+ +
+ +
+
+
+ +
`, + width: isMobile() ? "80vw" : "520px", + }); + const inputElement = dialog.element.querySelector("input"); + const btnsElement = dialog.element.querySelectorAll(".b3-button"); + dialog.bindInput(inputElement, () => { + (btnsElement[1] as HTMLButtonElement).click(); + }); + inputElement.focus(); + btnsElement[0].addEventListener("click", () => { + dialog.destroy(); + }); + btnsElement[1].addEventListener("click", () => { + window.siyuan.storage[Constants.LOCAL_AI].push({ + name: inputElement.value, + memo: dialog.element.querySelector("textarea").value, + }) + setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]); + dialog.destroy(); + }); + } + }] + window.siyuan.storage[Constants.LOCAL_AI].forEach((item: { name: string, memo: string }) => { + customMenu.push({ + iconHTML: Constants.ZWSP, + action: "iconCloseRound", + label: escapeHtml(item.name), + bind: (element) => { + element.addEventListener("click", (event) => { + if (hasClosestByClassName(event.target as Element, "b3-menu__action")) { + window.siyuan.storage[Constants.LOCAL_AI].find((subItem: { + name: string, + memo: string + }, index: number) => { + if (element.querySelector(".b3-menu__label").textContent.trim() === subItem.name) { + window.siyuan.storage[Constants.LOCAL_AI].splice(index, 1); + setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]); + element.remove(); + return true; + } + }) + } else { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: item.memo, + }, (response) => { + let respContent = ""; + if (response.data && "" !== response.data) { + respContent = "\n\n" + response.data; + } + fillContent(protyle, `${item.memo}${respContent}`, elements); + }); + window.siyuan.menus.menu.remove(); + } + event.preventDefault(); + event.stopPropagation(); + }); + } + }) + }); window.siyuan.menus.menu.append(new MenuItem({ icon: "iconSparkles", label: window.siyuan.languages.ai, type: "submenu", submenu: [{ + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiContinueWrite, click() { fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Continue writing"}, (response) => { @@ -35,115 +150,126 @@ export const AIActions = (elements: Element[], protyle: IProtyle) => { }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate, type: "submenu", submenu: [{ + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_zh_CN, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [zh_CN]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [zh_CN]" + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_ja_JP, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [ja_JP]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [ja_JP]" + }, (response) => { focusByRange(protyle.toolbar.range); insertHTML(response.data, protyle, true); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_ko_KR, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [ko_KR]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [ko_KR]" + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_en_US, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [en_US]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [en_US]" + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_es_ES, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [es_ES]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [es_ES]" + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_fr_FR, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [fr_FR]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [fr_FR]" + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiTranslate_de_DE, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Translate as follows to [de_DE]"}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: "Translate as follows to [de_DE]" + }, (response) => { fillContent(protyle, response.data, elements); }); } }] }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiExtractSummary, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: window.siyuan.languages.aiExtractSummary}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: window.siyuan.languages.aiExtractSummary + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiBrainStorm, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: window.siyuan.languages.aiBrainStorm}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: window.siyuan.languages.aiBrainStorm + }, (response) => { fillContent(protyle, response.data, elements); }); } }, { + iconHTML: Constants.ZWSP, label: window.siyuan.languages.aiFixGrammarSpell, click() { - fetchPost("/api/ai/chatGPTWithAction", {ids, action: window.siyuan.languages.aiFixGrammarSpell}, (response) => { + fetchPost("/api/ai/chatGPTWithAction", { + ids, + action: window.siyuan.languages.aiFixGrammarSpell + }, (response) => { fillContent(protyle, response.data, elements); }); } - },{ - label: window.siyuan.languages.aiCustomAction, - click() { - const dialog = new Dialog({ - title: window.siyuan.languages.aiCustomAction, - content: `
-
-
- -
`, - width: isMobile() ? "80vw" : "520px", - }); - const inputElement = dialog.element.querySelector("input") as HTMLInputElement; - const btnsElement = dialog.element.querySelectorAll(".b3-button"); - dialog.bindInput(inputElement, () => { - (btnsElement[1] as HTMLButtonElement).click(); - }); - inputElement.focus(); - btnsElement[0].addEventListener("click", () => { - dialog.destroy(); - }); - btnsElement[1].addEventListener("click", () => { - fetchPost("/api/ai/chatGPTWithAction", { - ids, - action: inputElement.value, - }, (response) => { - dialog.destroy(); - let respContent = ""; - if (response.data && "" !== response.data) { - respContent = "\n\n" + response.data; - } - fillContent(protyle, `${inputElement.value}${respContent}`, elements); - }); - }); - } + }, { + iconHTML: Constants.ZWSP, + label: window.siyuan.languages.custom, + type: "submenu", + submenu: customMenu }] }).element); }; diff --git a/app/src/constants.ts b/app/src/constants.ts index 9cfc237f6..ea39d4184 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -79,6 +79,7 @@ export abstract class Constants { public static readonly LOCAL_BAZAAR = "local-bazaar"; public static readonly LOCAL_PDFTHEME = "local-pdftheme"; public static readonly LOCAL_LAYOUTS = "local-layouts"; + public static readonly LOCAL_AI = "local-ai"; // timeout public static readonly TIMEOUT_DBLCLICK = 190; diff --git a/app/src/menus/workspace.ts b/app/src/menus/workspace.ts index b76c82327..f5019e3ac 100644 --- a/app/src/menus/workspace.ts +++ b/app/src/menus/workspace.ts @@ -105,10 +105,10 @@ export const workspaceMenu = (rect: DOMRect) => { /// #endif const layoutSubMenu: IMenu[] = [{ iconHTML: Constants.ZWSP, - label: window.siyuan.languages.saveLayout, + label: window.siyuan.languages.save, click() { const saveDialog = new Dialog({ - title: window.siyuan.languages.saveLayout, + title: window.siyuan.languages.save, content: `
diff --git a/app/src/protyle/util/compatibility.ts b/app/src/protyle/util/compatibility.ts index e7741385d..746fe8ad9 100644 --- a/app/src/protyle/util/compatibility.ts +++ b/app/src/protyle/util/compatibility.ts @@ -156,6 +156,7 @@ export const getLocalStorage = (cb: () => void) => { }; defaultStorage[Constants.LOCAL_PDFTHEME] = {light: "light", dark: "dark", annoColor: "var(--b3-pdf-background1)"}; defaultStorage[Constants.LOCAL_LAYOUTS] = []; // {name: "", layout:{}} + defaultStorage[Constants.LOCAL_AI] = []; // {name: "", memo: ""} defaultStorage[Constants.LOCAL_BAZAAR] = { theme: "0", template: "0", @@ -209,7 +210,7 @@ export const getLocalStorage = (cb: () => void) => { [Constants.LOCAL_EXPORTIMG, Constants.LOCAL_SEARCHKEYS, Constants.LOCAL_PDFTHEME, Constants.LOCAL_BAZAAR, Constants.LOCAL_EXPORTWORD, Constants.LOCAL_EXPORTPDF, Constants.LOCAL_DOCINFO, Constants.LOCAL_FONTSTYLES, Constants.LOCAL_SEARCHDATA, - Constants.LOCAL_ZOOM, Constants.LOCAL_SEARCHKEY, Constants.LOCAL_LAYOUTS].forEach((key) => { + Constants.LOCAL_ZOOM, Constants.LOCAL_SEARCHKEY, Constants.LOCAL_LAYOUTS, Constants.LOCAL_AI].forEach((key) => { if (typeof response.data[key] === "string") { try { window.siyuan.storage[key] = Object.assign(defaultStorage[key], JSON.parse(response.data[key]));