diff --git a/src/components/fileTree/index.js b/src/components/fileTree/index.js index c402c738d..16b129c44 100644 --- a/src/components/fileTree/index.js +++ b/src/components/fileTree/index.js @@ -167,6 +167,7 @@ export default class FileTree { // Child file tree for nested folders let childTree = null; + $content._fileTree = null; const toggle = async () => { const isExpanded = !$wrapper.classList.contains("hidden"); @@ -179,6 +180,7 @@ export default class FileTree { childTree.destroy(); this.childTrees.delete(url); childTree = null; + $content._fileTree = null; } this.options.onExpandedChange?.(url, false); } else { @@ -192,6 +194,7 @@ export default class FileTree { _depth: this.depth + 1, }); this.childTrees.set(url, childTree); + $content._fileTree = childTree; try { await childTree.load(url); } finally { @@ -216,20 +219,34 @@ export default class FileTree { queueMicrotask(() => toggle()); } - // Add properties for external access (keep unclasped for collapsableList compatibility) - Object.defineProperties($wrapper, { - collapsed: { get: () => $wrapper.classList.contains("hidden") }, - expanded: { get: () => !$wrapper.classList.contains("hidden") }, - unclasped: { get: () => !$wrapper.classList.contains("hidden") }, // Legacy compatibility - $title: { get: () => $title }, - $ul: { get: () => $content }, - expand: { - value: () => !$wrapper.classList.contains("hidden") || toggle(), - }, - collapse: { - value: () => $wrapper.classList.contains("hidden") || toggle(), - }, - }); + const defineCollapsibleAccessors = ($el, { includeTitle = true } = {}) => { + const properties = { + collapsed: { get: () => $wrapper.classList.contains("hidden") }, + expanded: { get: () => !$wrapper.classList.contains("hidden") }, + unclasped: { get: () => !$wrapper.classList.contains("hidden") }, // Legacy compatibility + $ul: { get: () => $content }, + fileTree: { get: () => childTree }, + refresh: { + value: () => childTree?.refresh(), + }, + expand: { + value: () => !$wrapper.classList.contains("hidden") || toggle(), + }, + collapse: { + value: () => $wrapper.classList.contains("hidden") || toggle(), + }, + }; + + if (includeTitle) { + properties.$title = { get: () => $title }; + } + + Object.defineProperties($el, properties); + }; + + // Keep nested folders compatible with the legacy collapsableList API. + defineCollapsibleAccessors($wrapper); + defineCollapsibleAccessors($title, { includeTitle: false }); return $wrapper; } @@ -281,11 +298,7 @@ export default class FileTree { * Clear all rendered content */ clear() { - // Destroy all child trees - for (const childTree of this.childTrees.values()) { - childTree.destroy(); - } - this.childTrees.clear(); + this.destroyChildTrees(); if (this.virtualList) { this.virtualList.destroy(); @@ -322,6 +335,16 @@ export default class FileTree { } } + /** + * Destroy all expanded child trees and clear their references. + */ + destroyChildTrees() { + for (const childTree of this.childTrees.values()) { + childTree.destroy(); + } + this.childTrees.clear(); + } + /** * Append a new entry to the tree * @param {string} name @@ -356,6 +379,7 @@ export default class FileTree { this.virtualList.setItems(this.entries); } else { // Fragment mode: re-render + this.destroyChildTrees(); this.container.innerHTML = ""; this.renderWithFragment(); } diff --git a/src/lib/openFolder.js b/src/lib/openFolder.js index c542a6073..b286535ac 100644 --- a/src/lib/openFolder.js +++ b/src/lib/openFolder.js @@ -702,20 +702,14 @@ function execOperation(type, action, url, $target, name) { if (!newUrl.created) return; if (isNestedPath) { - openFolder.find(url)?.reload(); + await refreshOpenFolder(url); await FileList.refresh(); toast(strings.success); return; } newName = Url.basename(newUrl.uri); - if ($target.unclasped) { - if (newUrl.type === "file") { - appendTile($target, createFileTile(newName, newUrl.uri)); - } else if (newUrl.type === "folder") { - appendList($target, createFolderTile(newName, newUrl.uri)); - } - } + appendEntryToOpenFolder(url, newUrl.uri, newUrl.type); FileList.append(url, newUrl.uri); toast(strings.success); @@ -995,6 +989,72 @@ function appendList($target, $list) { else $target.append($list); } +/** + * Get the active file tree for a folder element, if it has been loaded. + * @param {HTMLElement} $el + * @returns {FileTree|null} + */ +function getLoadedFileTree($el) { + return ( + $el?.$ul?._fileTree || $el?.fileTree || $el?.nextElementSibling?._fileTree + ); +} + +/** + * Update matching expanded folder views with a new entry. + * @param {string} parentUrl + * @param {string} entryUrl + * @param {"file"|"folder"} type + */ +function appendEntryToOpenFolder(parentUrl, entryUrl, type) { + const filesApp = sidebarApps.get("files"); + const $els = filesApp.getAll(`[data-url="${parentUrl}"]`); + const isDirectory = type === "folder"; + const name = Url.basename(entryUrl); + + Array.from($els).forEach(($el) => { + if (!(helpers.isDir($el.dataset.type) || $el.dataset.type === "root")) { + return; + } + + if (!$el.unclasped) return; + + const fileTree = getLoadedFileTree($el); + if (fileTree) { + fileTree.appendEntry(name, entryUrl, isDirectory); + return; + } + + if (isDirectory) { + appendList($el, createFolderTile(name, entryUrl)); + } else { + appendTile($el, createFileTile(name, entryUrl)); + } + }); +} + +/** + * Refresh matching expanded folder views. + * @param {string} folderUrl + */ +async function refreshOpenFolder(folderUrl) { + const filesApp = sidebarApps.get("files"); + const $els = filesApp.getAll(`[data-url="${folderUrl}"]`); + + await Promise.all( + Array.from($els).map(async ($el) => { + if (!(helpers.isDir($el.dataset.type) || $el.dataset.type === "root")) { + return; + } + + const fileTree = getLoadedFileTree($el); + if (fileTree) { + await fileTree.refresh(); + } + }), + ); +} + /** * Create a folder tile * @param {string} name @@ -1039,18 +1099,7 @@ function createFileTile(name, url) { openFolder.add = async (url, type) => { const { url: parent } = await fsOperation(Url.dirname(url)).stat(); FileList.append(parent, url); - - const filesApp = sidebarApps.get("files"); - const $els = filesApp.getAll(`[data-url="${parent}"]`); - Array.from($els).forEach(($el) => { - if ($el.dataset.type !== "dir") return; - - if (type === "file") { - appendTile($el, createFileTile(Url.basename(url), url)); - } else { - appendList($el, createFolderTile(Url.basename(url), url)); - } - }); + appendEntryToOpenFolder(parent, url, type); }; openFolder.renameItem = (oldFile, newFile, newFilename) => {