This commit is contained in:
Liang Ding 2022-05-26 15:18:53 +08:00
parent e650b8100c
commit f40ed985e1
No known key found for this signature in database
GPG key ID: 136F30F901A2231D
1214 changed files with 345766 additions and 9 deletions

271
app/src/util/Tree.ts Normal file
View file

@ -0,0 +1,271 @@
import {getIconByType} from "../editor/getIcon";
import {hasClosestByTag} from "../protyle/util/hasClosest";
import {isMobile} from "./functions";
import {mathRender} from "../protyle/markdown/mathRender";
export class Tree {
public element: HTMLElement;
private data: IBlockTree[];
private blockExtHTML: string;
private click: (element: HTMLElement, event: MouseEvent) => void;
private ctrlClick: (element: HTMLElement) => void;
private shiftClick: (element: HTMLElement) => void;
private altClick: (element: HTMLElement) => void;
private rightClick: (element: HTMLElement, event: MouseEvent) => void;
constructor(options: {
element: HTMLElement,
data: IBlockTree[],
blockExtHTML?: string,
click?(element: HTMLElement, event: MouseEvent): void
ctrlClick?(element: HTMLElement): void
altClick?(element: HTMLElement): void
shiftClick?(element: HTMLElement): void
rightClick?(element: HTMLElement, event: MouseEvent): void
}) {
this.click = options.click;
this.ctrlClick = options.ctrlClick;
this.altClick = options.altClick;
this.shiftClick = options.shiftClick;
this.rightClick = options.rightClick;
this.element = options.element;
this.blockExtHTML = options.blockExtHTML;
this.updateData(options.data);
this.bindEvent();
}
public updateData(data: IBlockTree[]) {
this.data = data;
if (!this.data || this.data.length === 0) {
this.element.innerHTML = `<ul class="b3-list b3-list--background"><li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li></ul>`;
} else {
this.element.innerHTML = this.genHTML(this.data);
mathRender(this.element);
}
}
private genHTML(data: IBlockTree[]) {
let html = `<ul${data[0].depth === 0 ? " class='b3-list b3-list--background'" : ""}>`;
data.forEach((item) => {
let iconHTML = '<svg class="b3-list-item__graphic"><use xlink:href="#iconFolder"></use></svg>';
if (item.type === "bookmark") {
iconHTML = '<svg class="b3-list-item__graphic"><use xlink:href="#iconBookmark"></use></svg>';
} else if (item.type === "tag") {
iconHTML = '<svg class="b3-list-item__graphic"><use xlink:href="#iconTags"></use></svg>';
} else if (item.type === "backlink") {
iconHTML = `<svg class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.nodeType, item.subType)}"></use></svg>`;
} else if (item.type === "NodeHeading") {
iconHTML = `<svg class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.type, item.subType)}"></use></svg>`
}
let countHTML = "";
if (item.count) {
countHTML = `<span class="counter">${item.count}</span>`;
}
html += `<li class="b3-list-item"
${(item.nodeType !== "NodeDocument" && item.type === "backlink") ? 'draggable="true"' : ""}
${item.id ? 'data-node-id="' + item.id + '"' : ""}
data-treetype="${item.type}"
data-type="${item.nodeType}"
data-subtype="${item.subType}"
${item.label ? "data-label='" + item.label + "'" : ""}>
<span style="padding-left: ${item.depth * 16}px" class="b3-list-item__toggle">
<svg data-id="${item.full || (item.name + item.depth)}" class="b3-list-item__arrow ${((item.children && item.children.length > 0) || (item.blocks && item.blocks.length > 0)) ? "b3-list-item__arrow--open" : "fn__hidden"}"><use xlink:href="#iconRight"></use></svg>
</span>
${iconHTML}
<span class="b3-list-item__text">${item.name}</span>
${countHTML}
</li>`;
if (item.children && item.children.length > 0) {
html += this.genHTML(item.children) + "</ul>";
}
if (item.blocks && item.blocks.length > 0) {
html += this.genBlockHTML(item.blocks, true, item.type) + "</ul>";
}
});
return html;
}
private genBlockHTML(data: IBlock[], show = false, type: string) {
let html = `<ul class="${!show ? "fn__none" : ""}">`;
data.forEach((item: IBlock & {
subType: string;
count: string;
}) => {
let countHTML = "";
if (item.count) {
countHTML = `<span class="counter">${item.count}</span>`;
}
html += `<li ${type === "backlink" ? 'draggable="true"' : ""}
class="b3-list-item ${isMobile() ? "" : "b3-list-item--hide-action"}"
data-node-id="${item.id}"
data-ref-text="${encodeURIComponent(item.refText)}"
data-def-id="${item.defID}"
data-type="${item.type}"
data-subtype="${item.subType}"
data-treetype="${type}"
data-def-path="${item.defPath}">
<span style="padding-left: ${item.depth * 16}px" class="b3-list-item__toggle">
<svg data-id="${item.id}" class="b3-list-item__arrow${item.children ? "" : " fn__hidden"}"><use xlink:href="#iconRight"></use></svg>
</span>
<svg data-defids='["${item.defID}"]' class="b3-list-item__graphic popover__block" data-id="${item.id}"><use xlink:href="#${getIconByType(item.type, item.subType)}"></use></svg>
<span class="b3-list-item__text">${item.content}</span>
${countHTML}
${this.blockExtHTML || ""}
</li>`;
if (item.children && item.children.length > 0) {
html += this.genBlockHTML(item.children, false, type) + "</ul>";
}
});
return html;
}
private toggleBlocks(liElement: HTMLElement) {
if (!liElement.nextElementSibling) {
return;
}
const svgElement = liElement.firstElementChild.firstElementChild;
if (svgElement.classList.contains("b3-list-item__arrow--open")) {
svgElement.classList.remove("b3-list-item__arrow--open");
liElement.nextElementSibling.classList.add("fn__none");
if (liElement.nextElementSibling.nextElementSibling && liElement.nextElementSibling.nextElementSibling.tagName === "UL") {
liElement.nextElementSibling.nextElementSibling.classList.add("fn__none");
}
} else {
svgElement.classList.add("b3-list-item__arrow--open");
liElement.nextElementSibling.classList.remove("fn__none");
if (liElement.nextElementSibling.nextElementSibling && liElement.nextElementSibling.nextElementSibling.tagName === "UL") {
liElement.nextElementSibling.nextElementSibling.classList.remove("fn__none");
}
}
}
private setCurrent(target: HTMLElement) {
if (target.classList.contains("b3-list--empty")) {
return;
}
this.element.querySelectorAll("li").forEach((liItem) => {
liItem.classList.remove("b3-list-item--focus");
});
target.classList.add("b3-list-item--focus");
}
private bindEvent() {
this.element.addEventListener("contextmenu", (event) => {
let target = event.target as HTMLElement;
while (target && !target.isEqualNode(this.element)) {
if (target.tagName === "LI" && this.rightClick) {
this.rightClick(target, event);
event.preventDefault();
event.stopPropagation();
break;
}
target = target.parentElement;
}
});
this.element.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
let target = event.target as HTMLElement;
while (target && !target.isEqualNode(this.element)) {
if (target.classList.contains("b3-list-item__toggle") && !target.firstElementChild.classList.contains("fn__hidden")) {
this.toggleBlocks(target.parentElement);
this.setCurrent(target.parentElement);
event.preventDefault();
break;
}
if (target.tagName === "LI") {
this.setCurrent(target);
if (target.getAttribute("data-node-id") || target.getAttribute("data-treetype") === "tag") {
if (this.ctrlClick && window.siyuan.ctrlIsPressed) {
this.ctrlClick(target);
} else if (this.altClick && window.siyuan.altIsPressed) {
this.altClick(target);
} else if (this.shiftClick && window.siyuan.shiftIsPressed) {
this.shiftClick(target);
} else if (this.click) {
this.click(target, event);
}
event.stopPropagation();
} else {
this.toggleBlocks(target);
}
event.preventDefault();
break;
}
target = target.parentElement;
}
});
this.element.addEventListener("dragstart", (event: DragEvent & { target: HTMLElement }) => {
const liElement = hasClosestByTag(event.target, "LI");
if (liElement) {
event.dataTransfer.setData("text/html", liElement.outerHTML);
// event.dataTransfer.setData(Constants.SIYUAN_DROP_FILE, liElement.parentElement);
event.dataTransfer.dropEffect = "move";
liElement.style.opacity = "0.1";
window.siyuan.dragElement = liElement;
}
});
this.element.addEventListener("dragend", (event: DragEvent & { target: HTMLElement }) => {
const liElement = hasClosestByTag(event.target, "LI");
if (liElement) {
liElement.style.opacity = "1";
}
window.siyuan.dragElement = undefined;
});
}
public expandAll() {
this.element.querySelectorAll("ul").forEach(item => {
if (!item.classList.contains("b3-list")) {
item.classList.remove("fn__none");
}
});
this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => {
item.classList.add("b3-list-item__arrow--open");
});
}
public collapseAll(isFirst = false) {
this.element.querySelectorAll("ul").forEach(item => {
if (!item.classList.contains("b3-list")) {
if (isFirst && item.parentElement.classList.contains("b3-list")) {
// 第一层级不进行缩放
} else {
item.classList.add("fn__none");
}
}
});
this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => {
if (isFirst && item.parentElement.parentElement.parentElement.classList.contains("b3-list")) {
// 第一层级不进行缩放
} else {
item.classList.remove("b3-list-item__arrow--open");
}
});
}
public getExpandIds() {
const ids: string[] = [];
this.element.querySelectorAll(".b3-list-item__arrow--open").forEach(item => {
ids.push(item.getAttribute("data-id"));
});
return ids;
}
public setExpandIds(ids: string[]) {
this.element.querySelectorAll(".b3-list-item__arrow").forEach(item => {
if (ids.includes(item.getAttribute("data-id"))) {
item.classList.add("b3-list-item__arrow--open");
if (item.parentElement.parentElement.nextElementSibling) {
item.parentElement.parentElement.nextElementSibling.classList.remove("fn__none");
}
} else {
item.classList.remove("b3-list-item__arrow--open");
if (item.parentElement.parentElement.nextElementSibling && item.parentElement.parentElement.nextElementSibling.tagName === "UL") {
item.parentElement.parentElement.nextElementSibling.classList.add("fn__none");
}
}
});
}
}

107
app/src/util/assets.ts Normal file
View file

@ -0,0 +1,107 @@
import {Constants} from "../constants";
import {addScript} from "../protyle/util/addScript";
import {addStyle} from "../protyle/util/addStyle";
import {setCodeTheme} from "../protyle/ui/setCodeTheme";
import {isMobile} from "./functions";
import {getAllModels} from "../layout/getAll";
export const loadAssets = (data: IAppearance) => {
const defaultStyleElement = document.getElementById("themeDefaultStyle");
let defaultThemeAddress = `/appearance/themes/${data.mode === 1 ? "midnight" : "daylight"}/${data.customCSS ? "custom" : "theme"}.css?v=${data.customCSS ? new Date().getTime() : Constants.SIYUAN_VERSION}`;
if ((data.mode === 1 && data.themeDark !== "midnight") || (data.mode === 0 && data.themeLight !== "daylight")) {
defaultThemeAddress = `/appearance/themes/${data.mode === 1 ? "midnight" : "daylight"}/theme.css?v=${Constants.SIYUAN_VERSION}`;
}
if (defaultStyleElement) {
if (!defaultStyleElement.getAttribute("href").startsWith(defaultThemeAddress)) {
defaultStyleElement.remove();
addStyle(defaultThemeAddress, "themeDefaultStyle");
}
} else {
addStyle(defaultThemeAddress, "themeDefaultStyle");
}
const styleElement = document.getElementById("themeStyle");
if ((data.mode === 1 && data.themeDark !== "midnight") || (data.mode === 0 && data.themeLight !== "daylight")) {
const themeAddress = `/appearance/themes/${data.mode === 1 ? data.themeDark : data.themeLight}/${data.customCSS ? "custom" : "theme"}.css?v=${data.customCSS ? new Date().getTime() : data.themeVer}`;
if (styleElement) {
if (!styleElement.getAttribute("href").startsWith(themeAddress)) {
styleElement.remove();
addStyle(themeAddress, "themeStyle");
}
} else {
addStyle(themeAddress, "themeStyle");
}
} else if (styleElement) {
styleElement.remove();
}
if (!isMobile()) {
getAllModels().graph.forEach(item => {
item.searchGraph(false);
});
}
setCodeTheme();
const themeScriptElement = document.getElementById("themeScript");
const themeScriptAddress = `/appearance/themes/${data.mode === 1 ? data.themeDark : data.themeLight}/theme.js?v=${data.themeVer}`;
if (themeScriptElement) {
if (!themeScriptElement.getAttribute("src").startsWith(themeScriptAddress)) {
themeScriptElement.remove();
addScript(themeScriptAddress, "themeScript");
}
} else {
addScript(themeScriptAddress, "themeScript");
}
const scriptElement = document.getElementById("iconScript");
const iconURL = `/appearance/icons/${data.icon}/icon.js?v=${data.iconVer}`;
if (scriptElement) {
if (!scriptElement.getAttribute("src").startsWith(iconURL)) {
scriptElement.remove();
addScript(iconURL, "iconScript");
}
} else {
addScript(iconURL, "iconScript");
}
};
export const initAssets = () => {
const emojiElement = document.getElementById("emojiScript");
const loadingElement = document.getElementById("loading");
if (!emojiElement && !window.siyuan.config.appearance.nativeEmoji && !isMobile()) {
addScript("/appearance/emojis/twitter-emoji.js?v=1.0.0", "emojiScript").then(() => {
if (loadingElement) {
loadingElement.remove();
}
});
} else if (loadingElement) {
setTimeout(() => {
loadingElement.remove();
}, 160);
}
};
export const setInlineStyle = (set = true) => {
const height = Math.floor(window.siyuan.config.editor.fontSize * 1.625);
let style = `.b3-typography, .protyle-wysiwyg, .protyle-title {font-size:${window.siyuan.config.editor.fontSize}px !important}
.b3-typography code:not(.hljs), .protyle-wysiwyg code:not(.hljs) { font-variant-ligatures: ${window.siyuan.config.editor.codeLigatures ? "normal" : "none"} }
.li > .protyle-action {height:${height + 8}px;line-height: ${height + 8}px}
.protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h1, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h2, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h3, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h4, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h5, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h6 {line-height:${height + 8}px;}
.protyle-wysiwyg [data-node-id].li > .protyle-action:after {height: ${window.siyuan.config.editor.fontSize}px;width: ${window.siyuan.config.editor.fontSize}px;margin:-${window.siyuan.config.editor.fontSize / 2}px 0 0 -${window.siyuan.config.editor.fontSize / 2}px}
.protyle-wysiwyg [data-node-id].li > .protyle-action svg {height: ${Math.max(14, window.siyuan.config.editor.fontSize - 8)}px}
.protyle-wysiwyg [data-node-id] [spellcheck="false"] {min-height:${height}px}
.protyle-wysiwyg .li {min-height:${height + 8}px}
.protyle-gutters button svg {height:${height}px}
.protyle-wysiwyg img.emoji, .b3-typography img.emoji {width:${height - 8}px}
.protyle-wysiwyg .h1 img.emoji, .b3-typography h1 img.emoji {width:${Math.floor(window.siyuan.config.editor.fontSize * 1.75 * 1.25)}px}
.protyle-wysiwyg .h2 img.emoji, .b3-typography h2 img.emoji {width:${Math.floor(window.siyuan.config.editor.fontSize * 1.55 * 1.25)}px}
.protyle-wysiwyg .h3 img.emoji, .b3-typography h3 img.emoji {width:${Math.floor(window.siyuan.config.editor.fontSize * 1.38 * 1.25)}px}
.protyle-wysiwyg .h4 img.emoji, .b3-typography h4 img.emoji {width:${Math.floor(window.siyuan.config.editor.fontSize * 1.25 * 1.25)}px}
.protyle-wysiwyg .h5 img.emoji, .b3-typography h5 img.emoji {width:${Math.floor(window.siyuan.config.editor.fontSize * 1.13 * 1.25)}px}
.protyle-wysiwyg .h6 img.emoji, .b3-typography h6 img.emoji {width:${Math.floor(window.siyuan.config.editor.fontSize * 1.25)}px}`;
if (window.siyuan.config.editor.fontFamily) {
style += `.b3-typography, .protyle-wysiwyg, .protyle-title, .protyle-title__input{font-family: "${window.siyuan.config.editor.fontFamily}", "quote", "Helvetica Neue", "Luxi Sans", "DejaVu Sans", "Hiragino Sans GB", "Microsoft Yahei", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", "Segoe UI Symbol", "Android Emoji", "EmojiSymbols" !important;}`;
}
if (set) {
document.getElementById("editorFontSize").innerHTML = style;
}
return style;
};

