mirror of
https://github.com/siyuan-note/siyuan.git
synced 2026-03-12 23:46:13 +01:00
❤️ 完整开源界面和内核 https://github.com/siyuan-note/siyuan/issues/5013
This commit is contained in:
parent
e650b8100c
commit
f40ed985e1
1214 changed files with 345766 additions and 9 deletions
271
app/src/util/Tree.ts
Normal file
271
app/src/util/Tree.ts
Normal 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
107
app/src/util/assets.ts
Normal 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
280
app/src/util/backForward.ts
Normal 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
3
app/src/util/escape.ts
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
export const escapeHtml = (html: string) => {
|
||||
return html.replace(/&/g, "&").replace(/</g, "<");
|
||||
};
|
||||
74
app/src/util/fetch.ts
Normal file
74
app/src/util/fetch.ts
Normal 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
38
app/src/util/functions.ts
Normal 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
3
app/src/util/genID.ts
Normal 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)
|
||||
);
|
||||
808
app/src/util/globalShortcut.ts
Normal file
808
app/src/util/globalShortcut.ts
Normal 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;
|
||||
}
|
||||
};
|
||||
75
app/src/util/highlightById.ts
Normal file
75
app/src/util/highlightById.ts
Normal 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
306
app/src/util/history.ts
Normal 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
104
app/src/util/mount.ts
Normal 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();
|
||||
});
|
||||
};
|
||||
15
app/src/util/needSubscribe.ts
Normal file
15
app/src/util/needSubscribe.ts
Normal 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
94
app/src/util/newFile.ts
Normal 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
450
app/src/util/onGetConfig.ts
Normal 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
199
app/src/util/pathName.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
34
app/src/util/processMessage.ts
Normal file
34
app/src/util/processMessage.ts
Normal 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;
|
||||
};
|
||||
35
app/src/util/setPosition.ts
Normal file
35
app/src/util/setPosition.ts
Normal 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";
|
||||
}
|
||||
};
|
||||
42
app/src/util/upDownHint.ts
Normal file
42
app/src/util/upDownHint.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue