Every remaining file

This commit is contained in:
Lost Paul 2025-08-11 14:39:33 +02:00
parent 113cf5f40c
commit 7c31c7cec5
27 changed files with 1333 additions and 619 deletions

View file

@ -1,11 +1,35 @@
import type { App, Menu, TAbstractFile, Editor, MarkdownView } from 'obsidian';
import { TFolder, Notice, TFile, Platform } from 'obsidian';
import {
TFolder,
Notice,
TFile,
Platform,
type App,
type Menu,
type TAbstractFile,
type Editor,
type MarkdownView,
} from 'obsidian';
import type FolderNotesPlugin from './main';
import { getFolderNote, createFolderNote, deleteFolderNote, turnIntoFolderNote, openFolderNote, extractFolderName, detachFolderNote } from './functions/folderNoteFunctions';
import {
getFolderNote,
createFolderNote,
deleteFolderNote,
turnIntoFolderNote,
openFolderNote,
extractFolderName,
detachFolderNote,
} from './functions/folderNoteFunctions';
import { ExcludedFolder } from './ExcludeFolders/ExcludeFolder';
import { getFolderPathFromString, getFileExplorerActiveFolder } from './functions/utils';
import { deleteExcludedFolder, getDetachedFolder, getExcludedFolder } from './ExcludeFolders/functions/folderFunctions';
import { hideFolderNoteInFileExplorer, showFolderNoteInFileExplorer } from './functions/styleFunctions';
import {
deleteExcludedFolder,
getDetachedFolder,
getExcludedFolder,
} from './ExcludeFolders/functions/folderFunctions';
import {
hideFolderNoteInFileExplorer,
showFolderNoteInFileExplorer,
} from './functions/styleFunctions';
@ -16,12 +40,13 @@ export class Commands {
this.plugin = plugin;
this.app = app;
}
registerCommands() {
registerCommands(): void {
this.editorCommands();
this.fileCommands();
this.regularCommands();
}
regularCommands() {
regularCommands(): void {
this.plugin.addCommand({
id: 'turn-into-folder-note',
name: 'Use this file as the folder note for its parent folder',
@ -52,7 +77,8 @@ export class Commands {
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote = this.plugin.settings.autoCreate;
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
@ -187,6 +213,7 @@ export class Commands {
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
for (const char of blacklist) {
if (text.includes(char)) {
// eslint-disable-next-line max-len
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
return false;
}
@ -207,13 +234,18 @@ export class Commands {
createFolderNote(this.plugin, text, false);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(folderPath + '/' + text);
const folderFullPath = folderPath + '/' + text;
folder = this.plugin.app.vault.getAbstractFileByPath(folderFullPath);
if (folder instanceof TFolder) {
new Notice('Folder note already exists');
return false;
}
if (this.plugin.settings.storageLocation === 'parentFolder') {
if (this.app.vault.getAbstractFileByPath(folderPath + '/' + text + this.plugin.settings.folderNoteType)) {
if (
this.app.vault.getAbstractFileByPath(
folderPath + '/' + text + this.plugin.settings.folderNoteType,
)
) {
new Notice('File already exists');
return false;
}
@ -221,7 +253,9 @@ export class Commands {
this.plugin.app.vault.createFolder(folderPath + '/' + text);
createFolderNote(this.plugin, folderPath + '/' + text, false);
}
const fileName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', text);
const { folderNoteName } = this.plugin.settings;
const fileName = folderNoteName.replace('{{folder_name}}', text);
if (fileName !== text) {
editor.replaceSelection(`[[${fileName}]]`);
} else {
@ -234,213 +268,243 @@ export class Commands {
});
}
fileCommands() {
this.plugin.registerEvent(this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
let folder: TAbstractFile | TFolder | null = file.parent;
if (file instanceof TFile) {
if (this.plugin.settings.storageLocation === 'insideFolder') {
folder = file.parent;
} else {
const fileName = extractFolderName(this.plugin.settings.folderNoteName, file.basename);
if (fileName) {
if (file.parent?.path === '' || file.parent?.path === '/') {
folder = this.plugin.app.vault.getAbstractFileByPath(fileName);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(file.parent?.path + '/' + fileName);
}
}
}
if (folder instanceof TFolder) {
const folderNote = getFolderNote(this.plugin, folder.path);
const excludedFolder = getExcludedFolder(this.plugin, folder.path, true);
if (folderNote?.path === file.path && !excludedFolder?.detached) { return; }
} else if (file.parent instanceof TFolder) {
folder = file.parent;
}
}
menu.addItem(async (item) => {
if (Platform.isDesktop && !Platform.isTablet && this.plugin.settings.useSubmenus) {
item
.setTitle('Folder Note Commands')
.setIcon('folder-edit');
}
let subMenu: Menu;
if (!Platform.isDesktopApp || !Platform.isDesktop || Platform.isTablet || !this.plugin.settings.useSubmenus) {
subMenu = menu;
item.setDisabled(true);
} else {
// @ts-ignore
subMenu = item.setSubmenu() as Menu;
}
fileCommands(): void {
this.plugin.registerEvent(
this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
let folder: TAbstractFile | TFolder | null = file.parent;
if (file instanceof TFile) {
// @ts-ignore
subMenu.addItem((item) => {
item.setTitle('Create folder note')
.setIcon('edit')
.onClick(async () => {
if (!folder) return;
let newPath = folder.path + '/' + file.basename;
if (folder.path === '' || folder.path === '/') {
newPath = file.basename;
}
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote = this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const newFolder = this.plugin.app.vault.getAbstractFileByPath(newPath);
if (!(newFolder instanceof TFolder)) return;
await createFolderNote(this.plugin, newFolder.path, true, '.' + file.extension, false, file);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
this.plugin.saveSettings();
});
});
if (getFolderPathFromString(file.path) === '') return;
if (!(folder instanceof TFolder)) return;
if (folder.path === '' || folder.path === '/') return;
subMenu.addItem((item) => {
item.setTitle(`Turn into folder note for ${folder?.name}`)
.setIcon('edit')
.onClick(() => {
if (!folder || !(folder instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, folder.path);
turnIntoFolderNote(this.plugin, file, folder, folderNote);
});
});
}
if (!(file instanceof TFolder)) return;
const excludedFolder = getExcludedFolder(this.plugin, file.path, false);
const detachedExcludedFolder = getDetachedFolder(this.plugin, file.path);
if (excludedFolder && !excludedFolder.hideInSettings) {
// I'm not sure if I'm ever going to add this because of the possibility that a folder got more than one excluded
// subMenu.addItem((item) => {
// item.setTitle('Manage excluded folder')
// .setIcon('settings-2')
// .onClick(() => {
// if (excludedFolder instanceof ExcludedFolder) {
// new ExcludedFolderSettings(this.plugin.app, this.plugin, excludedFolder).open();
// } else if (excludedFolder instanceof ExcludePattern) {
// new PatternSettings(this.plugin.app, this.plugin, excludedFolder).open();
// }
// })
// })
subMenu.addItem((item) => {
item.setTitle('Remove folder from excluded folders')
.setIcon('trash')
.onClick(() => {
this.plugin.settings.excludeFolders = this.plugin.settings.excludeFolders.filter(
(folder) => (folder.path !== file.path) || folder.detached);
this.plugin.saveSettings(true);
new Notice('Successfully removed folder from excluded folders');
});
});
return;
}
if (detachedExcludedFolder) {
subMenu.addItem((item) => {
item.setTitle('Remove folder from detached folders')
.setIcon('trash')
.onClick(() => {
deleteExcludedFolder(this.plugin, detachedExcludedFolder);
});
});
}
if (detachedExcludedFolder) { return; }
subMenu.addItem((item) => {
item.setTitle('Exclude folder from folder notes')
.setIcon('x-circle')
.onClick(() => {
const excludedFolder = new ExcludedFolder(file.path, this.plugin.settings.excludeFolders.length, undefined, this.plugin);
this.plugin.settings.excludeFolders.push(excludedFolder);
this.plugin.saveSettings(true);
new Notice('Successfully excluded folder from folder notes');
});
});
if (!(file instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, file.path);
if (folderNote instanceof TFile && !detachedExcludedFolder) {
subMenu.addItem((item) => {
item.setTitle('Delete folder note')
.setIcon('trash')
.onClick(() => {
deleteFolderNote(this.plugin, folderNote, true);
});
});
subMenu.addItem((item) => {
item.setTitle('Open folder note')
.setIcon('chevron-right-square')
.onClick(() => {
openFolderNote(this.plugin, folderNote);
});
});
subMenu.addItem((item) => {
item.setTitle('Detach folder note')
.setIcon('unlink')
.onClick(() => {
detachFolderNote(this.plugin, folderNote);
});
});
subMenu.addItem((item) => {
item.setTitle('Copy Obsidian URL')
.setIcon('link')
.onClick(() => {
// @ts-ignore
this.app.copyObsidianUrl(folderNote);
});
});
if (this.plugin.settings.hideFolderNote) {
if (excludedFolder?.showFolderNote) {
subMenu.addItem((item) => {
item.setTitle('Hide folder note in explorer')
.setIcon('eye-off')
.onClick(() => {
hideFolderNoteInFileExplorer(file.path, this.plugin);
});
});
} else {
subMenu.addItem((item) => {
item.setTitle('Show folder note in explorer')
.setIcon('eye')
.onClick(() => {
showFolderNoteInFileExplorer(file.path, this.plugin);
});
});
if (this.plugin.settings.storageLocation === 'insideFolder') {
folder = file.parent;
} else {
const { folderNoteName } = this.plugin.settings;
const fileName = extractFolderName(folderNoteName, file.basename);
if (fileName) {
if (file.parent?.path === '' || file.parent?.path === '/') {
folder = this.plugin.app.vault.getAbstractFileByPath(fileName);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(
file.parent?.path + '/' + fileName,
);
}
}
}
} else {
subMenu.addItem((item) => {
item.setTitle('Create markdown folder note')
.setIcon('edit')
.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.md');
});
});
if (folder instanceof TFolder) {
const folderNote = getFolderNote(this.plugin, folder.path);
const excludedFolder = getExcludedFolder(this.plugin, folder.path, true);
if (folderNote?.path === file.path && !excludedFolder?.detached) { return; }
} else if (file.parent instanceof TFolder) {
folder = file.parent;
}
}
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
if (fileType === 'md') return;
subMenu.addItem((item) => {
item.setTitle(`Create ${fileType} folder note`)
// eslint-disable-next-line complexity
menu.addItem(async (menuItem) => {
if (
Platform.isDesktop &&
!Platform.isTablet &&
this.plugin.settings.useSubmenus
) {
menuItem
.setTitle('Folder Note Commands')
.setIcon('folder-edit');
}
let subMenu: Menu;
if (
!Platform.isDesktopApp ||
!Platform.isDesktop ||
Platform.isTablet ||
!this.plugin.settings.useSubmenus
) {
subMenu = menu;
menuItem.setDisabled(true);
} else {
subMenu = menuItem.setSubmenu() as Menu;
}
if (file instanceof TFile) {
subMenu.addItem((subItem) => {
subItem.setTitle('Create folder note')
.setIcon('edit')
.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.' + fileType);
.onClick(async () => {
if (!folder) return;
let newPath = folder.path + '/' + file.basename;
if (folder.path === '' || folder.path === '/') {
newPath = file.basename;
}
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const newFolder = this.plugin.app.vault
.getAbstractFileByPath(newPath);
if (!(newFolder instanceof TFolder)) return;
await createFolderNote(
this.plugin,
newFolder.path,
true,
'.' + file.extension,
false,
file,
);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
this.plugin.saveSettings();
});
});
if (getFolderPathFromString(file.path) === '') return;
if (!(folder instanceof TFolder)) return;
if (folder.path === '' || folder.path === '/') return;
subMenu.addItem((item) => {
item.setTitle(`Turn into folder note for ${folder?.name}`)
.setIcon('edit')
.onClick(() => {
if (!folder || !(folder instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, folder.path);
turnIntoFolderNote(this.plugin, file, folder, folderNote);
});
});
}
if (!(file instanceof TFolder)) return;
const excludedFolder = getExcludedFolder(this.plugin, file.path, false);
const detachedExcludedFolder = getDetachedFolder(this.plugin, file.path);
if (excludedFolder && !excludedFolder.hideInSettings) {
// I'm not sure if I'm ever going to add this because of the possibility that a folder got more than one excluded
// subMenu.addItem((item) => {
// item.setTitle('Manage excluded folder')
// .setIcon('settings-2')
// .onClick(() => {
// if (excludedFolder instanceof ExcludedFolder) {
// new ExcludedFolderSettings(this.plugin.app, this.plugin, excludedFolder).open();
// } else if (excludedFolder instanceof ExcludePattern) {
// new PatternSettings(this.plugin.app, this.plugin, excludedFolder).open();
// }
// })
// })
subMenu.addItem((item) => {
item.setTitle('Remove folder from excluded folders')
.setIcon('trash')
.onClick(() => {
this.plugin.settings.excludeFolders =
this.plugin.settings.excludeFolders.filter(
(excluded) =>
(excluded.path !== file.path) || excluded.detached,
);
this.plugin.saveSettings(true);
new Notice('Successfully removed folder from excluded folders');
});
});
return;
}
if (detachedExcludedFolder) {
subMenu.addItem((item) => {
item.setTitle('Remove folder from detached folders')
.setIcon('trash')
.onClick(() => {
deleteExcludedFolder(this.plugin, detachedExcludedFolder);
});
});
}
if (detachedExcludedFolder) { return; }
subMenu.addItem((item) => {
item.setTitle('Exclude folder from folder notes')
.setIcon('x-circle')
.onClick(() => {
const newExcludedFolder = new ExcludedFolder(
file.path,
this.plugin.settings.excludeFolders.length,
undefined,
this.plugin,
);
this.plugin.settings.excludeFolders.push(newExcludedFolder);
this.plugin.saveSettings(true);
new Notice('Successfully excluded folder from folder notes');
});
});
}
});
}));
if (!(file instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, file.path);
if (folderNote instanceof TFile && !detachedExcludedFolder) {
subMenu.addItem((item) => {
item.setTitle('Delete folder note')
.setIcon('trash')
.onClick(() => {
deleteFolderNote(this.plugin, folderNote, true);
});
});
subMenu.addItem((item) => {
item.setTitle('Open folder note')
.setIcon('chevron-right-square')
.onClick(() => {
openFolderNote(this.plugin, folderNote);
});
});
subMenu.addItem((item) => {
item.setTitle('Detach folder note')
.setIcon('unlink')
.onClick(() => {
detachFolderNote(this.plugin, folderNote);
});
});
subMenu.addItem((item) => {
item.setTitle('Copy Obsidian URL')
.setIcon('link')
.onClick(() => {
this.app.copyObsidianUrl(folderNote);
});
});
if (this.plugin.settings.hideFolderNote) {
if (excludedFolder?.showFolderNote) {
subMenu.addItem((item) => {
item.setTitle('Hide folder note in explorer')
.setIcon('eye-off')
.onClick(() => {
hideFolderNoteInFileExplorer(file.path, this.plugin);
});
});
} else {
subMenu.addItem((item) => {
item.setTitle('Show folder note in explorer')
.setIcon('eye')
.onClick(() => {
showFolderNoteInFileExplorer(file.path, this.plugin);
});
});
}
}
} else {
subMenu.addItem((item) => {
item.setTitle('Create markdown folder note')
.setIcon('edit')
.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.md');
});
});
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
if (fileType === 'md') return;
subMenu.addItem((item) => {
item.setTitle(`Create ${fileType} folder note`)
.setIcon('edit')
.onClick(() => {
// eslint-disable-next-line max-len
createFolderNote(this.plugin, file.path, true, '.' + fileType);
});
});
});
}
});
}));
}
editorCommands() {
editorCommands(): void {
// eslint-disable-next-line max-len
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => {
const text = editor.getSelection().trim();
if (!text || text.trim() === '') return;
@ -453,6 +517,7 @@ export class Commands {
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
for (const char of blacklist) {
if (text.includes(char)) {
// eslint-disable-next-line max-len
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
return;
}
@ -461,9 +526,11 @@ export class Commands {
new Notice('File name cannot end with a dot');
return;
}
let folder: TAbstractFile | null;
const folderPath = getFolderPathFromString(file.path);
const fileName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', text);
const { folderNoteName } = this.plugin.settings;
const fileName = folderNoteName.replace('{{folder_name}}', text);
if (folderPath === '') {
folder = this.plugin.app.vault.getAbstractFileByPath(text);
if (folder instanceof TFolder) {
@ -473,12 +540,21 @@ export class Commands {
createFolderNote(this.plugin, text, false);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(folderPath + '/' + text);
folder = this.plugin.app.vault.getAbstractFileByPath(
folderPath + '/' + text,
);
if (folder instanceof TFolder) {
return new Notice('Folder note already exists');
}
if (this.plugin.settings.storageLocation === 'parentFolder') {
if (this.app.vault.getAbstractFileByPath(folderPath + '/' + fileName + this.plugin.settings.folderNoteType)) {
if (
this.app.vault.getAbstractFileByPath(
folderPath +
'/' +
fileName +
this.plugin.settings.folderNoteType,
)
) {
return new Notice('File already exists');
}
}

View file

@ -18,19 +18,19 @@ export class ListComponent {
this.defaultValues = defaultValues;
}
on(event: string, listener: (data?: any) => void) {
on(event: string, listener: (data?: unknown) => void): void {
this.emitter.on(event, listener);
}
off(event: string, listener: (data?: any) => void) {
off(event: string, listener: (data?: unknown) => void): void {
this.emitter.off(event, listener);
}
private emit(event: string, data?: any) {
private emit(event: string, data?: unknown): void {
this.emitter.emit(event, data);
}
setValues(values: string[]) {
setValues(values: string[]): void {
this.removeElements();
this.values = values;
if (values.length !== 0) {
@ -41,11 +41,11 @@ export class ListComponent {
this.emit('update', this.values);
}
removeElements() {
removeElements(): void {
this.listEl.empty();
}
addElement(value: string) {
addElement(value: string): void {
this.listEl.createSpan('setting-hotkey', (span) => {
if (value.toLocaleLowerCase() === 'md') {
span.innerText = 'markdown';
@ -53,35 +53,39 @@ export class ListComponent {
span.innerText = value;
}
span.setAttribute('extension', value);
// eslint-disable-next-line max-len
const removeSpan = span.createEl('span', { cls: 'ofn-list-item-remove setting-hotkey-icon' });
// eslint-disable-next-line max-len
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
const svgElement = removeSpan.createEl('span', { cls: 'ofn-list-item-remove-icon' });
svgElement.innerHTML = svg;
removeSpan.onClickEvent((e) => {
removeSpan.onClickEvent(() => {
this.removeValue(value);
span.remove();
});
});
}
async addValue(value: string) {
async addValue(value: string): Promise<void> {
this.values.push(value);
this.addElement(value);
this.emit('add', value);
this.emit('update', this.values);
}
addResetButton() {
addResetButton(): this {
// eslint-disable-next-line max-len
const resetButton = this.controlEl.createEl('span', { cls: 'clickable-icon setting-restore-hotkey-button' });
// eslint-disable-next-line max-len
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-rotate-ccw"><path d="M3 2v6h6"></path><path d="M3 13a9 9 0 1 0 3-7.7L3 8"></path></svg>';
resetButton.innerHTML = svg;
resetButton.onClickEvent((e) => {
resetButton.onClickEvent(() => {
this.setValues(this.defaultValues);
});
return this;
}
removeValue(value: string) {
removeValue(value: string): void {
this.values = this.values.filter((v) => v !== value);
this.listEl.find(`[extension='${value}']`).remove();
this.emit('remove', value);

View file

@ -1,19 +1,36 @@
import type { WorkspaceLeaf, App } from 'obsidian';
export async function openExcalidrawView(leaf: WorkspaceLeaf) {
const { excalidraw, excalidrawEnabled } = await getExcalidrawPlugin(this.app);
if (excalidrawEnabled) {
interface ExcalidrawPlugin {
setExcalidrawView(leaf: WorkspaceLeaf): void;
}
export async function openExcalidrawView(
app: App,
leaf: WorkspaceLeaf,
): Promise<void> {
const { excalidraw, excalidrawEnabled } = await getExcalidrawPlugin(app);
if (excalidrawEnabled && excalidraw) {
excalidraw.setExcalidrawView(leaf);
}
}
export async function getExcalidrawPlugin(app: App) {
const excalidraw = (app as any).plugins.plugins['obsidian-excalidraw-plugin'];
const excalidrawEnabled = (app as any).plugins.enabledPlugins.has(
'obsidian-excalidraw-plugin',
export async function getExcalidrawPlugin(
app: App,
): Promise<{ excalidraw: ExcalidrawPlugin | null; excalidrawEnabled: boolean }> {
const { plugins: pluginManager } = app as App & {
plugins: {
plugins: Record<string, unknown>;
enabledPlugins: Set<string>;
};
};
const excalidraw = (
pluginManager.plugins[
'obsidian-excalidraw-plugin'
] as unknown as ExcalidrawPlugin | undefined
);
const excalidrawEnabled = pluginManager.enabledPlugins.has('obsidian-excalidraw-plugin');
return {
excalidraw,
excalidraw: excalidraw ?? null,
excalidrawEnabled,
};
}

View file

@ -1,15 +1,39 @@
/* eslint-disable complexity */
/* eslint-disable max-len */
import type FolderNotesPlugin from '../main';
import ExistingFolderNoteModal from '../modals/ExistingNote';
import { applyTemplate } from '../template';
import type { TAbstractFile } from 'obsidian';
import { TFolder, TFile, Keymap } from 'obsidian';
import {
TFolder,
TFile,
Keymap,
type TAbstractFile,
type MarkdownView,
type WorkspaceLeaf,
} from 'obsidian';
import DeleteConfirmationModal from '../modals/DeleteConfirmation';
import { addExcludedFolder, deleteExcludedFolder, getDetachedFolder, getExcludedFolder, updateExcludedFolder } from '../ExcludeFolders/functions/folderFunctions';
import {
addExcludedFolder,
deleteExcludedFolder,
getDetachedFolder,
getExcludedFolder,
updateExcludedFolder,
} from '../ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from '../ExcludeFolders/ExcludeFolder';
import { openExcalidrawView } from './excalidraw';
import { AskForExtensionModal } from 'src/modals/AskForExtension';
import { addCSSClassToFileExplorerEl, removeCSSClassFromFileExplorerEL, removeActiveFolder, setActiveFolder } from 'src/functions/styleFunctions';
import { getFolderNameFromPathString, getFolderPathFromString, removeExtension } from 'src/functions/utils';
import {
addCSSClassToFileExplorerEl,
removeCSSClassFromFileExplorerEL,
removeActiveFolder,
setActiveFolder,
} from 'src/functions/styleFunctions';
import {
getFolderNameFromPathString,
getFolderPathFromString,
removeExtension,
} from 'src/functions/utils';
const defaultExcalidrawTemplate = `---
@ -27,24 +51,36 @@ tags: [excalidraw]
\`\`\`
%%`;
export async function createFolderNote(plugin: FolderNotesPlugin, folderPath: string, openFile: boolean, extension?: string, displayModal?: boolean, preexistingNote?: TFile) {
const leaf = plugin.app.workspace.getLeaf(false);
const folderName = getFolderNameFromPathString(folderPath);
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
let folderNote = getFolderNote(plugin, folderPath);
if (preexistingNote) {
folderNote = preexistingNote;
}
let folderNoteType = extension ?? plugin.settings.folderNoteType;
const detachedFolder = getDetachedFolder(plugin, folderPath);
let path = '';
export async function createFolderNote(
plugin: FolderNotesPlugin,
folderPath: string,
openFile: boolean,
extension?: string,
displayModal?: boolean,
preexistingNote?: TFile,
): Promise<void> {
let {
leaf,
fileName,
folderNote,
folderNoteType,
detachedFolder,
path,
} = getArgs(plugin, folderPath, extension, preexistingNote);
if (folderNoteType === '.excalidraw') {
folderNoteType = '.md';
extension = '.excalidraw';
} else if (folderNoteType === '.ask') {
if (plugin.askModalCurrentlyOpen) return;
return new AskForExtensionModal(plugin, folderPath, openFile, folderNoteType, displayModal, preexistingNote).open();
return new AskForExtensionModal(
plugin,
folderPath,
openFile,
folderNoteType,
displayModal,
preexistingNote,
).open();
}
if (plugin.settings.storageLocation === 'parentFolder') {
@ -61,27 +97,7 @@ export async function createFolderNote(plugin: FolderNotesPlugin, folderPath: st
}
if (detachedFolder && folderNote?.extension !== extension && folderNote) {
deleteExcludedFolder(plugin, detachedFolder);
removeCSSClassFromFileExplorerEL(folderNote?.path, 'is-folder-note', false, plugin);
const folder = plugin.app.vault.getAbstractFileByPath(folderPath) as TFolder;
if (!folderNote || folderNote.basename !== fileName) return;
let count = 1;
let newName = removeExtension(folderNote.path) + ` (${count}).${folderNote.path.split('.').pop()}`;
while (count < 100 && plugin.app.vault.getAbstractFileByPath(newName)) {
count++;
newName = removeExtension(folderNote.path) + ` (${count}).${folderNote.path.split('.').pop()}`;
}
const [excludedFolder, excludedFolderExisted, disabledSync] = await tempDisableSync(plugin, folder);
await plugin.app.fileManager.renameFile(folderNote, newName).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
await handleTurnNoteIntoFolderNote(plugin, folderNote, detachedFolder, folderPath, fileName);
}
if (!extension) {
@ -89,33 +105,7 @@ export async function createFolderNote(plugin: FolderNotesPlugin, folderPath: st
}
if (!folderNote) {
let content = '';
if (extension !== '.md' && extension) {
if (plugin.settings.templatePath && folderNoteType.split('.').pop() === plugin.settings.templatePath.split('.').pop()) {
const templateFile = plugin.app.vault.getAbstractFileByPath(plugin.settings.templatePath);
if (templateFile instanceof TFile) {
if (['md', 'canvas', 'txt'].includes(templateFile.extension)) {
content = await plugin.app.vault.read(templateFile);
if (extension === '.excalidraw' && !content.includes('==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==')) {
content = defaultExcalidrawTemplate;
}
} else {
return plugin.app.vault.readBinary(templateFile).then(async (data) => {
folderNote = await plugin.app.vault.createBinary(path, data);
if (openFile) {
await leaf.openFile(folderNote);
}
});
}
}
} else if (plugin.settings.folderNoteType === '.excalidraw' || extension === '.excalidraw') {
content = defaultExcalidrawTemplate;
} else if (plugin.settings.folderNoteType === '.canvas') {
content = '{}';
}
}
folderNote = await plugin.app.vault.create(path, content);
folderNote = await handleCreateFolderNote(plugin, folderNoteType, openFile, leaf, folderNote, path, extension);
} else {
await plugin.app.fileManager.renameFile(folderNote, path);
}
@ -131,11 +121,13 @@ export async function createFolderNote(plugin: FolderNotesPlugin, folderPath: st
}
await leaf.openFile(folderNote);
if (plugin.settings.folderNoteType === '.excalidraw' || extension === '.excalidraw') {
openExcalidrawView(leaf);
openExcalidrawView(plugin.app, leaf);
}
}
const matchingExtension = extension?.split('.').pop() === plugin.settings.templatePath.split('.').pop();
const matchingExtension =
extension?.split('.').pop() ===
plugin.settings.templatePath.split('.').pop();
if (folderNote && matchingExtension && plugin.settings.folderNoteType !== '.excalidraw') {
applyTemplate(plugin, folderNote, leaf, plugin.settings.templatePath);
}
@ -146,19 +138,159 @@ export async function createFolderNote(plugin: FolderNotesPlugin, folderPath: st
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
}
export async function turnIntoFolderNote(plugin: FolderNotesPlugin, file: TFile, folder: TFolder, folderNote?: TFile | null | TAbstractFile, skipConfirmation?: boolean) {
function getArgs(
plugin: FolderNotesPlugin,
folderPath: string,
extension?: string,
preexistingNote?: TFile,
): {
leaf: WorkspaceLeaf;
fileName: string;
folderNote: TFile | null | undefined;
folderNoteType: string;
detachedFolder: ExcludedFolder | undefined;
path: string | '';
} {
const leaf = plugin.app.workspace.getLeaf(false);
const folderName = getFolderNameFromPathString(folderPath);
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
let folderNote = getFolderNote(plugin, folderPath);
if (preexistingNote) {
folderNote = preexistingNote;
}
let folderNoteType = extension ?? plugin.settings.folderNoteType;
const detachedFolder = getDetachedFolder(plugin, folderPath);
let path = '';
return {
leaf,
fileName,
folderNote,
folderNoteType,
detachedFolder,
path,
};
}
async function handleCreateFolderNote(plugin: FolderNotesPlugin, folderNoteType: string, openFile: boolean, leaf: WorkspaceLeaf, folderNote: TFile | null | undefined, path: string, extension?: string): Promise<TFile> {
let content = '';
if (extension !== '.md' && extension) {
if (
plugin.settings.templatePath &&
folderNoteType.split('.').pop() ===
plugin.settings.templatePath.split('.').pop()
) {
const templateFile = plugin.app.vault.getAbstractFileByPath(
plugin.settings.templatePath,
);
if (templateFile instanceof TFile) {
if (['md', 'canvas', 'txt'].includes(templateFile.extension)) {
content = await plugin.app.vault.read(templateFile);
if (
extension === '.excalidraw' &&
!content.includes(
'==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==',
)
) {
content = defaultExcalidrawTemplate;
}
} else {
plugin.app.vault.readBinary(templateFile).then(async (data) => {
folderNote = await plugin.app.vault.createBinary(path, data);
if (openFile) {
await leaf.openFile(folderNote);
}
return folderNote;
});
}
}
} else if (
plugin.settings.folderNoteType === '.excalidraw' ||
extension === '.excalidraw'
) {
content = defaultExcalidrawTemplate;
} else if (plugin.settings.folderNoteType === '.canvas') {
content = '{}';
}
}
folderNote = await plugin.app.vault.create(path, content);
return folderNote;
}
async function handleTurnNoteIntoFolderNote(
plugin: FolderNotesPlugin,
folderNote: TFile,
detachedFolder: ExcludedFolder,
folderPath: string,
fileName: string,
): Promise<void> {
deleteExcludedFolder(plugin, detachedFolder);
removeCSSClassFromFileExplorerEL(folderNote?.path, 'is-folder-note', false, plugin);
const folder = plugin.app.vault.getAbstractFileByPath(folderPath) as TFolder;
if (!folderNote || folderNote.basename !== fileName) return;
let count = 1;
const baseName = removeExtension(folderNote.path);
const ext = folderNote.path.split('.').pop();
let newName = `${baseName} (${count}).${ext}`;
const MAX_FOLDER_NOTE_RENAME_ATTEMPTS = 100;
while (
count < MAX_FOLDER_NOTE_RENAME_ATTEMPTS &&
plugin.app.vault.getAbstractFileByPath(newName)
) {
count++;
newName = `${baseName} (${count}).${ext}`;
}
const [
excludedFolder,
excludedFolderExisted,
disabledSync,
] = await tempDisableSync(plugin, folder);
await plugin.app.fileManager.renameFile(folderNote, newName).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
}
export async function turnIntoFolderNote(
plugin: FolderNotesPlugin,
file: TFile,
folder: TFolder,
folderNote?: TFile | null | TAbstractFile,
skipConfirmation?: boolean,
): Promise<void> {
const { extension } = file;
const detachedExcludedFolder = getDetachedFolder(plugin, folder.path);
if (folderNote) {
if (plugin.settings.showRenameConfirmation && !skipConfirmation && !detachedExcludedFolder) {
if (
plugin.settings.showRenameConfirmation &&
!skipConfirmation &&
!detachedExcludedFolder
) {
return new ExistingFolderNoteModal(plugin.app, plugin, file, folder, folderNote).open();
}
removeCSSClassFromFileExplorerEL(folderNote.path, 'is-folder-note', false, plugin);
const [excludedFolder, excludedFolderExisted, disabledSync] = await tempDisableSync(plugin, folder);
const [
excludedFolder,
excludedFolderExisted,
disabledSync,
] = await tempDisableSync(plugin, folder);
const CTIME_SLICE_START = 10;
const RANDOM_SUFFIX_MAX = 1000;
const randomSuffix = Math.floor(Math.random() * RANDOM_SUFFIX_MAX);
const ctimeSuffix = file.stat.ctime.toString().slice(CTIME_SLICE_START);
const newPath = `${folder.path}/${folder.name} (${ctimeSuffix}${randomSuffix}).${extension}`;
const newPath = `${folder.path}/${folder.name} (${file.stat.ctime.toString().slice(10) + Math.floor(Math.random() * 1000)}).${extension}`;
plugin.app.fileManager.renameFile(folderNote, newPath).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
@ -195,14 +327,28 @@ export async function turnIntoFolderNote(plugin: FolderNotesPlugin, file: TFile,
setActiveFolder(folder.path, plugin);
}
export async function tempDisableSync(plugin: FolderNotesPlugin, folder: TFolder): Promise<[excludedFolder: ExcludedFolder | undefined, excludedFolderExisted: boolean, disabledSync: boolean]> {
export async function tempDisableSync(
plugin: FolderNotesPlugin,
folder: TFolder,
): Promise<
[
excludedFolder: ExcludedFolder | undefined,
excludedFolderExisted: boolean,
disabledSync: boolean
]
> {
let excludedFolder = getExcludedFolder(plugin, folder.path, false);
let excludedFolderExisted = true;
let disabledSync = false;
if (!excludedFolder) {
excludedFolderExisted = false;
excludedFolder = new ExcludedFolder(folder.path, plugin.settings.excludeFolders.length, undefined, plugin);
excludedFolder = new ExcludedFolder(
folder.path,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
excludedFolder.disableSync = true;
addExcludedFolder(plugin, excludedFolder);
} else if (!excludedFolder.disableSync) {
@ -214,7 +360,11 @@ export async function tempDisableSync(plugin: FolderNotesPlugin, folder: TFolder
return [excludedFolder, excludedFolderExisted, disabledSync];
}
export async function openFolderNote(plugin: FolderNotesPlugin, file: TAbstractFile, evt?: MouseEvent) {
export async function openFolderNote(
plugin: FolderNotesPlugin,
file: TAbstractFile,
evt?: MouseEvent,
): Promise<void> {
const { path } = file;
const focusExistingTab = plugin.settings.focusExistingTab && plugin.settings.openInNewTab;
const activeFilePath = plugin.app.workspace.getActiveFile()?.path;
@ -230,7 +380,7 @@ export async function openFolderNote(plugin: FolderNotesPlugin, file: TAbstractF
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (
leaf.getViewState().type === 'markdown' &&
(leaf.view as import('obsidian').MarkdownView).file?.path === path
(leaf.view as MarkdownView).file?.path === path
) {
foundLeaf = leaf;
}
@ -240,14 +390,19 @@ export async function openFolderNote(plugin: FolderNotesPlugin, file: TAbstractF
if (foundLeaf) {
plugin.app.workspace.setActiveLeaf(foundLeaf, { focus: true });
} else {
const leaf = plugin.app.workspace.getLeaf(Keymap.isModEvent(evt) || plugin.settings.openInNewTab);
const shouldOpenInNewTab = Keymap.isModEvent(evt) || plugin.settings.openInNewTab;
const leaf = plugin.app.workspace.getLeaf(shouldOpenInNewTab);
if (file instanceof TFile) {
await leaf.openFile(file);
}
}
}
export async function deleteFolderNote(plugin: FolderNotesPlugin, file: TFile, displayModal: boolean) {
export async function deleteFolderNote(
plugin: FolderNotesPlugin,
file: TFile,
displayModal: boolean,
): Promise<void> {
if (plugin.settings.showDeleteConfirmation && displayModal) {
return new DeleteConfirmationModal(plugin.app, plugin, file).open();
}
@ -272,7 +427,7 @@ export async function deleteFolderNote(plugin: FolderNotesPlugin, file: TFile, d
}
}
export function extractFolderName(template: string, changedFileName: string) {
export function extractFolderName(template: string, changedFileName: string): string | null {
const [prefix, suffix] = template.split('{{folder_name}}');
if (prefix.trim() === '' && suffix.trim() === '') {
return changedFileName;
@ -288,39 +443,23 @@ export function extractFolderName(template: string, changedFileName: string) {
return null;
}
export function getFolderNote(plugin: FolderNotesPlugin, folderPath: string, storageLocation?: string, file?: TFile, oldFolderNoteName?: string) {
if (!folderPath) return null;
const folder = {
path: folderPath,
name: getFolderNameFromPathString(folderPath),
};
const folderNoteName = oldFolderNoteName ?? plugin.settings.folderNoteName;
let fileName = folderNoteName.replace('{{folder_name}}', folder.name);
if (file) {
fileName = folderNoteName.replace('{{folder_name}}', file.basename);
}
if (!fileName) return null;
if ((plugin.settings.storageLocation === 'parentFolder' || storageLocation === 'parentFolder') && storageLocation !== 'insideFolder') {
folder.path = getFolderPathFromString(folderPath);
}
let path = `${folder.path}/${fileName}`;
folder.path === '/' ? path = fileName : path = `${folder.path}/${fileName}`;
let { folderNoteType } = plugin.settings;
if (folderNoteType === '.excalidraw') {
folderNoteType = '.md';
}
let folderNote = plugin.app.vault.getAbstractFileByPath(path + folderNoteType);
if (folderNote instanceof TFile && plugin.settings.supportedFileTypes.includes(plugin.settings.folderNoteType.replace('.', ''))) {
function findFolderNoteFile(
plugin: FolderNotesPlugin,
path: string,
primaryType: string,
): TFile | null {
let folderNote = plugin.app.vault.getAbstractFileByPath(path + primaryType);
if (
folderNote instanceof TFile &&
plugin.settings.supportedFileTypes.includes(primaryType.replace('.', ''))
) {
return folderNote;
}
const supportedFileTypes = plugin.settings.supportedFileTypes.filter((type) => type !== plugin.settings.folderNoteType.replace('.', ''));
const supportedFileTypes = plugin.settings.supportedFileTypes.filter(
(type) => type !== primaryType.replace('.', ''),
);
for (let type of supportedFileTypes) {
if (type === 'excalidraw' || type === '.excalidraw') {
type = '.md';
@ -333,13 +472,40 @@ export function getFolderNote(plugin: FolderNotesPlugin, folderPath: string, sto
return folderNote;
}
}
return null;
}
export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile) {
export function getFolderNote(
plugin: FolderNotesPlugin,
folderPath: string,
storageLocation?: string,
file?: TFile,
oldFolderNoteName?: string,
): TFile | null | undefined {
const folder = getFolderInfo(folderPath);
if (!folder) return null;
let fileName = resolveFileName(plugin, folder, file, oldFolderNoteName);
if (!fileName) return null;
adjustFolderPathForStorage(folder, folderPath, plugin, storageLocation);
const path = buildFullPath(folder, fileName);
const primaryType = normalizeFolderNoteType(plugin.settings.folderNoteType);
return findFolderNoteFile(plugin, path, primaryType);
}
export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile): void {
const folder = getFolder(plugin, file);
if (!folder) return;
const excludedFolder = new ExcludedFolder(folder.path, plugin.settings.excludeFolders.length, undefined, plugin);
const excludedFolder = new ExcludedFolder(
folder.path,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
excludedFolder.hideInSettings = true;
excludedFolder.disableFolderNote = true;
excludedFolder.disableSync = true;
@ -351,16 +517,28 @@ export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile) {
}
export function getFolder(plugin: FolderNotesPlugin, file: TFile, storageLocation?: string) {
export function getFolder(
plugin: FolderNotesPlugin,
file: TFile,
storageLocation?: string,
): TFolder | TAbstractFile | null {
if (!file) return null;
let folderName = extractFolderName(plugin.settings.folderNoteName, file.basename);
if (plugin.settings.folderNoteName === file.basename && plugin.settings.storageLocation === 'insideFolder') {
if (
plugin.settings.folderNoteName === file.basename &&
plugin.settings.storageLocation === 'insideFolder'
) {
folderName = file.parent?.name ?? '';
}
if (!folderName) return null;
let folderPath = getFolderPathFromString(file.path);
let folder: TFolder | TAbstractFile | null = null;
if ((plugin.settings.storageLocation === 'parentFolder' || storageLocation === 'parentFolder') && storageLocation !== 'insideFolder') {
if (
(plugin.settings.storageLocation === 'parentFolder' ||
storageLocation === 'parentFolder') &&
storageLocation !== 'insideFolder'
) {
if (folderPath.trim() === '' || folderPath === '/') {
folderPath = folderName;
} else {
@ -370,11 +548,16 @@ export function getFolder(plugin: FolderNotesPlugin, file: TFile, storageLocatio
} else {
folder = plugin.app.vault.getAbstractFileByPath(folderPath);
}
if (!folder) { return null; }
return folder;
}
export function getFolderNoteFolder(plugin: FolderNotesPlugin, folderNote: TFile | string, fileName: string) {
export function getFolderNoteFolder(
plugin: FolderNotesPlugin,
folderNote: TFile | string,
fileName: string,
): TFolder | TAbstractFile | null {
if (!folderNote) return null;
let filePath = '';
if (typeof folderNote === 'string') {
@ -399,3 +582,46 @@ export function getFolderNoteFolder(plugin: FolderNotesPlugin, folderNote: TFile
if (!folder) { return null; }
return folder;
}
function getFolderInfo(folderPath: string): { path: string; name: string } | null {
if (!folderPath) return null;
return {
path: folderPath,
name: getFolderNameFromPathString(folderPath),
};
}
function resolveFileName(
plugin: FolderNotesPlugin,
folder: { path: string; name: string },
file?: TFile,
oldFolderNoteName?: string,
): string | null {
const templateName = oldFolderNoteName ?? plugin.settings.folderNoteName;
if (!templateName) return null;
const nameSource = file ? file.basename : folder.name;
return templateName.replace('{{folder_name}}', nameSource);
}
function adjustFolderPathForStorage(
folder: { path: string; name: string },
folderPath: string,
plugin: FolderNotesPlugin,
storageLocation?: string,
): void {
if (
(plugin.settings.storageLocation === 'parentFolder' ||
storageLocation === 'parentFolder') &&
storageLocation !== 'insideFolder'
) {
folder.path = getFolderPathFromString(folderPath);
}
}
function buildFullPath(folder: { path: string }, fileName: string): string {
return folder.path === '/' ? fileName : `${folder.path}/${fileName}`;
}
function normalizeFolderNoteType(type: string): string {
return type === '.excalidraw' ? '.md' : type;
}

View file

@ -1,6 +1,10 @@
import { TFile, TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { getDetachedFolder, getExcludedFolder, addExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import {
getDetachedFolder,
getExcludedFolder,
addExcludedFolder,
} from 'src/ExcludeFolders/functions/folderFunctions';
import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions';
import { getFileExplorer } from './utils';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
@ -9,7 +13,7 @@ import type FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
/**
* @description Refreshes the CSS classes for all folder notes in the file explorer.
*/
export function refreshAllFolderStyles(forceReload = false, plugin: FolderNotesPlugin) {
export function refreshAllFolderStyles(forceReload = false, plugin: FolderNotesPlugin): void {
if (plugin.activeFileExplorer === getFileExplorer(plugin) && !forceReload) { return; }
plugin.activeFileExplorer = getFileExplorer(plugin);
plugin.app.vault.getAllLoadedFiles().forEach(async (file) => {
@ -22,7 +26,10 @@ export function refreshAllFolderStyles(forceReload = false, plugin: FolderNotesP
/**
* @description Updates the CSS classes for a specific folder in the file explorer.
*/
export async function updateCSSClassesForFolder(folderPath: string, plugin: FolderNotesPlugin) {
export async function updateCSSClassesForFolder(
folderPath: string,
plugin: FolderNotesPlugin,
): Promise<void> {
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
if (!folder || !(folder instanceof TFolder)) { return; }
@ -64,7 +71,10 @@ export async function updateCSSClassesForFolder(folderPath: string, plugin: Fold
/**
* @description Updates the CSS classes for a folder note file in the file explorer and then also updates the folder it belongs to.
*/
export async function updateCSSClassesForFolderNote(filePath: string, plugin: FolderNotesPlugin) {
export async function updateCSSClassesForFolderNote(
filePath: string,
plugin: FolderNotesPlugin,
): Promise<void> {
const file = plugin.app.vault.getAbstractFileByPath(filePath);
if (!file || !(file instanceof TFile)) { return; }
@ -74,17 +84,25 @@ export async function updateCSSClassesForFolderNote(filePath: string, plugin: Fo
updateCSSClassesForFolder(folder.path, plugin);
}
export function markFolderAndNoteWithClasses(file: TFile, folder: TFolder, plugin: FolderNotesPlugin) {
export function markFolderAndNoteWithClasses(
file: TFile,
folder: TFolder,
plugin: FolderNotesPlugin,
): void {
markFileAsFolderNote(file, plugin);
markFolderWithFolderNoteClasses(folder, plugin);
}
export function clearFolderAndNoteClasses(folder: TFolder, file: TFile, plugin: FolderNotesPlugin) {
export function clearFolderAndNoteClasses(
folder: TFolder,
file: TFile,
plugin: FolderNotesPlugin,
): void {
unmarkFileAsFolderNote(file, plugin);
clearFolderNoteClassesFromFolder(folder, plugin);
}
export function markFolderWithFolderNoteClasses(folder: TFolder, plugin: FolderNotesPlugin) {
export function markFolderWithFolderNoteClasses(folder: TFolder, plugin: FolderNotesPlugin): void {
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
addCSSClassToFileExplorerEl(folder.path, 'only-has-folder-note', true, plugin);
@ -93,20 +111,20 @@ export function markFolderWithFolderNoteClasses(folder: TFolder, plugin: FolderN
}
}
export function markFileAsFolderNote(file: TFile, plugin: FolderNotesPlugin) {
export function markFileAsFolderNote(file: TFile, plugin: FolderNotesPlugin): void {
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
}
export function unmarkFileAsFolderNote(file: TFile, plugin: FolderNotesPlugin) {
export function unmarkFileAsFolderNote(file: TFile, plugin: FolderNotesPlugin): void {
removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin);
}
export function unmarkFolderAsFolderNote(folder: TFolder, plugin: FolderNotesPlugin) {
export function unmarkFolderAsFolderNote(folder: TFolder, plugin: FolderNotesPlugin): void {
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
export function clearFolderNoteClassesFromFolder(folder: TFolder, plugin: FolderNotesPlugin) {
export function clearFolderNoteClassesFromFolder(folder: TFolder, plugin: FolderNotesPlugin): void {
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
@ -115,11 +133,21 @@ export function clearFolderNoteClassesFromFolder(folder: TFolder, plugin: Folder
* @param path Can be a folder or file path
* @returns nothing
*/
export async function addCSSClassToFileExplorerEl(path: string, cssClass: string, parent = false, plugin: FolderNotesPlugin, waitForCreate = false, count = 0) {
export async function addCSSClassToFileExplorerEl(
path: string,
cssClass: string,
parent = false,
plugin: FolderNotesPlugin,
waitForCreate = false,
count = 0,
): Promise<void> {
const fileExplorerItem = getFileExplorerElement(path, plugin);
const MAX_RETRIES = 5;
const RETRY_DELAY = 500;
if (!fileExplorerItem) {
if (waitForCreate && count < 5) {
await new Promise((r) => setTimeout(r, 500));
if (waitForCreate && count < MAX_RETRIES) {
await new Promise((r) => setTimeout(r, RETRY_DELAY));
addCSSClassToFileExplorerEl(path, cssClass, parent, plugin, waitForCreate, count + 1);
return;
}
@ -143,7 +171,12 @@ export async function addCSSClassToFileExplorerEl(path: string, cssClass: string
* @param cssClass The CSS class to remove from the file explorer element
* @returns nothing
*/
export function removeCSSClassFromFileExplorerEL(path: string | undefined, cssClass: string, parent: boolean, plugin: FolderNotesPlugin) {
export function removeCSSClassFromFileExplorerEL(
path: string | undefined,
cssClass: string,
parent: boolean,
plugin: FolderNotesPlugin,
): void {
if (!path) return;
const fileExplorerItem = getFileExplorerElement(path, plugin);
document.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
@ -161,15 +194,23 @@ export function removeCSSClassFromFileExplorerEL(path: string | undefined, cssCl
}
export function getFileExplorerElement(path: string, plugin: FolderNotesPlugin | FolderOverviewPlugin): HTMLElement | null {
export function getFileExplorerElement(
path: string,
plugin: FolderNotesPlugin | FolderOverviewPlugin,
): HTMLElement | null {
const fileExplorer = getFileExplorer(plugin);
if (!fileExplorer?.view?.fileItems) { return null; }
const fileExplorerItem = fileExplorer.view.fileItems?.[path];
return fileExplorerItem?.selfEl ?? fileExplorerItem?.titleEl ?? null;
}
export function showFolderNoteInFileExplorer(path: string, plugin: FolderNotesPlugin) {
const excludedFolder = new ExcludedFolder(path, plugin.settings.excludeFolders.length, undefined, plugin);
export function showFolderNoteInFileExplorer(path: string, plugin: FolderNotesPlugin): void {
const excludedFolder = new ExcludedFolder(
path,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
excludedFolder.subFolders = false;
excludedFolder.disableSync = false;
excludedFolder.disableAutoCreate = false;
@ -183,7 +224,7 @@ export function showFolderNoteInFileExplorer(path: string, plugin: FolderNotesPl
updateCSSClassesForFolder(path, plugin);
}
export function hideFolderNoteInFileExplorer(folderPath: string, plugin: FolderNotesPlugin) {
export function hideFolderNoteInFileExplorer(folderPath: string, plugin: FolderNotesPlugin): void {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(folder) => (folder.path !== folderPath) && folder.showFolderNote);
plugin.saveSettings(false);
@ -191,7 +232,7 @@ export function hideFolderNoteInFileExplorer(folderPath: string, plugin: FolderN
updateCSSClassesForFolder(folderPath, plugin);
}
export function setActiveFolder(folderPath: string, plugin: FolderNotesPlugin) {
export function setActiveFolder(folderPath: string, plugin: FolderNotesPlugin): void {
const fileExplorerItem = getFileExplorerElement(folderPath, plugin);
if (fileExplorerItem) {
fileExplorerItem.addClass('fn-is-active');
@ -199,7 +240,7 @@ export function setActiveFolder(folderPath: string, plugin: FolderNotesPlugin) {
}
}
export function removeActiveFolder(plugin: FolderNotesPlugin) {
export function removeActiveFolder(plugin: FolderNotesPlugin): void {
if (plugin.activeFolderDom) {
plugin.activeFolderDom.removeClass('fn-is-active');
plugin.activeFolderDom?.removeClass('has-focus');

View file

@ -2,7 +2,7 @@ import { TFolder, TFile, View } from 'obsidian';
import type { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import { getFolderNote } from './folderNoteFunctions';
import type FolderNotesPlugin from 'src/main';
import type { FileExplorerLeaf } from 'obsidian-typings';
import type { FileExplorerLeaf, FileTreeItem, TreeNode } from 'obsidian-typings';
import type FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
export function getFileNameFromPathString(path: string): string {
@ -10,11 +10,12 @@ export function getFileNameFromPathString(path: string): string {
}
export function getFolderNameFromPathString(path: string): string {
const PARENT_FOLDER_INDEX = -2;
const LAST_FOLDER_INDEX = -1;
if (path.endsWith('.md') || path.endsWith('.canvas')) {
return path.split('/').slice(-2)[0];
return path.split('/').slice(PARENT_FOLDER_INDEX)[0];
}
return path.split('/').slice(-1)[0];
return path.split('/').slice(LAST_FOLDER_INDEX)[0];
}
export function removeExtension(name: string): string {
@ -39,16 +40,20 @@ export function getParentFolderPath(path: string): string {
return this.getFolderPathFromString(this.getFolderPathFromString(path));
}
export function getFileExplorer(plugin: FolderNotesPlugin | FolderOverviewPlugin) {
return plugin.app.workspace.getLeavesOfType('file-explorer')[0] as any as FileExplorerWorkspaceLeaf;
export function getFileExplorer(
plugin: FolderNotesPlugin | FolderOverviewPlugin,
): FileExplorerWorkspaceLeaf {
// eslint-disable-next-line max-len
const leaf = plugin.app.workspace.getLeavesOfType('file-explorer')[0] as unknown as FileExplorerWorkspaceLeaf;
return leaf;
}
export function getFileExplorerView(plugin: FolderNotesPlugin) {
export function getFileExplorerView(plugin: FolderNotesPlugin): FileExplorerView {
return getFileExplorer(plugin).view;
}
export function getFocusedItem(plugin: FolderNotesPlugin) {
const fileExplorer = getFileExplorer(plugin) as any as FileExplorerLeaf;
export function getFocusedItem(plugin: FolderNotesPlugin): TreeNode<FileTreeItem> | null {
const fileExplorer = getFileExplorer(plugin) as unknown as FileExplorerLeaf;
const { focusedItem } = fileExplorer.view.tree;
return focusedItem;
}

2
src/globals.d.ts vendored
View file

@ -40,7 +40,6 @@ interface FileItem {
el: HTMLDivElement;
file: TFile;
fileExplorer: FileExplorerView;
info: any;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
}
@ -48,7 +47,6 @@ interface FileItem {
interface FolderItem {
el: HTMLDivElement;
fileExplorer: FileExplorerView;
info: any;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
file: TFolder;

View file

@ -1,13 +1,23 @@
import type { TAbstractFile, MarkdownPostProcessorContext, WorkspaceLeaf } from 'obsidian';
import { Plugin, TFile, TFolder, parseYaml, Notice, Keymap, requireApiVersion, Platform, debounce } from 'obsidian';
import type { FolderNotesSettings } from './settings/SettingsTab';
import { DEFAULT_SETTINGS, SettingsTab } from './settings/SettingsTab';
import {
type TAbstractFile,
type MarkdownPostProcessorContext,
type WorkspaceLeaf,
Plugin, TFile, TFolder,
parseYaml, Notice, Keymap,
requireApiVersion, Platform, debounce,
} from 'obsidian';
import {
type FolderNotesSettings, DEFAULT_SETTINGS, SettingsTab,
} from './settings/SettingsTab';
import { Commands } from './Commands';
import type { FileExplorerWorkspaceLeaf } from './globals';
import { registerFileExplorerObserver, unregisterFileExplorerObserver } from './events/MutationObserver';
import {
registerFileExplorerObserver, unregisterFileExplorerObserver,
} from './events/MutationObserver';
import { handleRename } from './events/handleRename';
import { getFolderNote, getFolder, openFolderNote, createFolderNote } from './functions/folderNoteFunctions';
import {
getFolderNote, getFolder, openFolderNote, createFolderNote,
} from './functions/folderNoteFunctions';
import { handleCreate } from './events/handleCreate';
import { FrontMatterTitlePluginHandler } from './events/FrontMatterTitle';
import { FolderOverviewSettings } from './obsidian-folder-overview/src/modals/Settings';
@ -15,10 +25,12 @@ import { FolderOverview } from './obsidian-folder-overview/src/FolderOverview';
import { TabManager } from './events/TabManager';
import './functions/ListComponent';
import { handleDelete } from './events/handleDelete';
import { addCSSClassToFileExplorerEl, getFileExplorerElement, removeCSSClassFromFileExplorerEL, refreshAllFolderStyles, setActiveFolder, removeActiveFolder } from './functions/styleFunctions';
import {
addCSSClassToFileExplorerEl, getFileExplorerElement, removeCSSClassFromFileExplorerEL,
refreshAllFolderStyles, setActiveFolder, removeActiveFolder,
} from './functions/styleFunctions';
import { getExcludedFolder } from './ExcludeFolders/functions/folderFunctions';
import type { FileExplorerView, InternalPlugin } from 'obsidian-typings';
// import { getFocusedItem } from './functions/utils';
import { FOLDER_OVERVIEW_VIEW, FolderOverviewView } from './obsidian-folder-overview/src/view';
import { registerOverviewCommands } from './obsidian-folder-overview/src/Commands';
import { updateOverviewView, updateViewDropdown } from './obsidian-folder-overview/src/main';
@ -26,7 +38,6 @@ import { FvIndexDB } from './obsidian-folder-overview/src/utils/IndexDB';
import { updateAllOverviews } from './obsidian-folder-overview/src/utils/functions';
export default class FolderNotesPlugin extends Plugin {
observer: MutationObserver;
settings: FolderNotesSettings;
settingsTab: SettingsTab;
activeFolderDom: HTMLElement | null;
@ -43,7 +54,7 @@ export default class FolderNotesPlugin extends Plugin {
private fileExplorerPlugin!: InternalPlugin;
private fileExplorerView!: FileExplorerView;
async onload() {
async onload(): Promise<void> {
console.log('loading folder notes plugin');
await this.loadSettings();
this.settingsTab = new SettingsTab(this.app, this);
@ -54,17 +65,29 @@ export default class FolderNotesPlugin extends Plugin {
// Add CSS Classes
document.body.classList.add('folder-notes-plugin');
if (this.settings.hideFolderNote) { document.body.classList.add('hide-folder-note'); }
if (this.settings.hideCollapsingIconForEmptyFolders) { document.body.classList.add('fn-hide-empty-collapse-icon'); }
if (this.settings.hideCollapsingIconForEmptyFolders) {
document.body.classList.add('fn-hide-empty-collapse-icon');
}
if (this.settings.underlineFolder) { document.body.classList.add('folder-note-underline'); }
if (this.settings.boldName) { document.body.classList.add('folder-note-bold'); }
if (this.settings.cursiveName) { document.body.classList.add('folder-note-cursive'); }
if (this.settings.boldNameInPath) { document.body.classList.add('folder-note-bold-path'); }
if (this.settings.cursiveNameInPath) { document.body.classList.add('folder-note-cursive-path'); }
if (this.settings.underlineFolderInPath) { document.body.classList.add('folder-note-underline-path'); }
if (this.settings.stopWhitespaceCollapsing) { document.body.classList.add('fn-whitespace-stop-collapsing'); }
if (this.settings.hideCollapsingIcon) { document.body.classList.add('fn-hide-collapse-icon'); }
if (!this.settings.highlightFolder) { document.body.classList.add('disable-folder-highlight'); }
// document.body.classList.add('fv-hide-link-list');
if (this.settings.cursiveNameInPath) {
document.body.classList.add('folder-note-cursive-path');
}
if (this.settings.underlineFolderInPath) {
document.body.classList.add('folder-note-underline-path');
}
if (this.settings.stopWhitespaceCollapsing) {
document.body.classList.add('fn-whitespace-stop-collapsing');
}
if (this.settings.hideCollapsingIcon) {
document.body.classList.add('fn-hide-collapse-icon');
}
if (!this.settings.highlightFolder) {
document.body.classList.add('disable-folder-highlight');
}
if (requireApiVersion('1.7.2')) {
document.body.classList.add('version-1-7-2');
}
@ -74,23 +97,11 @@ export default class FolderNotesPlugin extends Plugin {
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
// this.observer.observe(document.body, {
// childList: true,
// subtree: true,
// });
if (!this.settings.persistentSettingsTab.afterRestart) {
this.settings.settingsTab = 'general';
}
this.registerDomEvent(window, 'keydown', (event: KeyboardEvent) => {
// Was a bit too buggy
// if (event.key === 'Enter') {
// const folderNote = getFolderNote(this, getFocusedItem(this)?.file?.path || '');
// if (!folderNote) return;
// openFolderNote(this, folderNote);
// }
const { hoveredElement } = this;
if (this.hoverLinkTriggered) return;
if (!hoveredElement) return;
@ -143,12 +154,15 @@ export default class FolderNotesPlugin extends Plugin {
this.handleVaultChange();
}));
this.registerMarkdownCodeBlockProcessor('folder-overview', (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
this.handleOverviewBlock(source, el, ctx);
});
this.registerMarkdownCodeBlockProcessor(
'folder-overview',
(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
this.handleOverviewBlock(source, el, ctx);
},
);
}
onLayoutReady() {
onLayoutReady(): void {
if (!this._loaded) {
return;
}
@ -175,8 +189,9 @@ export default class FolderNotesPlugin extends Plugin {
const fileExplorerPlugin = this.app.internalPlugins.getEnabledPluginById('file-explorer');
if (fileExplorerPlugin) {
const originalRevealInFolder = fileExplorerPlugin.revealInFolder.bind(fileExplorerPlugin);
fileExplorerPlugin.revealInFolder = (file: TAbstractFile) => {
const originalRevealInFolder = fileExplorerPlugin.revealInFolder
.bind(fileExplorerPlugin);
fileExplorerPlugin.revealInFolder = (file: TAbstractFile): void => {
if (file instanceof TFile) {
const folder = getFolder(this, file);
if (folder instanceof TFolder) {
@ -186,9 +201,10 @@ export default class FolderNotesPlugin extends Plugin {
}
document.body.classList.remove('hide-folder-note');
originalRevealInFolder.call(fileExplorerPlugin, folder);
const FOLDER_REVEAL_DELAY = 100;
setTimeout(() => {
document.body.classList.add('hide-folder-note');
}, 100);
}, FOLDER_REVEAL_DELAY);
return;
}
}
@ -201,26 +217,29 @@ export default class FolderNotesPlugin extends Plugin {
if (!view) { return; }
// @ts-ignore
const editMode = view.editMode ?? view.sourceMode ?? this.app.workspace.activeEditor?.editMode;
// @ts-expect-error use internal API
const editMode = view.editMode ?? view.sourceMode
// @ts-expect-error use internal API
?? this.app.workspace.activeEditor?.editMode;
const plugin = this;
if (!editMode) { return; }
// @ts-ignore
const clipboardProto = editMode.clipboardManager.constructor.prototype;
const originalHandleDragOver = clipboardProto.handleDragOver;
const originalHandleDrop = clipboardProto.handleDrop;
clipboardProto.handleDragOver = function (evt: DragEvent, ...args: any[]) {
clipboardProto.handleDragOver = function (evt: DragEvent, ...args: unknown[]): void {
const { dragManager } = this.app;
const draggable = dragManager?.draggable;
if (draggable?.file instanceof TFolder) {
const folderNote = getFolderNote(plugin, draggable.file.path);
if (folderNote) {
dragManager.setAction(window.i18next.t('interface.drag-and-drop.insert-link-here'));
dragManager.setAction(
window.i18next.t('interface.drag-and-drop.insert-link-here'),
);
return;
}
}
@ -228,7 +247,7 @@ export default class FolderNotesPlugin extends Plugin {
return originalHandleDragOver.call(this, evt, ...args);
};
clipboardProto.handleDrop = function (evt: DragEvent, ...args: any[]) {
clipboardProto.handleDrop = function (evt: DragEvent, ...args: unknown[]): void {
const { dragManager } = this.app;
const draggable = dragManager?.draggable;
@ -248,57 +267,35 @@ export default class FolderNotesPlugin extends Plugin {
}
}
handleVaultChange() {
handleVaultChange(): void {
if (!this.settings.fvGlobalSettings.autoUpdateLinks) return;
const DEBOUNCE_DELAY = 2000;
debounce(() => {
updateAllOverviews(this);
}, 2000, true)();
}, DEBOUNCE_DELAY, true)();
}
handleFileExplorerClick(evt: MouseEvent) {
handleFileExplorerClick(evt: MouseEvent): void {
const target = evt.target as HTMLElement;
if (evt.shiftKey) return;
if (this.isMobileClickDisabled()) return;
if (Platform.isMobile && this.settings.disableOpenFolderNoteOnClick) return;
// Check if the click is on a folder
const folderTitleEl = target.closest('.nav-folder-title') as HTMLElement;
const { folderTitleEl, onlyClickedOnFolderTitle } = this.getFolderTitleInfo(target);
if (!folderTitleEl) return;
if (this.shouldIgnoreClickByWhitespaceOrCollapse(target, onlyClickedOnFolderTitle)) return;
const onlyClickedOnFolderTitle = !!target.closest('.nav-folder-title-content');
// const folderTitleContentEl = target.closest('.nav-folder-title-content') as HTMLElement;
// if (folderTitleContentEl) {
// const rect = folderTitleContentEl.getBoundingClientRect();
// const clickOffsetX = evt.clientX - rect.left;
// // Ignore clicks within the first N pixels, e.g., 20px where the icon is displayed
// if (clickOffsetX < 20) return;
// }
if (!this.settings.stopWhitespaceCollapsing && !onlyClickedOnFolderTitle) return;
// Ignore clicks on the collapse icon
if (target.closest('.collapse-icon')) return;
const folderPath = folderTitleEl.getAttribute('data-path');
const folderPath = this.getValidFolderPath(folderTitleEl);
if (!folderPath) return;
const excludedFolder = getExcludedFolder(this, folderPath, true);
if (excludedFolder?.disableFolderNote) return;
const usedCtrl = Platform.isMacOS ? evt.metaKey : evt.ctrlKey;
const usedCtrl = this.isCtrlUsed(evt);
const folderNote = getFolderNote(this, folderPath);
if (!folderNote && (evt.altKey || Keymap.isModEvent(evt) === 'tab')) {
if ((this.settings.altKey && evt.altKey) || (usedCtrl && this.settings.ctrlKey)) {
createFolderNote(this, folderPath, true, undefined, true);
addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, this);
removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, this);
return;
}
if (!folderNote && this.shouldCreateNote(evt, usedCtrl)) {
this.createNoteAndMark(folderPath);
return;
}
if (!(folderNote instanceof TFile)) return;
if (this.settings.openWithCtrl && !usedCtrl) return;
if (this.settings.openWithAlt && !evt.altKey) return;
if (!this.shouldOpenNote(usedCtrl, evt)) return;
if (!this.settings.enableCollapsing || usedCtrl) {
evt.preventDefault();
@ -308,6 +305,58 @@ export default class FolderNotesPlugin extends Plugin {
openFolderNote(this, folderNote, evt);
}
private isMobileClickDisabled(): boolean {
return Platform.isMobile && this.settings.disableOpenFolderNoteOnClick;
}
private getFolderTitleInfo(target: HTMLElement): {
folderTitleEl: HTMLElement | null;
onlyClickedOnFolderTitle: boolean;
} {
const folderTitleEl = target.closest('.nav-folder-title') as HTMLElement | null;
const onlyClickedOnFolderTitle = !!target.closest('.nav-folder-title-content');
return { folderTitleEl, onlyClickedOnFolderTitle };
}
private shouldIgnoreClickByWhitespaceOrCollapse(
target: HTMLElement,
onlyClickedOnFolderTitle: boolean,
): boolean {
if (!this.settings.stopWhitespaceCollapsing && !onlyClickedOnFolderTitle) return true;
if (target.closest('.collapse-icon')) return true;
return false;
}
private getValidFolderPath(folderTitleEl: HTMLElement): string | null {
const folderPath = folderTitleEl.getAttribute('data-path');
if (!folderPath) return null;
const excludedFolder = getExcludedFolder(this, folderPath, true);
if (excludedFolder?.disableFolderNote) return null;
return folderPath;
}
private isCtrlUsed(evt: MouseEvent): boolean {
return Platform.isMacOS ? evt.metaKey : evt.ctrlKey;
}
private shouldCreateNote(evt: MouseEvent, usedCtrl: boolean): boolean {
const isTabMod = Keymap.isModEvent(evt) === 'tab';
if (!(evt.altKey || isTabMod)) return false;
return (this.settings.altKey && evt.altKey) || (usedCtrl && this.settings.ctrlKey);
}
private createNoteAndMark(folderPath: string): void {
createFolderNote(this, folderPath, true, undefined, true);
addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, this);
removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, this);
}
private shouldOpenNote(usedCtrl: boolean, evt: MouseEvent): boolean {
if (this.settings.openWithCtrl && !usedCtrl) return false;
if (this.settings.openWithAlt && !evt.altKey) return false;
return true;
}
handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext): void {
const observer = new MutationObserver(() => {
const editButton = el.parentElement?.childNodes.item(1);
@ -317,8 +366,12 @@ export default class FolderNotesPlugin extends Plugin {
e.preventDefault();
e.stopPropagation();
new FolderOverviewSettings(
this.app, this, parseYaml(source),
ctx, el, this.settings.defaultOverview,
this.app,
this,
parseYaml(source),
ctx,
el,
this.settings.defaultOverview,
).open();
}, { capture: true });
}
@ -331,21 +384,35 @@ export default class FolderNotesPlugin extends Plugin {
try {
if (this.app.workspace.layoutReady) {
const folderOverview = new FolderOverview(this, ctx, source, el, this.settings.defaultOverview);
const { defaultOverview } = this.settings;
const folderOverview = new FolderOverview(
this,
ctx,
source,
el,
defaultOverview,
);
folderOverview.create(this, el, ctx);
} else {
this.app.workspace.onLayoutReady(() => {
const folderOverview = new FolderOverview(this, ctx, source, el, this.settings.defaultOverview);
const folderOverview = new FolderOverview(
this,
ctx,
source,
el,
this.settings.defaultOverview,
);
folderOverview.create(this, el, ctx);
});
}
} catch (e) {
// eslint-disable-next-line max-len
new Notice('Error creating folder overview (folder notes plugin) - check console for more details');
console.error(e);
}
}
async activateOverviewView() {
async activateOverviewView(): Promise<void> {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
@ -362,13 +429,14 @@ export default class FolderNotesPlugin extends Plugin {
workspace.revealLeaf(leaf);
}
updateOverviewView = updateOverviewView;
updateViewDropdown = updateViewDropdown;
updateOverviewView: typeof updateOverviewView = updateOverviewView;
updateViewDropdown: typeof updateViewDropdown = updateViewDropdown;
isEmptyFolderNoteFolder(folder: TFolder): boolean {
const attachmentFolderPath = this.app.vault.getConfig('attachmentFolderPath') as string;
const cleanAttachmentFolderPath = attachmentFolderPath?.replace('./', '') || '';
const attachmentsAreInRootFolder = attachmentFolderPath === './' || attachmentFolderPath === '';
const attachmentsAreInRootFolder = attachmentFolderPath === './'
|| attachmentFolderPath === '';
const threshold = this.settings.storageLocation === 'insideFolder' ? 1 : 0;
if (folder.children.length === 0) {
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
@ -379,10 +447,13 @@ export default class FolderNotesPlugin extends Plugin {
} else if (folder.children.length > threshold) {
if (attachmentsAreInRootFolder) {
return false;
} else if (this.settings.ignoreAttachmentFolder && this.app.vault.getAbstractFileByPath(`${folder.path}/${cleanAttachmentFolderPath}`)) {
} else if (this.settings.ignoreAttachmentFolder
&& this.app.vault.getAbstractFileByPath(
`${folder.path}/${cleanAttachmentFolderPath}`)) {
const folderPath = `${folder.path}/${cleanAttachmentFolderPath}`;
const attachmentFolder = this.app.vault.getAbstractFileByPath(folderPath);
if (attachmentFolder instanceof TFolder && folder.children.length <= threshold + 1) {
if (attachmentFolder instanceof TFolder
&& folder.children.length <= threshold + 1) {
if (!folder.collapsed) {
getFileExplorerElement(folder.path, this)?.click();
}
@ -395,13 +466,20 @@ export default class FolderNotesPlugin extends Plugin {
return true;
}
async changeFolderNameInExplorer(folder: TFolder, newName: string | null | undefined, waitForCreate = false, count = 0) {
async changeFolderNameInExplorer(
folder: TFolder,
newName: string | null | undefined,
waitForCreate = false,
count = 0,
): Promise<void> {
const MAX_RETRY_COUNT = 5;
const RETRY_DELAY_MS = 500;
if (!newName) newName = folder.name;
let fileExplorerItem = getFileExplorerElement(folder.path, this);
if (!fileExplorerItem) {
if (waitForCreate && count < 5) {
await new Promise((r) => setTimeout(r, 500));
this.changeFolderNameInExplorer(folder, newName, waitForCreate, count + 1);
if (waitForCreate && count < MAX_RETRY_COUNT) {
await new Promise<void>((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
void this.changeFolderNameInExplorer(folder, newName, waitForCreate, count + 1);
return;
}
return;
@ -410,15 +488,19 @@ export default class FolderNotesPlugin extends Plugin {
fileExplorerItem = fileExplorerItem?.querySelector('div.nav-folder-title-content');
if (!fileExplorerItem) { return; }
if (this.settings.frontMatterTitle.explorer && this.settings.frontMatterTitle.enabled) {
fileExplorerItem.innerText = newName;
fileExplorerItem.setAttribute('old-name', folder.name);
(fileExplorerItem as HTMLElement).innerText = newName;
(fileExplorerItem as HTMLElement).setAttribute('old-name', folder.name);
} else {
fileExplorerItem.innerText = folder.name;
fileExplorerItem.removeAttribute('old-name');
(fileExplorerItem as HTMLElement).innerText = folder.name;
(fileExplorerItem as HTMLElement).removeAttribute('old-name');
}
}
async changeFolderNameInPath(folder: TFolder, newName: string | null | undefined, breadcrumb: HTMLElement) {
async changeFolderNameInPath(
folder: TFolder,
newName: string | null | undefined,
breadcrumb: HTMLElement,
): Promise<void> {
if (!newName) newName = folder.name;
breadcrumb.textContent = folder.newName || folder.name;
@ -429,7 +511,7 @@ export default class FolderNotesPlugin extends Plugin {
/**
* Updates all folder names in the path above the note editor
*/
updateAllBreadcrumbs(remove?: boolean) {
updateAllBreadcrumbs(remove?: boolean): void {
if (!this.settings.frontMatterTitle.path && !remove) { return; }
const viewHeaderItems = document.querySelectorAll('span.view-header-breadcrumb');
const files = this.app.vault.getAllLoadedFiles().filter((file) => file instanceof TFolder);
@ -449,7 +531,7 @@ export default class FolderNotesPlugin extends Plugin {
});
}
onunload() {
onunload(): void {
unregisterFileExplorerObserver();
document.body.classList.remove('folder-notes-plugin');
document.body.classList.remove('folder-note-underline');
@ -461,7 +543,7 @@ export default class FolderNotesPlugin extends Plugin {
}
}
async loadSettings() {
async loadSettings(): Promise<void> {
const data = await this.loadData();
if (data) {
if (data.allowWhitespaceCollapsing === true) {
@ -479,12 +561,14 @@ export default class FolderNotesPlugin extends Plugin {
}
if (!data) { return; }
const overview = (data as any).defaultOverview;
const overview = data.defaultOverview;
if (!overview) { return; }
this.settings.defaultOverview = Object.assign({}, DEFAULT_SETTINGS.defaultOverview, overview);
this.settings.defaultOverview = Object.assign(
{}, DEFAULT_SETTINGS.defaultOverview, overview,
);
}
async saveSettings(reloadStyles?: boolean) {
async saveSettings(reloadStyles?: boolean): Promise<void> {
await this.saveData(this.settings);
// cleanup any css if we need too
if ((!this.settingsOpened || reloadStyles === true) && reloadStyles !== false) {

View file

@ -1,5 +1,4 @@
import type { App, SettingTab } from 'obsidian';
import { Modal, Setting, Notice } from 'obsidian';
import { Modal, Setting, Notice, type App, type SettingTab } from 'obsidian';
import type FolderNotesPlugin from '../main';
import type { ListComponent } from 'src/functions/ListComponent';
@ -17,7 +16,8 @@ export default class AddSupportedFileModal extends Modal {
this.list = list;
this.settingsTab = settingsTab;
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
// close when user presses enter
contentEl.addEventListener('keydown', (e) => {
@ -38,7 +38,7 @@ export default class AddSupportedFileModal extends Modal {
}),
);
}
async onClose() {
async onClose(): Promise<void> {
if (this.name.toLocaleLowerCase() === 'markdown') {
this.name = 'md';
}
@ -47,9 +47,9 @@ export default class AddSupportedFileModal extends Modal {
contentEl.empty();
this.settingsTab.display();
} else if (this.plugin.settings.supportedFileTypes.includes(this.name.toLowerCase())) {
return new Notice('This extension is already supported');
new Notice('This extension is already supported');
return;
} else {
// @ts-ignore
await this.list.addValue(this.name.toLowerCase());
this.settingsTab.display();
this.plugin.saveSettings();

View file

@ -1,5 +1,4 @@
import type { TFile } from 'obsidian';
import { FuzzySuggestModal } from 'obsidian';
import { FuzzySuggestModal, type TFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { createFolderNote } from 'src/functions/folderNoteFunctions';
export class AskForExtensionModal extends FuzzySuggestModal<string> {
@ -9,7 +8,14 @@ export class AskForExtensionModal extends FuzzySuggestModal<string> {
openFile: boolean;
useModal: boolean | undefined;
existingNote: TFile | undefined;
constructor(plugin: FolderNotesPlugin, folderPath: string, openFile: boolean, extension: string, useModal?: boolean, existingNote?: TFile) {
constructor(
plugin: FolderNotesPlugin,
folderPath: string,
openFile: boolean,
extension: string,
useModal?: boolean,
existingNote?: TFile,
) {
super(plugin.app);
this.plugin = plugin;
this.folderPath = folderPath;
@ -21,17 +27,26 @@ export class AskForExtensionModal extends FuzzySuggestModal<string> {
}
getItems(): string[] {
return this.plugin.settings.supportedFileTypes.filter((item) => item.toLowerCase() !== '.ask');
return this.plugin.settings.supportedFileTypes.filter(
(item) => item.toLowerCase() !== '.ask',
);
}
getItemText(item: string): string {
return item;
}
onChooseItem(item: string, evt: MouseEvent | KeyboardEvent) {
onChooseItem(item: string, _evt: MouseEvent | KeyboardEvent): void {
this.plugin.askModalCurrentlyOpen = false;
this.extension = '.' + item;
createFolderNote(this.plugin, this.folderPath, this.openFile, this.extension, this.useModal, this.existingNote);
createFolderNote(
this.plugin,
this.folderPath,
this.openFile,
this.extension,
this.useModal,
this.existingNote,
);
this.close();
}
}

View file

@ -1,5 +1,4 @@
import type { App, TFile } from 'obsidian';
import { Modal, Platform } from 'obsidian';
import { Modal, Platform, type App, type TFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { deleteFolderNote } from 'src/functions/folderNoteFunctions';
export default class DeleteConfirmationModal extends Modal {
@ -12,21 +11,25 @@ export default class DeleteConfirmationModal extends Modal {
this.app = app;
this.file = file;
}
onOpen() {
onOpen(): void {
const { contentEl, plugin } = this;
const modalTitle = contentEl.createDiv({ cls: 'fn-modal-title' });
const modalContent = contentEl.createDiv({ cls: 'fn-modal-content' });
modalTitle.createEl('h2', { text: 'Delete folder note' });
// eslint-disable-next-line max-len
modalContent.createEl('p', { text: `Are you sure you want to delete the folder note '${this.file.name}' ?` });
switch (plugin.settings.deleteFilesAction) {
case 'trash':
modalContent.createEl('p', { text: 'It will be moved to your system trash.' });
break;
case 'obsidianTrash':
// eslint-disable-next-line max-len
modalContent.createEl('p', { text: 'It will be moved to your Obsidian trash, which is located in the ".trash" hidden folder in your vault.' });
break;
case 'delete':
modalContent.createEl('p', { text: 'It will be permanently deleted.' }).setCssStyles({ color: 'red' });
modalContent
.createEl('p', { text: 'It will be permanently deleted.' })
.setCssStyles({ color: 'red' });
break;
}
@ -47,7 +50,10 @@ export default class DeleteConfirmationModal extends Modal {
plugin.saveSettings();
});
} else {
const confirmButton = buttonContainer.createEl('button', { text: 'Delete and don\'t ask again', cls: 'mod-destructive' });
const confirmButton = buttonContainer.createEl('button', {
text: 'Delete and don\'t ask again',
cls: 'mod-destructive',
});
confirmButton.addEventListener('click', async () => {
plugin.settings.showDeleteConfirmation = false;
plugin.saveSettings();
@ -56,19 +62,25 @@ export default class DeleteConfirmationModal extends Modal {
});
}
const deleteButton = buttonContainer.createEl('button', { text: 'Delete', cls: 'mod-warning' });
const deleteButton = buttonContainer.createEl('button', {
text: 'Delete',
cls: 'mod-warning',
});
deleteButton.addEventListener('click', async () => {
this.close();
deleteFolderNote(plugin, this.file, false);
});
deleteButton.focus();
const cancelButton = buttonContainer.createEl('button', { text: 'Cancel', cls: 'mod-cancel' });
const cancelButton = buttonContainer.createEl('button', {
text: 'Cancel',
cls: 'mod-cancel',
});
cancelButton.addEventListener('click', async () => {
this.close();
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,5 +1,12 @@
import type { App, TFile, TFolder, TAbstractFile } from 'obsidian';
import { Modal, Setting, Platform } from 'obsidian';
import {
Modal,
Setting,
Platform,
type App,
type TFile,
type TFolder,
type TAbstractFile,
} from 'obsidian';
import type FolderNotesPlugin from '../main';
import { turnIntoFolderNote } from 'src/functions/folderNoteFunctions';
export default class ExistingFolderNoteModal extends Modal {
@ -8,7 +15,13 @@ export default class ExistingFolderNoteModal extends Modal {
file: TFile;
folder: TFolder;
folderNote: TAbstractFile;
constructor(app: App, plugin: FolderNotesPlugin, file: TFile, folder: TFolder, folderNote: TAbstractFile) {
constructor(
app: App,
plugin: FolderNotesPlugin,
file: TFile,
folder: TFolder,
folderNote: TAbstractFile,
) {
super(app);
this.plugin = plugin;
this.app = app;
@ -16,18 +29,22 @@ export default class ExistingFolderNoteModal extends Modal {
this.folder = folder;
this.folderNote = folderNote;
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'A folder note for this folder already exists' });
const setting = new Setting(contentEl);
// eslint-disable-next-line max-len
setting.infoEl.createEl('p', { text: 'Are you sure you want to turn the note into a folder note and rename the existing folder note?' });
setting.infoEl.parentElement?.classList.add('fn-delete-confirmation-modal');
// Create a container for the buttons and the checkbox
// eslint-disable-next-line max-len
const buttonContainer = setting.infoEl.createEl('div', { cls: 'fn-delete-confirmation-modal-buttons' });
if (Platform.isMobileApp) {
const confirmButton = buttonContainer.createEl('button', { text: 'Rename and don\'t ask again' });
const confirmButton = buttonContainer.createEl('button', {
text: 'Rename and don\'t ask again',
});
confirmButton.classList.add('mod-warning', 'fn-confirmation-modal-button');
confirmButton.addEventListener('click', async () => {
this.plugin.settings.showRenameConfirmation = false;
@ -64,7 +81,7 @@ export default class ExistingFolderNoteModal extends Modal {
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,5 +1,4 @@
import type { App, TFolder } from 'obsidian';
import { Modal, Setting } from 'obsidian';
import { Modal, Setting, type App, type TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
export default class FolderNameModal extends Modal {
plugin: FolderNotesPlugin;
@ -11,7 +10,8 @@ export default class FolderNameModal extends Modal {
this.app = app;
this.folder = folder;
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
// close when user presses enter
contentEl.addEventListener('keydown', (e) => {
@ -27,14 +27,25 @@ export default class FolderNameModal extends Modal {
.setValue(this.folder.name.replace(this.plugin.settings.folderNoteType, ''))
.onChange(async (value) => {
if (value.trim() !== '') {
if (!this.app.vault.getAbstractFileByPath(this.folder.path.slice(0, this.folder.path.lastIndexOf('/') + 1) + value.trim())) {
this.plugin.app.fileManager.renameFile(this.folder, this.folder.path.slice(0, this.folder.path.lastIndexOf('/') + 1) + value.trim());
const parentPath = this.folder.path.slice(
0,
this.folder.path.lastIndexOf('/') + 1,
);
const newFolderPath = parentPath + value.trim();
if (
!this.app.vault.getAbstractFileByPath(newFolderPath)
) {
this.plugin.app.fileManager.renameFile(
this.folder,
newFolderPath,
);
}
}
}),
);
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,5 +1,4 @@
import type { App, TFolder } from 'obsidian';
import { Modal } from 'obsidian';
import { Modal, type App, type TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
export default class NewFolderNameModal extends Modal {
plugin: FolderNotesPlugin;
@ -11,7 +10,8 @@ export default class NewFolderNameModal extends Modal {
this.app = app;
this.folder = folder;
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.addEventListener('keydown', (e) => {
@ -37,7 +37,7 @@ export default class NewFolderNameModal extends Modal {
},
});
textarea.addEventListener('focus', function() {
textarea.addEventListener('focus', function () {
this.select();
});
@ -50,23 +50,32 @@ export default class NewFolderNameModal extends Modal {
this.close();
});
const cancelButton = buttonContainer.createEl('button', { text: 'Cancel', cls: 'mod-cancel' });
const cancelButton = buttonContainer.createEl('button', {
text: 'Cancel',
cls: 'mod-cancel',
});
cancelButton.addEventListener('click', () => {
this.close();
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
saveFolderName() {
saveFolderName(): void {
const textarea = this.contentEl.querySelector('textarea');
if (textarea) {
const newName = textarea.value.trim();
if (newName.trim() !== '') {
if (!this.app.vault.getAbstractFileByPath(this.folder.path.slice(0, this.folder.path.lastIndexOf('/') + 1) + newName.trim())) {
this.plugin.app.fileManager.renameFile(this.folder, this.folder.path.slice(0, this.folder.path.lastIndexOf('/') + 1) + newName.trim());
const folderBasePath = this.folder.path.slice(
0,
this.folder.path.lastIndexOf('/') + 1,
);
const newFolderPath = folderBasePath + newName.trim();
if (!this.app.vault.getAbstractFileByPath(newFolderPath)) {
this.plugin.app.fileManager.renameFile(this.folder, newFolderPath);
}
}
}

View file

@ -1,4 +1,7 @@
import { addExcludeFolderListItem, addExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import {
addExcludeFolderListItem,
addExcludedFolder,
} from 'src/ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { addExcludePatternListItem } from 'src/ExcludeFolders/functions/patternFunctions';
import { Setting } from 'obsidian';
@ -8,7 +11,7 @@ import PatternSettings from 'src/ExcludeFolders/modals/PatternSettings';
import WhitelistedFoldersSettings from 'src/ExcludeFolders/modals/WhitelistedFoldersSettings';
// import ExcludedFoldersWhitelist from 'src/ExcludeFolders/modals/WhitelistModal';
export async function renderExcludeFolders(settingsTab: SettingsTab) {
export async function renderExcludeFolders(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
const manageExcluded = new Setting(containerEl)
.setHeading()
@ -25,9 +28,12 @@ export async function renderExcludeFolders(settingsTab: SettingsTab) {
'Use * after the folder name to exclude folders that start with the folder name.',
);
manageExcluded.setDesc(desc3);
// eslint-disable-next-line max-len
manageExcluded.infoEl.appendText('The regexes and wildcards are only for the folder name, not the path.');
manageExcluded.infoEl.createEl('br');
// eslint-disable-next-line max-len
manageExcluded.infoEl.appendText('If you want to switch to a folder path delete the pattern first.');
// eslint-disable-next-line max-len
manageExcluded.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -48,7 +54,11 @@ export async function renderExcludeFolders(settingsTab: SettingsTab) {
cb.setButtonText('Manage');
cb.setCta();
cb.onClick(async () => {
new ExcludedFolderSettings(settingsTab.app, settingsTab.plugin, settingsTab.plugin.settings.excludeFolderDefaultSettings).open();
new ExcludedFolderSettings(
settingsTab.app,
settingsTab.plugin,
settingsTab.plugin.settings.excludeFolderDefaultSettings,
).open();
});
});
@ -58,7 +68,11 @@ export async function renderExcludeFolders(settingsTab: SettingsTab) {
cb.setButtonText('Manage');
cb.setCta();
cb.onClick(async () => {
new PatternSettings(settingsTab.app, settingsTab.plugin, settingsTab.plugin.settings.excludePatternDefaultSettings).open();
new PatternSettings(
settingsTab.app,
settingsTab.plugin,
settingsTab.plugin.settings.excludePatternDefaultSettings,
).open();
});
});
@ -71,18 +85,29 @@ export async function renderExcludeFolders(settingsTab: SettingsTab) {
cb.setClass('add-exclude-folder');
cb.setTooltip('Add excluded folder');
cb.onClick(() => {
const excludedFolder = new ExcludedFolder('', settingsTab.plugin.settings.excludeFolders.length, undefined, settingsTab.plugin);
const excludedFolder = new ExcludedFolder(
'',
settingsTab.plugin.settings.excludeFolders.length,
undefined,
settingsTab.plugin,
);
addExcludeFolderListItem(settingsTab, containerEl, excludedFolder);
addExcludedFolder(settingsTab.plugin, excludedFolder);
settingsTab.display();
});
});
settingsTab.plugin.settings.excludeFolders.filter((folder) => !folder.hideInSettings).sort((a, b) => a.position - b.position).forEach((excludedFolder) => {
if (excludedFolder.string?.trim() !== '' && excludedFolder.path?.trim() === '') {
addExcludePatternListItem(settingsTab, containerEl, excludedFolder);
} else {
addExcludeFolderListItem(settingsTab, containerEl, excludedFolder);
}
});
settingsTab.plugin.settings.excludeFolders
.filter((folder) => !folder.hideInSettings)
.sort((a, b) => a.position - b.position)
.forEach((excludedFolder) => {
if (
excludedFolder.string?.trim() !== '' &&
excludedFolder.path?.trim() === ''
) {
addExcludePatternListItem(settingsTab, containerEl, excludedFolder);
} else {
addExcludeFolderListItem(settingsTab, containerEl, excludedFolder);
}
});
}

View file

@ -1,6 +1,7 @@
/* eslint-disable max-len */
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
export async function renderFileExplorer(settingsTab: SettingsTab) {
export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
@ -34,7 +35,8 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
);
setting2.infoEl.appendText('Requires a restart to take effect');
setting2.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
const setting2AccentColor = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
setting2.infoEl.style.color = setting2AccentColor;
new Setting(containerEl)
.setName('Open folder notes by only clicking directly on the folder name')
@ -65,7 +67,8 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
}),
);
disableSetting.infoEl.appendText('Requires a restart to take effect');
disableSetting.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
const accentColor = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
disableSetting.infoEl.style.color = accentColor;
new Setting(containerEl)
.setName('Use submenus')
@ -91,7 +94,15 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
settingsTab.plugin.settings.frontMatterTitle.explorer = value;
await settingsTab.plugin.saveSettings();
settingsTab.plugin.app.vault.getFiles().forEach((file) => {
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName({ id: '', result: false, path: file.path, pathOnly: false }, false);
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName(
{
id: '',
result: false,
path: file.path,
pathOnly: false,
},
false,
);
});
}),
);

View file

@ -2,7 +2,7 @@ import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
import { createOverviewSettings } from 'src/obsidian-folder-overview/src/settings';
export async function renderFolderOverview(settingsTab: SettingsTab) {
export async function renderFolderOverview(settingsTab: SettingsTab): Promise<void> {
const { plugin } = settingsTab;
const defaultOverviewSettings = plugin.settings.defaultOverview;
const containerEl = settingsTab.settingsPage;
@ -10,6 +10,7 @@ export async function renderFolderOverview(settingsTab: SettingsTab) {
containerEl.createEl('h3', { text: 'Global settings' });
new Setting(containerEl)
.setName('Auto-update links without opening the overview')
// eslint-disable-next-line max-len
.setDesc('If enabled, the links that appear in the graph view will be updated even when you don\'t have the overview open somewhere.')
.addToggle((toggle) =>
toggle
@ -35,5 +36,15 @@ export async function renderFolderOverview(settingsTab: SettingsTab) {
span.setAttr('style', `color: ${accentColor};`);
pEl.appendChild(span);
createOverviewSettings(containerEl, defaultOverviewSettings, plugin, plugin.settings.defaultOverview, settingsTab.display, undefined, undefined, undefined, settingsTab);
createOverviewSettings(
containerEl,
defaultOverviewSettings,
plugin,
plugin.settings.defaultOverview,
settingsTab.display,
undefined,
undefined,
undefined,
settingsTab,
);
}

View file

@ -1,3 +1,4 @@
/* eslint-disable max-len */
import { Setting, Platform } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
import { ListComponent } from '../functions/ListComponent';
@ -11,7 +12,8 @@ import RenameFolderNotesModal from './modals/RenameFns';
let debounceTimer: NodeJS.Timeout;
export async function renderGeneral(settingsTab: SettingsTab) {
// eslint-disable-next-line complexity
export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
const nameSetting = new Setting(containerEl)
.setName('Folder note name template')
@ -25,6 +27,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
await settingsTab.plugin.saveSettings();
clearTimeout(debounceTimer);
const FOLDER_NOTE_NAME_DEBOUNCE_MS = 2000;
debounceTimer = setTimeout(() => {
if (!value.includes('{{folder_name}}')) {
if (!settingsTab.showFolderNameInTabTitleSetting) {
@ -37,7 +40,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.showFolderNameInTabTitleSetting = false;
}
}
}, 2000);
}, FOLDER_NOTE_NAME_DEBOUNCE_MS);
}),
)
.addButton((button) =>
@ -91,13 +94,24 @@ export async function renderGeneral(settingsTab: SettingsTab) {
}
});
if (!settingsTab.plugin.settings.supportedFileTypes.includes(settingsTab.plugin.settings.folderNoteType.replace('.', '')) && settingsTab.plugin.settings.folderNoteType !== '.ask') {
if (
!settingsTab.plugin.settings.supportedFileTypes.includes(
settingsTab.plugin.settings.folderNoteType.replace('.', ''),
) &&
settingsTab.plugin.settings.folderNoteType !== '.ask'
) {
settingsTab.plugin.settings.folderNoteType = '.md';
settingsTab.plugin.saveSettings();
}
let defaultType = settingsTab.plugin.settings.folderNoteType.startsWith('.') ? settingsTab.plugin.settings.folderNoteType : '.' + settingsTab.plugin.settings.folderNoteType;
if (!settingsTab.plugin.settings.supportedFileTypes.includes(defaultType.replace('.', ''))) {
let defaultType = settingsTab.plugin.settings.folderNoteType.startsWith('.')
? settingsTab.plugin.settings.folderNoteType
: '.' + settingsTab.plugin.settings.folderNoteType;
if (
!settingsTab.plugin.settings.supportedFileTypes.includes(
defaultType.replace('.', ''),
)
) {
defaultType = '.ask';
settingsTab.plugin.settings.folderNoteType = defaultType;
}
@ -118,14 +132,22 @@ export async function renderGeneral(settingsTab: SettingsTab) {
'Specify which file types are allowed as folder notes. Applies to both new and existing folders. Adding many types may affect performance.',
);
setting0.setDesc(desc0);
const list = new ListComponent(setting0.settingEl, settingsTab.plugin.settings.supportedFileTypes || [], ['md', 'canvas']);
const list = new ListComponent(
setting0.settingEl,
settingsTab.plugin.settings.supportedFileTypes || [],
['md', 'canvas'],
);
list.on('update', async (values: string[]) => {
settingsTab.plugin.settings.supportedFileTypes = values;
await settingsTab.plugin.saveSettings();
settingsTab.display();
});
if (!settingsTab.plugin.settings.supportedFileTypes.includes('md') || !settingsTab.plugin.settings.supportedFileTypes.includes('canvas') || !settingsTab.plugin.settings.supportedFileTypes.includes('excalidraw')) {
if (
!settingsTab.plugin.settings.supportedFileTypes.includes('md') ||
!settingsTab.plugin.settings.supportedFileTypes.includes('canvas') ||
!settingsTab.plugin.settings.supportedFileTypes.includes('excalidraw')
) {
setting0.addDropdown((dropdown) => {
const options = [
{ value: 'md', label: 'Markdown' },
@ -144,7 +166,12 @@ export async function renderGeneral(settingsTab: SettingsTab) {
dropdown.setValue('+');
dropdown.onChange(async (value) => {
if (value === 'custom') {
return new AddSupportedFileModal(settingsTab.app, settingsTab.plugin, settingsTab, list as ListComponent).open();
return new AddSupportedFileModal(
settingsTab.app,
settingsTab.plugin,
settingsTab,
list as ListComponent,
).open();
}
await list.addValue(value.toLowerCase());
settingsTab.display();
@ -157,7 +184,12 @@ export async function renderGeneral(settingsTab: SettingsTab) {
.setButtonText('Add custom file type')
.setCta()
.onClick(async () => {
new AddSupportedFileModal(settingsTab.app, settingsTab.plugin, settingsTab, list as ListComponent).open();
new AddSupportedFileModal(
settingsTab.app,
settingsTab.plugin,
settingsTab,
list as ListComponent,
).open();
}),
);
}
@ -169,7 +201,11 @@ export async function renderGeneral(settingsTab: SettingsTab) {
.addSearch((cb) => {
new TemplateSuggest(cb.inputEl, settingsTab.plugin);
cb.setPlaceholder('Template path');
cb.setValue(settingsTab.plugin.app.vault.getAbstractFileByPath(settingsTab.plugin.settings.templatePath)?.name.replace('.md', '') || '');
const templateFile = settingsTab.plugin.app.vault.getAbstractFileByPath(
settingsTab.plugin.settings.templatePath,
);
const templateName = templateFile?.name.replace('.md', '') || '';
cb.setValue(templateName);
cb.onChange(async (value) => {
if (value.trim() === '') {
settingsTab.plugin.settings.templatePath = '';
@ -464,16 +500,26 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.frontMatterTitle.enabled = value;
await settingsTab.plugin.saveSettings();
if (value) {
settingsTab.plugin.fmtpHandler = new FrontMatterTitlePluginHandler(settingsTab.plugin);
settingsTab.plugin.fmtpHandler =
new FrontMatterTitlePluginHandler(settingsTab.plugin);
} else {
if (settingsTab.plugin.fmtpHandler) {
settingsTab.plugin.updateAllBreadcrumbs(true);
}
settingsTab.plugin.app.vault.getFiles().forEach((file) => {
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName({ id: '', result: false, path: file.path, pathOnly: false }, false);
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName(
{
id: '',
result: false,
path: file.path,
pathOnly: false,
},
false,
);
});
settingsTab.plugin.fmtpHandler?.deleteEvent();
settingsTab.plugin.fmtpHandler = new FrontMatterTitlePluginHandler(settingsTab.plugin);
settingsTab.plugin.fmtpHandler =
new FrontMatterTitlePluginHandler(settingsTab.plugin);
}
settingsTab.display();
}),

View file

@ -1,6 +1,7 @@
/* eslint-disable max-len */
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
export async function renderPath(settingsTab: SettingsTab) {
export async function renderPath(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
.setName('Open folder note through path')

View file

@ -1,5 +1,7 @@
import type { App, MarkdownPostProcessorContext } from 'obsidian';
import { Notice, PluginSettingTab, TFile, TFolder } from 'obsidian';
import {
Notice, PluginSettingTab, TFile,
TFolder, type App, type MarkdownPostProcessorContext,
} from 'obsidian';
import type FolderNotesPlugin from '../main';
import type { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import type { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
@ -236,7 +238,8 @@ export class SettingsTab extends PluginSettingTab {
id: 'path',
},
};
renderSettingsPage(tabId: string) {
renderSettingsPage(tabId: string): void {
this.settingsPage.empty();
switch (tabId.toLocaleLowerCase()) {
case this.TABS.GENERAL.id:
@ -257,7 +260,17 @@ export class SettingsTab extends PluginSettingTab {
}
}
display(contentEl?: HTMLElement, yaml?: defaultOverviewSettings, plugin?: FolderNotesPlugin, defaultSettings?: boolean, display?: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: this) {
display(
contentEl?: HTMLElement,
yaml?: defaultOverviewSettings,
plugin?: FolderNotesPlugin,
defaultSettings?: boolean,
display?: CallableFunction,
el?: HTMLElement,
ctx?: MarkdownPostProcessorContext,
file?: TFile | null,
settingsTab?: this,
): void {
plugin = this?.plugin ?? plugin;
if (plugin) {
plugin.settingsOpened = true;
@ -274,13 +287,17 @@ export class SettingsTab extends PluginSettingTab {
for (const [tabId, tabInfo] of Object.entries(settingsTab.TABS)) {
const tabEl = tabBar.createEl('div', { cls: 'fn-settings-tab' });
tabEl.createEl('div', { cls: 'fn-settings-tab-name', text: tabInfo.name });
if (plugin && plugin.settings.settingsTab.toLocaleLowerCase() === tabId.toLocaleLowerCase()) {
if (
plugin &&
plugin.settings.settingsTab.toLocaleLowerCase() ===
tabId.toLocaleLowerCase()
) {
tabEl.addClass('fn-settings-tab-active');
}
tabEl.addEventListener('click', () => {
// @ts-ignore
for (const tabEl of tabBar.children) {
tabEl.removeClass('fn-settings-tab-active');
// @ts-expect-error: tabBar.children may not have removeClass method, but we know it works in this context
for (const child of tabBar.children) {
child.removeClass('fn-settings-tab-active');
if (!plugin) { return; }
plugin.settings.settingsTab = tabId.toLocaleLowerCase();
plugin.saveSettings();
@ -300,23 +317,31 @@ export class SettingsTab extends PluginSettingTab {
}
}
renameFolderNotes() {
renameFolderNotes(): void {
new Notice('Starting to update folder notes...');
const oldTemplate = this.plugin.settings.oldFolderNoteName ?? '{{folder_name}}';
for (const folder of this.app.vault.getAllLoadedFiles()) {
if (folder instanceof TFolder) {
const folderNote = getFolderNote(this.plugin, folder.path, undefined, undefined, oldTemplate);
const folderNote = getFolderNote(
this.plugin,
folder.path,
undefined,
undefined,
oldTemplate,
);
if (!(folderNote instanceof TFile)) { continue; }
const folderName = extractFolderName(oldTemplate, folderNote.basename) ?? '';
const newFolderNoteName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
const newFolderNoteName = this.plugin.settings.folderNoteName
.replace('{{folder_name}}', folderName);
let newPath = '';
if (this.plugin.settings.storageLocation === 'parentFolder') {
if (getFolderPathFromString(folder.path).trim() === '/') {
newPath = `${newFolderNoteName}.${folderNote.extension}`;
} else {
// eslint-disable-next-line max-len
newPath = `${folderNote.parent?.path}/${newFolderNoteName}.${folderNote.extension}`;
}
} else if (this.plugin.settings.storageLocation === 'insideFolder') {
@ -332,7 +357,7 @@ export class SettingsTab extends PluginSettingTab {
new Notice('Finished updating folder notes');
}
switchStorageLocation(oldMethod: string) {
switchStorageLocation(oldMethod: string): void {
new Notice('Starting to switch storage location...');
this.app.vault.getAllLoadedFiles().forEach((file) => {
if (file instanceof TFolder) {

View file

@ -5,10 +5,16 @@ export default class BackupWarningModal extends Modal {
plugin: FolderNotesPlugin;
title: string;
desc: string;
callback: (...args: any[]) => void;
args: any[];
callback: (...args: unknown[]) => void;
args: unknown[];
constructor(plugin: FolderNotesPlugin, title: string, description: string, callback: (...args: any[]) => void, args: any[] = []) {
constructor(
plugin: FolderNotesPlugin,
title: string,
description: string,
callback: (...args: unknown[]) => void,
args: unknown[] = [],
) {
super(plugin.app);
this.plugin = plugin;
this.title = title;
@ -17,7 +23,7 @@ export default class BackupWarningModal extends Modal {
this.desc = description;
}
onOpen() {
onOpen(): void {
this.modalEl.addClass('fn-backup-warning-modal');
const { contentEl } = this;
@ -25,8 +31,7 @@ export default class BackupWarningModal extends Modal {
contentEl.createEl('p', { text: this.desc });
this.insertCustomHtml();
// eslint-disable-next-line max-len
contentEl.createEl('p', { text: 'Make sure to backup your vault before using this feature.' }).style.color = '#fb464c';
const buttonContainer = contentEl.createDiv({ cls: 'fn-modal-button-container' });
@ -45,11 +50,7 @@ export default class BackupWarningModal extends Modal {
});
}
insertCustomHtml(): void {
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,5 +1,4 @@
import type { App, ButtonComponent } from 'obsidian';
import { Modal, Setting, TFolder, Notice } from 'obsidian';
import { Modal, Setting, TFolder, Notice, type App, type ButtonComponent } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import { createFolderNote, getFolderNote } from 'src/functions/folderNoteFunctions';
import { getTemplatePlugins } from 'src/template';
@ -15,7 +14,8 @@ export default class ConfirmationModal extends Modal {
this.app = app;
this.extension = plugin.settings.folderNoteType;
}
onOpen() {
onOpen(): void {
this.modalEl.addClass('fn-confirmation-modal');
let templateFolderPath: string;
const { templateFolder, templaterPlugin } = getTemplatePlugins(this.plugin.app);
@ -23,19 +23,29 @@ export default class ConfirmationModal extends Modal {
templateFolderPath = '';
}
if (templaterPlugin) {
templateFolderPath = templaterPlugin.plugin?.settings?.templates_folder as string;
} else {
templateFolderPath = (
templaterPlugin as unknown as {
plugin?: { settings?: { templates_folder?: string } }
}
).plugin?.settings?.templates_folder as string;
} else if (templateFolder) {
templateFolderPath = templateFolder;
}
const { contentEl } = this;
contentEl.createEl('h2', { text: 'Create folder note for every folder' });
const setting = new Setting(contentEl);
// eslint-disable-next-line max-len
setting.infoEl.createEl('p', { text: 'Make sure to backup your vault before using this feature.' }).style.color = '#fb464c';
// eslint-disable-next-line max-len
setting.infoEl.createEl('p', { text: 'This feature will create a folder note for every folder in your vault.' });
// eslint-disable-next-line max-len
setting.infoEl.createEl('p', { text: 'Every folder that already has a folder note will be ignored.' });
setting.infoEl.createEl('p', { text: 'Every excluded folder will be ignored.' });
if (!this.plugin.settings.templatePath || this.plugin.settings.templatePath?.trim() === '') {
if (
!this.plugin.settings.templatePath ||
this.plugin.settings.templatePath?.trim() === ''
) {
new Setting(contentEl)
.setName('Folder note file extension')
.setDesc('Choose the file extension for the folder notes.')
@ -56,17 +66,24 @@ export default class ConfirmationModal extends Modal {
cb.setCta();
cb.buttonEl.focus();
cb.onClick(async () => {
if (this.plugin.settings.templatePath && this.plugin.settings.templatePath.trim() !== '') {
if (
this.plugin.settings.templatePath &&
this.plugin.settings.templatePath.trim() !== ''
) {
this.extension = '.' + this.plugin.settings.templatePath.split('.').pop();
}
if (this.extension === '.ask') {
return new Notice('Please choose a file extension');
}
this.close();
const folders = this.app.vault.getAllLoadedFiles().filter((file) => file.parent instanceof TFolder);
const folders = this.app.vault
.getAllLoadedFiles()
.filter((file) => file.parent instanceof TFolder);
for (const folder of folders) {
if (folder instanceof TFolder) {
const excludedFolder = getExcludedFolder(this.plugin, folder.path, true);
const excludedFolder = getExcludedFolder(
this.plugin, folder.path, true,
);
if (excludedFolder) continue;
if (folder.path === templateFolderPath) continue;
const folderNote = getFolderNote(this.plugin, folder.path);
@ -83,7 +100,8 @@ export default class ConfirmationModal extends Modal {
});
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -3,7 +3,13 @@ import type FolderNotesPlugin from 'src/main';
import { Setting } from 'obsidian';
export default class RenameFolderNotesModal extends BackupWarningModal {
constructor(plugin: FolderNotesPlugin, title: string, description: string, callback: (...args: any[]) => void, args: any[] = []) {
constructor(
plugin: FolderNotesPlugin,
title: string,
description: string,
callback: (...args: unknown[]) => void,
args: unknown[] = [],
) {
super(plugin, title, description, callback, args);
}
@ -11,6 +17,7 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
const { contentEl } = this;
new Setting(contentEl)
.setName('Old Folder Note Name')
// eslint-disable-next-line max-len
.setDesc('Every folder note that matches this name will be renamed to the new folder note name.')
.addText((text) => text
.setPlaceholder('Enter the old folder note name')
@ -22,6 +29,7 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
new Setting(contentEl)
.setName('New Folder Note Name')
// eslint-disable-next-line max-len
.setDesc('Every folder note that matches the old folder note name will be renamed to this name.')
.addText((text) => text
.setPlaceholder('Enter the new folder note name')

View file

@ -1,7 +1,6 @@
// 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 { TFolder } from 'obsidian';
import { TFolder, type TAbstractFile } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import type FolderNotesPlugin from '../main';
export enum FileSuggestMode {
@ -36,13 +35,18 @@ export class FolderSuggest extends TextInputSuggest<TFolder> {
if (this.folder) {
files = this.folder.children;
} else {
files = this.plugin.app.vault.getAllLoadedFiles().slice(0,100);
const MAX_FILE_SUGGESTIONS = 100;
files = this.plugin.app.vault.getAllLoadedFiles().slice(0, MAX_FILE_SUGGESTIONS);
}
files.forEach((folder: TAbstractFile) => {
if (
folder instanceof TFolder &&
folder.path.toLowerCase().contains(lower_input_str) &&
(!this.plugin.settings.excludeFolders.find((f) => f.path === folder.path) || this.whitelistSuggester)
folder.path.toLowerCase().contains(lower_input_str) &&
(
!this.plugin.settings.excludeFolders.find(
(f) => f.path === folder.path,
) || this.whitelistSuggester
)
) {
folders.push(folder);
}

View file

@ -1,10 +1,7 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import type { ISuggestOwner } from 'obsidian';
import { Scope } from 'obsidian';
import type { Instance as PopperInstance } from '@popperjs/core';
import { createPopper } from '@popperjs/core';
import { Scope, type ISuggestOwner } from 'obsidian';
import { createPopper, type Instance as PopperInstance } from '@popperjs/core';
import type FolderNotesPlugin from 'src/main';
const wrapAround = (value: number, size: number): number => {
@ -73,7 +70,7 @@ class Suggest<T> {
this.setSelectedItem(item, false);
}
setSuggestions(values: T[]) {
setSuggestions(values: T[]): void {
this.containerEl.empty();
const suggestionEls: HTMLDivElement[] = [];
@ -88,14 +85,14 @@ class Suggest<T> {
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent) {
useSelectedItem(event: MouseEvent | KeyboardEvent): void {
const currentValue = this.values[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
setSelectedItem(selectedIndex: number, scrollIntoView: boolean): void {
const normalizedIndex = wrapAround(
selectedIndex,
this.suggestions.length,
@ -157,7 +154,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
if (suggestions.length > 0) {
this.suggest.setSuggestions(suggestions);
// @ts-ignore
// @ts-expect-error App may not exist
this.open(app.dom.appContainerEl, this.inputEl);
} else {
this.close();
@ -175,7 +172,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
{
name: 'sameWidth',
enabled: true,
fn: ({ state, instance }) => {
fn: ({ state, instance }): void => {
// Note: positioning needs to be calculated twice -
// first pass - positioning it according to the width of the popper
// second pass - position it with the width bound to the reference element

View file

@ -1,5 +1,4 @@
import type { TAbstractFile } from 'obsidian';
import { TFile, TFolder, Vault, AbstractInputSuggest } from 'obsidian';
import { TFile, TFolder, Vault, AbstractInputSuggest, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { getTemplatePlugins } from 'src/template';
export enum FileSuggestMode {
@ -39,12 +38,21 @@ export class TemplateSuggest extends AbstractInputSuggest<TFile> {
let folder: TFolder | TAbstractFile | null = null;
if (templaterPlugin) {
folder = this.plugin.app.vault.getAbstractFileByPath(
templaterPlugin.plugin?.settings?.templates_folder as string,
(templaterPlugin as unknown as {
plugin?: { settings?: { templates_folder?: string } }
}).plugin?.settings?.templates_folder as string,
);
if (!(folder instanceof TFolder)) {
return [{ path: '', name: 'You need to set the Templates folder in the Templater settings first.' } as TFile];
return [
{
path: '',
name:
// eslint-disable-next-line max-len
'You need to set the Templates folder in the Templater settings first.',
} as TFile,
];
}
} else {
} else if (templateFolder) {
folder = this.plugin.app.vault.getAbstractFileByPath(templateFolder) as TFolder;
}

View file

@ -1,12 +1,41 @@
import { TFile, WorkspaceLeaf, type App } from 'obsidian';
import type FolderNotesPlugin from './main';
interface TemplatesPlugin {
enabled: boolean;
instance: {
options: {
folder: string;
};
insertTemplate: (templateFile: TFile) => Promise<void>;
};
}
interface TemplaterPlugin {
settings?: {
empty_file_template?: string;
template_folder?: string;
};
templater?: {
write_template_to_file: (templateFile: TFile, targetFile: TFile) => Promise<void>;
};
}
interface TemplatePluginReturn {
templatesPlugin: TemplatesPlugin | null;
templatesEnabled: boolean;
templaterPlugin: TemplaterPlugin['templater'] | null;
templaterEnabled: boolean;
templaterEmptyFileTemplate?: string;
templateFolder?: string;
}
export async function applyTemplate(
plugin: FolderNotesPlugin,
file: TFile,
leaf?: WorkspaceLeaf | null,
templatePath?: string,
) {
): Promise<void> {
const fileContent = await plugin.app.vault.read(file).catch((err) => {
console.error(`Error reading file ${file.path}:`, err);
});
@ -25,14 +54,15 @@ export async function applyTemplate(
templaterPlugin,
} = getTemplatePlugins(plugin.app);
const templateContent = await plugin.app.vault.read(templateFile);
// eslint-disable-next-line max-len
if (templateContent.includes('==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==')) {
return;
}
// Prioritize Templater if both plugins are enabled
if (templaterEnabled) {
if (templaterEnabled && templaterPlugin) {
return await templaterPlugin.write_template_to_file(templateFile, file);
} else if (templatesEnabled) {
} else if (templatesEnabled && templatesPlugin) {
if (leaf instanceof WorkspaceLeaf) {
await leaf.openFile(file);
}
@ -47,23 +77,37 @@ export async function applyTemplate(
}
}
export function getTemplatePlugins(app: App) {
const templatesPlugin = (app as any).internalPlugins.plugins.templates;
const templatesEnabled = templatesPlugin.enabled;
const templaterPlugin = (app as any).plugins.plugins['templater-obsidian'];
const templaterEnabled = (app as any).plugins.enabledPlugins.has('templater-obsidian');
export function getTemplatePlugins(app: App): TemplatePluginReturn {
const appAsUnknown = app as unknown as {
internalPlugins: {
plugins: {
templates: TemplatesPlugin;
};
};
plugins: {
plugins: {
'templater-obsidian': TemplaterPlugin;
};
enabledPlugins: Set<string>;
};
};
const templatesPlugin = appAsUnknown.internalPlugins.plugins.templates;
const templatesEnabled = templatesPlugin?.enabled ?? false;
const templaterPlugin = appAsUnknown.plugins.plugins['templater-obsidian'];
const templaterEnabled = appAsUnknown.plugins.enabledPlugins.has('templater-obsidian');
const templaterEmptyFileTemplate =
templaterPlugin && templaterPlugin.settings?.empty_file_template;
const templateFolder = templatesEnabled
? templatesPlugin.instance.options.folder
: templaterPlugin?.settings.template_folder;
: templaterPlugin?.settings?.template_folder;
return {
templatesPlugin,
templatesPlugin: templatesPlugin || null,
templatesEnabled,
templaterPlugin: templaterPlugin?.templater,
templaterPlugin: templaterPlugin?.templater || null,
templaterEnabled,
templaterEmptyFileTemplate,
templateFolder,