280
app/src/util/backForward.ts Normal file
View file

@ -0,0 +1,280 @@
import {hasClosestBlock, hasClosestByAttribute} from "../protyle/util/hasClosest";
import {getContenteditableElement} from "../protyle/wysiwyg/getBlock";
import {focusByOffset, focusByRange, getSelectionOffset} from "../protyle/util/selection";
import {hideElements} from "../protyle/ui/hideElements";
import {fetchPost, fetchSyncPost} from "./fetch";
import {Constants} from "../constants";
import {Wnd} from "../layout/Wnd";
import {getInstanceById, getWndByLayout} from "../layout/util";
import {Tab} from "../layout/Tab";
import {Editor} from "../editor";
import {onGet} from "../protyle/util/onGet";
import {scrollCenter} from "./highlightById";
import {lockFile} from "../dialog/processSystem";
import {zoomOut} from "../menus/protyle";
let forwardStack: IBackStack[] = [];
let previousIsBack = false;
const focusStack = async (stack: IBackStack) => {
hideElements(["gutter", "toolbar", "hint", "util", "dialog"], stack.protyle);
let blockElement: Element;
if (!stack.protyle.element.parentElement) {
const response = await fetchSyncPost("/api/block/checkBlockExist", {id: stack.protyle.block.rootID});
if (!response.data) {
// 页签删除
return false;
}
let wnd: Wnd;
// 获取光标所在 tab
const element = document.querySelector(".layout__wnd--active");
if (element) {
wnd = getInstanceById(element.getAttribute("data-id")) as Wnd;
}
if (!wnd) {
// 中心 tab
wnd = getWndByLayout(window.siyuan.layout.centerLayout);
}
if (wnd) {
const info = await fetchSyncPost("/api/block/getBlockInfo", {id: stack.id});
if (info.code === 2) {
// 文件被锁定
lockFile(info.data);
return false;
}
const tab = new Tab({
title: info.data.rootTitle,
docIcon: info.data.rootIcon,
callback(tab) {
const editor = new Editor({
tab,
blockId: info.data.rootChildID,
});
tab.addModel(editor);
}
});
wnd.addTab(tab);
wnd.showHeading();
// 页签关闭
setTimeout(() => {
const protyle = (tab.model as Editor).editor.protyle;
forwardStack.find(item => {
if (!item.protyle.element.parentElement && item.protyle.block.rootID === protyle.block.rootID) {
item.protyle = protyle;
}
});
window.siyuan.backStack.find(item => {
if (!item.protyle.element.parentElement && item.protyle.block.rootID === protyle.block.rootID) {
item.protyle = protyle;
}
});
if (protyle.block.rootID === stack.id) {
focusByOffset(protyle.title.editElement, stack.position.start, stack.position.end);
} else {
Array.from(protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${stack.id}"]`)).find(item => {
if (!hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
blockElement = item;
return true;
}
});
focusByOffset(blockElement, stack.position.start, stack.position.end);
scrollCenter(protyle, blockElement);
}
}, 500);
return true;
} else {
return false;
}
}
if (stack.protyle.block.rootID === stack.id) {
if (stack.protyle.title.editElement.getBoundingClientRect().height === 0) {
// 切换 tab
stack.protyle.model.parent.parent.switchTab(stack.protyle.model.parent.headElement);
}
focusByOffset(stack.protyle.title.editElement, stack.position.start, stack.position.end);
return true;
}
Array.from(stack.protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${stack.id}"]`)).find(item => {
if (!hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
blockElement = item;
return true;
}
});
if (blockElement) {
if (blockElement.getBoundingClientRect().height === 0) {
// 切换 tab
stack.protyle.model.parent.parent.switchTab(stack.protyle.model.parent.headElement);
}
if (stack.isZoom) {
zoomOut(stack.protyle, stack.id, undefined, false);
return true;
}
if (blockElement && !stack.protyle.block.showAll) {
focusByOffset(blockElement, stack.position.start, stack.position.end);
scrollCenter(stack.protyle, blockElement);
return true;
}
// 缩放不一致
fetchPost("/api/filetree/getDoc", {
id: stack.id,
mode: stack.isZoom ? 0 : 3,
size: stack.isZoom ? Constants.SIZE_GET_MAX : Constants.SIZE_GET,
}, getResponse => {
onGet(getResponse, stack.protyle);
Array.from(stack.protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${stack.id}"]`)).find(item => {
if (!hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
blockElement = item;
return true;
}
});
focusByOffset(blockElement, stack.position.start, stack.position.end);
setTimeout(() => {
// 图片、视频等加载完成后再定位
scrollCenter(stack.protyle, blockElement, true);
}, Constants.TIMEOUT_BLOCKLOAD);
});
return true;
}
if (stack.protyle.element.parentElement) {
const response = await fetchSyncPost("/api/block/checkBlockExist", {id: stack.id});
if (!response.data) {
// 块被删除
if (getSelection().rangeCount > 0) {
focusByRange(getSelection().getRangeAt(0));
}
return false;
}
const info = await fetchSyncPost("/api/block/getBlockInfo", {id: stack.id});
if (info.code === 2) {
// 文件被锁定
lockFile(info.data);
return false;
}
fetchPost("/api/filetree/getDoc", {
id: info.data.rootChildID,
mode: stack.isZoom ? 0 : 3,
size: stack.isZoom ? Constants.SIZE_GET_MAX : Constants.SIZE_GET,
}, getResponse => {
onGet(getResponse, stack.protyle);
Array.from(stack.protyle.wysiwyg.element.querySelectorAll(`[data-node-id="${stack.id}"]`)).find(item => {
if (!hasClosestByAttribute(item, "data-type", "NodeBlockQueryEmbed")) {
blockElement = item;
return true;
}
});
focusByOffset(blockElement, stack.position.start, stack.position.end);
setTimeout(() => {
scrollCenter(stack.protyle, blockElement);
}, Constants.TIMEOUT_INPUT);
});
return true;
}
};
export const goBack = async () => {
if (window.siyuan.backStack.length === 0) {
if (forwardStack.length > 0) {
await focusStack(forwardStack[forwardStack.length - 1]);
}
return;
}
document.querySelector("#barForward").classList.remove("toolbar__item--disabled");
if (!previousIsBack) {
forwardStack.push(window.siyuan.backStack.pop());
}
let stack = window.siyuan.backStack.pop();
while (stack) {
const isFocus = await focusStack(stack);
if (isFocus) {
forwardStack.push(stack);
break;
} else {
stack = window.siyuan.backStack.pop();
}
}
if (window.siyuan.backStack.length === 0) {
document.querySelector("#barBack").classList.add("toolbar__item--disabled");
}
previousIsBack = true;
};
export const goForward = async () => {
if (forwardStack.length === 0) {
if (window.siyuan.backStack.length > 0) {
await focusStack(window.siyuan.backStack[window.siyuan.backStack.length - 1]);
}
return;
}
document.querySelector("#barBack").classList.remove("toolbar__item--disabled");
if (previousIsBack) {
window.siyuan.backStack.push(forwardStack.pop());
}
let stack = forwardStack.pop();
while (stack) {
const isFocus = await focusStack(stack);
if (isFocus) {
window.siyuan.backStack.push(stack);
break;
} else {
stack = forwardStack.pop();
}
}
if (forwardStack.length === 0) {
document.querySelector("#barForward").classList.add("toolbar__item--disabled");
}
previousIsBack = false;
};
export const pushBack = (protyle: IProtyle, range?: Range, blockElement?: Element) => {
if (!protyle.model) {
return;
}
if (!blockElement) {
blockElement = hasClosestBlock(range.startContainer) as Element;
}
if (!blockElement) {
return;
}
let editElement;
if (blockElement.classList.contains("protyle-title__input")) {
editElement = blockElement;
} else {
editElement = getContenteditableElement(blockElement);
}
if (editElement) {
const position = getSelectionOffset(editElement, undefined, range);
const id = blockElement.getAttribute("data-node-id") || protyle.block.rootID;
const lastStack = window.siyuan.backStack[window.siyuan.backStack.length - 1];
const isZoom = protyle.block.id !== protyle.block.rootID;
if (lastStack && lastStack.id === id && lastStack.isZoom === isZoom) {
lastStack.position = position;
} else {
if (forwardStack.length > 0) {
if (previousIsBack) {
window.siyuan.backStack.push(forwardStack.pop());
}
forwardStack = [];
document.querySelector("#barForward").classList.add("toolbar__item--disabled");
}
window.siyuan.backStack.push({
position,
id,
protyle,
isZoom
});
if (window.siyuan.backStack.length > Constants.SIZE_UNDO) {
window.siyuan.backStack.shift();
}
previousIsBack = false;
}
if (window.siyuan.backStack.length > 1) {
document.querySelector("#barBack").classList.remove("toolbar__item--disabled");
}
}
};

3
app/src/util/escape.ts Normal file
View file

@ -0,0 +1,3 @@
export const escapeHtml = (html: string) => {
return html.replace(/&/g, "&amp;").replace(/</g, "&lt;");
};

74
app/src/util/fetch.ts Normal file
View file

@ -0,0 +1,74 @@
import {Constants} from "../constants";
/// #if !BROWSER
import {ipcRenderer} from "electron";
/// #endif
import {processMessage} from "./processMessage";
import {kernelError} from "../dialog/processSystem";
export const fetchPost = (url: string, data?: any, cb?: (response: IWebSocketData) => void, headers?: IObject) => {
const init: RequestInit = {
method: "POST",
};
if (data) {
if (["/api/search/searchRefBlock", "/api/graph/getGraph", "/api/graph/getLocalGraph"].includes(url)) {
window.siyuan.reqIds[url] = new Date().getTime();
data.reqId = window.siyuan.reqIds[url];
}
if (data instanceof FormData) {
init.body = data;
} else {
init.body = JSON.stringify(data);
}
}
if (headers) {
init.headers = headers;
}
fetch(url, init).then((response) => {
return response.json();
}).then((response: IWebSocketData) => {
if (["/api/search/searchRefBlock", "/api/graph/getGraph", "/api/graph/getLocalGraph"].includes(url)) {
if (response.data.reqId && window.siyuan.reqIds[url] && window.siyuan.reqIds[url] > response.data.reqId) {
return;
}
}
if (processMessage(response) && cb) {
cb(response);
}
}).catch((e) => {
console.warn("fetch post error", e);
if (url === "/api/transactions" && e.message === "Failed to fetch") {
kernelError();
return;
}
/// #if !BROWSER
if (url === "/api/system/exit" || url === "/api/system/setWorkspaceDir" || (
url === "/api/system/setUILayout" && data.exit // 内核中断,点关闭处理
)) {
ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSETRAY);
ipcRenderer.send(Constants.SIYUAN_QUIT);
}
/// #endif
});
};
export const fetchSyncPost = async (url: string, data?: any) => {
const init: RequestInit = {
method: "POST",
};
if (data) {
init.body = JSON.stringify(data);
}
const res = await fetch(url, init);
const res2 = await res.json() as IWebSocketData;
processMessage(res2);
return res2;
};
export const fetchGet = (url: string, cb: (response: IWebSocketData | IEmoji[]) => void) => {
fetch(url).then((response) => {
return response.json();
}).then((response: IWebSocketData) => {
cb(response);
});
};

38
app/src/util/functions.ts Normal file
View file

@ -0,0 +1,38 @@
export const isMobile = () => {
return !document.getElementById("dockBottom");
};
export const getRandom = (min: number, max: number) => {
return Math.floor(Math.random() * (max - min + 1)) + min; //含最大值,含最小值
};
export const getSearch = (key: string, link = window.location.search) => {
if (link.indexOf("?") === -1) {
return "";
}
let value = "";
const data = link.split("?")[1].split("&");
data.find(item => {
const keyValue = item.split("=");
if (keyValue[0] === key) {
value = keyValue[1];
return true;
}
});
return value;
};
export const isBrowser = () => {
/// #if BROWSER
return true;
/// #else
return false;
/// #endif
};
export const isDynamicRef = (text: string) => {
return /^\(\(\d{14}-\w{7} '.*'\)\)$/.test(text);
};
export const isFileAnnotation = (text: string) => {
return /^<<assets\/.+\/\d{14}-\w{7} ".+">>$/.test(text);
};

3
app/src/util/genID.ts Normal file
View file

@ -0,0 +1,3 @@
export const genUUID = () => ([1e7].toString() + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
(parseInt(c, 10) ^ (window.crypto.getRandomValues(new Uint32Array(1))[0] & (15 >> (parseInt(c, 10) / 4)))).toString(16)
);

View file

@ -0,0 +1,808 @@
import {isCtrl, isMac, writeText} from "../protyle/util/compatibility";
import {matchHotKey} from "../protyle/util/hotKey";
import {openSearch} from "../search/spread";
import {
hasClosestBlock,
hasClosestByAttribute,
hasClosestByClassName, hasClosestByMatchTag,
hasTopClosestByClassName,
hasTopClosestByTag,
} from "../protyle/util/hasClosest";
import {newFile} from "./newFile";
import {Constants} from "../constants";
import {openSetting} from "../config";
import {getDockByType, getInstanceById, setPanelFocus} from "../layout/util";
import {Tab} from "../layout/Tab";
import {Editor} from "../editor";
import {setEditMode} from "../protyle/util/setEditMode";
import {rename} from "../editor/rename";
import {Files} from "../layout/dock/Files";
import {newDailyNote} from "./mount";
import {hideElements} from "../protyle/ui/hideElements";
import {fetchPost} from "./fetch";
import {goBack, goForward} from "./backForward";
import {onGet} from "../protyle/util/onGet";
import {getDisplayName, getNotebookName, movePathTo} from "./pathName";
import {confirmDialog} from "../dialog/confirmDialog";
import {openFileById} from "../editor/util";
import {getAllDocks, getAllModels, getAllTabs} from "../layout/getAll";
import {openGlobalSearch} from "../search/util";
import {getColIndex} from "../protyle/util/table";
import {focusBlock, focusByRange} from "../protyle/util/selection";
import {initFileMenu, initNavigationMenu} from "../menus/navigation";
import {bindMenuKeydown} from "../menus/Menu";
import {showMessage} from "../dialog/message";
import {openHistory} from "./history";
const getRightBlock = (element: HTMLElement, x: number, y: number) => {
let index = 1;
let nodeElement = element;
while (nodeElement && (nodeElement.classList.contains("list") || nodeElement.classList.contains("li"))) {
nodeElement = document.elementFromPoint(x + 73 * index, y) as HTMLElement;
nodeElement = hasClosestBlock(nodeElement) as HTMLElement;
index++;
}
return nodeElement;
};
export const globalShortcut = () => {
window.addEventListener("mousemove", (event) => {
if (window.siyuan.hideBreadcrumb) {
getAllModels().editor.forEach(item => {
item.editor.protyle.breadcrumb.show();
});
window.siyuan.blockPanels.forEach(item => {
item.editors.forEach(edit => {
edit.protyle.breadcrumb.show();
});
});
}
const eventPath0 = event.composedPath()[0] as HTMLElement;
if (eventPath0 && eventPath0.nodeType !== 3 && eventPath0.classList.contains("protyle-wysiwyg")) {
// 光标在编辑器右边也需要进行显示
const mouseElement = document.elementFromPoint(eventPath0.getBoundingClientRect().left + parseInt(eventPath0.style.paddingLeft) + 13, event.clientY);
const blockElement = hasClosestBlock(mouseElement);
if (blockElement) {
const targetBlockElement = getRightBlock(blockElement, blockElement.getBoundingClientRect().left + 1, event.clientY);
if (!targetBlockElement) {
return;
}
const hasTab = getAllModels().editor.find(item => {
if (item.editor.protyle.wysiwyg.element.isSameNode(eventPath0)) {
item.editor.protyle.gutter.render(targetBlockElement, item.editor.protyle.wysiwyg.element);
return true;
}
});
if (!hasTab) {
window.siyuan.blockPanels.find(item => {
const hasEdit = item.editors.find(eItem => {
if (eItem.protyle.wysiwyg.element.contains(eventPath0)) {
eItem.protyle.gutter.render(targetBlockElement, eItem.protyle.wysiwyg.element);
return true;
}
});
if (hasEdit) {
return true;
}
});
}
}
return;
}
if (eventPath0 && eventPath0.nodeType !== 3 && (eventPath0.classList.contains("li") || eventPath0.classList.contains("list"))) {
// 光标在列表下部应显示右侧的元素,而不是列表本身
const targetBlockElement = getRightBlock(eventPath0, eventPath0.getBoundingClientRect().left + 1, event.clientY);
if (!targetBlockElement) {
return;
}
const hasTab = getAllModels().editor.find(item => {
if (item.editor.protyle.wysiwyg.element.contains(eventPath0)) {
item.editor.protyle.gutter.render(targetBlockElement, item.editor.protyle.wysiwyg.element);
return true;
}
});
if (!hasTab) {
window.siyuan.blockPanels.find(item => {
const hasEdit = item.editors.find(eItem => {
if (eItem.protyle.wysiwyg.element.contains(eventPath0)) {
eItem.protyle.gutter.render(targetBlockElement, eItem.protyle.wysiwyg.element);
return true;
}
});
if (hasEdit) {
return true;
}
});
}
return;
}
const target = event.target as Element;
const blockElement = hasClosestBlock(target);
if (blockElement && blockElement.style.cursor !== "col-resize" && !hasClosestByClassName(blockElement, "protyle-wysiwyg__embed")) {
const cellElement = (hasClosestByMatchTag(target, "TH") || hasClosestByMatchTag(target, "TD")) as HTMLTableCellElement;
if (cellElement) {
const tableElement = blockElement.querySelector("table");
const tableHeight = blockElement.querySelector("table").clientHeight;
const resizeElement = blockElement.querySelector(".table__resize");
if (blockElement.style.textAlign === "center" || blockElement.style.textAlign === "right") {
resizeElement.parentElement.style.left = tableElement.offsetLeft + "px";
} else {
resizeElement.parentElement.style.left = "";
}
const rect = cellElement.getBoundingClientRect();
if (rect.right - event.clientX < 3 && rect.right - event.clientX > 0) {
resizeElement.setAttribute("data-col-index", (getColIndex(cellElement) + cellElement.colSpan - 1).toString());
resizeElement.setAttribute("style", `height:${tableHeight}px;left: ${Math.round(cellElement.offsetWidth + cellElement.offsetLeft - blockElement.firstElementChild.scrollLeft - 3)}px;display:block`);
} else if (event.clientX - rect.left < 3 && event.clientX - rect.left > 0 && cellElement.previousElementSibling) {
resizeElement.setAttribute("data-col-index", (getColIndex(cellElement) - 1).toString());
resizeElement.setAttribute("style", `height:${tableHeight}px;left: ${Math.round(cellElement.offsetLeft - blockElement.firstElementChild.scrollLeft - 3)}px;display:block`);
}
}
}
});
window.addEventListener("mouseup", (event) => {
if (event.button === 3) {
event.stopPropagation();
event.preventDefault();
goBack();
} else if (event.button === 4) {
event.stopPropagation();
event.preventDefault();
goForward();
}
});
window.addEventListener("keyup", (event) => {
window.siyuan.ctrlIsPressed = false;
window.siyuan.shiftIsPressed = false;
window.siyuan.altIsPressed = false;
if ((event.target as HTMLElement).tagName === "INPUT" || (event.target as HTMLElement).tagName === "TEXTAREA") {
return;
}
if (!hasClosestBlock(document.activeElement)) {
return;
}
});
window.addEventListener("keydown", (event) => {
if (document.getElementById("errorLog") || event.isComposing) {
return;
}
// 仅处理以下快捷键操作
if (!event.ctrlKey && !isCtrl(event) && event.key !== "Escape" && !event.shiftKey && !event.altKey &&
!/^F\d{1,2}$/.test(event.key) && event.key.indexOf("Arrow") === -1 && event.key !== "Enter" && event.key !== "Backspace" && event.key !== "Delete") {
return;
}
if (!event.altKey && !event.shiftKey && isCtrl(event)) {
if (event.key === "Meta" || event.key === "Control" || event.ctrlKey || event.metaKey) {
window.siyuan.ctrlIsPressed = true;
} else {
window.siyuan.ctrlIsPressed = false;
}
}
if (!event.altKey && event.shiftKey && !isCtrl(event)) {
if (event.key === "Shift") {
window.siyuan.shiftIsPressed = true;
} else {
window.siyuan.shiftIsPressed = false;
}
}
if (event.altKey && !event.shiftKey && !isCtrl(event)) {
if (event.key === "Alt") {
window.siyuan.altIsPressed = true;
} else {
window.siyuan.altIsPressed = false;
}
}
if (event.ctrlKey && !event.metaKey && event.key === "Tab") {
const allTabs = getAllTabs();
if (allTabs.length === 0) {
return;
}
let currentTabElement = document.querySelector(".layout__wnd--active .layout-tab-bar > .item--focus");
if (!currentTabElement) {
currentTabElement = allTabs[0].headElement.parentElement.querySelector(".item--focus");
if (!currentTabElement) {
return;
}
}
let currentIndex = 0;
allTabs.find((item, index) => {
if (item.id === currentTabElement.getAttribute("data-id")) {
currentIndex = index;
return true;
}
});
if (allTabs[currentIndex].model instanceof Editor) {
// range 已在新页面上才会触发 focusout导致无法记录原有页面的 range因此需要先记录一次。
(allTabs[currentIndex].model as Editor).editor.protyle.wysiwyg.element.dispatchEvent(new CustomEvent("focusout"));
}
if (event.shiftKey) {
if (currentIndex === 0) {
currentIndex = allTabs.length - 1;
} else {
currentIndex--;
}
} else {
if (currentIndex === allTabs.length - 1) {
currentIndex = 0;
} else {
currentIndex++;
}
}
allTabs[currentIndex].parent.switchTab(allTabs[currentIndex].headElement);
setPanelFocus(allTabs[currentIndex].headElement.parentElement.parentElement);
return;
}
if (event.key === "F9") {
document.getElementById("barSync").dispatchEvent(new Event("click"));
event.preventDefault();
return;
}
if (matchHotKey(window.siyuan.config.keymap.general.lockScreen.custom, event)) {
fetchPost("/api/system/logoutAuth", {}, () => {
window.location.href = "/";
});
event.preventDefault();
return;
}
if (!window.siyuan.config.readonly && matchHotKey(window.siyuan.config.keymap.general.history.custom, event)) {
openHistory();
event.preventDefault();
return;
}
if (!window.siyuan.config.readonly && matchHotKey(window.siyuan.config.keymap.general.config.custom, event)) {
openSetting();
event.preventDefault();
return;
}
// https://github.com/siyuan-note/insider/issues/445
if (matchHotKey("⌘S", event)) {
event.preventDefault();
event.stopPropagation();
return true;
}
const target = event.target as HTMLElement;
if (matchHotKey("⌘A", event) && target.tagName !== "INPUT" && target.tagName !== "TEXTAREA") {
event.preventDefault();
return;
}
const matchDock = getAllDocks().find(item => {
if (matchHotKey(window.siyuan.config.keymap.general[item.hotkeyLangId].custom, event)) {
getDockByType(item.type).toggleModel(item.type);
if (target.classList.contains("protyle-wysiwyg") ||
target.classList.contains("protyle-title__input") ||
target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
target.blur();
}
event.preventDefault();
return true;
}
});
if (matchDock) {
return;
}
if (matchHotKey(window.siyuan.config.keymap.general.dailyNote.custom, event)) {
newDailyNote();
if (target.classList.contains("protyle-wysiwyg") ||
target.classList.contains("protyle-title__input") ||
target.tagName === "INPUT" || target.tagName === "TEXTAREA") {
target.blur();
}
event.preventDefault();
return;
}
if (matchHotKey(window.siyuan.config.keymap.general.newFile.custom, event)) {
newFile(undefined, undefined, true);
event.preventDefault();
return;
}
if (event.key === "Escape" && !event.isComposing) {
const imgPreviewElement = document.querySelector(".protyle-img");
if (imgPreviewElement) {
imgPreviewElement.remove();
return;
}
if (!window.siyuan.menus.menu.element.classList.contains("fn__none")) {
window.siyuan.menus.menu.remove();
return;
}
if (window.siyuan.dialogs.length > 0) {
hideElements(["dialog"]);
return;
}
// remove blockpopover
const maxEditLevels: { [key: string]: number } = {oid: 0};
window.siyuan.blockPanels.forEach((item) => {
if (item.targetElement && item.element.getAttribute("data-pin") === "true") {
const level = parseInt(item.element.getAttribute("data-level"));
const oid = item.element.getAttribute("data-oid");
if (maxEditLevels[oid]) {
if (level > maxEditLevels[oid]) {
maxEditLevels[oid] = level;
}
} else {
maxEditLevels[oid] = 1;
}
}
});
let destroyBlock = false;
for (let i = 0; i < window.siyuan.blockPanels.length; i++) {
const item = window.siyuan.blockPanels[i];
if (item.targetElement && item.element.getAttribute("data-pin") === "false" &&
parseInt(item.element.getAttribute("data-level")) > (maxEditLevels[item.element.getAttribute("data-oid")] || 0)) {
item.destroy();
if (item.esc) {
item.esc();
}
destroyBlock = true;
i--;
}
}
if (destroyBlock) {
return;
}
// 光标在文档树等面板中,按 Esc 回到编辑器中 https://github.com/siyuan-note/siyuan/issues/4289
let range;
if (getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0);
const protypleElement = hasClosestByClassName(range.startContainer, "protyle-content", true);
if (protypleElement) {
focusByRange(range);
return;
}
} else {
range = document.createRange();
}
const lastBackStack = window.siyuan.backStack[window.siyuan.backStack.length - 1];
if (lastBackStack && lastBackStack.protyle.toolbar.range) {
focusByRange(lastBackStack.protyle.toolbar.range);
} else {
const editor = getAllModels().editor[0];
if (editor) {
focusBlock(editor.editor.protyle.wysiwyg.element.firstElementChild);
}
}
event.preventDefault();
return;
}
if (matchHotKey(window.siyuan.config.keymap.general.goForward.custom, event)) {
goForward();
event.stopPropagation();
event.preventDefault();
return;
}
if (matchHotKey(window.siyuan.config.keymap.general.goBack.custom, event)) {
goBack();
event.stopPropagation();
event.preventDefault();
return;
}
const confirmElement = document.querySelector("#confirmDialogConfirmBtn");
if (confirmElement && event.key === "Enter") {
confirmElement.dispatchEvent(new CustomEvent("click"));
event.preventDefault();
return;
}
// 面板折叠展开操作
if (matchHotKey("⌘↑", event) || matchHotKey("⌘↓", event)) {
let activePanelElement = document.querySelector(".block__icons--active");
if (!activePanelElement) {
Array.from(document.querySelectorAll(".layout__wnd--active .layout-tab-container > div")).forEach(item => {
if (!item.classList.contains("fn__none")) {
activePanelElement = item;
return true;
}
});
}
if (activePanelElement) {
if (matchHotKey("⌘↑", event)) {
if (activePanelElement.querySelector('.block__icon[data-type="collapse"]')) {
activePanelElement.querySelector('.block__icon[data-type="collapse"]').dispatchEvent(new CustomEvent("click"));
}
} else if (matchHotKey("⌘↓", event)) {
if (activePanelElement.querySelector('.block__icon[data-type="expand"]')) {
activePanelElement.querySelector('.block__icon[data-type="expand"]').dispatchEvent(new CustomEvent("click"));
}
}
}
event.stopPropagation();
event.preventDefault();
return;
}
// close tab
if (matchHotKey(window.siyuan.config.keymap.general.closeTab.custom, event)) {
event.preventDefault();
event.stopPropagation();
let activeTabElement = document.querySelector(".block__icons--active");
if (activeTabElement && activeTabElement.getBoundingClientRect().width > 0) {
const type = activeTabElement.parentElement.className.split("sy__")[1] as TDockType;
getDockByType(type).toggleModel(type, false, true);
return;
}
activeTabElement = document.querySelector(".layout__wnd--active .item--focus");
if (activeTabElement) {
const tab = getInstanceById(activeTabElement.getAttribute("data-id")) as Tab;
tab.parent.removeTab(tab.id);
return;
}
getAllTabs().find(item => {
if (item.headElement?.classList.contains("item--focus")) {
item.parent.removeTab(item.id);
return;
}
});
return;
}
if (matchHotKey(window.siyuan.config.keymap.general.stickSearch.custom, event)) {
if (getSelection().rangeCount > 0) {
const range = getSelection().getRangeAt(0);
openGlobalSearch(range.toString(), false);
} else {
openGlobalSearch("", false);
}
event.preventDefault();
return;
}
if (editKeydown(event)) {
return;
}
// 文件树的操作
if (fileTreeKeydown(event)) {
return;
}
let searchKey = "";
if (matchHotKey(window.siyuan.config.keymap.general.replace.custom, event)) {
searchKey = window.siyuan.config.keymap.general.replace.custom;
} else if (!hasClosestByClassName(target, "pdf__outer") && matchHotKey(window.siyuan.config.keymap.general.search.custom, event)) {
searchKey = window.siyuan.config.keymap.general.search.custom;
} else if (matchHotKey(window.siyuan.config.keymap.general.globalSearch.custom, event)) {
searchKey = window.siyuan.config.keymap.general.globalSearch.custom;
}
if (searchKey) {
if (getSelection().rangeCount > 0) {
const range = getSelection().getRangeAt(0);
openSearch(searchKey, range.toString());
} else {
openSearch(searchKey);
}
event.preventDefault();
return;
}
});
window.addEventListener("blur", () => {
window.siyuan.ctrlIsPressed = false;
window.siyuan.shiftIsPressed = false;
window.siyuan.altIsPressed = false;
});
window.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => {
if (!window.siyuan.menus.menu.element.contains(event.target) && !hasClosestByAttribute(event.target, "data-menu", "true")) {
window.siyuan.menus.menu.remove();
}
if (!hasClosestByClassName(event.target, "pdf__outer")) {
document.querySelectorAll(".pdf__util").forEach(item => {
item.classList.add("fn__none");
});
}
const copyElement = hasTopClosestByClassName(event.target, "protyle-action__copy");
if (copyElement) {
writeText(copyElement.parentElement.nextElementSibling.textContent.trimEnd());
showMessage(window.siyuan.languages.copied, 2000);
event.preventDefault();
event.stopPropagation();
}
// 点击空白pdf 搜索、更多消失
if (hasClosestByAttribute(event.target, "id", "secondaryToolbarToggle") ||
hasClosestByAttribute(event.target, "id", "viewFind") ||
hasClosestByAttribute(event.target, "id", "findbar")) {
return;
}
let currentPDFViewerObject: any;
getAllModels().asset.find(item => {
if (item.pdfObject &&
!item.pdfObject.appConfig.appContainer.classList.contains("fn__none")) {
currentPDFViewerObject = item.pdfObject;
return true;
}
});
if (!currentPDFViewerObject) {
return;
}
if (currentPDFViewerObject.secondaryToolbar.isOpen) {
currentPDFViewerObject.secondaryToolbar.close();
}
if (
!currentPDFViewerObject.supportsIntegratedFind &&
currentPDFViewerObject.findBar.opened
) {
currentPDFViewerObject.findBar.close();
}
});
};
const editKeydown = (event: KeyboardEvent) => {
const activeTabElement = document.querySelector(".layout__wnd--active .item--focus");
let protyle: IProtyle;
if (activeTabElement) {
const tab = getInstanceById(activeTabElement.getAttribute("data-id")) as Tab;
if (!(tab.model instanceof Editor)) {
return false;
}
protyle = tab.model.editor.protyle;
} else {
const editor = getAllModels().editor.find(item => {
if (item.parent.headElement.classList.contains("item--focus")) {
return true;
}
});
if (!editor) {
return false;
}
protyle = editor.editor.protyle;
}
const activePanelElement = document.querySelector(".block__icons--active");
let isFileFocus = false;
if (activePanelElement && activePanelElement.parentElement.classList.contains("sy__file")) {
isFileFocus = true;
}
let searchKey = "";
if (matchHotKey(window.siyuan.config.keymap.general.replace.custom, event)) {
searchKey = window.siyuan.config.keymap.general.replace.custom;
} else if (matchHotKey(window.siyuan.config.keymap.general.search.custom, event)) {
searchKey = window.siyuan.config.keymap.general.search.custom;
}
if (!isFileFocus && searchKey) {
let range: Range;
if (getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0);
}
if (range && protyle.element.contains(range.startContainer)) {
openSearch(searchKey, range.toString(), protyle.notebookId, protyle.path);
} else {
openSearch(searchKey);
}
event.preventDefault();
return true;
}
if (!isFileFocus && matchHotKey(window.siyuan.config.keymap.general.move.custom, event)) {
let range: Range;
let nodeElement: false | HTMLElement;
if (getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0);
nodeElement = hasClosestBlock(range.startContainer);
}
if (nodeElement && range && protyle.element.contains(range.startContainer)) {
protyle.toolbar.showFile(protyle, [nodeElement], range);
} else {
movePathTo(protyle.notebookId, protyle.path);
}
event.preventDefault();
return true;
}
const target = event.target as HTMLElement;
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || hasClosestByAttribute(target, "contenteditable", null)) {
return false;
}
if (matchHotKey(window.siyuan.config.keymap.editor.general.preview.custom, event)) {
setEditMode(protyle, "preview");
event.preventDefault();
return true;
}
if (matchHotKey(window.siyuan.config.keymap.editor.general.wysiwyg.custom, event)) {
setEditMode(protyle, "wysiwyg");
protyle.scroll.lastScrollTop = 0;
fetchPost("/api/filetree/getDoc", {
id: protyle.block.parentID,
size: Constants.SIZE_GET,
}, getResponse => {
onGet(getResponse, protyle);
});
event.preventDefault();
return true;
}
// 没有光标时,无法撤销 https://ld246.com/article/1624021111567
if (matchHotKey(window.siyuan.config.keymap.editor.general.undo.custom, event)) {
protyle.undo.undo(protyle);
event.preventDefault();
return true;
}
if (matchHotKey(window.siyuan.config.keymap.editor.general.redo.custom, event)) {
protyle.undo.redo(protyle);
event.preventDefault();
return true;
}
return false;
};
const fileTreeKeydown = (event: KeyboardEvent) => {
const dockFile = getDockByType("file");
if (!dockFile) {
return false;
}
const files = dockFile.data.file as Files;
if (!files.element.previousElementSibling.classList.contains("block__icons--active")) {
return false;
}
let liElement = files.element.querySelector(".b3-list-item--focus");
if (!liElement) {
if (event.key.startsWith("Arrow")) {
liElement = files.element.querySelector(".b3-list-item");
if (liElement) {
liElement.classList.add("b3-list-item--focus");
}
event.preventDefault();
}
return false;
}
const topULElement = hasTopClosestByTag(liElement, "UL");
if (!topULElement) {
return false;
}
const notebookId = topULElement.getAttribute("data-url");
const pathString = liElement.getAttribute("data-path");
const isFile = liElement.getAttribute("data-type") === "navigation-file";
if (matchHotKey(window.siyuan.config.keymap.editor.general.rename.custom, event)) {
rename({
notebookId,
path: pathString,
name: isFile ? getDisplayName(liElement.getAttribute("data-name"), false, true) : getNotebookName(notebookId),
type: isFile ? "file" : "notebook",
});
return true;
}
if (matchHotKey("⌘/", event)) {
const liRect = liElement.getBoundingClientRect();
if (isFile) {
initFileMenu(notebookId, pathString, liElement.getAttribute("data-node-id"), liElement.getAttribute("data-name")).popup({
x: liRect.right - 15,
y: liRect.top + 15
});
} else {
initNavigationMenu(liElement as HTMLElement).popup({x: liRect.right - 15, y: liRect.top + 15});
}
return true;
}
if (isFile && matchHotKey(window.siyuan.config.keymap.general.move.custom, event)) {
movePathTo(notebookId, pathString, false);
event.preventDefault();
return true;
}
let searchKey = "";
if (matchHotKey(window.siyuan.config.keymap.general.replace.custom, event)) {
searchKey = window.siyuan.config.keymap.general.replace.custom;
} else if (matchHotKey(window.siyuan.config.keymap.general.search.custom, event)) {
searchKey = window.siyuan.config.keymap.general.search.custom;
}
if (searchKey) {
if (isFile) {
openSearch(searchKey, undefined, notebookId, getDisplayName(pathString, false, true));
} else {
openSearch(searchKey, undefined, notebookId);
}
event.preventDefault();
return true;
}
const target = event.target as HTMLElement;
if (target.tagName === "INPUT" || target.tagName === "TEXTAREA" || hasClosestByAttribute(target, "contenteditable", null)) {
return false;
}
if (bindMenuKeydown(event)) {
event.stopPropagation();
event.preventDefault();
return true;
}
if (event.key === "ArrowDown") {
let nextElement = liElement;
while (nextElement) {
if (nextElement.nextElementSibling) {
if (nextElement.nextElementSibling.tagName === "UL") {
nextElement = nextElement.nextElementSibling.firstElementChild;
} else {
nextElement = nextElement.nextElementSibling;
}
break;
} else {
if (nextElement.parentElement.classList.contains("fn__flex-1")) {
break;
} else {
nextElement = nextElement.parentElement;
}
}
}
if (nextElement.classList.contains("b3-list-item")) {
liElement.classList.remove("b3-list-item--focus");
nextElement.classList.add("b3-list-item--focus");
}
event.preventDefault();
return true;
}
if (event.key === "ArrowUp") {
let previousElement = liElement;
while (previousElement) {
if (previousElement.previousElementSibling) {
if (previousElement.previousElementSibling.tagName === "LI") {
previousElement = previousElement.previousElementSibling;
} else {
const liElements = previousElement.previousElementSibling.querySelectorAll(".b3-list-item");
previousElement = liElements[liElements.length - 1];
}
break;
} else {
if (previousElement.parentElement.classList.contains("fn__flex-1")) {
break;
} else {
previousElement = previousElement.parentElement;
}
}
}
if (previousElement.classList.contains("b3-list-item")) {
liElement.classList.remove("b3-list-item--focus");
previousElement.classList.add("b3-list-item--focus");
}
event.preventDefault();
return true;
}
if (event.key === "ArrowRight" || event.key === "ArrowLeft") {
files.getLeaf(liElement, notebookId);
event.preventDefault();
return true;
}
if (event.key === "Delete" || (event.key === "Backspace" && isMac())) {
if (isFile) {
fetchPost("/api/block/getDocInfo", {
id: getDisplayName(pathString, true, true)
}, (response) => {
const name = getDisplayName(liElement.getAttribute("data-name"), false, true);
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
});
});
});
} else {
confirmDialog(window.siyuan.languages.delete,
`${window.siyuan.languages.confirmDelete} <b>${Lute.EscapeHTMLStr(getNotebookName(notebookId))}</b>?`, () => {
fetchPost("/api/notebook/removeNotebook", {
notebook: notebookId,
callback: Constants.CB_MOUNT_REMOVE
});
});
}
return true;
}
if (event.key === "Enter") {
if (isFile) {
openFileById({id: liElement.getAttribute("data-node-id"), action: [Constants.CB_GET_FOCUS]});
} else {
files.getLeaf(liElement, notebookId);
}
return true;
}
};

View file

@ -0,0 +1,75 @@
import {hasClosestBlock, hasClosestByAttribute} from "../protyle/util/hasClosest";
import {getEditorRange, getSelectionPosition} from "../protyle/util/selection";
const bgFade = (element: HTMLElement) => {
element.classList.add("protyle-wysiwyg--hl");
setTimeout(function () {
element.classList.remove("protyle-wysiwyg--hl");
}, 1024);
};
export const highlightById = (protyle: IProtyle, id: string, top = false) => {
let nodeElement: HTMLElement;
const protyleElement = protyle.wysiwyg.element;
if (!protyle.preview.element.classList.contains("fn__none")) {
// 预览定位
nodeElement = document.getElementById(id);
if (nodeElement) {
protyle.preview.element.scrollTop = nodeElement.offsetTop;
bgFade(nodeElement);
}
return undefined;
}
Array.from(protyleElement.querySelectorAll(`[data-node-id="${id}"]`)).find((item: HTMLElement) => {
if (!hasClosestByAttribute(item, "data-type", "block-render", true)) {
nodeElement = item;
return true;
}
});
if (nodeElement) {
scrollCenter(protyle, nodeElement, top);
bgFade(nodeElement);
return nodeElement;// 仅配合前进后退使用
}
if (id === protyle.block.rootID && protyle.options.render.title) {
bgFade(protyle.title.editElement);
return protyle.title.editElement;
}
};
export const scrollCenter = (protyle: IProtyle, nodeElement?: Element, top = false) => {
if (!top && getSelection().rangeCount > 0 && hasClosestBlock(getSelection().getRangeAt(0).startContainer)) {
const editorElement = protyle.contentElement;
const cursorTop = getSelectionPosition(editorElement).top - editorElement.getBoundingClientRect().top;
if (cursorTop < 0) {
editorElement.scrollTop = editorElement.scrollTop + cursorTop;
} else if (cursorTop > editorElement.clientHeight - 34) {
editorElement.scrollTop = editorElement.scrollTop + (cursorTop + 34 - editorElement.clientHeight);
}
return;
}
if (!nodeElement) {
nodeElement = hasClosestBlock(getEditorRange(protyle.wysiwyg.element).startContainer) as HTMLElement;
}
if (!nodeElement) {
return;
}
let offsetTop = 0;
let parentNodeElement = nodeElement;
while (!parentNodeElement.classList.contains("protyle-wysiwyg")) {
offsetTop += (parentNodeElement as HTMLElement).offsetTop;
parentNodeElement = parentNodeElement.parentElement;
}
if (top) {
protyle.contentElement.scrollTop = offsetTop - 32;
return;
}
if (protyle.contentElement.scrollTop > offsetTop - 32) {
protyle.contentElement.scrollTop = offsetTop - 32;
} else if (protyle.contentElement.scrollTop + protyle.contentElement.clientHeight < offsetTop + nodeElement.clientHeight - 32) {
protyle.contentElement.scrollTop = offsetTop + nodeElement.clientHeight - 32 - protyle.contentElement.clientHeight;
}
};

306
app/src/util/history.ts Normal file
View file

