diff --git a/app/src/constants.ts b/app/src/constants.ts index 3842033bd..c214d122d 100644 --- a/app/src/constants.ts +++ b/app/src/constants.ts @@ -117,6 +117,7 @@ export abstract class Constants { public static readonly LOCAL_PLUGINTOPUNPIN = "local-plugintopunpin"; public static readonly LOCAL_FLASHCARD = "local-flashcard"; public static readonly LOCAL_FILEPOSITION = "local-fileposition"; + public static readonly LOCAL_FILESPATHS = "local-filespaths"; public static readonly LOCAL_DIALOGPOSITION = "local-dialogposition"; public static readonly LOCAL_SESSION_FIRSTLOAD = "local-session-firstload"; public static readonly LOCAL_OUTLINE = "local-outline"; diff --git a/app/src/layout/dock/Files.ts b/app/src/layout/dock/Files.ts index e206ed611..cba4f82a3 100644 --- a/app/src/layout/dock/Files.ts +++ b/app/src/layout/dock/Files.ts @@ -14,13 +14,18 @@ import {fetchPost, fetchSyncPost} from "../../util/fetch"; import {openEmojiPanel, unicode2Emoji} from "../../emoji"; import {mountHelp, newNotebook} from "../../util/mount"; import {confirmDialog} from "../../dialog/confirmDialog"; -import {isNotCtrl, isOnlyMeta, updateHotkeyTip} from "../../protyle/util/compatibility"; +import {isNotCtrl, isOnlyMeta, setStorageVal, updateHotkeyTip} from "../../protyle/util/compatibility"; import {openFileById} from "../../editor/util"; import {hasClosestByAttribute, hasClosestByTag, hasTopClosestByTag} from "../../protyle/util/hasClosest"; import {isTouchDevice} from "../../util/functions"; import {App} from "../../index"; import {refreshFileTree} from "../../dialog/processSystem"; +type filesPath = { + notebookId: string, + openPaths: string[] +} + export class Files extends Model { public element: HTMLElement; public parent: Tab; @@ -732,6 +737,11 @@ export class Files extends Model { } else { counterElement.classList.add("fn__none"); } + window.siyuan.storage[Constants.LOCAL_FILESPATHS].forEach((item: filesPath) => { + item.openPaths.forEach((openPath) => { + this.selectItem(item.notebookId, openPath, undefined, false); + }) + }) if (!init) { return; } @@ -932,18 +942,18 @@ export class Files extends Model { }, 2); } - private onLsSelect(data: { files: IFile[], box: string, path: string }, filePath: string) { + private onLsSelect(data: { files: IFile[], box: string, path: string }, filePath: string, setStorage: boolean) { let fileHTML = ""; data.files.forEach((item: IFile) => { fileHTML += this.genFileHTML(item); if (filePath === item.path) { - this.selectItem(data.box, filePath); + this.selectItem(data.box, filePath, undefined, setStorage); } else if (filePath.startsWith(item.path.replace(".sy", ""))) { fetchPost("/api/filetree/listDocsByPath", { notebook: data.box, path: item.path }, response => { - this.selectItem(response.data.box, filePath, response.data); + this.selectItem(response.data.box, filePath, response.data, setStorage); }); } }); @@ -963,7 +973,9 @@ export class Files extends Model { emojiElement.textContent = unicode2Emoji(Constants.SIYUAN_IMAGE_FOLDER); } liElement.insertAdjacentHTML("afterend", ``); - this.setCurrent(this.element.querySelector(`ul[data-url="${data.box}"] li[data-path="${filePath}"]`)); + if (setStorage) { + this.setCurrent(this.element.querySelector(`ul[data-url="${data.box}"] li[data-path="${filePath}"]`)); + } } private setCurrent(target: HTMLElement, isScroll = true) { @@ -989,6 +1001,7 @@ export class Files extends Model { if (toggleElement.classList.contains("b3-list-item__arrow--open")) { toggleElement.classList.remove("b3-list-item__arrow--open"); liElement.nextElementSibling?.remove(); + this.getOpenPaths(); return; } fetchPost("/api/filetree/listDocsByPath", { @@ -1000,10 +1013,15 @@ export class Files extends Model { return; } this.onLsHTML(response.data); + this.getOpenPaths(); }); } - public selectItem(notebookId: string, filePath: string, data?: { files: IFile[], box: string, path: string }) { + public selectItem(notebookId: string, filePath: string, data?: { + files: IFile[], + box: string, + path: string + }, setStorage = true) { const treeElement = this.element.querySelector(`[data-url="${notebookId}"]`); if (!treeElement) { // 有文件树和编辑器的布局初始化时,文件树还未挂载 @@ -1024,22 +1042,62 @@ export class Files extends Model { } if (liElement.getAttribute("data-path") === filePath) { - this.setCurrent(liElement); + if (setStorage) { + this.setCurrent(liElement); + this.getOpenPaths(); + } else { + this.element.querySelector(".b3-list-item--focus")?.classList.remove("b3-list-item--focus"); + } return; } if (data && data.path === currentPath) { - this.onLsSelect(data, filePath); + this.onLsSelect(data, filePath, setStorage); } else { fetchPost("/api/filetree/listDocsByPath", { notebook: notebookId, path: currentPath }, response => { - this.onLsSelect(response.data, filePath); + this.onLsSelect(response.data, filePath, setStorage); }); } } + private getOpenPaths() { + const filesPaths: filesPath[] = []; + this.element.querySelectorAll('.b3-list[data-url]').forEach((item: HTMLElement) => { + const notebookPaths: filesPath = { + notebookId: item.getAttribute("data-url"), + openPaths: [] + } + item.querySelectorAll(".b3-list-item__arrow--open").forEach((openItem) => { + const liElement = hasClosestByTag(openItem, "LI"); + if (liElement) { + notebookPaths.openPaths.push(liElement.getAttribute("data-path")); + } + }) + if (notebookPaths.openPaths.length > 0) { + for (let i = 0; i < notebookPaths.openPaths.length; i++) { + for (let j = i + 1; j < notebookPaths.openPaths.length; j++) { + if (notebookPaths.openPaths[j].startsWith(notebookPaths.openPaths[i].replace(".sy", ""))) { + notebookPaths.openPaths.splice(i, 1); + j--; + } + } + } + notebookPaths.openPaths.forEach((openPath, index) => { + const nextPath = this.element.querySelector(`[data-url="${notebookPaths.notebookId}"] li[data-path="${openPath}"]`)?.nextElementSibling?.firstElementChild?.getAttribute("data-path"); + if (nextPath) { + notebookPaths.openPaths[index] = nextPath; + } + }); + filesPaths.push(notebookPaths); + } + }); + window.siyuan.storage[Constants.LOCAL_FILESPATHS] = filesPaths; + setStorageVal(Constants.LOCAL_FILESPATHS, filesPaths); + } + private genFileHTML = (item: IFile) => { let countHTML = ""; if (item.count && item.count > 0) { diff --git a/app/src/mobile/dock/MobileFiles.ts b/app/src/mobile/dock/MobileFiles.ts index 2b7f8e3eb..db3736275 100644 --- a/app/src/mobile/dock/MobileFiles.ts +++ b/app/src/mobile/dock/MobileFiles.ts @@ -1,4 +1,4 @@ -import {hasTopClosestByTag} from "../../protyle/util/hasClosest"; +import {hasClosestByTag, hasTopClosestByTag} from "../../protyle/util/hasClosest"; import {escapeHtml} from "../../util/escape"; import {Model} from "../../layout/Model"; import {Constants} from "../../constants"; @@ -15,6 +15,12 @@ import {newFile} from "../../util/newFile"; import {MenuItem} from "../../menus/Menu"; import {App} from "../../index"; import {refreshFileTree} from "../../dialog/processSystem"; +import {setStorageVal} from "../../protyle/util/compatibility"; + +type filesPath = { + notebookId: string, + openPaths: string[] +} export class MobileFiles extends Model { public element: HTMLElement; @@ -312,6 +318,11 @@ export class MobileFiles extends Model { } else { counterElement.classList.add("fn__none"); } + window.siyuan.storage[Constants.LOCAL_FILESPATHS].forEach((item: filesPath) => { + item.openPaths.forEach((openPath) => { + this.selectItem(item.notebookId, openPath, undefined, false); + }) + }) if (!init) { return; } @@ -430,7 +441,7 @@ export class MobileFiles extends Model { removeElement.remove(); const counterElement = this.closeElement.querySelector(".counter"); counterElement.textContent = (parseInt(counterElement.textContent) - 1).toString(); - if (counterElement.textContent === "0") { + if (counterElement.textContent === "0") { counterElement.classList.add("fn__none"); } } @@ -545,18 +556,18 @@ export class MobileFiles extends Model { }, 2); } - private onLsSelect(data: { files: IFile[], box: string, path: string }, filePath: string) { + private onLsSelect(data: { files: IFile[], box: string, path: string }, filePath: string, setStorage: boolean) { let fileHTML = ""; data.files.forEach((item: IFile) => { fileHTML += this.genFileHTML(item); if (filePath === item.path) { - this.selectItem(data.box, filePath); + this.selectItem(data.box, filePath, undefined, setStorage); } else if (filePath.startsWith(item.path.replace(".sy", ""))) { fetchPost("/api/filetree/listDocsByPath", { notebook: data.box, path: item.path }, response => { - this.selectItem(response.data.box, filePath, response.data); + this.selectItem(response.data.box, filePath, response.data, setStorage); }); } }); @@ -570,7 +581,9 @@ export class MobileFiles extends Model { } liElement.querySelector(".b3-list-item__arrow").classList.add("b3-list-item__arrow--open"); liElement.insertAdjacentHTML("afterend", ``); - this.setCurrent(this.element.querySelector(`ul[data-url="${data.box}"] li[data-path="${filePath}"]`)); + if (setStorage) { + this.setCurrent(this.element.querySelector(`ul[data-url="${data.box}"] li[data-path="${filePath}"]`)); + } } private setCurrent(target: HTMLElement) { @@ -594,6 +607,7 @@ export class MobileFiles extends Model { if (toggleElement.classList.contains("b3-list-item__arrow--open")) { toggleElement.classList.remove("b3-list-item__arrow--open"); liElement.nextElementSibling?.remove(); + this.getOpenPaths(); return; } fetchPost("/api/filetree/listDocsByPath", { @@ -605,10 +619,15 @@ export class MobileFiles extends Model { return; } this.onLsHTML(response.data); + this.getOpenPaths(); }); } - public selectItem(notebookId: string, filePath: string, data?: { files: IFile[], box: string, path: string }) { + public selectItem(notebookId: string, filePath: string, data?: { + files: IFile[], + box: string, + path: string + }, setStorage = true) { const treeElement = this.element.querySelector(`[data-url="${notebookId}"]`); if (!treeElement) { // 有文件树和编辑器的布局初始化时,文件树还未挂载 @@ -629,22 +648,62 @@ export class MobileFiles extends Model { } if (liElement.getAttribute("data-path") === filePath) { - this.setCurrent(liElement); + if (setStorage) { + this.setCurrent(liElement); + this.getOpenPaths(); + } else { + this.element.querySelector(".b3-list-item--focus")?.classList.remove("b3-list-item--focus"); + } return; } if (data && data.path === currentPath) { - this.onLsSelect(data, filePath); + this.onLsSelect(data, filePath, setStorage); } else { fetchPost("/api/filetree/listDocsByPath", { notebook: notebookId, path: currentPath }, response => { - this.onLsSelect(response.data, filePath); + this.onLsSelect(response.data, filePath, setStorage); }); } } + private getOpenPaths() { + const filesPaths: filesPath[] = []; + this.element.querySelectorAll('.b3-list[data-url]').forEach((item: HTMLElement) => { + const notebookPaths: filesPath = { + notebookId: item.getAttribute("data-url"), + openPaths: [] + } + item.querySelectorAll(".b3-list-item__arrow--open").forEach((openItem) => { + const liElement = hasClosestByTag(openItem, "LI"); + if (liElement) { + notebookPaths.openPaths.push(liElement.getAttribute("data-path")); + } + }) + if (notebookPaths.openPaths.length > 0) { + for (let i = 0; i < notebookPaths.openPaths.length; i++) { + for (let j = i + 1; j < notebookPaths.openPaths.length; j++) { + if (notebookPaths.openPaths[j].startsWith(notebookPaths.openPaths[i].replace(".sy", ""))) { + notebookPaths.openPaths.splice(i, 1); + j--; + } + } + } + notebookPaths.openPaths.forEach((openPath, index) => { + const nextPath = this.element.querySelector(`[data-url="${notebookPaths.notebookId}"] li[data-path="${openPath}"]`)?.nextElementSibling?.firstElementChild?.getAttribute("data-path"); + if (nextPath) { + notebookPaths.openPaths[index] = nextPath; + } + }); + filesPaths.push(notebookPaths); + } + }); + window.siyuan.storage[Constants.LOCAL_FILESPATHS] = filesPaths; + setStorageVal(Constants.LOCAL_FILESPATHS, filesPaths); + } + private genFileHTML = (item: IFile) => { let countHTML = ""; if (item.count && item.count > 0) { diff --git a/app/src/protyle/util/compatibility.ts b/app/src/protyle/util/compatibility.ts index 0d5841104..fcf3fe856 100644 --- a/app/src/protyle/util/compatibility.ts +++ b/app/src/protyle/util/compatibility.ts @@ -231,6 +231,7 @@ export const getLocalStorage = (cb: () => void) => { id: "", }; defaultStorage[Constants.LOCAL_FONTSTYLES] = []; + defaultStorage[Constants.LOCAL_FILESPATHS] = []; defaultStorage[Constants.LOCAL_SEARCHDATA] = { page: 1, sort: 0, @@ -265,7 +266,7 @@ export const getLocalStorage = (cb: () => void) => { Constants.LOCAL_SEARCHDATA, Constants.LOCAL_ZOOM, Constants.LOCAL_LAYOUTS, Constants.LOCAL_AI, Constants.LOCAL_PLUGINTOPUNPIN, Constants.LOCAL_SEARCHASSET, Constants.LOCAL_FLASHCARD, Constants.LOCAL_DIALOGPOSITION, Constants.LOCAL_SEARCHUNREF, Constants.LOCAL_HISTORY, - Constants.LOCAL_OUTLINE, Constants.LOCAL_FILEPOSITION].forEach((key) => { + Constants.LOCAL_OUTLINE, Constants.LOCAL_FILEPOSITION, Constants.LOCAL_FILESPATHS].forEach((key) => { if (typeof response.data[key] === "string") { try { const parseData = JSON.parse(response.data[key]);