A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
1import {Constants} from "../../constants";
2import {onGet} from "../util/onGet";
3import {fetchPost} from "../../util/fetch";
4import {updateHotkeyTip} from "../util/compatibility";
5import {hasClosestByClassName} from "../util/hasClosest";
6import {goEnd, goHome} from "../wysiwyg/commonHotkey";
7import {showTooltip} from "../../dialog/tooltip";
8
9export class Scroll {
10 public element: HTMLElement;
11 private parentElement: HTMLElement;
12 private inputElement: HTMLInputElement;
13 public lastScrollTop: number;
14 public keepLazyLoad: boolean; // 保持加载内容
15
16 constructor(protyle: IProtyle) {
17 this.parentElement = document.createElement("div");
18 this.parentElement.classList.add("protyle-scroll");
19 this.parentElement.innerHTML = `<div class="protyle-scroll__up ariaLabel" data-position="north" aria-label="${updateHotkeyTip("⌘Home")}">
20 <svg><use xlink:href="#iconUp"></use></svg>
21</div>
22<div class="fn__none protyle-scroll__bar ariaLabel" data-position="2west" aria-label="Blocks 1/1">
23 <input class="b3-slider" type="range" max="1" min="1" step="1" value="1" />
24</div>
25<div class="protyle-scroll__down ariaLabel" aria-label="${updateHotkeyTip("⌘End")}">
26 <svg><use xlink:href="#iconDown"></use></svg>
27</div>`;
28
29 this.element = this.parentElement.querySelector(".protyle-scroll__bar");
30 this.keepLazyLoad = false;
31 if (!protyle.options.render.scroll) {
32 this.parentElement.classList.add("fn__none");
33 }
34 this.lastScrollTop = 0;
35 this.inputElement = this.element.firstElementChild as HTMLInputElement;
36 this.inputElement.addEventListener("input", () => {
37 this.element.setAttribute("aria-label", `Blocks ${this.inputElement.value}/${protyle.block.blockCount}`);
38 showTooltip(this.element.getAttribute("aria-label"), this.element);
39 });
40 /// #if BROWSER
41 this.inputElement.addEventListener("change", () => {
42 this.setIndex(protyle);
43 });
44 this.inputElement.addEventListener("touchend", () => {
45 this.setIndex(protyle);
46 });
47 /// #endif
48 this.parentElement.addEventListener("click", (event) => {
49 const target = event.target as HTMLElement;
50 if (hasClosestByClassName(target, "protyle-scroll__up")) {
51 goHome(protyle);
52 } else if (hasClosestByClassName(target, "protyle-scroll__down")) {
53 goEnd(protyle);
54 } else if (target.classList.contains("b3-slider")) {
55 this.setIndex(protyle);
56 }
57 });
58 this.parentElement.addEventListener("mousewheel", (event: WheelEvent) => {
59 if (event.deltaY !== 0 && protyle.scroll.lastScrollTop !== -1) {
60 protyle.contentElement.scrollTop += event.deltaY;
61 }
62 }, {passive: true});
63 }
64
65 private setIndex(protyle: IProtyle) {
66 if (protyle.wysiwyg.element.getAttribute("data-top")) {
67 return;
68 }
69 protyle.wysiwyg.element.setAttribute("data-top", protyle.wysiwyg.element.scrollTop.toString());
70 protyle.contentElement.style.overflow = "hidden";
71 fetchPost("/api/filetree/getDoc", {
72 index: parseInt(this.inputElement.value),
73 id: protyle.block.parentID,
74 mode: 0,
75 size: window.siyuan.config.editor.dynamicLoadBlocks,
76 }, getResponse => {
77 onGet({
78 data: getResponse,
79 protyle,
80 action: [Constants.CB_GET_FOCUSFIRST, Constants.CB_GET_UNCHANGEID],
81 afterCB: () => {
82 setTimeout(() => {
83 protyle.contentElement.style.overflow = "";
84 }, Constants.TIMEOUT_INPUT); // 需和 onGet 中的 preventScroll 保持一致
85 showTooltip(this.element.getAttribute("aria-label"), this.element);
86 }
87 });
88 });
89 }
90
91 public updateIndex(protyle: IProtyle, id: string, cb?: (index: number) => void) {
92 fetchPost("/api/block/getBlockIndex", {id}, (response) => {
93 if (!response.data) {
94 return;
95 }
96 const inputElement = protyle.scroll.element.querySelector(".b3-slider") as HTMLInputElement;
97 inputElement.value = response.data;
98 protyle.scroll.element.setAttribute("aria-label", `Blocks ${response.data}/${protyle.block.blockCount}`);
99 if (cb) {
100 cb(response.data);
101 }
102 });
103 }
104
105 public update(protyle: IProtyle) {
106 if (typeof protyle.block.blockCount === "number") {
107 this.inputElement.setAttribute("max", protyle.block.blockCount.toString());
108 this.element.setAttribute("aria-label", `Blocks ${this.inputElement.value}/${protyle.block.blockCount}`);
109 }
110 if (protyle.block.showAll) {
111 this.element.classList.add("fn__none");
112 } else {
113 if (protyle.block.scroll && !protyle.contentElement.classList.contains("fn__none")) {
114 this.element.classList.remove("fn__none");
115 } else {
116 this.element.classList.add("fn__none");
117 }
118 }
119 }
120}