@ -0,0 +1,306 @@
import {Dialog} from "../dialog";
import {confirmDialog} from "../dialog/confirmDialog";
import {fetchPost} from "./fetch";
import {Constants} from "../constants";
import {MenuItem} from "../menus/Menu";
import {unicode2Emoji} from "../emoji";
import {escapeHtml} from "./escape";
const renderDoc = (notebook: INotebook, element: HTMLElement) => {
if (!notebook || !notebook.id) {
return;
}
fetchPost("/api/history/getDocHistory", {
notebook: notebook.id
}, (response) => {
const switchHTML = `<li style="background-color: var(--b3-theme-background)" data-type="switchNotebook" data-menu="true" class="b3-list-item">
<span class="b3-list-item__icon">${unicode2Emoji(notebook.icon || Constants.SIYUAN_IMAGE_NOTE)}</span>
<span class="b3-list-item__text">${escapeHtml(notebook.name)}</span>
<span class="b3-list-item__action" data-type="switchNotebook">
<svg style="height: 10px"><use xlink:href="#iconDown"></use></svg>
</span>
</li>`;
if (response.data.histories.length === 0) {
element.lastElementChild.innerHTML = "";
element.firstElementChild.innerHTML = `${switchHTML}<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
return;
}
let logsHTML = switchHTML;
response.data.histories.forEach((item: { items: { path: string, title: string }[], time: string }, index: number) => {
logsHTML += `<li class="b3-list-item" style="padding-left: 0">
<span style="padding-left: 8px" class="b3-list-item__toggle"><svg class="b3-list-item__arrow${index === 0 ? " b3-list-item__arrow--open" : ""}${item.items.length > 0 ? "" : " fn__hidden"}"><use xlink:href="#iconRight"></use></svg></span>
<span class="b3-list-item__text">${item.time}</span>
</li>`;
if (item.items.length > 0) {
logsHTML += `<ul class="${index === 0 ? "" : "fn__none"}">`;
item.items.forEach((docItem, docIndex) => {
logsHTML += `<li title="${escapeHtml(docItem.title)}" data-type="doc" data-path="${docItem.path}" class="b3-list-item b3-list-item--hide-action${(index === 0 && docIndex === 0) ? " b3-list-item--focus" : ""}" style="padding-left: 32px">
<span class="b3-list-item__text">${escapeHtml(docItem.title)}</span>
<span class="fn__space"></span>
<span class="b3-list-item__action b3-tooltips b3-tooltips__w" aria-label="${window.siyuan.languages.rollback}">
<svg><use xlink:href="#iconUndo"></use></svg>
</span>
</li>`;
});
logsHTML += "</ul>";
if (index === 0) {
fetchPost("/api/history/getDocHistoryContent", {
historyPath: item.items[0].path
}, (response) => {
element.lastElementChild.innerHTML = response.data.content;
});
}
}
});
element.firstElementChild.innerHTML = logsHTML;
});
};
const renderAssets = (element: HTMLElement) => {
element.setAttribute("data-init", "true");
fetchPost("/api/history/getAssetsHistory", {}, (response) => {
if (response.data.histories.length === 0) {
element.lastElementChild.innerHTML = "";
element.firstElementChild.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
return;
}
let logsHTML = "";
response.data.histories.forEach((item: { items: { path: string, title: string }[], time: string }, index: number) => {
logsHTML += `<li class="b3-list-item" style="padding-left: 0">
<span style="padding-left: 8px" class="b3-list-item__toggle"><svg class="b3-list-item__arrow${index === 0 ? " b3-list-item__arrow--open" : ""}${item.items.length > 0 ? "" : " fn__hidden"}"><use xlink:href="#iconRight"></use></svg></span>
<span class="b3-list-item__text">${item.time}</span>
</li>`;
if (item.items.length > 0) {
logsHTML += `<ul class="${index === 0 ? "" : "fn__none"}">`;
item.items.forEach((docItem, docIndex) => {
logsHTML += `<li title="${escapeHtml(docItem.title)}" data-type="assets" data-path="${docItem.path}" class="b3-list-item b3-list-item--hide-action${(index === 0 && docIndex === 0) ? " b3-list-item--focus" : ""}" style="padding-left: 32px">
<span class="b3-list-item__text">${escapeHtml(docItem.title)}</span>
<span class="fn__space"></span>
<span class="b3-list-item__action b3-tooltips b3-tooltips__w" aria-label="${window.siyuan.languages.rollback}">
<svg><use xlink:href="#iconUndo"></use></svg>
</span>
</li>`;
});
logsHTML += "</ul>";
if (index === 0) {
const type = item.items[0].title.substr(item.items[0].title.lastIndexOf(".")).toLowerCase();
if (Constants.SIYUAN_ASSETS_IMAGE.includes(type)) {
element.lastElementChild.innerHTML = `<img src="${item.items[0].path}">`;
} else if (Constants.SIYUAN_ASSETS_AUDIO.includes(type)) {
element.lastElementChild.innerHTML = `<audio controls="controls" src="${item.items[0].path}"></audio>`;
} else if (Constants.SIYUAN_ASSETS_VIDEO.includes(type)) {
element.lastElementChild.innerHTML = `<video controls="controls" src="${item.items[0].path}"></video>`;
} else {
element.lastElementChild.innerHTML = item.items[0].path;
}
}
}
});
element.firstElementChild.innerHTML = logsHTML;
});
};
const renderRmNotebook = (element: HTMLElement) => {
element.setAttribute("data-init", "true");
fetchPost("/api/history/getNotebookHistory", {}, (response) => {
if (response.data.histories.length === 0) {
element.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
return;
}
let logsHTML = "";
response.data.histories.forEach((item: { items: { path: string, title: string }[], time: string }, index: number) => {
logsHTML += `<li class="b3-list-item" style="padding-left: 0">
<span style="padding-left: 8px" class="b3-list-item__toggle"><svg class="b3-list-item__arrow${index === 0 ? " b3-list-item__arrow--open" : ""}${item.items.length > 0 ? "" : " fn__hidden"}"><use xlink:href="#iconRight"></use></svg></span>
<span class="b3-list-item__text">${item.time}</span>
</li>`;
if (item.items.length > 0) {
logsHTML += `<ul class="${index === 0 ? "" : "fn__none"}">`;
item.items.forEach((docItem) => {
logsHTML += `<li data-type="notebook" data-path="${docItem.path}" class="b3-list-item" style="padding-left: 32px">
<span class="b3-list-item__text">${escapeHtml(docItem.title)}</span>
<span class="fn__space"></span>
<span class="b3-list-item__action">
<svg><use xlink:href="#iconUndo"></use></svg><span class="fn__space"></span>${window.siyuan.languages.rollback}
</span>
</li>`;
});
logsHTML += "</ul>";
}
});
element.innerHTML = logsHTML;
});
};
export const openHistory = () => {
const exitDialog = window.siyuan.dialogs.find((item) => {
if (item.element.querySelector("#historyContainer")) {
item.destroy();
return true;
}
});
if (exitDialog) {
return;
}
const dialog = new Dialog({
content: `<div class="fn__flex-column" style="height: 100%;">
<div class="layout-tab-bar fn__flex" style="border-radius: 4px 4px 0 0">
<div data-type="doc" class="item item--focus"><span class="item__text">${window.siyuan.languages.doc}</span></div>
<div data-type="assets" class="item"><span class="item__text">${window.siyuan.languages.assets}</span></div>
<div data-type="notebook" class="item"><span class="item__text">${window.siyuan.languages.removedNotebook}</span></div>
</div>
<div class="fn__flex-1 fn__flex" id="historyContainer">
<div data-type="doc" class="fn__flex fn__block" data-init="true">
<ul style="width:200px;overflow: auto;" class="b3-list b3-list--background">
<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
</ul>
<textarea class="fn__flex-1 b3-typography history__text" readonly></textarea>
</div>
<div data-type="assets" class="fn__flex fn__none">
<ul style="width:200px;overflow: auto;" class="b3-list b3-list--background">
<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
</ul>
<div class="fn__flex-1 history__asset"></div>
</div>
<ul data-type="notebook" style="background-color: var(--b3-theme-background);border-radius: 0 0 4px 4px" class="fn__none b3-list b3-list--background">
<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
</ul>
</div>
</div>`,
width: "80vw",
height: "80vh",
});
let currentNotebook: INotebook = {
name: window.siyuan.languages.newFileTip,
id: "",
closed: true,
icon: "",
sort: 0
};
const currentNotebookId = localStorage.getItem(Constants.LOCAL_HISTORYNOTEID);
window.siyuan.notebooks.find((item) => {
if (!item.closed) {
if (!currentNotebook.id) {
currentNotebook = item;
}
if (currentNotebookId) {
if (item.id === currentNotebookId) {
currentNotebook = item;
return true;
}
} else {
currentNotebook = item;
return true;
}
}
});
const firstPanelElement = dialog.element.querySelector("#historyContainer [data-type=doc]") as HTMLElement;
renderDoc(currentNotebook, firstPanelElement);
dialog.element.addEventListener("click", (event) => {
let target = event.target as HTMLElement;
while (target && !target.isEqualNode(dialog.element)) {
if (target.classList.contains("item")) {
const currentType = target.getAttribute("data-type");
target.parentElement.querySelector(".item--focus").classList.remove("item--focus");
Array.from(dialog.element.querySelector("#historyContainer").children).forEach((item: HTMLElement) => {
if (item.getAttribute("data-type") === currentType) {
item.classList.remove("fn__none");
item.classList.add("fn__block");
target.classList.add("item--focus");
if (item.getAttribute("data-init") !== "true") {
if (currentType === "assets") {
renderAssets(item);
} else if (currentType === "notebook") {
renderRmNotebook(item);
}
}
} else {
item.classList.add("fn__none");
item.classList.remove("fn__block");
}
});
break;
} else if (target.getAttribute("data-type") === "switchNotebook") {
window.siyuan.menus.menu.remove();
window.siyuan.notebooks.forEach(item => {
if (!item.closed) {
window.siyuan.menus.menu.append(new MenuItem({
label: item.name,
click: () => {
if (item.id === currentNotebook.id) {
return;
}
currentNotebook = item;
window.localStorage.setItem(Constants.LOCAL_HISTORYNOTEID, item.id);
renderDoc(item, firstPanelElement);
}
}).element);
}
});
window.siyuan.menus.menu.popup({x: event.clientX, y: event.clientY});
break;
} else if (target.classList.contains("b3-list-item__action")) {
confirmDialog("⚠️ " + window.siyuan.languages.rollback, `${window.siyuan.languages.rollbackConfirm.replace("${date}", target.parentElement.textContent.trim())}`, () => {
if (target.parentElement.getAttribute("data-type") === "assets") {
fetchPost("/api/history/rollbackAssetsHistory", {
historyPath: target.parentElement.getAttribute("data-path")
});
} else if (target.parentElement.getAttribute("data-type") === "doc") {
fetchPost("/api/history/rollbackDocHistory", {
notebook: currentNotebook.id,
historyPath: target.parentElement.getAttribute("data-path")
});
} else {
fetchPost("/api/history/rollbackNotebookHistory", {
historyPath: target.parentElement.getAttribute("data-path")
});
}
});
break;
} else if (target.classList.contains("b3-list-item")) {
if (!target.classList.contains("b3-list-item--hide-action")) {
if (target.getAttribute("data-type") === "notebook") {
break;
}
target.nextElementSibling.classList.toggle("fn__none");
target.firstElementChild.firstElementChild.classList.toggle("b3-list-item__arrow--open");
break;
}
const dataPath = target.getAttribute("data-path");
const dataType = target.getAttribute("data-type");
if (dataPath && dataType !== "notebook") {
let currentItem;
if (dataType === "assets") {
const type = dataPath.substr(dataPath.lastIndexOf(".")).toLowerCase();
if (Constants.SIYUAN_ASSETS_IMAGE.includes(type)) {
firstPanelElement.nextElementSibling.lastElementChild.innerHTML = `<img src="${dataPath}">`;
} else if (Constants.SIYUAN_ASSETS_AUDIO.includes(type)) {
firstPanelElement.nextElementSibling.lastElementChild.innerHTML = `<audio controls="controls" src="${dataPath}"></audio>`;
} else if (Constants.SIYUAN_ASSETS_VIDEO.includes(type)) {
firstPanelElement.nextElementSibling.lastElementChild.innerHTML = `<video controls="controls" src="${dataPath}"></video>`;
} else {
firstPanelElement.nextElementSibling.lastElementChild.innerHTML = dataPath;
}
currentItem = firstPanelElement.nextElementSibling.querySelector(".b3-list-item--focus");
} else if (dataType === "doc") {
fetchPost("/api/history/getDocHistoryContent", {
historyPath: dataPath
}, (response) => {
firstPanelElement.lastElementChild.innerHTML = response.data.content;
});
currentItem = firstPanelElement.querySelector(".b3-list-item--focus");
}
if (currentItem) {
currentItem.classList.remove("b3-list-item--focus");
}
target.classList.add("b3-list-item--focus");
}
break;
}
target = target.parentElement;
}
});
};

104
app/src/util/mount.ts Normal file
View file

@ -0,0 +1,104 @@
import {Constants} from "../constants";
import {showMessage} from "../dialog/message";
import {isMobile} from "./functions";
import {fetchPost} from "./fetch";
import {Dialog} from "../dialog";
import {getNotebookName, getOpenNotebookCount} from "./pathName";
import {validateName} from "../editor/rename";
export const newDailyNote = () => {
const openCount = getOpenNotebookCount();
if (openCount === 0) {
showMessage(window.siyuan.languages.newFileTip);
return;
}
if (openCount === 1) {
let notebookId = "";
window.siyuan.notebooks.find(item => {
if (!item.closed) {
notebookId = item.id;
}
});
fetchPost("/api/filetree/createDailyNote", {
notebook: notebookId
});
return;
}
const localNotebookId = window.localStorage.getItem(Constants.LOCAL_DAILYNOTEID);
if (localNotebookId && getNotebookName(localNotebookId)) {
fetchPost("/api/filetree/createDailyNote", {
notebook:localNotebookId
});
} else {
let optionsHTML = "";
window.siyuan.notebooks.forEach(item => {
if (!item.closed) {
optionsHTML += `<option value="${item.id}">${item.name}</option>`;
}
});
const dialog = new Dialog({
content: `<div class="b3-dialog__content">
<select class="b3-select fn__block">${optionsHTML}</select>
</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>`,
width: isMobile() ? "80vw" : "520px",
});
const btnsElement = dialog.element.querySelectorAll(".b3-button");
const selectElement = dialog.element.querySelector(".b3-select") as HTMLSelectElement;
btnsElement[0].addEventListener("click", () => {
dialog.destroy();
});
btnsElement[1].addEventListener("click", () => {
const notebook = selectElement.value;
window.localStorage.setItem(Constants.LOCAL_DAILYNOTEID, notebook);
fetchPost("/api/filetree/createDailyNote", {
notebook
});
dialog.destroy();
});
}
};
export const mountHelp = () => {
const notebookId = Constants.HELP_PATH[window.siyuan.config.appearance.lang as "zh_CN" | "en_US"];
fetchPost("/api/notebook/removeNotebook", {notebook: notebookId, callback:Constants.CB_MOUNT_REMOVE}, () => {
fetchPost("/api/notebook/openNotebook", {
callback: Constants.CB_MOUNT_HELP,
notebook: notebookId
});
});
};
export const newNotebook = () => {
const dialog = new Dialog({
title: window.siyuan.languages.newNotebook,
content: `<div class="b3-dialog__content">
<input placeholder="${window.siyuan.languages.notebookName}" class="b3-text-field fn__block">
</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>`,
width: isMobile() ? "80vw" : "520px"
});
const btnsElement = dialog.element.querySelectorAll(".b3-button");
dialog.bindInput(dialog.element.querySelector("input"), () => {
btnsElement[1].dispatchEvent(new CustomEvent("click"));
});
btnsElement[0].addEventListener("click", () => {
dialog.destroy();
});
btnsElement[1].addEventListener("click", () => {
const name = dialog.element.querySelector("input").value;
if (!validateName(name)) {
return false;
}
fetchPost("/api/notebook/createNotebook", {
name
});
dialog.destroy();
});
};

View file

@ -0,0 +1,15 @@
import {showMessage} from "../dialog/message";
export const needSubscribe = (tip = window.siyuan.languages._kernel[29]) => {
if (window.siyuan.user && (window.siyuan.user.userSiYuanProExpireTime === -1 || window.siyuan.user.userSiYuanProExpireTime > 0)) {
return false;
}
if (tip) {
if (tip === window.siyuan.languages._kernel[29] && window.siyuan.config.system.container === "ios") {
showMessage(window.siyuan.languages._kernel[122]);
} else {
showMessage(tip);
}
}
return true;
};

94
app/src/util/newFile.ts Normal file
View file

@ -0,0 +1,94 @@
import {showMessage} from "../dialog/message";
import {getAllModels} from "../layout/getAll";
import {hasTopClosestByTag} from "../protyle/util/hasClosest";
import {getDockByType} from "../layout/util";
import {Files} from "../layout/dock/Files";
import {fetchPost} from "./fetch";
import {getDisplayName, getOpenNotebookCount, pathPosix} from "./pathName";
import {openFileById} from "../editor/util";
import {Constants} from "../constants";
export const newFile = (notebookId?: string, currentPath?: string, open?: boolean) => {
if (getOpenNotebookCount() === 0) {
showMessage(window.siyuan.languages.newFileTip);
return;
}
if (!notebookId) {
getAllModels().editor.find((item) => {
const currentElement = item.parent.headElement;
if (currentElement.classList.contains("item--focus")) {
notebookId = item.editor.protyle.notebookId;
currentPath = pathPosix().dirname(item.editor.protyle.path);
if (currentElement.parentElement.parentElement.classList.contains("layout__wnd--active")) {
return true;
}
}
});
if (!notebookId) {
const fileModel = getDockByType("file").data.file;
if (fileModel instanceof Files) {
const currentElement = fileModel.element.querySelector(".b3-list-item--focus");
if (currentElement) {
const topElement = hasTopClosestByTag(currentElement, "UL");
if (topElement) {
notebookId = topElement.getAttribute("data-url");
}
const selectPath = currentElement.getAttribute("data-path");
currentPath = pathPosix().dirname(selectPath);
}
}
}
if (!notebookId) {
window.siyuan.notebooks.find(item => {
if (!item.closed) {
notebookId = item.id;
currentPath = "/";
return true;
}
});
}
}
fetchPost("/api/filetree/getDocNameTemplate", {notebook: notebookId}, (data) => {
const id = Lute.NewNodeID();
fetchPost("/api/filetree/createDoc", {
notebook: notebookId,
path: pathPosix().join(getDisplayName(currentPath, false, true), id + ".sy"),
title: data.data.name || window.siyuan.languages.untitled,
md: "",
}, () => {
if (open) {
openFileById({id, hasContext: true, action: [Constants.CB_GET_HL]});
}
});
});
};
export const getSavePath = (pathString: string, notebookId: string, cb: (p: string) => void) => {
fetchPost("/api/notebook/getNotebookConf", {
notebook: notebookId
}, (data) => {
let savePath = data.data.conf.refCreateSavePath;
if (!savePath) {
savePath = window.siyuan.config.fileTree.refCreateSavePath;
}
if (savePath) {
if (savePath.startsWith("/")) {
cb(getDisplayName(savePath, false, true));
} else {
fetchPost("/api/filetree/getHPathByPath", {
notebook: notebookId,
path: pathString
}, (response) => {
cb(getDisplayName(pathPosix().join(response.data, savePath), false, true));
});
}
} else {
fetchPost("/api/filetree/getHPathByPath", {
notebook: notebookId,
path: pathString
}, (response) => {
cb(getDisplayName(response.data, false, true));
});
}
});
};

450
app/src/util/onGetConfig.ts Normal file
View file

@ -0,0 +1,450 @@
import {openSearch} from "../search/spread";
import {exportLayout, JSONToLayout, resizeTabs} from "../layout/util";
import {hotKey2Electron, updateHotkeyTip} from "../protyle/util/compatibility";
/// #if !BROWSER
import {ipcRenderer} from "electron";
import {getCurrentWindow} from "@electron/remote";
/// #endif
import {Constants} from "../constants";
import {appearance} from "../config/appearance";
import {initToolbarMore} from "../menus/toolbar";
import {globalShortcut} from "./globalShortcut";
import {fetchPost} from "./fetch";
import {mountHelp, newDailyNote} from "./mount";
import {MenuItem} from "../menus/Menu";
import {initAssets, loadAssets, setInlineStyle} from "./assets";
import {showMessage} from "../dialog/message";
import {needSubscribe} from "./needSubscribe";
import {goBack, goForward} from "./backForward";
import {getOpenNotebookCount} from "./pathName";
import {openFileById} from "../editor/util";
import {focusByRange} from "../protyle/util/selection";
import {exitSiYuan} from "../dialog/processSystem";
const matchKeymap = (keymap: Record<string, IKeymapItem>, key1: "general" | "editor", key2?: "general" | "insert" | "heading" | "list" | "table") => {
if (key1 === "general") {
if (!window.siyuan.config.keymap[key1]) {
window.siyuan.config.keymap[key1] = keymap;
return false;
}
} else {
if (!window.siyuan.config.keymap[key1]) {
window.siyuan.config.keymap[key1] = Constants.SIYUAN_KEYMAP.editor;
return false;
}
if (!window.siyuan.config.keymap[key1][key2]) {
window.siyuan.config.keymap[key1][key2] = keymap;
return false;
}
}
let match = true;
Object.keys(keymap).forEach(key => {
if (key1 === "general") {
if (!window.siyuan.config.keymap[key1][key] || window.siyuan.config.keymap[key1][key].default !== keymap[key].default) {
match = false;
window.siyuan.config.keymap[key1][key] = keymap[key];
}
} else {
if (!window.siyuan.config.keymap[key1][key2][key] || window.siyuan.config.keymap[key1][key2][key].default !== keymap[key].default) {
match = false;
window.siyuan.config.keymap[key1][key2][key] = keymap[key];
}
}
});
return match;
};
const hasKeymap = (keymap: Record<string, IKeymapItem>, key1: "general" | "editor", key2?: "general" | "insert" | "heading" | "list" | "table") => {
let match = true;
if (key1 === "editor") {
if (Object.keys(window.siyuan.config.keymap[key1][key2]).length !== Object.keys(Constants.SIYUAN_KEYMAP[key1][key2]).length) {
Object.keys(window.siyuan.config.keymap[key1][key2]).forEach(item => {
if (!Constants.SIYUAN_KEYMAP[key1][key2][item]) {
match = false;
delete window.siyuan.config.keymap[key1][key2][item];
}
});
}
} else {
if (Object.keys(window.siyuan.config.keymap[key1]).length !== Object.keys(Constants.SIYUAN_KEYMAP[key1]).length) {
Object.keys(window.siyuan.config.keymap[key1]).forEach(item => {
if (!Constants.SIYUAN_KEYMAP[key1][item]) {
match = false;
delete window.siyuan.config.keymap[key1][item];
}
});
}
}
return match;
};
export const setProxy = () => {
/// #if !BROWSER
const session = getCurrentWindow().webContents.session;
if (window.siyuan.config.system.networkProxy.scheme) {
session.closeAllConnections().then(() => {
session.setProxy({proxyRules: `${window.siyuan.config.system.networkProxy.scheme}://${window.siyuan.config.system.networkProxy.host}:${window.siyuan.config.system.networkProxy.port}`}).then();
});
}
/// #endif
};
export const onGetConfig = () => {
const matchKeymap1 = matchKeymap(Constants.SIYUAN_KEYMAP.general, "general");
const matchKeymap2 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.general, "editor", "general");
const matchKeymap3 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.insert, "editor", "insert");
const matchKeymap4 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.heading, "editor", "heading");
const matchKeymap5 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.list, "editor", "list");
const matchKeymap6 = matchKeymap(Constants.SIYUAN_KEYMAP.editor.table, "editor", "table");
const hasKeymap1 = hasKeymap(Constants.SIYUAN_KEYMAP.general, "general");
const hasKeymap2 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.general, "editor", "general");
const hasKeymap3 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.insert, "editor", "insert");
const hasKeymap4 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.heading, "editor", "heading");
const hasKeymap5 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.list, "editor", "list");
const hasKeymap6 = hasKeymap(Constants.SIYUAN_KEYMAP.editor.table, "editor", "table");
if (!window.siyuan.config.readonly && (!matchKeymap1 || !matchKeymap2 || !matchKeymap3 || !matchKeymap4 || !matchKeymap5 || !matchKeymap6) &&
(!hasKeymap1 || !hasKeymap2 || !hasKeymap3 || !hasKeymap4 || !hasKeymap5 || !hasKeymap6)) {
fetchPost("/api/setting/setKeymap", {
data: window.siyuan.config.keymap
}, () => {
/// #if !BROWSER
ipcRenderer.send(Constants.SIYUAN_HOTKEY, hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom));
/// #endif
});
}
/// #if !BROWSER
ipcRenderer.send(Constants.SIYUAN_CONFIG_CLOSE, window.siyuan.config.appearance.closeButtonBehavior);
ipcRenderer.send(Constants.SIYUAN_INIT);
ipcRenderer.send(Constants.SIYUAN_HOTKEY, hotKey2Electron(window.siyuan.config.keymap.general.toggleWin.custom));
/// #endif
if (!window.siyuan.config.uiLayout || (window.siyuan.config.uiLayout && !window.siyuan.config.uiLayout.left)) {
window.siyuan.config.uiLayout = Constants.SIYUAN_EMPTY_LAYOUT;
}
globalShortcut();
fetchPost("/api/system/getEmojiConf", {}, response => {
window.siyuan.emojis = response.data as IEmoji[];
try {
JSONToLayout();
} catch (e) {
fetchPost("/api/system/setUILayout", {layout: {}}, () => {
window.location.reload();
});
}
});
initBar();
initWindow();
appearance.onSetappearance(window.siyuan.config.appearance);
initAssets();
setInlineStyle();
let resizeTimeout = 0;
window.addEventListener("resize", () => {
window.clearTimeout(resizeTimeout);
resizeTimeout = window.setTimeout(() => {
resizeTabs();
}, 200);
});
if (window.siyuan.config.newbie) {
mountHelp();
}
};
const initBar = () => {
document.querySelector(".toolbar").innerHTML = `<div id="toolbarVIP" class="fn__flex"></div>
<div id="barDailyNote" data-menu="true" aria-label="${window.siyuan.languages.dailyNote} ${updateHotkeyTip(window.siyuan.config.keymap.general.dailyNote.custom)}" class="toolbar__item b3-tooltips b3-tooltips__se${window.siyuan.config.readonly ? " fn__none" : ""}">
<svg>
<use xlink:href="#iconCalendar"></use>
</svg>
</div>
<div id="barSearch" class="toolbar__item b3-tooltips b3-tooltips__se" aria-label="${window.siyuan.languages.globalSearch} ${updateHotkeyTip(window.siyuan.config.keymap.general.globalSearch.custom)}">
<svg>
<use xlink:href="#iconSearch"></use>
</svg>
</div>
<div id="barDock" class="toolbar__item b3-tooltips b3-tooltips__s${window.siyuan.config.readonly ? " fn__none" : ""}" aria-label="${window.siyuan.config.uiLayout.hideDock ? window.siyuan.languages.showDock : window.siyuan.languages.hideDock}">
<svg>
<use xlink:href="#${window.siyuan.config.uiLayout.hideDock ? "iconRestore" : "iconMax"}"></use>
</svg>
</div>
<div id="barThemeMode" class="toolbar__item b3-tooltips b3-tooltips__se${window.siyuan.config.appearance.mode === 1 ? " toolbar__item--active" : ""}" aria-label="${window.siyuan.languages.darkMode}">
<svg>
<use xlink:href="#iconMoon"></use>
</svg>
</div>
<div id="barSync" class="toolbar__item b3-tooltips b3-tooltips__se" aria-label="${window.siyuan.config.sync.stat || (window.siyuan.languages.syncNow + " F9")}">
<svg>
<use xlink:href="#iconRefresh"></use>
</svg>
</div>
<div data-menu="true" id="barMore" class="toolbar__item b3-tooltips b3-tooltips__se" aria-label="${window.siyuan.languages.more}">
<svg>
<use xlink:href="#iconMore"></use>
</svg>
</div>
<button id="barBack" data-menu="true" class="toolbar__item toolbar__item--disabled b3-tooltips b3-tooltips__se" aria-label="${window.siyuan.languages.goBack} ${updateHotkeyTip(window.siyuan.config.keymap.general.goBack.custom)}">
<svg>
<use xlink:href="#iconLeft"></use>
</svg>
</button>
<button id="barForward" data-menu="true" class="toolbar__item toolbar__item--disabled b3-tooltips b3-tooltips__se" aria-label="${window.siyuan.languages.goForward} ${updateHotkeyTip(window.siyuan.config.keymap.general.goForward.custom)}">
<svg>
<use xlink:href="#iconRight"></use>
</svg>
</button>
<div class="fn__flex-1 fn__ellipsis" id="drag"><span class="fn__none">使 Development version, please backup before use</span></div>
<div class="fn__flex" style="top: -1px;z-index: 302;right: -1px;position: relative;" id="windowControls"></div>`;
document.getElementById("barBack").addEventListener("click", () => {
goBack();
});
document.getElementById("barForward").addEventListener("click", () => {
goForward();
});
document.getElementById("barMore").addEventListener("click", (event) => {
initToolbarMore().popup({x: event.clientX, y: event.clientY});
});
const baSyncElement = document.getElementById("barSync");
baSyncElement.addEventListener("click", () => {
if (needSubscribe()) {
return;
}
if (!window.siyuan.config.sync.enabled) {
showMessage(window.siyuan.languages._kernel[124]);
return;
}
if (baSyncElement.firstElementChild.classList.contains("fn__rotate")) {
return;
}
fetchPost("/api/sync/performSync", {});
});
const barThemeModeElement = document.getElementById("barThemeMode");
barThemeModeElement.addEventListener("click", () => {
if (barThemeModeElement.getAttribute("disabled")) {
return;
}
if (barThemeModeElement.classList.contains("toolbar__item--active")) {
barThemeModeElement.classList.remove("toolbar__item--active");
} else {
barThemeModeElement.classList.add("toolbar__item--active");
}
barThemeModeElement.setAttribute("disabled", "disabled");
fetchPost("/api/system/setAppearanceMode", {
mode: barThemeModeElement.classList.contains("toolbar__item--active") ? 1 : 0
}, response => {
if (window.siyuan.config.appearance.themeJS) {
exportLayout(true);
return;
}
window.siyuan.config.appearance = response.data.appearance;
barThemeModeElement.removeAttribute("disabled");
/// #if !BROWSER
ipcRenderer.send(Constants.SIYUAN_CONFIG_THEME, response.data.mode === 1 ? "dark" : "light");
/// #endif
loadAssets(response.data.appearance);
});
});
const barDockElement = document.getElementById("barDock");
const useElement = document.querySelector("#barDock use");
barDockElement.addEventListener("click", () => {
const dockIsShow = useElement.getAttribute("xlink:href") === "#iconMax";
if (dockIsShow) {
useElement.setAttribute("xlink:href", "#iconRestore");
barDockElement.setAttribute("aria-label", window.siyuan.languages.showDock);
} else {
useElement.setAttribute("xlink:href", "#iconMax");
barDockElement.setAttribute("aria-label", window.siyuan.languages.hideDock);
}
document.querySelectorAll(".dock").forEach(item => {
if (dockIsShow) {
if (item.querySelector(".dock__item")) {
item.classList.add("fn__none");
}
} else {
if (item.querySelector(".dock__item")) {
item.classList.remove("fn__none");
}
}
});
resizeTabs();
});
document.getElementById("barDailyNote").addEventListener("click", (event) => {
if (getOpenNotebookCount() < 2) {
newDailyNote();
} else {
window.siyuan.menus.menu.remove();
window.siyuan.notebooks.forEach(item => {
if (!item.closed) {
window.siyuan.menus.menu.append(new MenuItem({
label: item.name,
click: () => {
fetchPost("/api/filetree/createDailyNote", {
notebook: item.id
});
window.localStorage.setItem(Constants.LOCAL_DAILYNOTEID, item.id);
}
}).element);
}
});
window.siyuan.menus.menu.popup({x: event.clientX, y: event.clientY});
}
});
document.getElementById("barSearch").addEventListener("click", () => {
openSearch(window.siyuan.config.keymap.general.globalSearch.custom);
});
setProxy();
};
const winOnFocus = () => {
if (getSelection().rangeCount > 0) {
const range = getSelection().getRangeAt(0);
const startNode = range.startContainer.childNodes[range.startOffset] as HTMLElement;
if (startNode && startNode.nodeType !== 3 && (startNode.tagName === "TEXTAREA" || startNode.tagName === "INPUT")) {
startNode.focus();
} else {
focusByRange(getSelection().getRangeAt(0));
}
}
exportLayout(false);
window.siyuan.altIsPressed = false;
window.siyuan.ctrlIsPressed = false;
window.siyuan.shiftIsPressed = false;
document.body.classList.remove("body--blur");
};
const winOnClose = (currentWindow: Electron.BrowserWindow, close = false) => {
/// #if !BROWSER
exportLayout(false, () => {
if (window.siyuan.config.appearance.closeButtonBehavior === 1 && !close) {
// 最小化
if ("windows" === window.siyuan.config.system.os) {
ipcRenderer.send(Constants.SIYUAN_CONFIG_TRAY);
} else {
if (currentWindow.isFullScreen()) {
currentWindow.once("leave-full-screen", () => currentWindow.hide());
currentWindow.setFullScreen(false);
} else {
currentWindow.hide();
}
}
} else {
exitSiYuan();
}
});
/// #endif
};
const initWindow = () => {
/// #if !BROWSER
const currentWindow = getCurrentWindow();
currentWindow.on("focus", winOnFocus);
ipcRenderer.on(Constants.SIYUAN_OPENURL, (event, url) => {
const params = url.split("?");
openFileById({
id: url.substr(16, 22),
hasContext: true,
action: [Constants.CB_GET_FOCUS],
zoomIn: params.length === 2 && params[1].startsWith("focus=1")
});
});
ipcRenderer.on(Constants.SIYUAN_SAVE_CLOSE, (event, close) => {
winOnClose(currentWindow, close);
});
window.addEventListener("beforeunload", () => {
currentWindow.off("focus", winOnFocus);
}, false);
if ("windows" !== window.siyuan.config.system.os && "linux" !== window.siyuan.config.system.os) {
document.getElementById("drag").addEventListener("dblclick", () => {
if (currentWindow.isMaximized()) {
currentWindow.unmaximize();
} else {
currentWindow.maximize();
}
});
const toolbarElement = document.getElementById("toolbar");
currentWindow.on("enter-full-screen", () => {
toolbarElement.style.paddingLeft = "0";
});
currentWindow.on("leave-full-screen", () => {
toolbarElement.setAttribute("style", "");
});
if (currentWindow.isFullScreen()) {
toolbarElement.style.paddingLeft = "0";
}
return;
}
const controlsElement = document.querySelector("#windowControls");
document.body.classList.add("body--win32");
controlsElement.innerHTML = `<div class="toolbar__item toolbar__item--win b3-tooltips b3-tooltips__sw" aria-label="${window.siyuan.languages.min}" id="minWindow">
<svg>
<use xlink:href="#iconMin"></use>
</svg>
</div>
<div aria-label="${window.siyuan.languages.max}" class="b3-tooltips b3-tooltips__sw toolbar__item toolbar__item--win" id="maxWindow">
<svg>
<use xlink:href="#iconMax"></use>
</svg>
</div>
<div aria-label="${window.siyuan.languages.restore}" class="b3-tooltips b3-tooltips__sw toolbar__item toolbar__item--win" id="restoreWindow">
<svg>
<use xlink:href="#iconRestore"></use>
</svg>
</div>
<div aria-label="${window.siyuan.languages.close}" class="b3-tooltips b3-tooltips__sw toolbar__item toolbar__item--close" id="closeWindow">
<svg>
<use xlink:href="#iconClose"></use>
</svg>
</div>`;
const maxBtnElement = document.getElementById("maxWindow");
const restoreBtnElement = document.getElementById("restoreWindow");
restoreBtnElement.addEventListener("click", () => {
if (currentWindow.isFullScreen()) {
currentWindow.setFullScreen(false);
} else {
currentWindow.unmaximize();
}
});
maxBtnElement.addEventListener("click", () => {
currentWindow.maximize();
});
const toggleMaxRestoreButtons = () => {
if (currentWindow.isMaximized() || currentWindow.isFullScreen()) {
restoreBtnElement.style.display = "flex";
maxBtnElement.style.display = "none";
} else {
restoreBtnElement.style.display = "none";
maxBtnElement.style.display = "flex";
}
};
toggleMaxRestoreButtons();
currentWindow.on("maximize", toggleMaxRestoreButtons);
currentWindow.on("unmaximize", toggleMaxRestoreButtons);
currentWindow.on("enter-full-screen", () => {
restoreBtnElement.style.display = "flex";
maxBtnElement.style.display = "none";
});
currentWindow.on("leave-full-screen", toggleMaxRestoreButtons);
currentWindow.on("blur", () => {
document.body.classList.add("body--blur");
});
const minBtnElement = document.getElementById("minWindow");
const closeBtnElement = document.getElementById("closeWindow");
minBtnElement.addEventListener("click", () => {
if (minBtnElement.classList.contains("window-controls__item--disabled")) {
return;
}
currentWindow.minimize();
});
closeBtnElement.addEventListener("click", () => {
winOnClose(currentWindow);
});
/// #else
document.querySelector(".toolbar").classList.add("toolbar--browser");
window.addEventListener("beforeunload", () => {
exportLayout(false);
}, false);
/// #endif
};

