mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-12-21 17:10:12 +01:00
❤️ 完整开源界面和内核 https://github.com/siyuan-note/siyuan/issues/5013
This commit is contained in:
parent
e650b8100c
commit
f40ed985e1
1214 changed files with 345766 additions and 9 deletions
811
app/src/menus/commonMenuItem.ts
Normal file
811
app/src/menus/commonMenuItem.ts
Normal file
|
|
@ -0,0 +1,811 @@
|
|||
/// #if !BROWSER
|
||||
import {shell} from "electron";
|
||||
/// #endif
|
||||
import {newFile} from "../util/newFile";
|
||||
import {getDockByType} from "../layout/util";
|
||||
import {confirmDialog} from "../dialog/confirmDialog";
|
||||
import {getSearch, isMobile} from "../util/functions";
|
||||
import {getDisplayName, isLocalPath, movePathTo} from "../util/pathName";
|
||||
import {MenuItem} from "./Menu";
|
||||
import {hasClosestByClassName} from "../protyle/util/hasClosest";
|
||||
import {saveExport} from "../protyle/export";
|
||||
import {writeText} from "../protyle/util/compatibility";
|
||||
import {fetchPost} from "../util/fetch";
|
||||
import {hideMessage, showMessage} from "../dialog/message";
|
||||
import {Dialog} from "../dialog";
|
||||
import {getAllModels} from "../layout/getAll";
|
||||
import {focusBlock, focusByRange, getEditorRange} from "../protyle/util/selection";
|
||||
import {setPosition} from "../util/setPosition";
|
||||
import {updateTransaction} from "../protyle/wysiwyg/transaction";
|
||||
import {Bookmark} from "../layout/dock/Bookmark";
|
||||
import {rename} from "../editor/rename";
|
||||
import {openAsset, openBy} from "../editor/util";
|
||||
import {matchHotKey} from "../protyle/util/hotKey";
|
||||
import * as dayjs from "dayjs";
|
||||
import {Constants} from "../constants";
|
||||
|
||||
const bindAttrInput = (inputElement: HTMLInputElement, confirmElement: Element) => {
|
||||
inputElement.addEventListener("keydown", (event) => {
|
||||
if (event.isComposing) {
|
||||
return;
|
||||
}
|
||||
if (matchHotKey("⌘Enter", event)) {
|
||||
confirmElement.dispatchEvent(new CustomEvent("click"));
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const openWechatNotify = (nodeElement: Element) => {
|
||||
const id = nodeElement.getAttribute("data-node-id");
|
||||
const range = getEditorRange(nodeElement);
|
||||
const reminder = nodeElement.getAttribute("custom-reminder-wechat");
|
||||
let reminderFormat = "";
|
||||
if (reminder) {
|
||||
reminderFormat = dayjs(reminder).format("YYYY-MM-DDTHH:mm");
|
||||
}
|
||||
const dialog = new Dialog({
|
||||
width: isMobile() ? "80vw" : "50vw",
|
||||
title: window.siyuan.languages.wechatReminder,
|
||||
content: `<div class="b3-dialog__content custom-attr">
|
||||
<div class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.notifyTime}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" type="datetime-local" value="${reminderFormat}">
|
||||
</div>
|
||||
<div class="b3-label__text" style="text-align: center">${window.siyuan.languages.wechatTip}</div>
|
||||
</div>
|
||||
<div class="b3-dialog__action">
|
||||
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text">${window.siyuan.languages.remove}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
|
||||
</div>`,
|
||||
destroyCallback() {
|
||||
focusByRange(range);
|
||||
}
|
||||
});
|
||||
const btnsElement = dialog.element.querySelectorAll(".b3-button");
|
||||
btnsElement[0].addEventListener("click", () => {
|
||||
dialog.destroy();
|
||||
});
|
||||
btnsElement[1].addEventListener("click", () => {
|
||||
if (btnsElement[1].getAttribute("disabled")) {
|
||||
return;
|
||||
}
|
||||
btnsElement[1].setAttribute("disabled", "disabled");
|
||||
fetchPost("/api/block/setBlockReminder", {id, timed: "0"}, () => {
|
||||
nodeElement.removeAttribute("custom-reminder-wechat");
|
||||
dialog.destroy();
|
||||
});
|
||||
});
|
||||
btnsElement[2].addEventListener("click", () => {
|
||||
const date = dialog.element.querySelector("input").value;
|
||||
if (date) {
|
||||
if (new Date(date) <= new Date()) {
|
||||
showMessage(window.siyuan.languages.reminderTip);
|
||||
return;
|
||||
}
|
||||
if (btnsElement[2].getAttribute("disabled")) {
|
||||
return;
|
||||
}
|
||||
btnsElement[2].setAttribute("disabled", "disabled");
|
||||
const timed = dayjs(date).format("YYYYMMDDHHmmss");
|
||||
fetchPost("/api/block/setBlockReminder", {id, timed}, () => {
|
||||
nodeElement.setAttribute("custom-reminder-wechat", timed);
|
||||
dialog.destroy();
|
||||
});
|
||||
} else {
|
||||
showMessage(window.siyuan.languages.notEmpty);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const openFileWechatNotify = (protyle: IProtyle) => {
|
||||
fetchPost("/api/block/getDocInfo", {
|
||||
id: protyle.block.rootID
|
||||
}, (response) => {
|
||||
const reminder = response.data.ial["custom-reminder-wechat"];
|
||||
let reminderFormat = "";
|
||||
if (reminder) {
|
||||
reminderFormat = dayjs(reminder).format("YYYY-MM-DDTHH:mm");
|
||||
}
|
||||
const dialog = new Dialog({
|
||||
width: isMobile() ? "80vw" : "50vw",
|
||||
title: window.siyuan.languages.wechatReminder,
|
||||
content: `<div class="b3-dialog__content custom-attr">
|
||||
<div class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.notifyTime}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" type="datetime-local" value="${reminderFormat}">
|
||||
</div>
|
||||
<div class="b3-label__text" style="text-align: center">${window.siyuan.languages.wechatTip}</div>
|
||||
</div>
|
||||
<div class="b3-dialog__action">
|
||||
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text">${window.siyuan.languages.remove}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
|
||||
</div>`
|
||||
});
|
||||
const btnsElement = dialog.element.querySelectorAll(".b3-button");
|
||||
btnsElement[0].addEventListener("click", () => {
|
||||
dialog.destroy();
|
||||
});
|
||||
btnsElement[1].addEventListener("click", () => {
|
||||
fetchPost("/api/block/setBlockReminder", {id: protyle.block.rootID, timed: "0"}, () => {
|
||||
dialog.destroy();
|
||||
});
|
||||
});
|
||||
btnsElement[2].addEventListener("click", () => {
|
||||
const date = dialog.element.querySelector("input").value;
|
||||
if (date) {
|
||||
if (new Date(date) <= new Date()) {
|
||||
showMessage(window.siyuan.languages.reminderTip);
|
||||
return;
|
||||
}
|
||||
fetchPost("/api/block/setBlockReminder", {
|
||||
id: protyle.block.rootID,
|
||||
timed: dayjs(date).format("YYYYMMDDHHmmss")
|
||||
}, () => {
|
||||
dialog.destroy();
|
||||
});
|
||||
} else {
|
||||
showMessage(window.siyuan.languages.notEmpty);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const openFileAttr = (attrs: IObject, id: string, focusName = "bookmark") => {
|
||||
let customHTML = "";
|
||||
let notifyHTML = "";
|
||||
Object.keys(attrs).forEach(item => {
|
||||
if (item === "custom-reminder-wechat") {
|
||||
notifyHTML = `<label class="fn__flex customItem">
|
||||
<span class="ft__on-surface fn__flex-center fn__ellipsis" style="text-align: right;width: 100px">${window.siyuan.languages.wechatReminder}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" type="datetime-local" readonly data-name="${item}" value="${dayjs(attrs[item]).format("YYYY-MM-DDTHH:mm")}">
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center" style="opacity: 1;"><svg></svg></span>
|
||||
</label><div class="fn__hr--b"></div>`;
|
||||
} else if (item.indexOf("custom") > -1) {
|
||||
customHTML += `<label class="fn__flex customItem">
|
||||
<span class="ft__on-surface fn__flex-center fn__ellipsis" title="${item.replace("custom-", "")}" style="text-align: right;width: 100px">${item.replace("custom-", "")}</span>
|
||||
<div class="fn__space"></div>
|
||||
<textarea class="b3-text-field fn__flex-1" rows="1" data-name="${item}">${attrs[item]}</textarea>
|
||||
<div class="fn__space"></div>
|
||||
<span data-action="remove" class="block__icon fn__flex-center" style="opacity: 1;"><svg><use xlink:href="#iconMin"></use></svg></span>
|
||||
</label><div class="fn__hr--b"></div>`;
|
||||
}
|
||||
});
|
||||
const dialog = new Dialog({
|
||||
width: isMobile() ? "80vw" : "50vw",
|
||||
title: window.siyuan.languages.attr,
|
||||
content: `<div class="b3-dialog__content custom-attr">
|
||||
<div class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.bookmark}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" data-name="bookmark">
|
||||
<div class="fn__space"></div>
|
||||
<span data-action="bookmark" class="block__icon fn__flex-center" style="opacity: 1;"><svg><use xlink:href="#iconDown"></use></svg></span>
|
||||
</div>
|
||||
<div class="fn__hr--b"></div>
|
||||
<label class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.name}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" data-name="name">
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center"><svg></svg></span>
|
||||
</label>
|
||||
<div class="fn__hr--b"></div>
|
||||
<label class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.alias}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" data-name="alias">
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center"><svg></svg></span>
|
||||
</label>
|
||||
<div class="fn__hr--b"></div>
|
||||
<label class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.memo}</span>
|
||||
<div class="fn__space"></div>
|
||||
<textarea rows="2" class="b3-text-field fn__flex-1" data-name="memo">${attrs.memo || ""}</textarea>
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center"><svg></svg></span>
|
||||
</label>
|
||||
<div style="background-color: var(--b3-border-color);height: 1px;margin: 16px 0;"></div>
|
||||
<div class="custom-attr__add">
|
||||
${notifyHTML}
|
||||
${customHTML}
|
||||
<button class="b3-button b3-button--outline" style="width: 100px">
|
||||
<svg><use xlink:href="#iconAdd"></use></svg>
|
||||
${window.siyuan.languages.addAttr}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="b3-dialog__action">
|
||||
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
|
||||
</div>`,
|
||||
});
|
||||
(dialog.element.querySelector('.b3-text-field[data-name="bookmark"]') as HTMLInputElement).value = attrs.bookmark || "";
|
||||
(dialog.element.querySelector('.b3-text-field[data-name="name"]') as HTMLInputElement).value = attrs.name || "";
|
||||
(dialog.element.querySelector('.b3-text-field[data-name="alias"]') as HTMLInputElement).value = attrs.alias || "";
|
||||
const removeAttrs: string[] = [];
|
||||
dialog.element.addEventListener("click", (event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const actionElement = hasClosestByClassName(target, "block__icon");
|
||||
if (!actionElement) {
|
||||
return;
|
||||
}
|
||||
switch (actionElement.getAttribute("data-action")) {
|
||||
case "remove":
|
||||
if (actionElement.parentElement.firstElementChild.tagName === "SPAN") {
|
||||
removeAttrs.push(actionElement.parentElement.querySelector("textarea").getAttribute("data-name"));
|
||||
}
|
||||
actionElement.parentElement.nextElementSibling.remove();
|
||||
actionElement.parentElement.remove();
|
||||
break;
|
||||
case "bookmark":
|
||||
fetchPost("/api/attr/getBookmarkLabels", {}, (response) => {
|
||||
window.siyuan.menus.menu.remove();
|
||||
if (response.data.length === 0) {
|
||||
window.siyuan.menus.menu.append(new MenuItem({
|
||||
label: window.siyuan.languages.emptyContent,
|
||||
type: "readonly",
|
||||
}).element);
|
||||
} else {
|
||||
response.data.forEach((item: string) => {
|
||||
window.siyuan.menus.menu.append(new MenuItem({
|
||||
label: item,
|
||||
click() {
|
||||
actionElement.parentElement.querySelector("input").value = item;
|
||||
}
|
||||
}).element);
|
||||
});
|
||||
}
|
||||
window.siyuan.menus.menu.element.classList.remove("fn__none");
|
||||
setPosition(window.siyuan.menus.menu.element, event.clientX, event.clientY + 16);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
const btnsElement = dialog.element.querySelectorAll(".b3-button");
|
||||
btnsElement[0].addEventListener("click", () => {
|
||||
btnsElement[0].insertAdjacentHTML("beforebegin", `<div class="fn__flex customItem">
|
||||
<input placeholder="${window.siyuan.languages.attrName}" class="b3-text-field" style="width: 100px;text-align: right">
|
||||
<div class="fn__space"></div>
|
||||
<textarea rows="1" class="b3-text-field fn__flex-1" placeholder="${window.siyuan.languages.attrValue1}"></textarea>
|
||||
<div class="fn__space"></div>
|
||||
<span data-action="remove" class="block__icon fn__flex-center" style="opacity: 1;"><svg><use xlink:href="#iconMin"></use></svg></span>
|
||||
</div><div class="fn__hr--b"></div>`);
|
||||
const inputElements = dialog.element.querySelectorAll(".b3-text-field") as NodeListOf<HTMLInputElement>;
|
||||
inputElements[inputElements.length - 2].focus();
|
||||
bindAttrInput(inputElements[inputElements.length - 1], btnsElement[2]);
|
||||
bindAttrInput(inputElements[inputElements.length - 2], btnsElement[2]);
|
||||
});
|
||||
btnsElement[1].addEventListener("click", () => {
|
||||
dialog.destroy();
|
||||
});
|
||||
btnsElement[2].addEventListener("click", () => {
|
||||
let nodeAttrHTML = "";
|
||||
let errorTip = "";
|
||||
const attrsResult: IObject = {};
|
||||
dialog.element.querySelectorAll(".fn__flex-1").forEach((item: HTMLInputElement) => {
|
||||
const name = item.getAttribute("data-name") || ("custom-" + (item.previousElementSibling.previousElementSibling as HTMLInputElement).value);
|
||||
if (item.value.trim()) {
|
||||
if (!/^[0-9a-zA-Z\-]*$/.test(name.replace("custom-", "")) || name === "custom-") {
|
||||
errorTip += name.replace("custom-", "") + ", ";
|
||||
return;
|
||||
}
|
||||
attrsResult[name] = item.value;
|
||||
const escapeHTML = Lute.EscapeHTMLStr(item.value);
|
||||
if (name === "bookmark") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--bookmark">${escapeHTML}</div>`;
|
||||
} else if (name === "name") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--name"><svg><use xlink:href="#iconN"></use></svg>${escapeHTML}</div>`;
|
||||
} else if (name === "alias") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--alias"><svg><use xlink:href="#iconA"></use></svg>${escapeHTML}</div>`;
|
||||
} else if (name === "memo") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--memo b3-tooltips b3-tooltips__sw" aria-label="${escapeHTML}"><svg><use xlink:href="#iconM"></use></svg></div>`;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (errorTip) {
|
||||
showMessage(errorTip.substr(0, errorTip.length - 2) + " " + window.siyuan.languages.invalid);
|
||||
}
|
||||
getAllModels().editor.forEach(item => {
|
||||
if (item.editor.protyle.block.rootID === id) {
|
||||
const refElement = item.editor.protyle.title.element.querySelector(".protyle-attr--refcount");
|
||||
if (refElement) {
|
||||
nodeAttrHTML += refElement.outerHTML;
|
||||
}
|
||||
item.editor.protyle.title.element.querySelector(".protyle-attr").innerHTML = nodeAttrHTML;
|
||||
item.editor.protyle.wysiwyg.renderCustom(attrsResult);
|
||||
}
|
||||
});
|
||||
fetchPost("/api/attr/resetBlockAttrs", {id, attrs: attrsResult}, () => {
|
||||
if (attrsResult.bookmark !== attrs.bookmark) {
|
||||
const bookmark = getDockByType("bookmark").data.bookmark;
|
||||
if (bookmark instanceof Bookmark) {
|
||||
bookmark.update();
|
||||
}
|
||||
}
|
||||
});
|
||||
dialog.destroy();
|
||||
});
|
||||
dialog.element.querySelectorAll(".b3-text-field").forEach((item: HTMLInputElement) => {
|
||||
if (focusName === item.getAttribute("data-name")) {
|
||||
item.focus();
|
||||
}
|
||||
bindAttrInput(item, btnsElement[2]);
|
||||
});
|
||||
};
|
||||
|
||||
export const openAttr = (nodeElement: Element, protyle: IProtyle, focusName = "bookmark") => {
|
||||
if (nodeElement.getAttribute("data-type") === "NodeThematicBreak") {
|
||||
return;
|
||||
}
|
||||
const id = nodeElement.getAttribute("data-node-id");
|
||||
const range = getEditorRange(nodeElement);
|
||||
fetchPost("/api/attr/getBlockAttrs", {id}, (response) => {
|
||||
let customHTML = "";
|
||||
let notifyHTML = "";
|
||||
Object.keys(response.data).forEach(item => {
|
||||
if (item === "custom-reminder-wechat") {
|
||||
notifyHTML = `<label class="fn__flex customItem">
|
||||
<span class="ft__on-surface fn__flex-center fn__ellipsis" style="text-align: right;width: 100px">${window.siyuan.languages.wechatReminder}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" type="datetime-local" readonly data-name="${item}" value="${dayjs(response.data[item]).format("YYYY-MM-DDTHH:mm")}">
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center" style="opacity: 1;"><svg></svg></span>
|
||||
</label><div class="fn__hr--b"></div>`;
|
||||
} else if (item.indexOf("custom") > -1 && item !== "custom-reminder-wechat") {
|
||||
customHTML += `<label class="fn__flex customItem">
|
||||
<span class="ft__on-surface fn__flex-center fn__ellipsis" style="text-align: right;width: 100px" title="${item.replace("custom-", "")}">${item.replace("custom-", "")}</span>
|
||||
<div class="fn__space"></div>
|
||||
<textarea class="b3-text-field fn__flex-1" rows="1" data-name="${item}">${response.data[item]}</textarea>
|
||||
<div class="fn__space"></div>
|
||||
<span data-action="remove" class="block__icon fn__flex-center" style="opacity: 1;"><svg><use xlink:href="#iconMin"></use></svg></span>
|
||||
</label><div class="fn__hr--b"></div>`;
|
||||
}
|
||||
});
|
||||
const dialog = new Dialog({
|
||||
width: isMobile() ? "80vw" : "50vw",
|
||||
title: window.siyuan.languages.attr,
|
||||
content: `<div class="b3-dialog__content custom-attr">
|
||||
<div class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.bookmark}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" data-name="bookmark">
|
||||
<div class="fn__space"></div>
|
||||
<span data-action="bookmark" class="block__icon fn__flex-center" style="opacity: 1;"><svg><use xlink:href="#iconDown"></use></svg></span>
|
||||
</div>
|
||||
<div class="fn__hr--b"></div>
|
||||
<label class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.name}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" data-name="name">
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center"><svg></svg></span>
|
||||
</label>
|
||||
<div class="fn__hr--b"></div>
|
||||
<label class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.alias}</span>
|
||||
<div class="fn__space"></div>
|
||||
<input class="b3-text-field fn__flex-1" data-name="alias">
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center"><svg></svg></span>
|
||||
</label>
|
||||
<div class="fn__hr--b"></div>
|
||||
<label class="fn__flex">
|
||||
<span class="ft__on-surface fn__flex-center" style="text-align: right;white-space: nowrap;width: 100px">${window.siyuan.languages.memo}</span>
|
||||
<div class="fn__space"></div>
|
||||
<textarea class="b3-text-field fn__flex-1" rows="2" data-name="memo">${response.data.memo || ""}</textarea>
|
||||
<div class="fn__space"></div>
|
||||
<span class="block__icon fn__flex-center"><svg></svg></span>
|
||||
</label>
|
||||
<div style="background-color: var(--b3-border-color);height: 1px;margin: 16px 0;"></div>
|
||||
<div class="custom-attr__add">
|
||||
${notifyHTML}
|
||||
${customHTML}
|
||||
<button class="b3-button b3-button--outline" style="width: 100px">
|
||||
<svg><use xlink:href="#iconAdd"></use></svg>
|
||||
${window.siyuan.languages.addAttr}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="b3-dialog__action">
|
||||
<button class="b3-button b3-button--cancel">${window.siyuan.languages.cancel}</button><div class="fn__space"></div>
|
||||
<button class="b3-button b3-button--text">${window.siyuan.languages.confirm}</button>
|
||||
</div>`,
|
||||
destroyCallback() {
|
||||
focusByRange(range);
|
||||
}
|
||||
});
|
||||
(dialog.element.querySelector('.b3-text-field[data-name="bookmark"]') as HTMLInputElement).value = response.data.bookmark || "";
|
||||
(dialog.element.querySelector('.b3-text-field[data-name="name"]') as HTMLInputElement).value = response.data.name || "";
|
||||
(dialog.element.querySelector('.b3-text-field[data-name="alias"]') as HTMLInputElement).value = response.data.alias || "";
|
||||
const removeAttrs: string[] = [];
|
||||
dialog.element.addEventListener("click", (event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
const actionElement = hasClosestByClassName(target, "block__icon");
|
||||
if (!actionElement) {
|
||||
return;
|
||||
}
|
||||
switch (actionElement.getAttribute("data-action")) {
|
||||
case "remove":
|
||||
if (actionElement.parentElement.firstElementChild.tagName === "SPAN") {
|
||||
removeAttrs.push(actionElement.parentElement.querySelector("textarea").getAttribute("data-name"));
|
||||
}
|
||||
actionElement.parentElement.nextElementSibling.remove();
|
||||
actionElement.parentElement.remove();
|
||||
break;
|
||||
case "bookmark":
|
||||
fetchPost("/api/attr/getBookmarkLabels", {}, (response) => {
|
||||
window.siyuan.menus.menu.remove();
|
||||
if (response.data.length === 0) {
|
||||
window.siyuan.menus.menu.append(new MenuItem({
|
||||
label: window.siyuan.languages.emptyContent,
|
||||
type: "readonly",
|
||||
}).element);
|
||||
} else {
|
||||
response.data.forEach((item: string) => {
|
||||
window.siyuan.menus.menu.append(new MenuItem({
|
||||
label: item,
|
||||
click() {
|
||||
actionElement.parentElement.querySelector("input").value = item;
|
||||
}
|
||||
}).element);
|
||||
});
|
||||
}
|
||||
window.siyuan.menus.menu.element.classList.remove("fn__none");
|
||||
setPosition(window.siyuan.menus.menu.element, event.clientX, event.clientY + 16);
|
||||
});
|
||||
break;
|
||||
}
|
||||
});
|
||||
const btnsElement = dialog.element.querySelectorAll(".b3-button");
|
||||
btnsElement[0].addEventListener("click", () => {
|
||||
btnsElement[0].insertAdjacentHTML("beforebegin", `<div class="fn__flex customItem">
|
||||
<input placeholder="${window.siyuan.languages.attrName}" class="b3-text-field" style="width: 100px;text-align: right">
|
||||
<div class="fn__space"></div>
|
||||
<textarea class="b3-text-field fn__flex-1" rows="1" placeholder="${window.siyuan.languages.attrValue1}"></textarea>
|
||||
<div class="fn__space"></div>
|
||||
<span data-action="remove" class="block__icon fn__flex-center" style="opacity: 1;"><svg><use xlink:href="#iconMin"></use></svg></span>
|
||||
</div><div class="fn__hr--b"></div>`);
|
||||
const inputElements = dialog.element.querySelectorAll(".b3-text-field") as NodeListOf<HTMLInputElement>;
|
||||
inputElements[inputElements.length - 2].focus();
|
||||
bindAttrInput(inputElements[inputElements.length - 1], btnsElement[2]);
|
||||
bindAttrInput(inputElements[inputElements.length - 2], btnsElement[2]);
|
||||
});
|
||||
btnsElement[1].addEventListener("click", () => {
|
||||
dialog.destroy();
|
||||
});
|
||||
btnsElement[2].addEventListener("click", () => {
|
||||
let nodeAttrHTML = "";
|
||||
const oldHTML = nodeElement.outerHTML;
|
||||
let errorTip = "";
|
||||
dialog.element.querySelectorAll(".fn__flex-1").forEach((item: HTMLInputElement) => {
|
||||
const name = item.getAttribute("data-name") || ("custom-" + (item.previousElementSibling.previousElementSibling as HTMLInputElement).value);
|
||||
if (item.value.trim()) {
|
||||
if (!/^[0-9a-zA-Z\-]*$/.test(name.replace("custom-", "")) || name === "custom-") {
|
||||
errorTip += name.replace("custom-", "") + ", ";
|
||||
return;
|
||||
}
|
||||
if (removeAttrs.includes(name)) {
|
||||
removeAttrs.find((rmAttr, index) => {
|
||||
if (rmAttr === name) {
|
||||
removeAttrs.splice(index, 1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
const escapeHTML = Lute.EscapeHTMLStr(item.value);
|
||||
nodeElement.setAttribute(name, escapeHTML);
|
||||
if (name === "bookmark") {
|
||||
if (escapeHTML !== response.data.bookmark && !isMobile()) {
|
||||
const bookmark = getDockByType("bookmark").data.bookmark;
|
||||
if (bookmark instanceof Bookmark) {
|
||||
setTimeout(() => {
|
||||
bookmark.update();
|
||||
}, 219);
|
||||
}
|
||||
}
|
||||
nodeAttrHTML += `<div class="protyle-attr--bookmark">${escapeHTML}</div>`;
|
||||
} else if (name === "name") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--name"><svg><use xlink:href="#iconN"></use></svg>${escapeHTML}</div>`;
|
||||
} else if (name === "alias") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--alias"><svg><use xlink:href="#iconA"></use></svg>${escapeHTML}</div>`;
|
||||
} else if (name === "memo") {
|
||||
nodeAttrHTML += `<div class="protyle-attr--memo b3-tooltips b3-tooltips__sw" aria-label="${escapeHTML}"><svg><use xlink:href="#iconM"></use></svg></div>`;
|
||||
}
|
||||
} else {
|
||||
nodeElement.removeAttribute(name);
|
||||
}
|
||||
});
|
||||
removeAttrs.forEach(item => {
|
||||
nodeElement.removeAttribute(item);
|
||||
});
|
||||
if (errorTip) {
|
||||
showMessage(errorTip.substr(0, errorTip.length - 2) + " " + window.siyuan.languages.invalid);
|
||||
}
|
||||
const refElement = nodeElement.lastElementChild.querySelector(".protyle-attr--refcount");
|
||||
if (refElement) {
|
||||
nodeAttrHTML += refElement.outerHTML;
|
||||
}
|
||||
nodeElement.lastElementChild.innerHTML = nodeAttrHTML + Constants.ZWSP;
|
||||
updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML);
|
||||
dialog.destroy();
|
||||
});
|
||||
dialog.element.querySelectorAll(".b3-text-field").forEach((item: HTMLInputElement) => {
|
||||
if (focusName === item.getAttribute("data-name")) {
|
||||
item.focus();
|
||||
}
|
||||
bindAttrInput(item, btnsElement[2]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const copySubMenu = (id: string, name: string, accelerator = true, focusElement?: Element) => {
|
||||
return [
|
||||
{
|
||||
icon: "iconGraph",
|
||||
accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyBlockRef.custom : undefined,
|
||||
label: window.siyuan.languages.copyBlockRef,
|
||||
click: () => {
|
||||
if (name) {
|
||||
writeText(`((${id} "${name}"))`);
|
||||
} else {
|
||||
fetchPost("/api/block/getRefText", {id}, (response) => {
|
||||
writeText(`((${id} '${response.data}'))`);
|
||||
});
|
||||
}
|
||||
if (focusElement) {
|
||||
focusBlock(focusElement);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
icon: "iconSQL",
|
||||
label: window.siyuan.languages.copyBlockEmbed,
|
||||
accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyBlockEmbed.custom : undefined,
|
||||
click: () => {
|
||||
writeText(`{{select * from blocks where id='${id}'}}`);
|
||||
if (focusElement) {
|
||||
focusBlock(focusElement);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
icon: "iconSiYuan",
|
||||
label: window.siyuan.languages.copyProtocol,
|
||||
accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyProtocol.custom : undefined,
|
||||
click: () => {
|
||||
writeText(`siyuan://blocks/${id}`);
|
||||
if (focusElement) {
|
||||
focusBlock(focusElement);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
label: window.siyuan.languages.copyHPath,
|
||||
accelerator: accelerator ? window.siyuan.config.keymap.editor.general.copyHPath.custom : undefined,
|
||||
click: () => {
|
||||
fetchPost("/api/filetree/getHPathByID", {
|
||||
id
|
||||
}, (response) => {
|
||||
writeText(response.data);
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: window.siyuan.languages.copy + " ID",
|
||||
click: () => {
|
||||
writeText(id);
|
||||
if (focusElement) {
|
||||
focusBlock(focusElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
export const exportMd = (id: string) => {
|
||||
return new MenuItem({
|
||||
label: window.siyuan.languages.export,
|
||||
type: "submenu",
|
||||
icon: "iconUpload",
|
||||
submenu: [
|
||||
{
|
||||
label: window.siyuan.languages.template,
|
||||
icon: "iconMarkdown",
|
||||
click: () => {
|
||||
fetchPost("/api/template/docSaveAsTemplate", {
|
||||
id,
|
||||
overwrite: false
|
||||
}, response => {
|
||||
if (response.code === 1) {
|
||||
// 重名
|
||||
confirmDialog(window.siyuan.languages.export, window.siyuan.languages.exportTplTip, () => {
|
||||
fetchPost("/api/template/docSaveAsTemplate", {
|
||||
id,
|
||||
overwrite: true
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
showMessage(window.siyuan.languages.exportTplSucc);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "Markdown",
|
||||
icon: "iconMarkdown",
|
||||
click: () => {
|
||||
showMessage(window.siyuan.languages.exporting, -1);
|
||||
fetchPost("/api/export/exportMd", {
|
||||
id,
|
||||
}, response => {
|
||||
hideMessage();
|
||||
if (window.siyuan.config.system.container === "ios") {
|
||||
window.location.href = response.data.zip;
|
||||
} else if (window.siyuan.config.system.container === "android" && window.JSAndroid) {
|
||||
window.JSAndroid.openExternal(response.data.zip);
|
||||
} else {
|
||||
window.open(response.data.zip);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
label: "SiYuan .sy.zip",
|
||||
icon: "iconSiYuan",
|
||||
click: () => {
|
||||
showMessage(window.siyuan.languages.exporting, -1);
|
||||
fetchPost("/api/export/exportSY", {
|
||||
id,
|
||||
}, response => {
|
||||
hideMessage();
|
||||
if (window.siyuan.config.system.container === "ios") {
|
||||
window.location.href = response.data.zip;
|
||||
} else if (window.siyuan.config.system.container === "android" && window.JSAndroid) {
|
||||
window.JSAndroid.openExternal(response.data.zip);
|
||||
} else {
|
||||
window.open(response.data.zip);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
/// #if !BROWSER
|
||||
{
|
||||
label: "PDF",
|
||||
icon: "iconPDF",
|
||||
click: () => {
|
||||
saveExport({type: "pdf", id});
|
||||
}
|
||||
}, {
|
||||
label: "HTML (SiYuan)",
|
||||
icon: "iconHTML5",
|
||||
click: () => {
|
||||
saveExport({type: "html", id});
|
||||
}
|
||||
}, {
|
||||
label: "HTML (Markdown)",
|
||||
icon: "iconHTML5",
|
||||
click: () => {
|
||||
saveExport({type: "htmlmd", id});
|
||||
}
|
||||
}, {
|
||||
label: "Word .docx",
|
||||
icon: "iconExact",
|
||||
click: () => {
|
||||
saveExport({type: "word", id});
|
||||
}
|
||||
}
|
||||
/// #endif
|
||||
]
|
||||
}).element;
|
||||
};
|
||||
|
||||
export const openMenu = (src: string, onlyMenu = false) => {
|
||||
const submenu = [];
|
||||
if (isLocalPath(src)) {
|
||||
submenu.push({
|
||||
label: window.siyuan.languages.openInNewTab,
|
||||
click() {
|
||||
openAsset(src.trim(), parseInt(getSearch("page", src)));
|
||||
}
|
||||
});
|
||||
/// #if !BROWSER
|
||||
submenu.push({
|
||||
label: window.siyuan.languages.useDefault,
|
||||
click() {
|
||||
openBy(src, "app");
|
||||
}
|
||||
});
|
||||
submenu.push({
|
||||
label: window.siyuan.languages.showInFolder,
|
||||
click: () => {
|
||||
openBy(src, "folder");
|
||||
}
|
||||
});
|
||||
/// #endif
|
||||
} else {
|
||||
submenu.push({
|
||||
label: window.siyuan.languages.useBrowserView,
|
||||
click: () => {
|
||||
/// #if !BROWSER
|
||||
shell.openExternal(src).catch((e) => {
|
||||
console.log("openExternal error:" + e);
|
||||
});
|
||||
/// #else
|
||||
window.open(src);
|
||||
/// #endif
|
||||
}
|
||||
});
|
||||
}
|
||||
if (onlyMenu) {
|
||||
return submenu;
|
||||
}
|
||||
window.siyuan.menus.menu.append(new MenuItem({
|
||||
label: window.siyuan.languages.openBy,
|
||||
submenu
|
||||
}).element);
|
||||
};
|
||||
|
||||
export const newFileMenu = (notebookId?: string, path?: string, open?: boolean) => {
|
||||
return new MenuItem({
|
||||
icon: "iconFile",
|
||||
label: window.siyuan.languages.newFile,
|
||||
click: () => {
|
||||
newFile(notebookId, path, open);
|
||||
}
|
||||
}).element;
|
||||
};
|
||||
|
||||
export const deleteMenu = (notebookId: string, name: string, pathString: string) => {
|
||||
return new MenuItem({
|
||||
icon: "iconTrashcan",
|
||||
label: window.siyuan.languages.delete,
|
||||
accelerator: "⌦",
|
||||
click: () => {
|
||||
fetchPost("/api/block/getDocInfo", {
|
||||
id: getDisplayName(pathString, true, true)
|
||||
}, (response) => {
|
||||
let tip = `${window.siyuan.languages.confirmDelete} <b>${name}</b>?`;
|
||||
if (response.data.subFileCount > 0) {
|
||||
tip = `${window.siyuan.languages.confirmDelete} <b>${name}</b> ${window.siyuan.languages.andSubFile.replace("x", response.data.subFileCount)}?`;
|
||||
}
|
||||
confirmDialog(window.siyuan.languages.delete, tip, () => {
|
||||
fetchPost("/api/filetree/removeDoc", {
|
||||
notebook: notebookId,
|
||||
path: pathString
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}).element;
|
||||
};
|
||||
|
||||
export const renameMenu = (options: {
|
||||
path: string
|
||||
notebookId: string
|
||||
name: string,
|
||||
type: "notebook" | "file"
|
||||
}) => {
|
||||
return new MenuItem({
|
||||
accelerator: window.siyuan.config.keymap.editor.general.rename.custom,
|
||||
label: window.siyuan.languages.rename,
|
||||
click: () => {
|
||||
rename(options);
|
||||
}
|
||||
}).element;
|
||||
};
|
||||
|
||||
export const movePathToMenu = (notebookId: string, path: string) => {
|
||||
return new MenuItem({
|
||||
label: window.siyuan.languages.move,
|
||||
icon: "iconMove",
|
||||
accelerator: window.siyuan.config.keymap.general.move.custom,
|
||||
click() {
|
||||
movePathTo(notebookId, path);
|
||||
}
|
||||
}).element;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue