diff --git a/app/src/ai/actions.ts b/app/src/ai/actions.ts
index ca466a071..0aea42ec0 100644
--- a/app/src/ai/actions.ts
+++ b/app/src/ai/actions.ts
@@ -1,6 +1,5 @@
-import {MenuItem} from "../menus/Menu";
import {fetchPost} from "../util/fetch";
-import {setLastNodeRange} from "../protyle/util/selection";
+import {focusByRange, setLastNodeRange} from "../protyle/util/selection";
import {insertHTML} from "../protyle/util/insertHTML";
import {Dialog} from "../dialog";
import {isMobile} from "../util/functions";
@@ -10,9 +9,10 @@ import {processRender} from "../protyle/util/processCode";
import {highlightRender} from "../protyle/render/highlightRender";
import {Constants} from "../constants";
import {setStorageVal} from "../protyle/util/compatibility";
-import {hasClosestByClassName} from "../protyle/util/hasClosest";
import {escapeAriaLabel, escapeHtml} from "../util/escape";
import {showMessage} from "../dialog/message";
+import {Menu} from "../plugin/Menu";
+import {upDownHint} from "../util/upDownHint";
export const fillContent = (protyle: IProtyle, data: string, elements: Element[]) => {
if (!data) {
@@ -26,81 +26,10 @@ export const fillContent = (protyle: IProtyle, data: string, elements: Element[]
highlightRender(protyle.wysiwyg.element);
};
-export const AIActions = (elements: Element[], protyle: IProtyle) => {
- const ids: string[] = [];
- elements.forEach(item => {
- ids.push(item.getAttribute("data-node-id"));
- });
- const customMenu: IMenu[] = [{
- iconHTML: "",
- label: window.siyuan.languages.aiCustomAction,
- click() {
- const dialog = new Dialog({
- title: window.siyuan.languages.aiCustomAction,
- content: `
+const editDialog = (customName: string, customMemo: string) => {
+ const dialog = new Dialog({
+ title: window.siyuan.languages.update,
+ content: `
@@ -110,217 +39,264 @@ export const AIActions = (elements: Element[], protyle: IProtyle) => {
`,
- width: isMobile() ? "92vw" : "520px",
- });
- dialog.element.setAttribute("data-key", Constants.DIALOG_AIUPDATECUSTOMACTION);
- const nameElement = dialog.element.querySelector("input");
- nameElement.value = item.name;
- const customElement = dialog.element.querySelector("textarea");
- const btnsElement = dialog.element.querySelectorAll(".b3-button");
- dialog.bindInput(customElement, () => {
- (btnsElement[1] as HTMLButtonElement).click();
- });
- customElement.value = item.memo;
- btnsElement[0].addEventListener("click", () => {
- dialog.destroy();
- });
- btnsElement[1].addEventListener("click", () => {
- window.siyuan.storage[Constants.LOCAL_AI].find((subItem: {
- name: string,
- memo: string
- }) => {
- if (item.name === subItem.name && item.memo === subItem.memo) {
- item.name = nameElement.value;
- item.memo = customElement.value;
- setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]);
- return true;
- }
- });
- dialog.destroy();
- });
- btnsElement[2].addEventListener("click", () => {
- window.siyuan.storage[Constants.LOCAL_AI].find((subItem: {
- name: string,
- memo: string
- }, index: number) => {
- if (item.name === subItem.name && item.memo === subItem.memo) {
- window.siyuan.storage[Constants.LOCAL_AI].splice(index, 1);
- setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]);
- return true;
- }
- });
- dialog.destroy();
- });
- nameElement.focus();
+ width: isMobile() ? "92vw" : "520px",
+ });
+ dialog.element.setAttribute("data-key", Constants.DIALOG_AIUPDATECUSTOMACTION);
+ const nameElement = dialog.element.querySelector("input");
+ nameElement.value = customName;
+ const customElement = dialog.element.querySelector("textarea");
+ const btnsElement = dialog.element.querySelectorAll(".b3-button");
+ dialog.bindInput(customElement, () => {
+ (btnsElement[1] as HTMLButtonElement).click();
+ });
+ customElement.value = customMemo;
+ btnsElement[0].addEventListener("click", () => {
+ dialog.destroy();
+ });
+ btnsElement[1].addEventListener("click", () => {
+ window.siyuan.storage[Constants.LOCAL_AI].find((subItem: {
+ name: string,
+ memo: string
+ }) => {
+ if (customName === subItem.name && customMemo === subItem.memo) {
+ subItem.name = nameElement.value;
+ subItem.memo = customElement.value;
+ setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]);
+ return true;
+ }
+ });
+ dialog.destroy();
+ });
+ btnsElement[2].addEventListener("click", () => {
+ window.siyuan.storage[Constants.LOCAL_AI].find((subItem: {
+ name: string,
+ memo: string
+ }, index: number) => {
+ if (customName === subItem.name && customMemo === subItem.memo) {
+ window.siyuan.storage[Constants.LOCAL_AI].splice(index, 1);
+ setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]);
+ return true;
+ }
+ });
+ dialog.destroy();
+ });
+ nameElement.focus();
+}
+
+const customDialog = (protyle: IProtyle, ids: string[], elements: Element[]) => {
+ const dialog = new Dialog({
+ title: window.siyuan.languages.aiCustomAction,
+ content: `
+
+
+
+
+
`,
+ width: isMobile() ? "92vw" : "520px",
+ });
+ dialog.element.setAttribute("data-key", Constants.DIALOG_AICUSTOMACTION);
+ const nameElement = dialog.element.querySelector("input");
+ const customElement = dialog.element.querySelector("textarea");
+ const btnsElement = dialog.element.querySelectorAll(".b3-button");
+ dialog.bindInput(customElement, () => {
+ (btnsElement[1] as HTMLButtonElement).click();
+ });
+ btnsElement[0].addEventListener("click", () => {
+ dialog.destroy();
+ });
+ btnsElement[1].addEventListener("click", () => {
+ if (!customElement.value) {
+ showMessage(window.siyuan.languages["_kernel"][142]);
+ return;
+ }
+ fetchPost("/api/ai/chatGPTWithAction", {
+ ids,
+ action: customElement.value,
+ }, (response) => {
+ dialog.destroy();
+ fillContent(protyle, response.data, elements);
+ });
+ });
+ btnsElement[2].addEventListener("click", () => {
+ if (!nameElement.value && !customElement.value) {
+ showMessage(window.siyuan.languages["_kernel"][142]);
+ return;
+ }
+ window.siyuan.storage[Constants.LOCAL_AI].push({
+ name: nameElement.value,
+ memo: customElement.value
+ });
+ setStorageVal(Constants.LOCAL_AI, window.siyuan.storage[Constants.LOCAL_AI]);
+ dialog.destroy();
+ });
+ nameElement.focus();
+}
+
+const filterAI = (element: HTMLElement, inputElement: HTMLInputElement) => {
+ element.querySelectorAll(".b3-list-item").forEach(item => {
+ if (item.textContent.indexOf(inputElement.value) > -1) {
+ item.classList.remove("fn__none");
+ } else {
+ item.classList.add("fn__none");
+ }
+ })
+ element.querySelectorAll('.b3-menu__separator').forEach(item => {
+ if (inputElement.value) {
+ item.classList.add("fn__none");
+ } else {
+ item.classList.remove("fn__none");
+ }
+ })
+ element.querySelector(".b3-list-item--focus").classList.remove("b3-list-item--focus");
+ element.querySelector(".b3-list-item:not(.fn__none)").classList.add("b3-list-item--focus");
+}
+
+export const AIActions = (elements: Element[], protyle: IProtyle) => {
+ window.siyuan.menus.menu.remove();
+ const ids: string[] = [];
+ elements.forEach(item => {
+ ids.push(item.getAttribute("data-node-id"));
+ });
+ const menu = new Menu("ai", () => {
+ focusByRange(protyle.toolbar.range);
+ });
+ let customHTML = ""
+ window.siyuan.storage[Constants.LOCAL_AI].forEach((item: { name: string, memo: string }) => {
+ customHTML += `
+ ${escapeHtml(item.name)}
+
+
`;
+ });
+ if (customHTML) {
+ customHTML = `${customHTML}`;
+ }
+ menu.addItem({
+ iconHTML: "",
+ type: "empty",
+ label: `
+
+
+
+
+ ${window.siyuan.languages.aiContinueWrite}
+
+
+
+ ${window.siyuan.languages.aiTranslate_zh_Hans}
+
+
+ ${window.siyuan.languages.aiTranslate_zh_Hant}
+
+
+ ${window.siyuan.languages.aiTranslate_ja_JP}
+
+
+ ${window.siyuan.languages.aiTranslate_ko_KR}
+
+
+ ${window.siyuan.languages.aiTranslate_en_US}
+
+
+ ${window.siyuan.languages.aiTranslate_es_ES}
+
+
+ ${window.siyuan.languages.aiTranslate_fr_FR}
+
+
+ ${window.siyuan.languages.aiTranslate_de_DE}
+
+
+
+ ${window.siyuan.languages.aiExtractSummary}
+
+
+ ${window.siyuan.languages.aiBrainStorm}
+
+
+ ${window.siyuan.languages.aiFixGrammarSpell}
+
+
+ ${window.siyuan.languages.clearContext}
+
+
+ ${window.siyuan.languages.aiCustomAction}
+
+ ${customHTML}
+
+
`,
+ bind(element) {
+ const listElement = element.querySelector(".b3-list");
+ const inputElement = element.querySelector("input");
+ inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
+ if (event.isComposing) {
+ return;
+ }
+ const currentElement = upDownHint(listElement, event);
+ if (currentElement) {
+ event.stopPropagation();
+ }
+ if (event.key === "Enter") {
+ event.preventDefault();
+ event.stopPropagation();
+ const currentElement = listElement.querySelector(".b3-list-item--focus") as HTMLElement;
+ if (currentElement.dataset.type === "custom") {
+ customDialog(protyle, ids, elements);
} else {
fetchPost("/api/ai/chatGPTWithAction", {
ids,
- action: item.memo,
+ action: currentElement.dataset.action
}, (response) => {
fillContent(protyle, response.data, elements);
});
}
- window.siyuan.menus.menu.remove();
- event.preventDefault();
- event.stopPropagation();
- });
- }
- });
+ menu.close();
+ }
+ });
+ inputElement.addEventListener("compositionend", () => {
+ filterAI(element, inputElement);
+ })
+ inputElement.addEventListener("input", (event: KeyboardEvent) => {
+ if (event.isComposing) {
+ return;
+ }
+ filterAI(element, inputElement);
+ });
+ element.addEventListener("click", (event) => {
+ let target = event.target as HTMLElement;
+ while (target && !target.isSameNode(element)) {
+ if (target.classList.contains("b3-list-item__action")) {
+ editDialog(target.previousElementSibling.textContent, target.parentElement.getAttribute("aria-label"));
+ event.stopPropagation();
+ event.preventDefault();
+ break;
+ } else if (target.classList.contains("b3-list-item")) {
+ if (target.dataset.type === "custom") {
+ customDialog(protyle, ids, elements);
+ } else {
+ fetchPost("/api/ai/chatGPTWithAction", {ids, action: target.dataset.action}, (response) => {
+ fillContent(protyle, response.data, elements);
+ });
+ }
+ event.stopPropagation();
+ event.preventDefault();
+ break;
+ }
+ target = target.parentElement;
+ }
+ menu.close();
+ });
+ }
});
- window.siyuan.menus.menu.append(new MenuItem({
- icon: "iconSparkles",
- label: window.siyuan.languages.ai,
- type: "submenu",
- submenu: [{
- iconHTML: "",
- label: window.siyuan.languages.aiContinueWrite,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {ids, action: "Continue writing"}, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate,
- type: "submenu",
- submenu: [{
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_zh_Hans,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [zh-Hans]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_zh_Hant,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [zh-Hant]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_ja_JP,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [ja-JP]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_ko_KR,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [ko-KR]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_en_US,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [en-US]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_es_ES,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [es-ES]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_fr_FR,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [fr-FR]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiTranslate_de_DE,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Translate as follows to [de-DE]"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }]
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiExtractSummary,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: window.siyuan.languages.aiExtractSummary
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiBrainStorm,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: window.siyuan.languages.aiBrainStorm
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.aiFixGrammarSpell,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: window.siyuan.languages.aiFixGrammarSpell
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.clearContext,
- click() {
- fetchPost("/api/ai/chatGPTWithAction", {
- ids,
- action: "Clear context"
- }, (response) => {
- fillContent(protyle, response.data, elements);
- });
- }
- }, {
- iconHTML: "",
- label: window.siyuan.languages.custom,
- type: "submenu",
- submenu: customMenu
- }]
- }).element);
+ menu.element.querySelector(".b3-menu__items").setAttribute("style", "overflow: initial");
+ const rect = elements[elements.length - 1].getBoundingClientRect();
+ menu.open({
+ x: rect.left,
+ y: rect.bottom,
+ h: rect.height,
+ });
+ menu.element.querySelector("input").focus();
};
diff --git a/app/src/protyle/gutter/index.ts b/app/src/protyle/gutter/index.ts
index 36ee04470..254a51d94 100644
--- a/app/src/protyle/gutter/index.ts
+++ b/app/src/protyle/gutter/index.ts
@@ -714,7 +714,14 @@ export class Gutter {
}
}
if (!protyle.disabled) {
- AIActions(selectsElement, protyle);
+ window.siyuan.menus.menu.append(new MenuItem({
+ icon: "iconSparkles",
+ label: window.siyuan.languages.ai,
+ accelerator: window.siyuan.config.keymap.editor.general.ai.custom,
+ click() {
+ AIActions(selectsElement, protyle);
+ }
+ }).element);
}
const copyMenu: IMenu[] = [{
label: window.siyuan.languages.copy,
@@ -1175,7 +1182,14 @@ export class Gutter {
}).element);
}
if (!protyle.disabled && !nodeElement.classList.contains("hr")) {
- AIActions([nodeElement], protyle);
+ window.siyuan.menus.menu.append(new MenuItem({
+ icon: "iconSparkles",
+ label: window.siyuan.languages.ai,
+ accelerator: window.siyuan.config.keymap.editor.general.ai.custom,
+ click() {
+ AIActions([nodeElement], protyle);
+ }
+ }).element);
}
const copyMenu = (copySubMenu(id, true, nodeElement) as IMenu[]).concat([{
label: window.siyuan.languages.copy,
diff --git a/app/src/protyle/wysiwyg/keydown.ts b/app/src/protyle/wysiwyg/keydown.ts
index af3a7772f..374a53b0b 100644
--- a/app/src/protyle/wysiwyg/keydown.ts
+++ b/app/src/protyle/wysiwyg/keydown.ts
@@ -67,6 +67,7 @@ import {getSavePath, newFileBySelect} from "../../util/newFile";
import {removeSearchMark} from "../toolbar/util";
import {avKeydown} from "../render/av/keydown";
import {checkFold} from "../../util/noRelyPCFunction";
+import {AIActions} from "../../ai/actions";
export const getContentByInlineHTML = (range: Range, cb: (content: string) => void) => {
let html = "";
@@ -1404,6 +1405,17 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => {
return;
}
+ if (!event.repeat && matchHotKey(window.siyuan.config.keymap.editor.general.ai.custom, event)) {
+ event.preventDefault();
+ event.stopPropagation();
+ let selectsElement: HTMLElement[] = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select"));
+ if (selectsElement.length === 0) {
+ selectsElement = [nodeElement];
+ }
+ AIActions(selectsElement, protyle)
+ return;
+ }
+
// tab 需等待 list 和 table 处理完成
if (event.key === "Tab" && isNotCtrl(event) && !event.altKey) {
event.preventDefault();