199
app/src/util/pathName.ts Normal file
View file

@ -0,0 +1,199 @@
import * as path from "path";
import {fetchPost, fetchSyncPost} from "./fetch";
import {Dialog} from "../dialog";
import {escapeHtml} from "./escape";
import {isMobile} from "./functions";
import {focusByRange} from "../protyle/util/selection";
import {hasClosestByClassName} from "../protyle/util/hasClosest";
import {unicode2Emoji} from "../emoji";
export const addBaseURL = () => {
let baseURLElement = document.getElementById("baseURL");
if (!baseURLElement) {
baseURLElement = document.createElement("base");
baseURLElement.id = "baseURL";
}
baseURLElement.setAttribute("href", location.origin);
document.getElementsByTagName("head")[0].appendChild(baseURLElement);
};
export const getDisplayName = (filePath: string, basename = true, removeSY = false) => {
let name = filePath;
if (basename) {
name = pathPosix().basename(filePath);
}
if (removeSY && name.endsWith(".sy")) {
name = name.substr(0, name.length - 3);
}
return name;
};
export const isLocalPath = (link: string) => {
return link.startsWith("assets/") || link.startsWith("file://");
};
export const pathPosix = () => {
if (path.posix) {
return path.posix;
}
return path;
};
const moveToPath = (notebookId: string, path: string, toNotebookId: string, toFolderPath: string, dialog: Dialog) => {
fetchPost("/api/filetree/moveDoc", {
fromNotebook: notebookId,
toNotebook: toNotebookId,
fromPath: path,
toPath: toFolderPath,
});
dialog.destroy();
};
export const movePathTo = async (notebookId: string, path: string, focus = true) => {
const exitDialog = window.siyuan.dialogs.find((item) => {
if (item.element.querySelector("#foldList")) {
item.destroy();
return true;
}
});
if (exitDialog) {
return;
}
const response = await fetchSyncPost("/api/filetree/getHPathByPath", {
notebook: notebookId,
path
});
let range: Range;
if (getSelection().rangeCount > 0) {
range = getSelection().getRangeAt(0);
}
const dialog = new Dialog({
title: `${window.siyuan.languages.move} <span class="ft__smaller ft__on-surface">${escapeHtml(pathPosix().join(getNotebookName(notebookId), response.data))}</span>`,
content: `<div class="b3-form__icon b3-form__space">
<svg class="b3-form__icon-icon"><use xlink:href="#iconSearch"></use></svg>
<input class="b3-text-field fn__block b3-form__icon-input" value="" placeholder="${window.siyuan.languages.search}">
</div>
<ul id="foldList" class="b3-list b3-list--background" style="height: 50vh;overflow: auto;position: relative"></ul>`,
width: isMobile() ? "80vw" : "50vw",
destroyCallback() {
if (range && focus) {
focusByRange(range);
}
}
});
const searchPanelElement = dialog.element.querySelector("#foldList");
const inputElement = dialog.element.querySelector(".b3-text-field") as HTMLInputElement;
inputElement.focus();
const inputEvent = (event?: InputEvent) => {
if (event && event.isComposing) {
return;
}
fetchPost("/api/filetree/searchDocs", {
k: inputElement.value
}, (data) => {
let fileHTML = "";
data.data.forEach((item: { boxIcon:string, box: string, hPath: string, path: string }) => {
if (item.path === pathPosix().dirname(path) + "/" || item.path === path) {
return;
}
fileHTML += `<li class="b3-list-item${fileHTML === "" ? " b3-list-item--focus" : ""}" data-path="${item.path}" data-box="${item.box}">
${item.boxIcon ? ('<span class="b3-list-item__icon">' + unicode2Emoji(item.boxIcon) + "</span>") : ""}
<span class="b3-list-item__showall">${escapeHtml(item.hPath)}</span>
</li>`;
});
searchPanelElement.innerHTML = fileHTML;
});
};
inputEvent();
inputElement.addEventListener("compositionend", (event: InputEvent) => {
inputEvent(event);
});
inputElement.addEventListener("input", (event: InputEvent) => {
inputEvent(event);
});
const lineHeight = 28;
inputElement.addEventListener("keydown", (event: KeyboardEvent) => {
let currentList: HTMLElement = dialog.element.querySelector(".b3-list-item--focus");
if (!currentList) {
return;
}
if (event.key === "ArrowDown") {
currentList.classList.remove("b3-list-item--focus");
if (!currentList.nextElementSibling) {
searchPanelElement.children[0].classList.add("b3-list-item--focus");
} else {
currentList.nextElementSibling.classList.add("b3-list-item--focus");
}
currentList = searchPanelElement.querySelector(".b3-list-item--focus");
if (searchPanelElement.scrollTop < currentList.offsetTop - searchPanelElement.clientHeight + lineHeight ||
searchPanelElement.scrollTop > currentList.offsetTop) {
searchPanelElement.scrollTop = currentList.offsetTop - searchPanelElement.clientHeight + lineHeight;
}
event.preventDefault();
} else if (event.key === "ArrowUp") {
currentList.classList.remove("b3-list-item--focus");
if (!currentList.previousElementSibling) {
const length = searchPanelElement.children.length;
searchPanelElement.children[length - 1].classList.add("b3-list-item--focus");
} else {
currentList.previousElementSibling.classList.add("b3-list-item--focus");
}
currentList = searchPanelElement.querySelector(".b3-list-item--focus");
if (searchPanelElement.scrollTop < currentList.offsetTop - searchPanelElement.clientHeight + lineHeight ||
searchPanelElement.scrollTop > currentList.offsetTop - lineHeight * 2) {
searchPanelElement.scrollTop = currentList.offsetTop - lineHeight * 2;
}
event.preventDefault();
} else if (event.key === "Enter") {
moveToPath(notebookId, path, currentList.getAttribute("data-box"), currentList.getAttribute("data-path"), dialog);
event.preventDefault();
}
});
dialog.element.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const liElement = hasClosestByClassName(target, "b3-list-item");
if (liElement) {
moveToPath(notebookId, path, liElement.getAttribute("data-box"), liElement.getAttribute("data-path"), dialog);
}
});
};
export const getNotebookName = (id: string) => {
let rootPath = "";
window.siyuan.notebooks.find((item) => {
if (item.id === id) {
rootPath = item.name;
return true;
}
});
return rootPath;
};
export const setNotebookName = (id: string, name: string) => {
window.siyuan.notebooks.find((item) => {
if (item.id === id) {
item.name = name;
return true;
}
});
};
export const getOpenNotebookCount = () => {
let count = 0;
window.siyuan.notebooks.forEach(item => {
if (!item.closed) {
count++;
}
});
return count;
};
export const setNoteBook = (cb?: (notebook: INotebook[]) => void) => {
fetchPost("/api/notebook/lsNotebooks", {}, (response) => {
window.siyuan.notebooks = response.data.notebooks;
if (cb) {
cb(response.data.notebooks);
}
});
};

View file

@ -0,0 +1,34 @@
import {hideMessage, showMessage} from "../dialog/message";
import {exportLayout} from "../layout/util";
import {isMobile} from "./functions";
export const processMessage = (response: IWebSocketData) => {
if ("msg" === response.cmd) {
showMessage(response.msg, response.data.closeTimeout, response.code === 0 ? "info" : "error");
return false;
}
if ("cmsg" === response.cmd) {
hideMessage();
const progressElement = document.getElementById("progress");
if (progressElement) {
progressElement.remove();
}
return false;
}
if ("reloadui" === response.cmd) {
if (isMobile()) {
window.location.reload();
} else {
exportLayout(true);
}
return false;
}
// 小于 0 为提示:-2 提示;-1 报错,大于 0 的错误需处理,等于 0 的为正常操作
if (response.code < 0) {
showMessage(response.msg, response.data ? (response.data.closeTimeout || 0) : 0, response.code === -1 ? "error" : "info");
return false;
}
return response;
};

View file

@ -0,0 +1,35 @@
import {Constants} from "../constants";
export const setPosition = (element: HTMLElement, x: number, y: number, targetHeight = 0, targetLeft = 0) => {
element.style.top = y + "px";
element.style.left = x + "px";
const rect = element.getBoundingClientRect();
// windows 下悬浮菜单在 drag 位置时无法点击
let dragBarHeight = 0;
/// #if !BROWSER
if ("windows" === window.siyuan.config.system.os) {
dragBarHeight = document.getElementById("drag").clientHeight;
}
/// #endif
// 上下超出屏幕
if (rect.bottom > window.innerHeight || rect.top < dragBarHeight) {
const top = y - rect.height - targetHeight;
if (top > dragBarHeight && (top + rect.height) < window.innerHeight - dragBarHeight) {
// 上部
element.style.top = top + "px";
} else if (top <= dragBarHeight) {
// 位置超越到屏幕上方外时需移动到屏幕顶部。eg光标在第一个块然后滚动到上方看不见的位置按 ctrl+a
element.style.top = dragBarHeight + "px";
} else {
// 依旧展现在下部,只是位置上移
element.style.top = Math.max(Constants.SIZE_TOOLBAR_HEIGHT, window.innerHeight - rect.height) + "px";
}
}
if (rect.right > window.innerWidth) {
// 展现在左侧
element.style.left = `${window.innerWidth - rect.width - targetLeft}px`;
} else if (rect.left < 0) {
// 依旧展现在左侧,只是位置右移
element.style.left = "0";
}
};

View file

@ -0,0 +1,42 @@
export const upDownHint = (listElement: Element, event: KeyboardEvent) => {
let currentHintElement: HTMLElement = listElement.querySelector(".b3-list-item--focus");
if (event.key === "ArrowDown") {
event.preventDefault();
event.stopPropagation();
currentHintElement.classList.remove("b3-list-item--focus");
if (!currentHintElement.nextElementSibling) {
listElement.children[0].classList.add("b3-list-item--focus");
} else {
if (currentHintElement.nextElementSibling.classList.contains("b3-list-item")) {
currentHintElement.nextElementSibling.classList.add("b3-list-item--focus");
} else {
currentHintElement.nextElementSibling.nextElementSibling.classList.add("b3-list-item--focus");
}
}
currentHintElement = listElement.querySelector(".b3-list-item--focus");
if (listElement.scrollTop < currentHintElement.offsetTop - listElement.clientHeight + currentHintElement.clientHeight ||
listElement.scrollTop > currentHintElement.offsetTop) {
listElement.scrollTop = currentHintElement.offsetTop - listElement.clientHeight + currentHintElement.clientHeight;
}
} else if (event.key === "ArrowUp") {
event.preventDefault();
event.stopPropagation();
currentHintElement.classList.remove("b3-list-item--focus");
if (!currentHintElement.previousElementSibling) {
const length = listElement.children.length;
listElement.children[length - 1].classList.add("b3-list-item--focus");
} else {
if (currentHintElement.previousElementSibling.classList.contains("b3-list-item")) {
currentHintElement.previousElementSibling.classList.add("b3-list-item--focus");
} else {
currentHintElement.previousElementSibling.previousElementSibling.classList.add("b3-list-item--focus");
}
}
currentHintElement = listElement.querySelector(".b3-list-item--focus");
if (listElement.scrollTop < currentHintElement.offsetTop - listElement.clientHeight + currentHintElement.clientHeight ||
listElement.scrollTop > currentHintElement.offsetTop - currentHintElement.clientHeight * 2) {
listElement.scrollTop = currentHintElement.offsetTop - currentHintElement.clientHeight * 2;
}
}
};