This commit is contained in:
Vanessa 2023-08-04 14:16:47 +08:00
parent c737ecabb6
commit 901ea823cb
8 changed files with 174 additions and 42 deletions

View file

@ -93,11 +93,14 @@
&__title {
border-bottom: .5px solid var(--b3-theme-background-light);
line-height: 48px;
padding: 0 8px;
display: flex;
background-color: var(--b3-theme-background);
height: 48.5px;
& > .b3-menu__label {
line-height: 32.5px;
}
}
&__separator {

View file

@ -14,7 +14,8 @@
background-color: transparent;
height: 29px;
box-sizing: border-box;
font-size: 0;
font-size: 10px;
white-space: nowrap;
padding: 1px 6px;
&:focus {

View file

@ -23,7 +23,7 @@ import {copyPlainText, readText, setStorageVal, writeText} from "../protyle/util
import {preventScroll} from "../protyle/scroll/preventScroll";
import {onGet} from "../protyle/util/onGet";
import {getAllModels} from "../layout/getAll";
import {pasteAsPlainText, pasteText} from "../protyle/util/paste";
import {pasteAsPlainText, pasteEscaped, pasteText} from "../protyle/util/paste";
/// #if !MOBILE
import {openFileById, updateBacklinkGraph} from "../editor/util";
import {openGlobalSearch} from "../search/util";
@ -411,6 +411,9 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => {
export const contentMenu = (protyle: IProtyle, nodeElement: Element) => {
const range = getEditorRange(nodeElement);
window.siyuan.menus.menu.remove();
/// #if MOBILE
protyle.toolbar.showContent(protyle, range, nodeElement);
/// #else
if (range.toString() !== "" || (range.cloneContents().childNodes[0] as HTMLElement)?.classList?.contains("emoji")) {
window.siyuan.menus.menu.append(new MenuItem({
icon: "iconCopy",
@ -464,6 +467,7 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => {
if (!protyle.disabled) {
window.siyuan.menus.menu.append(new MenuItem({
label: window.siyuan.languages.paste,
icon: "iconPaste",
accelerator: "⌘V",
async click() {
if (document.queryCommandSupported("paste")) {
@ -488,46 +492,14 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => {
}).element);
window.siyuan.menus.menu.append(new MenuItem({
label: window.siyuan.languages.pasteEscaped,
async click() {
try {
// * _ [ ] ! \ ` < > & ~ { } ( ) = # $ ^ |
let clipText = await readText();
// https://github.com/siyuan-note/siyuan/issues/5446
// A\B\C\D\
// E
// task-blog-2~default~baiduj 无法原义粘贴含有 `~foo~` 的文本 https://github.com/siyuan-note/siyuan/issues/5523
// 这里必须多加一个反斜杆,因为 Lute 在进行 Markdown 嵌套节点转换平铺标记节点时会剔除 Backslash 节点,
// 多加入的一个反斜杆会作为文本节点保留下来,后续 Spin 时刚好用于转义标记符 https://github.com/siyuan-note/siyuan/issues/6341
clipText = clipText.replace(/\\/g, "\\\\\\\\")
.replace(/\*/g, "\\\\\\*")
.replace(/\_/g, "\\\\\\_")
.replace(/\[/g, "\\\\\\[")
.replace(/\]/g, "\\\\\\]")
.replace(/\!/g, "\\\\\\!")
.replace(/\`/g, "\\\\\\`")
.replace(/\</g, "\\\\\\<")
.replace(/\>/g, "\\\\\\>")
.replace(/\&/g, "\\\\\\&")
.replace(/\~/g, "\\\\\\~")
.replace(/\{/g, "\\\\\\{")
.replace(/\}/g, "\\\\\\}")
.replace(/\(/g, "\\\\\\(")
.replace(/\)/g, "\\\\\\)")
.replace(/\=/g, "\\\\\\=")
.replace(/\#/g, "\\\\\\#")
.replace(/\$/g, "\\\\\\$")
.replace(/\^/g, "\\\\\\^")
.replace(/\|/g, "\\\\\\|");
pasteText(protyle, clipText, nodeElement);
} catch (e) {
console.log(e);
}
click() {
pasteEscaped(protyle, nodeElement);
}
}).element);
}
window.siyuan.menus.menu.append(new MenuItem({
label: window.siyuan.languages.selectAll,
icon: "iconSelect",
accelerator: "⌘A",
click() {
selectAll(protyle, nodeElement, range);
@ -544,6 +516,7 @@ export const contentMenu = (protyle: IProtyle, nodeElement: Element) => {
}).element);
}
}
/// #endif
if (protyle?.app?.plugins) {
emitOpenMenu({
plugins: protyle.app.plugins,
@ -669,7 +642,7 @@ export const imgMenu = (protyle: IProtyle, range: Range, assetElement: HTMLEleme
iconHTML: "",
label: `<textarea style="margin: 4px 0" rows="1" class="b3-text-field fn__size200" placeholder="${window.siyuan.languages.imageURL}">${imgElement.getAttribute("src")}</textarea>`,
bind(element) {
element.querySelector("textarea").addEventListener("input", (event:InputEvent) => {
element.querySelector("textarea").addEventListener("input", (event: InputEvent) => {
if (event.isComposing) {
return;
}

View file

@ -6,7 +6,7 @@ import {
focusByRange,
focusByWbr,
getEditorRange,
getSelectionPosition,
getSelectionPosition, selectAll,
setFirstNodeRange,
setLastNodeRange
} from "../util/selection";
@ -15,7 +15,7 @@ import {Link} from "./Link";
import {setPosition} from "../../util/setPosition";
import {updateTransaction} from "../wysiwyg/transaction";
import {Constants} from "../../constants";
import {openByMobile, setStorageVal} from "../util/compatibility";
import {copyPlainText, openByMobile, readText, setStorageVal} from "../util/compatibility";
import {upDownHint} from "../../util/upDownHint";
import {highlightRender} from "../render/highlightRender";
import {getContenteditableElement, hasNextSibling, hasPreviousSibling} from "../wysiwyg/getBlock";
@ -45,6 +45,7 @@ import {mathRender} from "../render/mathRender";
import {linkMenu} from "../../menus/protyle";
import {addScript} from "../util/addScript";
import {confirmDialog} from "../../dialog/confirmDialog";
import {pasteAsPlainText, pasteEscaped, pasteText} from "../util/paste";
export class Toolbar {
public element: HTMLElement;
@ -835,6 +836,7 @@ export class Toolbar {
const autoHeight = () => {
textElement.style.height = textElement.scrollHeight + "px";
if (isMobile()) {
setPosition(this.subElement, 0, 0);
return;
}
if (this.subElement.firstElementChild.getAttribute("data-drag") === "true") {
@ -1258,6 +1260,8 @@ export class Toolbar {
/// #if !MOBILE
const nodeRect = languageElement.getBoundingClientRect();
setPosition(this.subElement, nodeRect.left, nodeRect.bottom, nodeRect.height);
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
this.element.classList.add("fn__none");
inputElement.select();
@ -1434,6 +1438,8 @@ export class Toolbar {
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
(this.subElement.firstElementChild as HTMLElement).style.maxHeight = Math.min(window.innerHeight * 0.8, window.innerHeight - this.subElement.getBoundingClientRect().top) - 16 + "px";
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
});
}
@ -1499,6 +1505,8 @@ export class Toolbar {
/// #if !MOBILE
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
});
}
@ -1605,6 +1613,8 @@ export class Toolbar {
/// #if !MOBILE
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
/// #else
setPosition(this.subElement, 0, 0);
/// #endif
this.element.classList.add("fn__none");
inputElement.select();
@ -1621,4 +1631,100 @@ export class Toolbar {
this.subElement.querySelector(".b3-list--background").innerHTML = html;
});
}
public showContent(protyle: IProtyle, range: Range, nodeElement: Element) {
this.range = range;
hideElements(["hint"], protyle);
this.subElement.style.width = "auto";
this.subElement.style.padding = "0 8px";
let html = ""
const hasCopy = range.toString() !== "" || (range.cloneContents().childNodes[0] as HTMLElement)?.classList?.contains("emoji");
if (hasCopy) {
html += `<button class="protyle-toolbar__item" data-action="copy"><svg><use xlink:href="#iconCopy"></use></svg></button>`
if (!protyle.disabled) {
html += `<button class="protyle-toolbar__item" data-action="cut"><svg><use xlink:href="#iconCut"></use></svg></button>
<button class="protyle-toolbar__item" data-action="delete"><svg><use xlink:href="#iconTrashcan"></use></svg></button>`
}
}
if (!protyle.disabled) {
html += `<button class="protyle-toolbar__item" data-action="paste"><svg><use xlink:href="#iconPaste"></use></svg></button>
<button class="protyle-toolbar__item" data-action="select"><svg><use xlink:href="#iconSelect"></use></svg></button>`
}
if (hasCopy || !protyle.disabled) {
html += `<button class="protyle-toolbar__item" data-action="more"><svg><use xlink:href="#iconMore"></use></svg></button>`
}
this.subElement.innerHTML = `<div class="fn__flex">${html}</div>`;
this.subElement.lastElementChild.addEventListener("click", async (event) => {
const btnElemen = hasClosestByClassName(event.target as HTMLElement, "protyle-toolbar__item");
if (!btnElemen) {
return
}
const action = btnElemen.getAttribute("data-action");
if (action === "copy") {
focusByRange(getEditorRange(nodeElement));
document.execCommand("copy");
this.subElement.classList.add("fn__none");
} else if (action === "cut") {
focusByRange(getEditorRange(nodeElement));
document.execCommand("cut");
this.subElement.classList.add("fn__none");
} else if (action === "delete") {
const currentRange = getEditorRange(nodeElement);
currentRange.insertNode(document.createElement("wbr"));
const oldHTML = nodeElement.outerHTML;
currentRange.extractContents();
focusByWbr(nodeElement, currentRange);
focusByRange(currentRange);
updateTransaction(protyle, nodeElement.getAttribute("data-node-id"), nodeElement.outerHTML, oldHTML);
this.subElement.classList.add("fn__none");
} else if (action === "paste") {
if (document.queryCommandSupported("paste")) {
document.execCommand("paste");
} else {
try {
const clipText = await readText();
pasteText(protyle, clipText, nodeElement);
} catch (e) {
console.log(e);
}
}
this.subElement.classList.add("fn__none");
} else if (action === "select") {
selectAll(protyle, nodeElement, range);
this.subElement.classList.add("fn__none");
} else if (action === "copyPlainText") {
focusByRange(getEditorRange(nodeElement));
const cloneContents = getSelection().getRangeAt(0).cloneContents();
cloneContents.querySelectorAll('[data-type="backslash"]').forEach(item => {
item.firstElementChild.remove();
});
copyPlainText(cloneContents.textContent);
this.subElement.classList.add("fn__none");
} else if (action === "pasteAsPlainText") {
focusByRange(getEditorRange(nodeElement));
pasteAsPlainText(protyle);
this.subElement.classList.add("fn__none");
} else if (action === "pasteEscaped") {
pasteEscaped(protyle, nodeElement);
this.subElement.classList.add("fn__none");
} else if (action === "back") {
this.subElement.lastElementChild.innerHTML = html;
} else if (action === "more") {
this.subElement.lastElementChild.innerHTML = `<button class="protyle-toolbar__item${hasCopy ? "" : " fn__none"}" data-action="copyPlainText">${window.siyuan.languages.copyPlainText}</button>
<div class="protyle-toolbar__divider${hasCopy ? "" : " fn__none"}"></div>
<button class="protyle-toolbar__item${protyle.disabled ? " fn__none" : ""}" data-action="pasteAsPlainText">${window.siyuan.languages.pasteAsPlainText}</button>
<div class="protyle-toolbar__divider${protyle.disabled ? " fn__none" : ""}"></div>
<button class="protyle-toolbar__item${protyle.disabled ? " fn__none" : ""}" data-action="pasteEscaped">${window.siyuan.languages.pasteEscaped}</button>
<div class="protyle-toolbar__divider${protyle.disabled ? " fn__none" : ""}"></div>
<button class="protyle-toolbar__item" data-action="back"><svg><use xlink:href="#iconBack"></use></svg></button>`
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
}
});
this.subElement.classList.remove("fn__none");
this.subElementCloseCB = undefined;
this.element.classList.add("fn__none");
const rangePosition = getSelectionPosition(nodeElement, range);
setPosition(this.subElement, rangePosition.left, rangePosition.top + 18, Constants.SIZE_TOOLBAR_HEIGHT);
}
}

View file

@ -1,7 +1,7 @@
import {Constants} from "../../constants";
import {uploadFiles, uploadLocalFiles} from "../upload";
import {processPasteCode, processRender} from "./processCode";
import {writeText} from "./compatibility";
import {readText, writeText} from "./compatibility";
/// #if !BROWSER
import {clipboard} from "electron";
/// #endif
@ -16,6 +16,43 @@ import {scrollCenter} from "../../util/highlightById";
import {hideElements} from "../ui/hideElements";
import {avRender} from "../render/av/render";
export const pasteEscaped = async (protyle:IProtyle, nodeElement:Element) => {
try {
// * _ [ ] ! \ ` < > & ~ { } ( ) = # $ ^ |
let clipText = await readText();
// https://github.com/siyuan-note/siyuan/issues/5446
// A\B\C\D\
// E
// task-blog-2~default~baiduj 无法原义粘贴含有 `~foo~` 的文本 https://github.com/siyuan-note/siyuan/issues/5523
// 这里必须多加一个反斜杆,因为 Lute 在进行 Markdown 嵌套节点转换平铺标记节点时会剔除 Backslash 节点,
// 多加入的一个反斜杆会作为文本节点保留下来,后续 Spin 时刚好用于转义标记符 https://github.com/siyuan-note/siyuan/issues/6341
clipText = clipText.replace(/\\/g, "\\\\\\\\")
.replace(/\*/g, "\\\\\\*")
.replace(/\_/g, "\\\\\\_")
.replace(/\[/g, "\\\\\\[")
.replace(/\]/g, "\\\\\\]")
.replace(/\!/g, "\\\\\\!")
.replace(/\`/g, "\\\\\\`")
.replace(/\</g, "\\\\\\<")
.replace(/\>/g, "\\\\\\>")
.replace(/\&/g, "\\\\\\&")
.replace(/\~/g, "\\\\\\~")
.replace(/\{/g, "\\\\\\{")
.replace(/\}/g, "\\\\\\}")
.replace(/\(/g, "\\\\\\(")
.replace(/\)/g, "\\\\\\)")
.replace(/\=/g, "\\\\\\=")
.replace(/\#/g, "\\\\\\#")
.replace(/\$/g, "\\\\\\$")
.replace(/\^/g, "\\\\\\^")
.replace(/\|/g, "\\\\\\|");
pasteText(protyle, clipText, nodeElement);
} catch (e) {
console.log(e);
}
}
const filterClipboardHint = (protyle: IProtyle, textPlain: string) => {
let needRender = true;
protyle.options.hint.extend.find(item => {