diff --git a/app/src/config/keymap.ts b/app/src/config/keymap.ts index 0335c068c..6b642afa0 100644 --- a/app/src/config/keymap.ts +++ b/app/src/config/keymap.ts @@ -464,7 +464,8 @@ export const keymap = { let hasConflict = false; const isAssistKey = ["⌘", "⇧", "⌥", "⌃"].includes(keymapStr.substr(keymapStr.length - 1, 1)); if (isAssistKey || - ["⌘A", "⌘X", "⌘C", "⌘V", "⌘-", "⌘=", "⌘0", "⇧⌘V", "⌘/", "⇧↑", "⇧↓", "⇧→", "⇧←", "⇧⇥", "⌃D", "⇧⌘→", "⇧⌘←", "⌘Home", "⌘End", "⇧↩", "↩", "PageUp", "PageDown", "⌫", "⌦", "Escape"].includes(keymapStr) || + ["⌘A", "⌘X", "⌘C", "⌘V", "⌘-", "⌘=", "⌘0", "⇧⌘V", "⌘/", "⇧↑", "⇧↓", "⇧→", "⇧←", "⇧⇥", "⌃D", "⇧⌘→", + "⇧⌘←", "⌘Home", "⌘End", "⇧↩", "⌥↩", "↩", "PageUp", "PageDown", "⌫", "⌦", "Escape"].includes(keymapStr) || // 跳转到下/上一个编辑页签不能包含 ctrl, 否则不能监听到 keyup (isMac() && keys[0] === "general" && ["goToEditTabNext", "goToEditTabPrev"].includes(keys[1]) && keymapStr.includes("⌘")) ) { diff --git a/app/src/constants.ts b/app/src/constants.ts index dc2e37fdd..8a4cb65fa 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -343,7 +343,7 @@ export abstract class Constants { // 冲突不使用 "⌘S/Q" // "⌘", "⇧", "⌥", "⌃" // "⌘A", "⌘X", "⌘C", "⌘V", "⌘-", "⌘=", "⌘0", "⇧⌘V", "⌘/", "⇧↑", "⇧↓", "⇧→", "⇧←", "⇧⇥", "⌃D", "⇧⌘→", "⇧⌘←", - // "⌘Home", "⌘End", "⇧↩", "↩", "PageUp", "PageDown", "⌫", "⌦", "Escape" 不可自定义 + // "⌘Home", "⌘End", "⇧↩", "⌥↩", "↩", "PageUp", "PageDown", "⌫", "⌦", "Escape" 不可自定义 public static readonly SIYUAN_KEYMAP: Config.IKeymap = { general: { mainMenu: {default: "⌥\\", custom: "⌥\\"}, diff --git a/app/src/protyle/toolbar/index.ts b/app/src/protyle/toolbar/index.ts index 4ab7da9a9..d0fbd62f6 100644 --- a/app/src/protyle/toolbar/index.ts +++ b/app/src/protyle/toolbar/index.ts @@ -15,7 +15,7 @@ import { import {hasClosestBlock, hasClosestByAttribute, hasClosestByClassName, hasClosestByTag} from "../util/hasClosest"; import {Link} from "./Link"; import {setPosition} from "../../util/setPosition"; -import {updateTransaction} from "../wysiwyg/transaction"; +import {transaction, updateTransaction} from "../wysiwyg/transaction"; import {Constants} from "../../constants"; import {copyPlainText, openByMobile, readClipboard, setStorageVal} from "../util/compatibility"; import {upDownHint} from "../../util/upDownHint"; @@ -1321,17 +1321,14 @@ export class Toolbar { }); } - public showCodeLanguage(protyle: IProtyle, languageElement: HTMLElement) { - const nodeElement = hasClosestBlock(languageElement); + public showCodeLanguage(protyle: IProtyle, languageElements: HTMLElement[]) { + const nodeElement = hasClosestBlock(languageElements[0]); if (!nodeElement) { return; } hideElements(["hint"], protyle); window.siyuan.menus.menu.remove(); this.range = getEditorRange(nodeElement); - const id = nodeElement.getAttribute("data-node-id"); - let oldHtml = nodeElement.outerHTML; - let html = `
${window.siyuan.languages.clear}
`; const hljsLanguages = Constants.ALIAS_CODE_LANGUAGES.concat(window.hljs?.listLanguages() ?? []).sort(); hljsLanguages.forEach((item, index) => { @@ -1354,7 +1351,7 @@ export class Toolbar { } upDownHint(listElement, event); if (event.key === "Enter") { - oldHtml = this.updateLanguage(languageElement, protyle, id, nodeElement, oldHtml, this.subElement.querySelector(".b3-list-item--focus").textContent); + this.updateLanguage(languageElements, protyle, this.subElement.querySelector(".b3-list-item--focus").textContent); event.preventDefault(); event.stopPropagation(); return; @@ -1410,13 +1407,13 @@ export class Toolbar { if (!listElement) { return; } - oldHtml = this.updateLanguage(languageElement, protyle, id, nodeElement, oldHtml, listElement.textContent); + this.updateLanguage(languageElements, protyle, listElement.textContent); }); this.subElement.style.zIndex = (++window.siyuan.zIndex).toString(); this.subElement.classList.remove("fn__none"); this.subElementCloseCB = undefined; /// #if !MOBILE - const nodeRect = languageElement.getBoundingClientRect(); + const nodeRect = languageElements[0].getBoundingClientRect(); setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height); /// #else setPosition(this.subElement, 0, 0); @@ -1841,28 +1838,46 @@ ${item.name} } } - private updateLanguage(languageElement: HTMLElement, protyle: IProtyle, id: string, nodeElement: HTMLElement, oldHtml: string, selectedLang: string) { - languageElement.textContent = selectedLang === window.siyuan.languages.clear ? "" : selectedLang; - if (!Constants.SIYUAN_RENDER_CODE_LANGUAGES.includes(languageElement.textContent)) { - window.siyuan.storage[Constants.LOCAL_CODELANG] = languageElement.textContent; + private updateLanguage(languageElement: HTMLElement[], protyle: IProtyle, selectedLang: string) { + const currentLang = selectedLang === window.siyuan.languages.clear ? "" : selectedLang; + if (!Constants.SIYUAN_RENDER_CODE_LANGUAGES.includes(currentLang)) { + window.siyuan.storage[Constants.LOCAL_CODELANG] = currentLang; setStorageVal(Constants.LOCAL_CODELANG, window.siyuan.storage[Constants.LOCAL_CODELANG]); } - const editElement = getContenteditableElement(nodeElement); - if (Constants.SIYUAN_RENDER_CODE_LANGUAGES.includes(languageElement.textContent)) { - nodeElement.dataset.content = editElement.textContent.trim(); - nodeElement.dataset.subtype = languageElement.textContent; - nodeElement.className = "render-node"; - nodeElement.innerHTML = `
${Constants.ZWSP}
`; - processRender(nodeElement); - } else { - (editElement as HTMLElement).textContent = editElement.textContent; - editElement.parentElement.removeAttribute("data-render"); - highlightRender(nodeElement); - } - nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); - updateTransaction(protyle, id, nodeElement.outerHTML, oldHtml); + const doOperations: IOperation[] = []; + const undoOperations: IOperation[] = []; + languageElement.forEach(item => { + const nodeElement = hasClosestBlock(item); + if (nodeElement) { + const id = nodeElement.getAttribute("data-node-id"); + undoOperations.push({ + id, + data: nodeElement.outerHTML, + action: "update" + }); + item.textContent = selectedLang === window.siyuan.languages.clear ? "" : selectedLang; + const editElement = getContenteditableElement(nodeElement); + if (Constants.SIYUAN_RENDER_CODE_LANGUAGES.includes(currentLang)) { + nodeElement.dataset.content = editElement.textContent.trim(); + nodeElement.dataset.subtype = currentLang; + nodeElement.className = "render-node"; + nodeElement.innerHTML = `
${Constants.ZWSP}
`; + processRender(nodeElement); + } else { + (editElement as HTMLElement).textContent = editElement.textContent; + editElement.parentElement.removeAttribute("data-render"); + highlightRender(nodeElement); + } + nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss")); + doOperations.push({ + id, + data: nodeElement.outerHTML, + action: "update" + }); + } + }); + transaction(protyle, doOperations, undoOperations); this.subElement.classList.add("fn__none"); focusByRange(this.range); - return nodeElement.outerHTML; } } diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index ba5ff183a..c78f83ab6 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -2506,7 +2506,7 @@ export class WYSIWYG { const languageElement = hasClosestByClassName(event.target, "protyle-action__language"); if (languageElement && !protyle.disabled && !ctrlIsPressed) { - protyle.toolbar.showCodeLanguage(protyle, languageElement); + protyle.toolbar.showCodeLanguage(protyle, [languageElement]); event.stopPropagation(); event.preventDefault(); return; diff --git a/app/src/protyle/wysiwyg/keydown.ts b/app/src/protyle/wysiwyg/keydown.ts index 68683b7b5..cd3df62dc 100644 --- a/app/src/protyle/wysiwyg/keydown.ts +++ b/app/src/protyle/wysiwyg/keydown.ts @@ -988,6 +988,29 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => { return; } + // 代码块语言选择 https://github.com/siyuan-note/siyuan/issues/14126 + if (matchHotKey("⌥↩", event) && selectText === "") { + const selectElements = Array.from(protyle.wysiwyg.element.querySelectorAll(".protyle-wysiwyg--select")); + if (selectElements.length === 0 && nodeElement.classList.contains("code-block")) { + selectElements.push(nodeElement); + } + if (selectElements.length > 0) { + const otherElement = selectElements.find(item => { + return !item.classList.contains("code-block"); + }); + if (!otherElement) { + const languageElements: HTMLElement[] = []; + selectElements.forEach(item => { + languageElements.push(item.querySelector(".protyle-action__language")); + }); + protyle.toolbar.showCodeLanguage(protyle, languageElements); + event.stopPropagation(); + event.preventDefault(); + return; + } + } + } + // 回车 if (isNotCtrl(event) && event.key === "Enter") { if (event.altKey) {