diff --git a/eslint.config.mts b/eslint.config.mts index bb047f9..fcf59e4 100644 --- a/eslint.config.mts +++ b/eslint.config.mts @@ -139,18 +139,23 @@ export default [ 'no-empty-function': 'warn', 'complexity': [ 'warn', - 10 + 15 ], 'max-len': [ 'warn', { - code: 100 + code: 100, + ignoreComments: true, } ], 'no-inline-comments': 'warn', 'no-magic-numbers': [ 'warn', { ignore: [0, 1], - enforceConst: true + enforceConst: true, + ignoreTypeIndexes: true, + ignoreEnums: true, + ignoreNumericLiteralTypes: true, + ignoreClassFieldInitialValues: true, } ], } diff --git a/src/events/EventEmitter.ts b/src/events/EventEmitter.ts index a5ba33e..607fb07 100644 --- a/src/events/EventEmitter.ts +++ b/src/events/EventEmitter.ts @@ -1,20 +1,20 @@ export class CustomEventEmitter { - private events: { [key: string]: Array<(data?: any) => void> } = {}; + private events: { [key: string]: Array<(data?: unknown) => void> } = {}; - on(event: string, listener: (data?: any) => void) { + on(event: string, listener: (data?: unknown) => void): void { if (!this.events[event]) { this.events[event] = []; } this.events[event].push(listener); } - off(event: string, listener: (data?: any) => void) { + off(event: string, listener: (data?: unknown) => void): void { if (!this.events[event]) return; this.events[event] = this.events[event].filter((l) => l !== listener); } - emit(event: string, data?: any) { + emit(event: string, data?: unknown): void { if (!this.events[event]) return; this.events[event].forEach((listener) => listener(data)); diff --git a/src/events/FrontMatterTitle.ts b/src/events/FrontMatterTitle.ts index 3586346..e694c87 100644 --- a/src/events/FrontMatterTitle.ts +++ b/src/events/FrontMatterTitle.ts @@ -1,9 +1,28 @@ import type FolderNotesPlugin from 'src/main'; -import type { Listener, Events, ApiInterface, DeferInterface, ListenerRef, EventDispatcherInterface } from 'front-matter-plugin-api-provider'; -import { getDefer } from 'front-matter-plugin-api-provider'; -import type { App } from 'obsidian'; -import { TFile, TFolder } from 'obsidian'; +import { + type Listener, + type Events, + type ApiInterface, + type DeferInterface, + type ListenerRef, + type EventDispatcherInterface, + getDefer, +} from 'front-matter-plugin-api-provider'; +import { type App, TFile, TFolder } from 'obsidian'; import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions'; + +interface UpdateData { + id: string; + result: boolean; + path: string; + pathOnly: boolean; + breadcrumb?: HTMLElement; +} + +interface WrappedUpdateData { + data: UpdateData; +} + export class FrontMatterTitlePluginHandler { plugin: FolderNotesPlugin; app: App; @@ -16,7 +35,7 @@ export class FrontMatterTitlePluginHandler { this.plugin = plugin; this.app = plugin.app; - (async () => { + (async (): Promise => { this.deffer = getDefer(this.app); if (this.deffer.isPluginReady()) { this.api = this.deffer.getApi(); @@ -35,8 +54,8 @@ export class FrontMatterTitlePluginHandler { } const event: Listener = { name: 'manager:update', - cb: (data) => { - this.fmptUpdateFileName(data as any, true); + cb: (data): void => { + this.fmptUpdateFileName(data as unknown as UpdateData, true); }, }; // Keep ref to remove listener @@ -51,20 +70,18 @@ export class FrontMatterTitlePluginHandler { } })(); } - deleteEvent() { + + deleteEvent(): void { if (this.eventRef) { this.dispatcher.removeListener(this.eventRef); } } - async fmptUpdateFileName(data: { - id: string; - result: boolean; - path: string; - pathOnly: boolean; - breadcrumb?: HTMLElement; - }, isEvent: boolean) { - if ((data as any).data) data = (data as any).data; - const file = this.app.vault.getAbstractFileByPath(data.path); + async fmptUpdateFileName(data: UpdateData, isEvent: boolean): Promise { + const hasNestedData = 'data' in (data as unknown as Record); + const actualData: UpdateData = hasNestedData + ? (data as unknown as WrappedUpdateData).data + : data; + const file = this.app.vault.getAbstractFileByPath(actualData.path); if (!(file instanceof TFile)) { return; } const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#'); @@ -75,11 +92,11 @@ export class FrontMatterTitlePluginHandler { const folderNote = getFolderNote(this.plugin, folder.path); if (!folderNote) { return; } if (folderNote !== file) { return; } - if (!data.pathOnly) { + if (!actualData.pathOnly) { this.plugin.changeFolderNameInExplorer(folder, newName); } - const { breadcrumb } = data; + const { breadcrumb } = actualData; if (breadcrumb) { this.plugin.changeFolderNameInPath(folder, newName, breadcrumb); } @@ -98,15 +115,12 @@ export class FrontMatterTitlePluginHandler { } - async fmptUpdateFolderName(data: { - id: string; - result: boolean; - path: string; - pathOnly: boolean; - breadcrumb?: HTMLElement; - }, replacePath: boolean) { - if ((data as any).data) data = (data as any).data; - const folder = this.app.vault.getAbstractFileByPath(data.path); + async fmptUpdateFolderName(data: UpdateData, _replacePath: boolean): Promise { + const hasNestedData = 'data' in (data as unknown as Record); + const actualData: UpdateData = hasNestedData + ? (data as unknown as WrappedUpdateData).data + : data; + const folder = this.app.vault.getAbstractFileByPath(actualData.path); if (!(folder instanceof TFolder)) { return; } const folderNote = getFolderNote(this.plugin, folder.path); if (!folderNote) { return; } @@ -115,11 +129,11 @@ export class FrontMatterTitlePluginHandler { const newName = resolver?.resolve(folderNote?.path ?? ''); if (!newName) return; - if (!data.pathOnly) { + if (!actualData.pathOnly) { this.plugin.changeFolderNameInExplorer(folder, newName); } - const { breadcrumb } = data; + const { breadcrumb } = actualData; if (breadcrumb) { this.plugin.changeFolderNameInPath(folder, newName, breadcrumb); } diff --git a/src/events/MutationObserver.ts b/src/events/MutationObserver.ts index a03a5e0..1a6a01d 100644 --- a/src/events/MutationObserver.ts +++ b/src/events/MutationObserver.ts @@ -7,7 +7,7 @@ import { updateCSSClassesForFolder } from 'src/functions/styleFunctions'; let fileExplorerMutationObserver: MutationObserver | null = null; -export function registerFileExplorerObserver(plugin: FolderNotesPlugin) { +export function registerFileExplorerObserver(plugin: FolderNotesPlugin): void { // Run once on initial layout plugin.app.workspace.onLayoutReady(() => { initializeFolderNoteFeatures(plugin); @@ -30,19 +30,19 @@ export function registerFileExplorerObserver(plugin: FolderNotesPlugin) { ); } -export function unregisterFileExplorerObserver() { +export function unregisterFileExplorerObserver(): void { if (fileExplorerMutationObserver) { fileExplorerMutationObserver.disconnect(); fileExplorerMutationObserver = null; } } -function initializeFolderNoteFeatures(plugin: FolderNotesPlugin) { +function initializeFolderNoteFeatures(plugin: FolderNotesPlugin): void { initializeAllFolderTitles(plugin); observeFolderTitleMutations(plugin); } -function initializeBreadcrumbs(plugin: FolderNotesPlugin) { +function initializeBreadcrumbs(plugin: FolderNotesPlugin): void { const titleContainers = document.querySelectorAll('.view-header-title-container'); if (!titleContainers.length) return; titleContainers.forEach((container) => { @@ -55,7 +55,7 @@ function initializeBreadcrumbs(plugin: FolderNotesPlugin) { * Observes the File Explorer for newly added folder elements and applies plugin logic (e.g., styles, event listeners) * automatically when folders are created, expanded, or when the File Explorer view is reopened. */ -function observeFolderTitleMutations(plugin: FolderNotesPlugin) { +function observeFolderTitleMutations(plugin: FolderNotesPlugin): void { if (fileExplorerMutationObserver) { fileExplorerMutationObserver.disconnect(); } @@ -71,7 +71,7 @@ function observeFolderTitleMutations(plugin: FolderNotesPlugin) { fileExplorerMutationObserver.observe(document, { childList: true, subtree: true }); } -function initializeAllFolderTitles(plugin: FolderNotesPlugin) { +function initializeAllFolderTitles(plugin: FolderNotesPlugin): void { const allTitles = document.querySelectorAll('.nav-folder-title-content'); for (const title of Array.from(allTitles)) { const folderTitle = title as HTMLElement; @@ -83,7 +83,7 @@ function initializeAllFolderTitles(plugin: FolderNotesPlugin) { } } -function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin) { +function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin): void { const titles: HTMLElement[] = []; if (node.matches('.nav-folder-title-content')) { titles.push(node); @@ -95,6 +95,7 @@ function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin) { titles.forEach((folderTitle) => { const folderEl = folderTitle.closest('.nav-folder-title'); const folderPath = folderEl?.getAttribute('data-path') || ''; + const RETRY_TIMEOUT = 50; if (!folderEl || !folderPath) { setTimeout(() => { const retryFolderEl = folderTitle.closest('.nav-folder-title'); @@ -102,14 +103,18 @@ function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin) { if (retryFolderEl && retryFolderPath) { setupFolderTitle(folderTitle, plugin, retryFolderPath); } - }, 50); + }, RETRY_TIMEOUT); return; } setupFolderTitle(folderTitle, plugin, folderPath); }); } -async function setupFolderTitle(folderTitle: HTMLElement, plugin: FolderNotesPlugin, folderPath: string) { +async function setupFolderTitle( + folderTitle: HTMLElement, + plugin: FolderNotesPlugin, + folderPath: string, +): Promise { if (folderTitle.dataset.initialized === 'true') return; if (!folderPath) return; @@ -117,7 +122,10 @@ async function setupFolderTitle(folderTitle: HTMLElement, plugin: FolderNotesPlu await updateCSSClassesForFolder(folderPath, plugin); if (plugin.settings.frontMatterTitle.enabled) { - plugin.fmtpHandler?.fmptUpdateFolderName({ id: '', result: false, path: folderPath, pathOnly: false }, false); + plugin.fmtpHandler?.fmptUpdateFolderName( + { id: '', result: false, path: folderPath, pathOnly: false }, + false, + ); } if (Platform.isMobile && plugin.settings.disableOpenFolderNoteOnClick) return; @@ -150,21 +158,24 @@ async function setupFolderTitle(folderTitle: HTMLElement, plugin: FolderNotesPlu }); } -async function updateFolderNamesInPath(plugin: FolderNotesPlugin, titleContainer: HTMLElement) { +async function updateFolderNamesInPath( + plugin: FolderNotesPlugin, + titleContainer: HTMLElement, +): Promise { const headers = titleContainer.querySelectorAll('span.view-header-breadcrumb'); let path = ''; + const TRAILING_SLASH_LENGTH = 1; headers.forEach(async (breadcrumb: HTMLElement) => { path += breadcrumb.getAttribute('old-name') ?? (breadcrumb as HTMLElement).innerText.trim(); path += '/'; - const folderPath = path.slice(0, -1); + const folderPath = path.slice(0, -TRAILING_SLASH_LENGTH); const excludedFolder = getExcludedFolder(plugin, folderPath, true); if (excludedFolder?.disableFolderNote) return; const folderNote = getFolderNote(plugin, folderPath); if (!folderNote) return; if (folderNote) breadcrumb.classList.add('has-folder-note'); - - breadcrumb?.setAttribute('data-path', path.slice(0, -1)); + breadcrumb?.setAttribute('data-path', path.slice(0, -TRAILING_SLASH_LENGTH)); if (!breadcrumb.onclick) { breadcrumb.addEventListener('click', (e) => { handleViewHeaderClick(e as MouseEvent, plugin); @@ -172,7 +183,10 @@ async function updateFolderNamesInPath(plugin: FolderNotesPlugin, titleContainer } if (plugin.settings.frontMatterTitle.enabled) { - plugin.fmtpHandler?.fmptUpdateFolderName({ id: '', result: false, path: folderPath, pathOnly: true, breadcrumb: breadcrumb }, true); + plugin.fmtpHandler?.fmptUpdateFolderName( + { id: '', result: false, path: folderPath, pathOnly: true, breadcrumb: breadcrumb }, + true, + ); } }); } @@ -180,10 +194,14 @@ async function updateFolderNamesInPath(plugin: FolderNotesPlugin, titleContainer // Schedules a callback to run when the browser is idle, or after a timeout as a fallback. // - callback: The function to execute when idle or after the timeout. // - options: Optional object with a 'timeout' property (in milliseconds). -function scheduleIdle(callback: () => void, options?: { timeout: number }) { +function scheduleIdle(callback: () => void, options?: { timeout: number }): void { + const DEFAULT_IDLE_TIMEOUT = 200; if ('requestIdleCallback' in window) { - (window as any).requestIdleCallback(callback, options); + const windowWithIdle = window as Window & { + requestIdleCallback: (callback: () => void, options?: { timeout: number }) => void + }; + windowWithIdle.requestIdleCallback(callback, options); } else { - setTimeout(callback, options?.timeout || 200); + setTimeout(callback, options?.timeout || DEFAULT_IDLE_TIMEOUT); } } diff --git a/src/events/TabManager.ts b/src/events/TabManager.ts index 11903ff..c9647bb 100644 --- a/src/events/TabManager.ts +++ b/src/events/TabManager.ts @@ -1,6 +1,5 @@ import type FolderNotesPlugin from 'src/main'; -import type { App } from 'obsidian'; -import { EditableFileView, TFolder } from 'obsidian'; +import { EditableFileView, TFolder, type App } from 'obsidian'; import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions'; export class TabManager { plugin: FolderNotesPlugin; @@ -10,19 +9,18 @@ export class TabManager { this.app = plugin.app; } - resetTabs() { + resetTabs(): void { if (!this.isEnabled()) return; this.app.workspace.iterateAllLeaves((leaf) => { if (!(leaf.view instanceof EditableFileView)) return; const file = leaf.view?.file; if (!file) return; - // @ts-ignore leaf.tabHeaderInnerTitleEl.setText(file.basename); }); } - updateTabs() { + updateTabs(): void { if (!this.isEnabled()) return; this.app.workspace.iterateAllLeaves((leaf) => { if (!(leaf.view instanceof EditableFileView)) return; @@ -30,12 +28,11 @@ export class TabManager { if (!file) return; const folder = getFolder(this.plugin, file); if (!folder) return; - // @ts-ignore leaf.tabHeaderInnerTitleEl.setText(folder.name); }); } - updateTab(folderPath: string) { + updateTab(folderPath: string): void { if (!this.isEnabled()) return; const folder = this.app.vault.getAbstractFileByPath(folderPath); @@ -49,13 +46,12 @@ export class TabManager { const file = leaf.view?.file; if (!file) return; if (file.path === folderNote.path) { - // @ts-ignore leaf.tabHeaderInnerTitleEl.setText(folder.name); } }); } - isEnabled() { + isEnabled(): boolean { if (this.plugin.settings.folderNoteName === '{{folder_name}}') return false; return this.plugin.settings.tabManagerEnabled; } diff --git a/src/events/handleClick.ts b/src/events/handleClick.ts index fb8dc6f..60ecabd 100644 --- a/src/events/handleClick.ts +++ b/src/events/handleClick.ts @@ -1,10 +1,18 @@ -import { Keymap, Platform } from 'obsidian'; +import { Keymap, Platform, type TFile } from 'obsidian'; import type FolderNotesPlugin from 'src/main'; import { openFolderNote, createFolderNote, getFolderNote } from 'src/functions/folderNoteFunctions'; import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions'; -import { addCSSClassToFileExplorerEl, removeCSSClassFromFileExplorerEL } from 'src/functions/styleFunctions'; +import { + addCSSClassToFileExplorerEl, + removeCSSClassFromFileExplorerEL, +} from 'src/functions/styleFunctions'; -export async function handleViewHeaderClick(event: MouseEvent, plugin: FolderNotesPlugin) { + + +export async function handleViewHeaderClick( + event: MouseEvent, + plugin: FolderNotesPlugin, +): Promise { if (!plugin.settings.openFolderNoteOnClickInPath) return; event.stopImmediatePropagation(); event.preventDefault(); @@ -13,37 +21,61 @@ export async function handleViewHeaderClick(event: MouseEvent, plugin: FolderNot const folderPath = event.target.getAttribute('data-path'); if (!folderPath) { return; } - const excludedFolder = getExcludedFolder(plugin, folderPath, true); - if (excludedFolder?.disableFolderNote) { - event.target.onclick = null; - event.target.click(); - return; - } else if (excludedFolder?.enableCollapsing || plugin.settings.enableCollapsing) { - event.target.onclick = null; - event.target.click(); - } + + if (await isExcludedFolder(event, plugin, folderPath)) return; const folderNote = getFolderNote(plugin, folderPath); if (folderNote) { - await openFolderNote(plugin, folderNote, event).then(async () => { - // @ts-ignore - const fileExplorerPlugin = plugin.app.internalPlugins.getEnabledPluginById('file-explorer'); - if (fileExplorerPlugin && Platform.isMobile && plugin.settings.openSidebar.mobile) { - setTimeout(() => { fileExplorerPlugin.revealInFolder(folderNote); }, 200); - } else if (fileExplorerPlugin && Platform.isDesktop && plugin.settings.openSidebar.desktop) { - fileExplorerPlugin.revealInFolder(folderNote); - } - }); + await openFolderNote(plugin, folderNote, event).then(() => + handleFolderNoteReveal(plugin, folderNote), + ); return; } else if (event.altKey || Keymap.isModEvent(event) === 'tab') { - const usedCtrl = Platform.isMacOS ? event.metaKey : event.ctrlKey; - if ((plugin.settings.altKey && event.altKey) || (usedCtrl && Keymap.isModEvent(event) === 'tab')) { - await createFolderNote(plugin, folderPath, true, undefined, true); - addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, plugin); - removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, plugin); - return; - } + if (await handleFolderNoteCreation(event, plugin, folderPath)) return; } - event.target.onclick = null; - event.target.click(); + (event.target as HTMLElement).onclick = null; + (event.target as HTMLElement).click(); +} + +async function isExcludedFolder( + event: MouseEvent, + plugin: FolderNotesPlugin, + folderPath: string, +): Promise { + const excludedFolder = getExcludedFolder(plugin, folderPath, true); + if (excludedFolder?.disableFolderNote) { + (event.target as HTMLElement).onclick = null; + (event.target as HTMLElement).click(); + return true; + } else if (excludedFolder?.enableCollapsing || plugin.settings.enableCollapsing) { + (event.target as HTMLElement).onclick = null; + (event.target as HTMLElement).click(); + } + return false; +} + +async function handleFolderNoteReveal(plugin: FolderNotesPlugin, folderNote: TFile): Promise { + const fileExplorerPlugin = plugin.app.internalPlugins.getEnabledPluginById('file-explorer'); + if (fileExplorerPlugin && Platform.isMobile && plugin.settings.openSidebar.mobile) { + const OPEN_SIDEBAR_DELAY = 200; + setTimeout(() => { fileExplorerPlugin.revealInFolder(folderNote); }, OPEN_SIDEBAR_DELAY); + } else if (fileExplorerPlugin && Platform.isDesktop && plugin.settings.openSidebar.desktop) { + fileExplorerPlugin.revealInFolder(folderNote); + } +} + +async function handleFolderNoteCreation( + event: MouseEvent, + plugin: FolderNotesPlugin, + folderPath: string, +): Promise { + const usedCtrl = Platform.isMacOS ? event.metaKey : event.ctrlKey; + if ((plugin.settings.altKey && event.altKey) || + (usedCtrl && Keymap.isModEvent(event) === 'tab')) { + await createFolderNote(plugin, folderPath, true, undefined, true); + addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, plugin); + removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, plugin); + return true; + } + return false; } diff --git a/src/events/handleCreate.ts b/src/events/handleCreate.ts index f65d3ed..c6239c3 100644 --- a/src/events/handleCreate.ts +++ b/src/events/handleCreate.ts @@ -1,11 +1,18 @@ -import type { TAbstractFile } from 'obsidian'; -import { TFolder, TFile } from 'obsidian'; +import { TFolder, TFile, type TAbstractFile } from 'obsidian'; import type FolderNotesPlugin from 'src/main'; -import { createFolderNote, getFolder, getFolderNote, turnIntoFolderNote } from 'src/functions/folderNoteFunctions'; +import { + createFolderNote, + getFolder, + getFolderNote, + turnIntoFolderNote, +} from 'src/functions/folderNoteFunctions'; import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions'; -import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl } from 'src/functions/styleFunctions'; +import { + removeCSSClassFromFileExplorerEL, + addCSSClassToFileExplorerEl, +} from 'src/functions/styleFunctions'; -export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin) { +export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin): Promise { if (!plugin.app.workspace.layoutReady) return; const folder = file.parent; @@ -24,7 +31,7 @@ export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugi } } -async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin) { +async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin): Promise { const folder = getFolder(plugin, file); if (!(folder instanceof TFolder) && plugin.settings.autoCreateForFiles) { @@ -51,7 +58,7 @@ async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin) { } } -async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin) { +async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin): Promise { let openFile = plugin.settings.autoCreateFocusFiles; const attachmentFolderPath = plugin.app.vault.getConfig('attachmentFolderPath') as string; diff --git a/src/events/handleDelete.ts b/src/events/handleDelete.ts index 089c1ff..e9d5c51 100644 --- a/src/events/handleDelete.ts +++ b/src/events/handleDelete.ts @@ -1,11 +1,14 @@ -import type { TAbstractFile } from 'obsidian'; -import { TFolder, TFile } from 'obsidian'; +import { TFolder, TFile, type TAbstractFile } from 'obsidian'; import type FolderNotesPlugin from 'src/main'; import { getFolderNote, getFolder, deleteFolderNote } from 'src/functions/folderNoteFunctions'; -import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl, hideFolderNoteInFileExplorer } from 'src/functions/styleFunctions'; +import { + removeCSSClassFromFileExplorerEL, + addCSSClassToFileExplorerEl, + hideFolderNoteInFileExplorer, +} from 'src/functions/styleFunctions'; import { getFolderPathFromString } from 'src/functions/utils'; -export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin) { +export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin): void { const folder = plugin.app.vault.getAbstractFileByPath(getFolderPathFromString(file.path)); if (folder instanceof TFolder) { if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) { @@ -16,13 +19,15 @@ export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin) { } if (file instanceof TFile) { - const folder = getFolder(plugin, file); - if (!folder) { return; } - const folderNote = getFolderNote(plugin, folder.path); + const folderNoteFolder = getFolder(plugin, file); + if (!folderNoteFolder) { return; } + const folderNote = getFolderNote(plugin, folderNoteFolder.path); if (folderNote) { return; } - removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin); - removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin); - hideFolderNoteInFileExplorer(folder.path, plugin); + removeCSSClassFromFileExplorerEL(folderNoteFolder.path, 'has-folder-note', false, plugin); + removeCSSClassFromFileExplorerEL( + folderNoteFolder.path, 'only-has-folder-note', true, plugin, + ); + hideFolderNoteInFileExplorer(folderNoteFolder.path, plugin); } if (!(file instanceof TFolder)) { return; } diff --git a/src/events/handleRename.ts b/src/events/handleRename.ts index 7ca75b2..c6fb0ea 100644 --- a/src/events/handleRename.ts +++ b/src/events/handleRename.ts @@ -1,14 +1,29 @@ -import type { TAbstractFile } from 'obsidian'; -import { TFile, TFolder, Notice } from 'obsidian'; +import { TFile, TFolder, Notice, type TAbstractFile } from 'obsidian'; import type FolderNotesPlugin from 'src/main'; -import { extractFolderName, getFolderNote, getFolderNoteFolder } from '../functions/folderNoteFunctions'; -import { getExcludedFolder, addExcludedFolder, updateExcludedFolder, deleteExcludedFolder, getDetachedFolder } from '../ExcludeFolders/functions/folderFunctions'; +import { + extractFolderName, getFolderNote, getFolderNoteFolder, +} from '../functions/folderNoteFunctions'; +import { + getExcludedFolder, addExcludedFolder, + updateExcludedFolder, deleteExcludedFolder, getDetachedFolder, +} from '../ExcludeFolders/functions/folderFunctions'; import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder'; -import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl, markFileAsFolderNote, unmarkFileAsFolderNote, unmarkFolderAsFolderNote, markFolderWithFolderNoteClasses, hideFolderNoteInFileExplorer, removeActiveFolder, setActiveFolder } from 'src/functions/styleFunctions'; -import { getFolderPathFromString, removeExtension, getFileNameFromPathString } from 'src/functions/utils'; +import { + removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl, + markFileAsFolderNote, unmarkFileAsFolderNote, + unmarkFolderAsFolderNote, markFolderWithFolderNoteClasses, + hideFolderNoteInFileExplorer, removeActiveFolder, setActiveFolder, +} from 'src/functions/styleFunctions'; +import { + getFolderPathFromString, removeExtension, getFileNameFromPathString, +} from 'src/functions/utils'; -export function handleRename(file: TAbstractFile, oldPath: string, plugin: FolderNotesPlugin) { - const folder = file.parent; +export function handleRename( + file: TAbstractFile, + oldPath: string, + plugin: FolderNotesPlugin, +): void { + let folder = file.parent; const oldFolder = plugin.app.vault.getAbstractFileByPath(getFolderPathFromString(oldPath)); if (folder instanceof TFolder) { @@ -28,19 +43,22 @@ export function handleRename(file: TAbstractFile, oldPath: string, plugin: Folde } if (file instanceof TFolder) { - const folder = file; + folder = file; plugin.tabManager.updateTab(folder.path); updateExcludedFolderPath(folder, oldPath, plugin); if (isFolderRename(folder, oldPath)) { - return handleFolderRename(folder, oldPath, plugin); + handleFolderRename(folder, oldPath, plugin); + return; } return handleFolderMove(folder, oldPath, plugin); } else if (file instanceof TFile) { if (isFileRename(file, oldPath)) { - return fmptUpdateFileName(file, oldPath, plugin); + handleFileRename(file, oldPath, plugin); + return; } - return handleFileMove(file, oldPath, plugin); + handleFileMove(file, oldPath, plugin); + return; } } @@ -64,7 +82,7 @@ function isFolderRename(folder: TFolder, oldPath: string): boolean { return oldParent === newParent && oldName !== newName; } -export function handleFolderMove(file: TFolder, oldPath: string, plugin: FolderNotesPlugin) { +export function handleFolderMove(file: TFolder, oldPath: string, plugin: FolderNotesPlugin): void { if (plugin.settings.storageLocation === 'insideFolder') { return; } if (!plugin.settings.syncMove) { return; } const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation); @@ -82,60 +100,43 @@ export function handleFolderMove(file: TFolder, oldPath: string, plugin: FolderN plugin.app.fileManager.renameFile(folderNote, newPath); } -export async function handleFileMove(file: TFile, oldPath: string, plugin: FolderNotesPlugin) { - const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename) || file.basename; - const oldFileName = removeExtension(getFileNameFromPathString(oldPath)); - const newFolder = getFolderNoteFolder(plugin, file, file.basename); - let excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true); - const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName); - const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file); +// eslint-disable-next-line complexity +export async function handleFileMove( + file: TFile, + oldPath: string, + plugin: FolderNotesPlugin, +): Promise { + const { folderName, oldFileName, newFolder, excludedFolder, oldFolder, folderNote } = getArgs( + plugin, file, oldPath, + ); - - const isFileNowFolderNoteInNewFolder = folderName === newFolder?.name; - const isFileMovedFromOldFolderNote = oldFolder && oldFolder.name === oldFileName && newFolder?.path !== oldFolder.path; + const isFolderNoteInNewFolder = folderName === newFolder?.name; + const fileMovedFromOldFolderNote = oldFolder && oldFolder.name === oldFileName + && newFolder?.path !== oldFolder.path; // this is for turning files into folder notes for folders that already have a folder note // e.g. Turn into folder note for "Folder name" - const isFileNowFolderNoteWithExistingNote = folderName === newFolder?.name && folderNote; + const isFileWithExistingNote = folderName === newFolder?.name && folderNote; - if (isFileNowFolderNoteWithExistingNote) { - let excludedFolderExisted = true; - let disabledSync = false; - - if (!excludedFolder) { - excludedFolderExisted = false; - excludedFolder = new ExcludedFolder(oldFolder?.path || '', plugin.settings.excludeFolders.length, undefined, plugin); - addExcludedFolder(plugin, excludedFolder); - } else if (!excludedFolder.disableSync) { - disabledSync = false; - excludedFolder.disableSync = true; - updateExcludedFolder(plugin, excludedFolder, excludedFolder); - } - return plugin.app.fileManager.renameFile(file, oldPath).then(() => { - if (!excludedFolder) { return; } - if (!excludedFolderExisted) { - deleteExcludedFolder(plugin, excludedFolder); - } else if (!disabledSync) { - excludedFolder.disableSync = false; - updateExcludedFolder(plugin, excludedFolder, excludedFolder); - } - }); - } else if (isFileNowFolderNoteInNewFolder) { - if (!excludedFolder?.disableFolderNote) { - markFileAsFolderNote(file, plugin); - if (newFolder instanceof TFolder) { - markFolderWithFolderNoteClasses(newFolder, plugin); - if (plugin.app.workspace.getActiveFile()?.path === file.path) { - removeActiveFolder(plugin); - setActiveFolder(newFolder.path, plugin); - } - } - if (oldFolder instanceof TFolder) { - hideFolderNoteInFileExplorer(oldFolder.path, plugin); - unmarkFolderAsFolderNote(oldFolder, plugin); + if (isFileWithExistingNote) { + renameExistingFolderNote( + file, oldPath, plugin, excludedFolder, oldFolder, + ); + } else if (isFolderNoteInNewFolder) { + if (excludedFolder?.disableFolderNote) { return; } + markFileAsFolderNote(file, plugin); + if (newFolder instanceof TFolder) { + markFolderWithFolderNoteClasses(newFolder, plugin); + if (plugin.app.workspace.getActiveFile()?.path === file.path) { + removeActiveFolder(plugin); + setActiveFolder(newFolder.path, plugin); } } - } else if (isFileMovedFromOldFolderNote) { + if (oldFolder instanceof TFolder) { + hideFolderNoteInFileExplorer(oldFolder.path, plugin); + unmarkFolderAsFolderNote(oldFolder, plugin); + } + } else if (fileMovedFromOldFolderNote) { unmarkFileAsFolderNote(file, plugin); if (oldFolder instanceof TFolder) { removeActiveFolder(plugin); @@ -145,9 +146,75 @@ export async function handleFileMove(file: TFile, oldPath: string, plugin: Folde } } -export async function handleFolderRename(file: TFolder, oldPath: string, plugin: FolderNotesPlugin) { + + +function getArgs(plugin: FolderNotesPlugin, file: TFile, oldPath: string): { + folderName: string; + oldFileName: string; + newFolder: TAbstractFile | null; + excludedFolder: ExcludedFolder | undefined; + oldFolder: TAbstractFile | null; + folderNote: TFile | null | undefined; +} { + const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename) + || file.basename; + const oldFileName = removeExtension(getFileNameFromPathString(oldPath)); + const newFolder = getFolderNoteFolder(plugin, file, file.basename); + let excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true); + const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName); + const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file); + + return { + folderName, + oldFileName, + newFolder, + excludedFolder, + oldFolder, + folderNote, + }; +} + +function renameExistingFolderNote( + file: TFile, oldPath: string, plugin: FolderNotesPlugin, + excludedFolder: ExcludedFolder | undefined, + oldFolder: TAbstractFile | null, +): void { + let excludedFolderExisted = true; + let disabledSync = false; + + if (!excludedFolder) { + excludedFolderExisted = false; + excludedFolder = new ExcludedFolder( + oldFolder?.path || '', + plugin.settings.excludeFolders.length, + undefined, + plugin, + ); + addExcludedFolder(plugin, excludedFolder); + } else if (!excludedFolder.disableSync) { + disabledSync = false; + excludedFolder.disableSync = true; + updateExcludedFolder(plugin, excludedFolder, excludedFolder); + } + plugin.app.fileManager.renameFile(file, oldPath).then(() => { + if (!excludedFolder) { return; } + if (!excludedFolderExisted) { + deleteExcludedFolder(plugin, excludedFolder); + } else if (!disabledSync) { + excludedFolder.disableSync = false; + updateExcludedFolder(plugin, excludedFolder, excludedFolder); + } + }); +} + + + +export async function handleFolderRename( + file: TFolder, oldPath: string, plugin: FolderNotesPlugin, +): Promise { const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', file.name); - const oldFileName = plugin.settings.folderNoteName.replace('{{folder_name}}', getFileNameFromPathString(oldPath)); + const oldFileName = plugin.settings.folderNoteName + .replace('{{folder_name}}', getFileNameFromPathString(oldPath)); if (fileName === oldFileName) { return; } @@ -183,50 +250,62 @@ export async function handleFolderRename(file: TFolder, oldPath: string, plugin: plugin.app.fileManager.renameFile(folderNote, newPath); } -export async function fmptUpdateFileName(file: TFile, oldPath: string, plugin: FolderNotesPlugin) { +// eslint-disable-next-line complexity +export async function handleFileRename( + file: TFile, + oldPath: string, + plugin: FolderNotesPlugin, +): Promise { const oldFileName = removeExtension(getFileNameFromPathString(oldPath)); const newFileName = file.basename; if (oldFileName === newFileName) { return; } const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName); - const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename) || file.basename; - const oldFolderName = extractFolderName(plugin.settings.folderNoteName, oldFileName) || oldFileName; + const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename) + || file.basename; + const oldFolderName = extractFolderName(plugin.settings.folderNoteName, oldFileName) + || oldFileName; const newFolder = getFolderNoteFolder(plugin, file, file.basename); const excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true); const detachedExcludedFolder = getDetachedFolder(plugin, newFolder?.path || ''); const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file); - if (!excludedFolder?.disableFolderNote && folderName === newFolder?.name && !detachedExcludedFolder) { - addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin); - addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin); - return; - } else if (excludedFolder?.disableFolderNote || (folderName !== newFolder?.name)) { - removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin); - removeCSSClassFromFileExplorerEL(newFolder?.path || '', 'has-folder-note', false, plugin); - } - - if (excludedFolder?.disableSync || !plugin.settings.syncFolderName) { return; } - - - if (folderName === newFolder?.name) { - addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin); - removeCSSClassFromFileExplorerEL(oldFolder?.path, 'has-folder-note', false, plugin); - addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin); + // Handle folder note creation + if (shouldCreateFolderNote(excludedFolder, folderName, newFolder, detachedExcludedFolder)) { + if (newFolder) { + handleFolderNoteCreation(file, newFolder, plugin); + } return; } - // file matched folder name before rename - // file hasnt moved just renamed - // Need to rename the folder - if (!oldFolder) return; - if (oldFolderName === oldFolder.name && newFolder?.path === oldFolder.path) { - return renameFolderOnFileRename(file, oldPath, oldFolder, plugin); - } else if (folderNote && oldFolderName === oldFolder.name) { - return renameFolderOnFileRename(file, oldPath, oldFolder, plugin); + // Handle folder note removal + if (shouldRemoveFolderNoteClasses(excludedFolder, folderName, newFolder)) { + handleFolderNoteRemoval(file, newFolder, plugin); + } + + // Early return if sync is disabled + if (excludedFolder?.disableSync || !plugin.settings.syncFolderName) { + return; + } + + // Handle same folder rename + if (folderName === newFolder?.name && newFolder) { + handleSameFolderRename(file, newFolder, oldFolder, plugin); + return; + } + + // Handle folder rename on file rename + if (shouldRenameFolderOnFileRename(oldFolderName, oldFolder, newFolder, folderNote)) { + return renameFolderOnFileRename(file, oldPath, oldFolder!, plugin); } } -async function renameFolderOnFileRename(file: TFile, oldPath: string, oldFolder: TAbstractFile, plugin: FolderNotesPlugin) { +async function renameFolderOnFileRename( + file: TFile, + oldPath: string, + oldFolder: TAbstractFile, + plugin: FolderNotesPlugin, +): Promise { const newFolderName = extractFolderName(plugin.settings.folderNoteName, file.basename); if (!newFolderName) { removeCSSClassFromFileExplorerEL(oldFolder.path, 'has-folder-note', false, plugin); @@ -256,12 +335,17 @@ async function renameFolderOnFileRename(file: TFile, oldPath: string, oldFolder: if (plugin.app.vault.getAbstractFileByPath(newFolderPath)) { await plugin.app.fileManager.renameFile(file, oldPath); - return new Notice('A folder with the same name already exists'); + new Notice('A folder with the same name already exists'); + return; } plugin.app.fileManager.renameFile(oldFolder, newFolderPath); } -function updateExcludedFolderPath(folder: TFolder, oldPath: string, plugin: FolderNotesPlugin) { +function updateExcludedFolderPath( + folder: TFolder, + oldPath: string, + plugin: FolderNotesPlugin, +): void { const excludedFolders = plugin.settings.excludeFolders.filter( (excludedFolder) => excludedFolder.path?.includes(oldPath), ); @@ -282,3 +366,68 @@ function updateExcludedFolderPath(folder: TFolder, oldPath: string, plugin: Fold }); plugin.saveSettings(); } + + +function shouldCreateFolderNote( + excludedFolder: ExcludedFolder | undefined, + folderName: string, + newFolder: TAbstractFile | null, + detachedExcludedFolder: ExcludedFolder | undefined, +): boolean { + return !excludedFolder?.disableFolderNote + && folderName === (newFolder as TFolder)?.name + && !detachedExcludedFolder; +} + +function shouldRemoveFolderNoteClasses( + excludedFolder: ExcludedFolder | undefined, + folderName: string, + newFolder: TAbstractFile | null, +): boolean { + return excludedFolder?.disableFolderNote || (folderName !== (newFolder as TFolder)?.name); +} + +function handleFolderNoteCreation( + file: TFile, + newFolder: TAbstractFile, + plugin: FolderNotesPlugin, +): void { + addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin); + addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin); +} + +function handleFolderNoteRemoval( + file: TFile, + newFolder: TAbstractFile | null, + plugin: FolderNotesPlugin, +): void { + removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin); + removeCSSClassFromFileExplorerEL(newFolder?.path || '', 'has-folder-note', false, plugin); +} + +function handleSameFolderRename( + file: TFile, + newFolder: TAbstractFile, + oldFolder: TAbstractFile | null, + plugin: FolderNotesPlugin, +): void { + addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin); + removeCSSClassFromFileExplorerEL(oldFolder?.path, 'has-folder-note', false, plugin); + addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin); +} + +function shouldRenameFolderOnFileRename( + oldFolderName: string, + oldFolder: TAbstractFile | null, + newFolder: TAbstractFile | null, + folderNote: TFile | null | undefined, +): boolean { + if (!oldFolder) return false; + + const oldFolderAsFolder = oldFolder as TFolder; + const newFolderAsFolder = newFolder as TFolder; + + return (oldFolderName === oldFolderAsFolder.name + && newFolderAsFolder?.path === oldFolderAsFolder.path) + || (folderNote !== null && oldFolderName === oldFolderAsFolder.name); +} diff --git a/src/main.ts b/src/main.ts index b72609a..6f18134 100644 --- a/src/main.ts +++ b/src/main.ts @@ -203,7 +203,7 @@ export default class FolderNotesPlugin extends Plugin { // @ts-ignore const editMode = view.editMode ?? view.sourceMode ?? this.app.workspace.activeEditor?.editMode; - // eslint-disable-next-line + const plugin = this; if (!editMode) { return; } @@ -308,7 +308,7 @@ export default class FolderNotesPlugin extends Plugin { openFolderNote(this, folderNote, evt); } - handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) { + handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext): void { const observer = new MutationObserver(() => { const editButton = el.parentElement?.childNodes.item(1); if (editButton) { @@ -316,7 +316,10 @@ export default class FolderNotesPlugin extends Plugin { e.stopImmediatePropagation(); e.preventDefault(); e.stopPropagation(); - new FolderOverviewSettings(this.app, this, parseYaml(source), ctx, el, this.settings.defaultOverview).open(); + new FolderOverviewSettings( + this.app, this, parseYaml(source), + ctx, el, this.settings.defaultOverview, + ).open(); }, { capture: true }); } }); @@ -329,11 +332,11 @@ export default class FolderNotesPlugin extends Plugin { try { if (this.app.workspace.layoutReady) { const folderOverview = new FolderOverview(this, ctx, source, el, this.settings.defaultOverview); - folderOverview.create(this, parseYaml(source), el, ctx); + folderOverview.create(this, el, ctx); } else { this.app.workspace.onLayoutReady(() => { const folderOverview = new FolderOverview(this, ctx, source, el, this.settings.defaultOverview); - folderOverview.create(this, parseYaml(source), el, ctx); + folderOverview.create(this, el, ctx); }); } } catch (e) { diff --git a/src/obsidian-folder-overview b/src/obsidian-folder-overview index 07825d6..5323f27 160000 --- a/src/obsidian-folder-overview +++ b/src/obsidian-folder-overview @@ -1 +1 @@ -Subproject commit 07825d6f187a9ddf2c9b29066eb0c06aa8b8fee2 +Subproject commit 5323f27922aff0165dca78770f24a7d8a99de7c0 diff --git a/src/suggesters/FileSuggester.ts b/src/suggesters/FileSuggester.ts index 941ba8d..3828036 100644 --- a/src/suggesters/FileSuggester.ts +++ b/src/suggesters/FileSuggester.ts @@ -1,7 +1,4 @@ -// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater - -import type { TAbstractFile } from 'obsidian'; -import { TFile } from 'obsidian'; +import { type TAbstractFile, TFile } from 'obsidian'; import { TextInputSuggest } from './Suggest'; import type FolderNotesPlugin from '../main'; export enum FileSuggestMode { diff --git a/src/template.ts b/src/template.ts index 4855c52..09753cb 100644 --- a/src/template.ts +++ b/src/template.ts @@ -1,5 +1,4 @@ -import type { App } from 'obsidian'; -import { TFile, WorkspaceLeaf } from 'obsidian'; +import { TFile, WorkspaceLeaf, type App } from 'obsidian'; import type FolderNotesPlugin from './main'; export async function applyTemplate(