Merge pull request #273 from LostPaul/code-style-changes

Code style changes
This commit is contained in:
Lost Paul 2025-08-11 15:13:07 +02:00 committed by GitHub
commit 68d9b0b8d5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 3908 additions and 2264 deletions

View file

@ -1,7 +0,0 @@
npm node_modules
build
*.js
*.json
*.md
*.css
LICENSE

150
.eslintrc
View file

@ -1,150 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "none"
}
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off",
"brace-style": [
"error",
"1tbs",
{
"allowSingleLine": true
}
],
"consistent-return": "off",
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/ban-types": [
"error",
{
"types": {
"Function": false
},
"extendDefaults": true
}
],
"no-mixed-spaces-and-tabs": "error",
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"arrow-parens": [
"error",
"always"
],
"eol-last": [
"error",
"always"
],
"func-call-spacing": [
"error",
"never"
],
"comma-dangle": [
"error",
"always-multiline"
],
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "off",
"semi": [
"error",
"always"
],
"semi-style": [
"error",
"last"
],
"space-in-parens": [
"error",
"never"
],
"block-spacing": [
"error",
"always"
],
"object-curly-spacing": [
"error",
"always"
],
"eqeqeq": [
"error",
"always",
{
"null": "ignore"
}
],
"spaced-comment": [
"error",
"always",
{
"markers": [
"!"
]
}
],
"yoda": "error",
"prefer-destructuring": [
"error",
{
"object": false,
"array": false
}
],
"operator-assignment": [
"error",
"always"
],
"no-useless-computed-key": "error",
"no-unneeded-ternary": [
"error",
{
"defaultAssignment": false
}
],
"no-invalid-regexp": "error",
"no-constant-condition": [
"error",
{
"checkLoops": false
}
],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"dot-notation": "error",
"no-useless-escape": [
"error"
]
}
}

163
eslint.config.mts Normal file
View file

@ -0,0 +1,163 @@
import tseslint from "typescript-eslint";
export default [
{
files: ["**/*.ts"],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
sourceType: "module"
}
},
ignores: [
"**/node_modules/**",
"**/dist/**",
],
plugins: {
"@typescript-eslint": tseslint.plugin
},
rules: {
"no-unused-vars": "off",
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"no-mixed-spaces-and-tabs": "error",
"indent": [
"error",
"tab",
{
"SwitchCase": 1
}
],
"arrow-parens": [
"error",
"always"
],
"eol-last": [
"error",
"always"
],
"func-call-spacing": [
"error",
"never"
],
"comma-dangle": [
"error",
"always-multiline"
],
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "off",
"semi": [
"error",
"always"
],
"semi-style": [
"error",
"last"
],
"space-in-parens": [
"error",
"never"
],
"block-spacing": [
"error",
"always"
],
"object-curly-spacing": [
"error",
"always"
],
"eqeqeq": [
"error",
"always",
{
"null": "ignore"
}
],
"spaced-comment": [
"error",
"always",
{
"markers": [
"!"
]
}
],
"yoda": "error",
"prefer-destructuring": [
"error",
{
"object": true,
"array": false
}
],
"operator-assignment": [
"error",
"always"
],
"no-useless-computed-key": "error",
"no-unneeded-ternary": [
"error",
{
"defaultAssignment": false
}
],
"no-invalid-regexp": "error",
"no-constant-condition": [
"error",
{
"checkLoops": false
}
],
"no-duplicate-imports": "error",
"no-extra-semi": "error",
"dot-notation": "error",
"no-useless-escape": "error",
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/consistent-type-definitions': [
'error',
'interface'],
'@typescript-eslint/explicit-function-return-type': 'warn',
'@typescript-eslint/ban-ts-comment': 'warn',
'array-bracket-spacing': [
'error', 'never'],
'linebreak-style': [
'error',
'unix'
],
'no-nested-ternary': 'error',
'no-shadow': 'error',
'no-return-await': 'error',
'no-else-return': 'error',
'no-empty-function': 'warn',
'complexity': [
'warn',
15
],
'max-len': [
'warn', {
code: 100,
ignoreComments: true,
}
],
'no-inline-comments': 'warn',
'no-magic-numbers': [
'warn', {
ignore: [0, 1],
enforceConst: true,
ignoreTypeIndexes: true,
ignoreEnums: true,
ignoreNumericLiteralTypes: true,
ignoreClassFieldInitialValues: true,
}
],
}
}
];

2115
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -16,16 +16,21 @@
"author": "Lost Paul",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@eslint/js": "^9.32.0",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"eslint": "^9.32.0",
"front-matter-plugin-api-provider": "^0.1.4-alpha",
"globals": "^16.3.0",
"jiti": "^2.5.1",
"obsidian": "latest",
"obsidian-typings": "^2.2.0",
"tslib": "2.4.0",
"typescript": "4.7.4"
"typescript": "^4.8.4",
"typescript-eslint": "^8.38.0"
},
"dependencies": {
"@popperjs/core": "^2.11.6"

View file

@ -1,10 +1,35 @@
import { App, TFolder, Menu, TAbstractFile, Notice, TFile, Editor, MarkdownView, Platform } from 'obsidian';
import FolderNotesPlugin from './main';
import { getFolderNote, createFolderNote, deleteFolderNote, turnIntoFolderNote, openFolderNote, extractFolderName, detachFolderNote } from './functions/folderNoteFunctions';
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 { 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';
@ -15,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',
@ -51,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);
@ -108,7 +135,7 @@ export class Commands {
// Everything is fine and not checking, let's create the folder note.
const ext = '.' + fileType;
const path = folder.path;
const { path } = folder;
createFolderNote(this.plugin, path, true, ext, false);
},
});
@ -179,13 +206,14 @@ export class Commands {
name: 'Create folder note from selection',
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownView) => {
const text = editor.getSelection().trim();
const file = view.file;
const { file } = view;
if (!(file instanceof TFile)) return false;
if (text && text.trim() !== '') {
if (checking) { return true; }
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;
}
@ -201,18 +229,23 @@ export class Commands {
if (folder instanceof TFolder) {
new Notice('Folder note already exists');
return false;
} else {
this.plugin.app.vault.createFolder(text);
createFolderNote(this.plugin, text, false);
}
this.plugin.app.vault.createFolder(text);
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;
}
@ -220,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 {
@ -233,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;
@ -447,11 +512,12 @@ export class Commands {
item.setTitle('Create folder note')
.setIcon('edit')
.onClick(() => {
const file = view.file;
const { file } = view;
if (!(file instanceof TFile)) return;
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;
}
@ -460,24 +526,35 @@ 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) {
return new Notice('Folder note already exists');
} else {
this.plugin.app.vault.createFolder(text);
createFolderNote(this.plugin, text, false);
}
this.plugin.app.vault.createFolder(text);
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

@ -1,4 +1,4 @@
import FolderNotesPlugin from '../main';
import type FolderNotesPlugin from '../main';
export class ExcludedFolder {
type: string;
id: string;
@ -25,6 +25,7 @@ export class ExcludedFolder {
this.disableFolderNote = plugin.settings.excludeFolderDefaultSettings.disableFolderNote;
this.enableCollapsing = plugin.settings.excludeFolderDefaultSettings.enableCollapsing;
this.position = position;
// eslint-disable-next-line max-len
this.excludeFromFolderOverview = plugin.settings.excludeFolderDefaultSettings.excludeFromFolderOverview;
this.string = '';
this.hideInSettings = false;

View file

@ -1,4 +1,4 @@
import FolderNotesPlugin from '../main';
import type FolderNotesPlugin from '../main';
export class ExcludePattern {
type: string;
id: string;
@ -15,7 +15,12 @@ export class ExcludePattern {
detached: boolean;
detachedFilePath?: string;
showFolderNote: boolean;
constructor(pattern: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {
constructor(
pattern: string,
position: number,
id: string | undefined,
plugin: FolderNotesPlugin,
) {
this.type = 'pattern';
this.id = id || crypto.randomUUID();
this.string = pattern;
@ -25,6 +30,7 @@ export class ExcludePattern {
this.disableAutoCreate = plugin.settings.excludePatternDefaultSettings.disableAutoCreate;
this.disableFolderNote = plugin.settings.excludePatternDefaultSettings.disableFolderNote;
this.enableCollapsing = plugin.settings.excludePatternDefaultSettings.enableCollapsing;
// eslint-disable-next-line max-len
this.excludeFromFolderOverview = plugin.settings.excludePatternDefaultSettings.excludeFromFolderOverview;
this.path = '';
this.hideInSettings = false;

View file

@ -1,4 +1,4 @@
import FolderNotesPlugin from '../main';
import type FolderNotesPlugin from '../main';
export class WhitelistedFolder {
type: string;
id: string;

View file

@ -1,4 +1,4 @@
import FolderNotesPlugin from '../main';
import type FolderNotesPlugin from '../main';
export class WhitelistedPattern {
type: string;
id: string;
@ -13,7 +13,12 @@ export class WhitelistedPattern {
showInFolderOverview: boolean;
hideInFileExplorer: boolean;
hideInSettings: boolean;
constructor(pattern: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {
constructor(
pattern: string,
position: number,
id: string | undefined,
plugin: FolderNotesPlugin,
) {
this.type = 'pattern';
this.id = id || crypto.randomUUID();
this.subFolders = plugin.settings.excludePatternDefaultSettings.subFolders;

View file

@ -1,29 +1,39 @@
import FolderNotesPlugin from '../../main';
import type FolderNotesPlugin from '../../main';
import { getFolderNameFromPathString, getFolderPathFromString } from '../../functions/utils';
import { ExcludedFolder } from '../ExcludeFolder';
import type { ExcludedFolder } from '../ExcludeFolder';
import { ExcludePattern } from '../ExcludePattern';
import { Platform, Setting } from 'obsidian';
import { FolderSuggest } from '../../suggesters/FolderSuggester';
import { SettingsTab } from '../../settings/SettingsTab';
import type { SettingsTab } from '../../settings/SettingsTab';
import ExcludedFolderSettings from '../modals/ExcludeFolderSettings';
import { updatePattern, getExcludedFoldersByPattern, addExcludePatternListItem } from './patternFunctions';
import {
updatePattern,
getExcludedFoldersByPattern,
addExcludePatternListItem,
} from './patternFunctions';
import { getWhitelistedFolder } from './whitelistFolderFunctions';
import { WhitelistedFolder } from '../WhitelistFolder';
import { WhitelistedPattern } from '../WhitelistPattern';
import type { WhitelistedFolder } from '../WhitelistFolder';
import type { WhitelistedPattern } from '../WhitelistPattern';
export function getExcludedFolder(plugin: FolderNotesPlugin, path: string, includeDetached: boolean, pathOnly?: boolean, ignoreWhitelist?: boolean) {
let excludedFolder = {} as ExcludedFolder | ExcludePattern | undefined;
const whitelistedFolder = getWhitelistedFolder(plugin, path) as WhitelistedFolder | WhitelistedPattern | undefined;
function combineExcluded(
plugin: FolderNotesPlugin,
path: string,
includeDetached: boolean,
pathOnly?: boolean,
): Array<ExcludedFolder | ExcludePattern> {
const folderName = getFolderNameFromPathString(path);
let matchedPatterns = getExcludedFoldersByPattern(plugin, folderName);
const excludedFolders = getExcludedFoldersByPath(plugin, path);
if (pathOnly) { matchedPatterns = []; }
let combinedExcludedFolders = [...matchedPatterns, ...excludedFolders];
if (!includeDetached) {
combinedExcludedFolders = combinedExcludedFolders.filter((f) => !f.detached);
}
const matchedPatterns = pathOnly ? [] : getExcludedFoldersByPattern(plugin, folderName);
const excludedByPath = getExcludedFoldersByPath(plugin, path);
let combined = [...matchedPatterns, ...excludedByPath];
if (!includeDetached) combined = combined.filter((f) => !f.detached);
return combined;
}
function aggregateFlags(
combinedExcludedFolders: Array<ExcludedFolder | ExcludePattern>,
): Partial<ExcludedFolder> | undefined {
if (combinedExcludedFolders.length === 0) return undefined;
const result: Partial<ExcludedFolder> = {};
const propertiesToCopy: (keyof ExcludedFolder)[] = [
'disableAutoCreate',
'disableFolderNote',
@ -35,32 +45,46 @@ export function getExcludedFolder(plugin: FolderNotesPlugin, path: string, inclu
'id',
'showFolderNote',
];
if (combinedExcludedFolders.length > 0) {
for (const matchedFolder of combinedExcludedFolders) {
propertiesToCopy.forEach((property) => {
if (matchedFolder[property] === true) {
(excludedFolder as any)[property] = true;
} else if (!matchedFolder[property]) {
(excludedFolder as any)[property] = false;
}
});
for (const matchedFolder of combinedExcludedFolders) {
for (const property of propertiesToCopy) {
const value = (matchedFolder as Partial<ExcludedFolder>)[property];
if (value === true) {
(result as Partial<ExcludedFolder>)[property] = true as never;
} else if (!value) {
(result as Partial<ExcludedFolder>)[property] = false as never;
}
}
} else {
excludedFolder = undefined;
}
return result;
}
if (excludedFolder?.detached) { ignoreWhitelist = true; }
function applyWhitelistOverrides(
excluded: Partial<ExcludedFolder>,
whitelisted: WhitelistedFolder | WhitelistedPattern,
): Partial<ExcludedFolder> {
const out: Partial<ExcludedFolder> = { ...excluded };
if (out.disableAutoCreate !== undefined) {
out.disableAutoCreate = !whitelisted.enableAutoCreate;
}
if (out.disableFolderNote !== undefined) {
out.disableFolderNote = !whitelisted.enableFolderNote;
}
if (out.disableSync !== undefined) {
out.disableSync = !whitelisted.enableSync;
}
out.enableCollapsing = !whitelisted.disableCollapsing;
if (out.excludeFromFolderOverview !== undefined) {
out.excludeFromFolderOverview = !whitelisted.showInFolderOverview;
}
out.showFolderNote = !whitelisted.hideInFileExplorer;
return out;
}
if (whitelistedFolder && excludedFolder && !ignoreWhitelist) {
excludedFolder.disableAutoCreate ? excludedFolder.disableAutoCreate = !whitelistedFolder.enableAutoCreate : '';
excludedFolder.disableFolderNote ? excludedFolder.disableFolderNote = !whitelistedFolder.enableFolderNote : '';
excludedFolder.disableSync ? excludedFolder.disableSync = !whitelistedFolder.enableSync : '';
excludedFolder.enableCollapsing = !whitelistedFolder.disableCollapsing;
excludedFolder.excludeFromFolderOverview ? excludedFolder.excludeFromFolderOverview = !whitelistedFolder.showInFolderOverview : '';
excludedFolder.showFolderNote = !whitelistedFolder.hideInFileExplorer;
} else if (excludedFolder && Object.keys(excludedFolder).length === 0) {
excludedFolder = {
function defaultExcludedIfEmpty(
value: Partial<ExcludedFolder> | undefined,
): ExcludedFolder | undefined {
if (value && Object.keys(value).length === 0) {
return {
type: 'folder',
id: '',
path: '',
@ -77,92 +101,153 @@ export function getExcludedFolder(plugin: FolderNotesPlugin, path: string, inclu
showFolderNote: false,
};
}
return excludedFolder;
return value as ExcludedFolder | undefined;
}
export function getDetachedFolder(plugin: FolderNotesPlugin, path: string) {
export function getExcludedFolder(
plugin: FolderNotesPlugin,
path: string,
includeDetached: boolean,
pathOnly?: boolean,
ignoreWhitelist?: boolean,
): ExcludedFolder | ExcludePattern | undefined {
const combined = combineExcluded(plugin, path, includeDetached, pathOnly);
let excluded = aggregateFlags(combined);
const whitelist = getWhitelistedFolder(
plugin,
path,
) as WhitelistedFolder | WhitelistedPattern | undefined;
let skipWhitelist = ignoreWhitelist ?? false;
if (excluded?.detached) skipWhitelist = true;
if (whitelist && excluded && !skipWhitelist) {
excluded = applyWhitelistOverrides(excluded, whitelist);
}
return defaultExcludedIfEmpty(excluded) as ExcludedFolder | ExcludePattern | undefined;
}
export function getDetachedFolder(
plugin: FolderNotesPlugin,
path: string,
): ExcludedFolder | undefined {
return plugin.settings.excludeFolders.find((f) => f.path === path && f.detached);
}
export function getExcludedFolderByPath(plugin: FolderNotesPlugin, path: string) {
export function getExcludedFolderByPath(
plugin: FolderNotesPlugin,
path: string,
): ExcludedFolder | undefined {
return plugin.settings.excludeFolders.find((excludedFolder) => {
if (path.trim() === '' || !excludedFolder.path) { return false; }
if (excludedFolder.path === path) { return true; }
if (!excludedFolder.subFolders) { return false; }
const excludedFolderPath = excludedFolder.path.includes('/') ? excludedFolder.path : excludedFolder.path + '/';
const excludedFolderPath = excludedFolder.path.includes('/')
? excludedFolder.path
: `${excludedFolder.path}/`;
let folderPath = getFolderPathFromString(path);
folderPath = folderPath.includes('/') ? folderPath : folderPath + '/';
folderPath = folderPath.includes('/') ? folderPath : `${folderPath}/`;
if (folderPath.includes('/') || folderPath.includes('\\')) {
return folderPath.startsWith(excludedFolderPath) || folderPath === excludedFolderPath;
} else {
return folderPath === excludedFolderPath;
}
return folderPath === excludedFolderPath;
});
}
export function getExcludedFoldersByPath(plugin: FolderNotesPlugin, path: string) {
export function getExcludedFoldersByPath(
plugin: FolderNotesPlugin,
path: string,
): ExcludedFolder[] {
return plugin.settings.excludeFolders.filter((excludedFolder) => {
if (path.trim() === '' || !excludedFolder.path) { return false; }
if (excludedFolder.path === path) { return true; }
if (!excludedFolder.subFolders) { return false; }
const excludedFolderPath = excludedFolder.path.includes('/') ? excludedFolder.path : excludedFolder.path + '/';
const excludedFolderPath = excludedFolder.path.includes('/')
? excludedFolder.path
: `${excludedFolder.path}/`;
let folderPath = getFolderPathFromString(path);
folderPath = folderPath.includes('/') ? folderPath : folderPath + '/';
folderPath = folderPath.includes('/') ? folderPath : `${folderPath}/`;
if (folderPath.includes('/') || folderPath.includes('\\')) {
return folderPath.startsWith(excludedFolderPath) || folderPath === excludedFolderPath;
} else {
return folderPath === excludedFolderPath;
}
return folderPath === excludedFolderPath;
});
}
export function addExcludedFolder(plugin: FolderNotesPlugin, excludedFolder: ExcludedFolder, reloadStyles = true) {
export function addExcludedFolder(
plugin: FolderNotesPlugin,
excludedFolder: ExcludedFolder,
reloadStyles = true,
): void {
plugin.settings.excludeFolders.push(excludedFolder);
plugin.saveSettings(reloadStyles);
void plugin.saveSettings(reloadStyles);
}
export async function deleteExcludedFolder(plugin: FolderNotesPlugin, excludedFolder: ExcludedFolder) {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter((folder) => folder.id !== excludedFolder.id || folder.type === 'pattern');
plugin.saveSettings(true);
export async function deleteExcludedFolder(
plugin: FolderNotesPlugin,
excludedFolder: ExcludedFolder,
): Promise<void> {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(folder) => folder.id !== excludedFolder.id || folder.type === 'pattern',
);
await plugin.saveSettings(true);
resyncArray(plugin);
}
export function updateExcludedFolder(plugin: FolderNotesPlugin, excludedFolder: ExcludePattern, newExcludeFolder: ExcludePattern) {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter((folder) => folder.id !== excludedFolder.id);
export function updateExcludedFolder(
plugin: FolderNotesPlugin,
excludedFolder: ExcludePattern,
newExcludeFolder: ExcludePattern,
): void {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(folder) => folder.id !== excludedFolder.id,
);
addExcludedFolder(plugin, newExcludeFolder);
}
export function resyncArray(plugin: FolderNotesPlugin) {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.sort((a, b) => a.position - b.position);
export function resyncArray(plugin: FolderNotesPlugin): void {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.sort(
(a, b) => a.position - b.position,
);
plugin.settings.excludeFolders.forEach((folder, index) => {
folder.position = index;
});
plugin.saveSettings();
void plugin.saveSettings();
}
export function addExcludeFolderListItem(settings: SettingsTab, containerEl: HTMLElement, excludedFolder: ExcludedFolder) {
const plugin: FolderNotesPlugin = settings.plugin;
export function addExcludeFolderListItem(
settings: SettingsTab,
containerEl: HTMLElement,
excludedFolder: ExcludedFolder,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
new FolderSuggest(
cb.inputEl,
plugin,
false
false,
);
// @ts-ignore
// @ts-expect-error Obsidian's public types don't include this property
cb.containerEl.addClass('fn-exclude-folder-path');
cb.setPlaceholder('Folder path');
cb.setValue(excludedFolder.path || '');
cb.onChange((value) => {
if (value.startsWith('{regex}') || value.includes('*')) {
deleteExcludedFolder(plugin, excludedFolder);
const pattern = new ExcludePattern(value, plugin.settings.excludeFolders.length, undefined, plugin);
const pattern = new ExcludePattern(
value,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
addExcludedFolder(plugin, pattern);
addExcludePatternListItem(settings, containerEl, pattern);
setting.clear();
@ -190,7 +275,9 @@ export function addExcludeFolderListItem(settings: SettingsTab, containerEl: HTM
if (excludedFolder.position === 0) { return; }
excludedFolder.position -= 1;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
const oldExcludedFolder = plugin.settings.excludeFolders.find((folder) => folder.position === excludedFolder.position);
const oldExcludedFolder = plugin.settings.excludeFolders.find(
(folder) => folder.position === excludedFolder.position,
);
if (oldExcludedFolder) {
oldExcludedFolder.position += 1;
if (oldExcludedFolder.type === 'pattern') {
@ -213,7 +300,9 @@ export function addExcludeFolderListItem(settings: SettingsTab, containerEl: HTM
excludedFolder.position += 1;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
const oldExcludedFolder = plugin.settings.excludeFolders.find((folder) => folder.position === excludedFolder.position);
const oldExcludedFolder = plugin.settings.excludeFolders.find(
(folder) => folder.position === excludedFolder.position,
);
if (oldExcludedFolder) {
oldExcludedFolder.position -= 1;
if (oldExcludedFolder.type === 'pattern') {

View file

@ -1,83 +1,101 @@
import FolderNotesPlugin from '../../main';
import { ExcludePattern } from '../ExcludePattern';
import type FolderNotesPlugin from '../../main';
import type { ExcludePattern } from '../ExcludePattern';
import { Setting, Platform } from 'obsidian';
import { SettingsTab } from '../../settings/SettingsTab';
import type { SettingsTab } from '../../settings/SettingsTab';
import { addExcludedFolder, resyncArray, updateExcludedFolder } from './folderFunctions';
import PatternSettings from '../modals/PatternSettings';
export function updatePattern(plugin: FolderNotesPlugin, pattern: ExcludePattern, newPattern: ExcludePattern) {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter((folder) => folder.id !== pattern.id);
const REGEX_PREFIX = '{regex}';
const STAR = '*';
const INDEX_START = 0;
const SLICE_START_ONE = 1;
const SLICE_EXCLUDE_LAST = -1;
function matchesPatternSpec(raw: string | undefined, folderName: string): boolean {
if (!raw) return false;
const string = raw.trim();
const isRegex = string.startsWith(REGEX_PREFIX);
const hasStartStar = string.startsWith(STAR);
const hasEndStar = string.endsWith(STAR);
if (!isRegex && !(hasStartStar || hasEndStar)) return false;
if (isRegex) {
const body = string.replace(REGEX_PREFIX, '').trim();
if (body === '') return false;
try {
return new RegExp(body).test(folderName);
} catch {
return false;
}
}
if (hasStartStar && hasEndStar) {
const inner = string.slice(SLICE_START_ONE, SLICE_EXCLUDE_LAST);
return folderName.includes(inner);
}
if (hasStartStar) {
const suffix = string.slice(SLICE_START_ONE);
return folderName.endsWith(suffix);
}
if (hasEndStar) {
const prefix = string.slice(INDEX_START, SLICE_EXCLUDE_LAST);
return folderName.startsWith(prefix);
}
return false;
}
export function updatePattern(
plugin: FolderNotesPlugin,
pattern: ExcludePattern,
newPattern: ExcludePattern,
): void {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(folder) => folder.id !== pattern.id,
);
addExcludedFolder(plugin, newPattern);
}
export function deletePattern(plugin: FolderNotesPlugin, pattern: ExcludePattern) {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter((folder) => folder.id !== pattern.id || folder.type === 'folder');
plugin.saveSettings(true);
export async function deletePattern(
plugin: FolderNotesPlugin,
pattern: ExcludePattern,
): Promise<void> {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(folder) => folder.id !== pattern.id || folder.type === 'folder',
);
await plugin.saveSettings(true);
resyncArray(plugin);
}
export function getExcludedFoldersByPattern(plugin: FolderNotesPlugin, folderName: string): ExcludePattern[] {
return plugin.settings.excludeFolders.filter((s) => s.type === 'pattern').filter((pattern) => {
if (!pattern.string) { return false; }
const string = pattern.string.trim();
if (!string.startsWith('{regex}') && !(string.startsWith('*') || string.endsWith('*'))) { return false; }
const regex = string.replace('{regex}', '').trim();
if (string.startsWith('{regex}') && regex === '') { return false; }
if (regex !== undefined && string.startsWith('{regex}')) {
const match = new RegExp(regex).exec(folderName);
if (match) {
return true;
}
} else if (string.startsWith('*') && string.endsWith('*')) {
if (folderName.includes(string.slice(1, -1))) {
return true;
}
} else if (string.startsWith('*')) {
if (folderName.endsWith(string.slice(1))) {
return true;
}
} else if (string.endsWith('*')) {
if (folderName.startsWith(string.slice(0, -1))) {
return true;
}
}
});
export function getExcludedFoldersByPattern(
plugin: FolderNotesPlugin,
folderName: string,
): ExcludePattern[] {
return plugin.settings.excludeFolders
.filter((s) => s.type === 'pattern')
.filter((pattern) => matchesPatternSpec(pattern.string, folderName)) as ExcludePattern[];
}
export function getExcludedFolderByPattern(plugin: FolderNotesPlugin, folderName: string): ExcludePattern | undefined{
return plugin.settings.excludeFolders.filter((s) => s.type === 'pattern').find((pattern) => {
if (!pattern.string) { return false; }
const string = pattern.string.trim();
if (!string.startsWith('{regex}') && !(string.startsWith('*') || string.endsWith('*'))) { return false; }
const regex = string.replace('{regex}', '').trim();
if (string.startsWith('{regex}') && regex === '') { return false; }
if (regex !== undefined && string.startsWith('{regex}')) {
const match = new RegExp(regex).exec(folderName);
if (match) {
return true;
}
} else if (string.startsWith('*') && string.endsWith('*')) {
if (folderName.includes(string.slice(1, -1))) {
return true;
}
} else if (string.startsWith('*')) {
if (folderName.endsWith(string.slice(1))) {
return true;
}
} else if (string.endsWith('*')) {
if (folderName.startsWith(string.slice(0, -1))) {
return true;
}
}
});
export function getExcludedFolderByPattern(
plugin: FolderNotesPlugin,
folderName: string,
): ExcludePattern | undefined {
return (
plugin.settings.excludeFolders
.filter((s) => s.type === 'pattern')
.find((pattern) => matchesPatternSpec(pattern.string, folderName))
) as ExcludePattern | undefined;
}
export function addExcludePatternListItem(settings: SettingsTab, containerEl: HTMLElement, pattern: ExcludePattern) {
const plugin: FolderNotesPlugin = settings.plugin;
export function addExcludePatternListItem(
settings: SettingsTab,
containerEl: HTMLElement,
pattern: ExcludePattern,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
// @ts-ignore
// @ts-expect-error Obsidian's public types don't include containerEl on this control
cb.containerEl.addClass('fn-exclude-folder-path');
cb.setPlaceholder('Pattern');
cb.setValue(pattern.string);
@ -102,11 +120,18 @@ export function addExcludePatternListItem(settings: SettingsTab, containerEl: HT
if (pattern.position === 0) { return; }
pattern.position -= 1;
updatePattern(plugin, pattern, pattern);
const oldPattern = plugin.settings.excludeFolders.find((folder) => folder.position === pattern.position);
const oldPattern = plugin.settings.excludeFolders.find(
(folder) => folder.position === pattern.position,
);
if (oldPattern) {
oldPattern.position += 1;
if (oldPattern.type === 'pattern') {
updatePattern(plugin, oldPattern, oldPattern);
const pat = oldPattern as ExcludePattern;
updatePattern(
plugin,
pat,
pat,
);
} else {
updateExcludedFolder(plugin, oldPattern, oldPattern);
}
@ -125,11 +150,18 @@ export function addExcludePatternListItem(settings: SettingsTab, containerEl: HT
pattern.position += 1;
updatePattern(plugin, pattern, pattern);
const oldPattern = plugin.settings.excludeFolders.find((folder) => folder.position === pattern.position);
const oldPattern = plugin.settings.excludeFolders.find(
(folder) => folder.position === pattern.position,
);
if (oldPattern) {
oldPattern.position -= 1;
if (oldPattern.type === 'pattern') {
updatePattern(plugin, oldPattern, oldPattern);
const pat = oldPattern as ExcludePattern;
updatePattern(
plugin,
pat,
pat,
);
} else {
updateExcludedFolder(plugin, oldPattern, oldPattern);
}
@ -143,7 +175,7 @@ export function addExcludePatternListItem(settings: SettingsTab, containerEl: HT
cb.setIcon('trash-2');
cb.setTooltip('Delete pattern');
cb.onClick(() => {
deletePattern(plugin, pattern);
void deletePattern(plugin, pattern);
setting.clear();
setting.settingEl.remove();
});

View file

@ -1,16 +1,22 @@
import FolderNotesPlugin from '../../main';
import type FolderNotesPlugin from '../../main';
import { getFolderNameFromPathString, getFolderPathFromString } from '../../functions/utils';
import { WhitelistedFolder } from '../WhitelistFolder';
import type { WhitelistedFolder } from '../WhitelistFolder';
import { WhitelistedPattern } from '../WhitelistPattern';
import { Setting, Platform, ButtonComponent } from 'obsidian';
import { Setting, ButtonComponent } from 'obsidian';
import { FolderSuggest } from '../../suggesters/FolderSuggester';
import { SettingsTab } from '../../settings/SettingsTab';
import type { SettingsTab } from '../../settings/SettingsTab';
import WhitelistFolderSettings from '../modals/WhitelistFolderSettings';
import { updateWhitelistedPattern, getWhitelistedFoldersByPattern, addWhitelistedPatternListItem } from './whitelistPatternFunctions';
Platform.isMobileApp;
import {
updateWhitelistedPattern,
getWhitelistedFoldersByPattern,
addWhitelistedPatternListItem,
} from './whitelistPatternFunctions';
export function getWhitelistedFolder(plugin: FolderNotesPlugin, path: string) {
let whitelistedFolder = {} as WhitelistedFolder | WhitelistedPattern | undefined;
export function getWhitelistedFolder(
plugin: FolderNotesPlugin,
path: string,
): WhitelistedFolder | WhitelistedPattern | undefined {
let whitelistedFolder: Partial<WhitelistedFolder> | undefined = {};
const folderName = getFolderNameFromPathString(path);
const matchedPatterns = getWhitelistedFoldersByPattern(plugin, folderName);
const whitelistedFolders = getWhitelistedFoldersByPath(plugin, path);
@ -25,21 +31,30 @@ export function getWhitelistedFolder(plugin: FolderNotesPlugin, path: string) {
if (combinedWhitelistedFolders.length > 0) {
for (const matchedFolder of combinedWhitelistedFolders) {
propertiesToCopy.forEach((property) => {
if (matchedFolder[property] === true) {
(whitelistedFolder as any)[property] = true;
} else if (!matchedFolder[property]) {
(whitelistedFolder as any)[property] = false;
const value = (matchedFolder as Partial<WhitelistedFolder>)[property];
if (value === true) {
(whitelistedFolder as Partial<WhitelistedFolder>)[property] = true as never;
} else if (!value) {
(whitelistedFolder as Partial<WhitelistedFolder>)[property] = false as never;
}
});
}
}
if ((whitelistedFolder instanceof Object) && Object.keys(whitelistedFolder).length === 0) { whitelistedFolder = undefined; }
if (
whitelistedFolder
&& Object.keys(whitelistedFolder).length === 0
) {
whitelistedFolder = undefined;
}
return whitelistedFolder;
return whitelistedFolder as WhitelistedFolder | WhitelistedPattern | undefined;
}
export function getWhitelistedFolderByPath(plugin: FolderNotesPlugin, path: string) {
export function getWhitelistedFolderByPath(
plugin: FolderNotesPlugin,
path: string,
): WhitelistedFolder | WhitelistedPattern | undefined {
return plugin.settings.whitelistFolders.find((whitelistedFolder) => {
if (whitelistedFolder.path === path) { return true; }
if (!whitelistedFolder.subFolders) { return false; }
@ -47,7 +62,10 @@ export function getWhitelistedFolderByPath(plugin: FolderNotesPlugin, path: stri
});
}
export function getWhitelistedFoldersByPath(plugin: FolderNotesPlugin, path: string) {
export function getWhitelistedFoldersByPath(
plugin: FolderNotesPlugin,
path: string,
): Array<WhitelistedFolder | WhitelistedPattern> {
return plugin.settings.whitelistFolders.filter((whitelistedFolder) => {
if (whitelistedFolder.path === path) { return true; }
if (!whitelistedFolder.subFolders) { return false; }
@ -55,52 +73,78 @@ export function getWhitelistedFoldersByPath(plugin: FolderNotesPlugin, path: str
});
}
export function addWhitelistedFolder(plugin: FolderNotesPlugin, whitelistedFolder: WhitelistedFolder) {
export function addWhitelistedFolder(
plugin: FolderNotesPlugin,
whitelistedFolder: WhitelistedFolder | WhitelistedPattern,
): void {
plugin.settings.whitelistFolders.push(whitelistedFolder);
plugin.saveSettings(true);
void plugin.saveSettings(true);
}
export function deleteWhitelistedFolder(plugin: FolderNotesPlugin, whitelistedFolder: WhitelistedFolder) {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter((folder) => folder.id !== whitelistedFolder.id || folder.type === 'pattern');
plugin.saveSettings(true);
export async function deleteWhitelistedFolder(
plugin: FolderNotesPlugin,
whitelistedFolder: WhitelistedFolder | WhitelistedPattern,
): Promise<void> {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter(
(folder) => folder.id !== whitelistedFolder.id || folder.type === 'pattern',
);
await plugin.saveSettings(true);
resyncArray(plugin);
}
export function updateWhitelistedFolder(plugin: FolderNotesPlugin, whitelistedFolder: WhitelistedFolder, newWhitelistFolder: WhitelistedFolder) {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter((folder) => folder.id !== whitelistedFolder.id);
export function updateWhitelistedFolder(
plugin: FolderNotesPlugin,
whitelistedFolder: WhitelistedFolder,
newWhitelistFolder: WhitelistedFolder,
): void {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter(
(folder) => folder.id !== whitelistedFolder.id,
);
addWhitelistedFolder(plugin, newWhitelistFolder);
}
export function resyncArray(plugin: FolderNotesPlugin) {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.sort((a, b) => a.position - b.position);
export function resyncArray(plugin: FolderNotesPlugin): void {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.sort(
(a, b) => a.position - b.position,
);
plugin.settings.whitelistFolders.forEach((folder, index) => {
folder.position = index;
});
plugin.saveSettings();
void plugin.saveSettings();
}
export function addWhitelistFolderListItem(settings: SettingsTab, containerEl: HTMLElement, whitelistedFolder: WhitelistedFolder) {
const plugin: FolderNotesPlugin = settings.plugin;
export function addWhitelistFolderListItem(
settings: SettingsTab,
containerEl: HTMLElement,
whitelistedFolder: WhitelistedFolder,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
const inputContainer = setting.settingEl.createDiv({ cls: 'fn-whitelist-folder-input-container' });
const inputContainer = setting.settingEl.createDiv({
cls: 'fn-whitelist-folder-input-container',
});
const SearchComponent = new Setting(inputContainer);
SearchComponent.addSearch((cb) => {
new FolderSuggest(
cb.inputEl,
plugin,
true
true,
);
// @ts-ignore
// @ts-expect-error Obsidian's public types don't include this property
cb.containerEl.addClass('fn-exclude-folder-path');
cb.setPlaceholder('Folder path');
cb.setValue(whitelistedFolder.path);
cb.onChange((value) => {
if (value.startsWith('{regex}') || value.includes('*')) {
deleteWhitelistedFolder(plugin, whitelistedFolder);
const pattern = new WhitelistedPattern(value, plugin.settings.whitelistFolders.length, undefined, plugin);
void deleteWhitelistedFolder(plugin, whitelistedFolder);
const pattern = new WhitelistedPattern(
value,
plugin.settings.whitelistFolders.length,
undefined,
plugin,
);
addWhitelistedFolder(plugin, pattern);
addWhitelistedPatternListItem(settings, containerEl, pattern);
setting.clear();
@ -127,7 +171,9 @@ export function addWhitelistFolderListItem(settings: SettingsTab, containerEl: H
if (whitelistedFolder.position === 0) { return; }
whitelistedFolder.position -= 1;
updateWhitelistedFolder(plugin, whitelistedFolder, whitelistedFolder);
const oldWhitelistedFolder = plugin.settings.whitelistFolders.find((folder) => folder.position === whitelistedFolder.position);
const oldWhitelistedFolder = plugin.settings.whitelistFolders.find(
(folder) => folder.position === whitelistedFolder.position,
);
if (oldWhitelistedFolder) {
oldWhitelistedFolder.position += 1;
if (oldWhitelistedFolder.type === 'pattern') {
@ -149,7 +195,9 @@ export function addWhitelistFolderListItem(settings: SettingsTab, containerEl: H
whitelistedFolder.position += 1;
updateWhitelistedFolder(plugin, whitelistedFolder, whitelistedFolder);
const oldWhitelistedFolder = plugin.settings.whitelistFolders.find((folder) => folder.position === whitelistedFolder.position);
const oldWhitelistedFolder = plugin.settings.whitelistFolders.find(
(folder) => folder.position === whitelistedFolder.position,
);
if (oldWhitelistedFolder) {
oldWhitelistedFolder.position -= 1;
if (oldWhitelistedFolder.type === 'pattern') {
@ -166,7 +214,7 @@ export function addWhitelistFolderListItem(settings: SettingsTab, containerEl: H
.setIcon('trash-2')
.setTooltip('Delete excluded folder')
.onClick(() => {
deleteWhitelistedFolder(plugin, whitelistedFolder);
void deleteWhitelistedFolder(plugin, whitelistedFolder);
setting.clear();
setting.settingEl.remove();
});

View file

@ -1,89 +1,111 @@
import FolderNotesPlugin from '../../main';
import type FolderNotesPlugin from '../../main';
import { Setting } from 'obsidian';
import { SettingsTab } from '../../settings/SettingsTab';
import type { SettingsTab } from '../../settings/SettingsTab';
import { resyncArray } from './folderFunctions';
import WhitelistPatternSettings from '../modals/WhitelistPatternSettings';
import { WhitelistedPattern } from '../WhitelistPattern';
import type { WhitelistedPattern } from '../WhitelistPattern';
import { addWhitelistedFolder, updateWhitelistedFolder } from './whitelistFolderFunctions';
export function updateWhitelistedPattern(plugin: FolderNotesPlugin, pattern: WhitelistedPattern, newPattern: WhitelistedPattern) {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter((folder) => folder.id !== pattern.id);
const REGEX_PREFIX = '{regex}';
const STAR = '*';
const SLICE_START_ONE = 1;
const SLICE_EXCLUDE_LAST = -1;
function matchesPatternSpec(raw: string | undefined, folderName: string): boolean {
if (!raw) return false;
const string = raw.trim();
const isRegex = string.startsWith(REGEX_PREFIX);
const hasStartStar = string.startsWith(STAR);
const hasEndStar = string.endsWith(STAR);
if (!isRegex && !(hasStartStar || hasEndStar)) return false;
if (isRegex) {
const body = string.replace(REGEX_PREFIX, '').trim();
if (body === '') return false;
try {
return new RegExp(body).test(folderName);
} catch {
return false;
}
}
if (hasStartStar && hasEndStar) {
const inner = string.slice(SLICE_START_ONE, SLICE_EXCLUDE_LAST);
return folderName.includes(inner);
}
if (hasStartStar) {
const suffix = string.slice(SLICE_START_ONE);
return folderName.endsWith(suffix);
}
if (hasEndStar) {
const prefix = string.slice(0, SLICE_EXCLUDE_LAST);
return folderName.startsWith(prefix);
}
return false;
}
export function updateWhitelistedPattern(
plugin: FolderNotesPlugin,
pattern: WhitelistedPattern,
newPattern: WhitelistedPattern,
): void {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter(
(folder) => folder.id !== pattern.id,
);
addWhitelistedFolder(plugin, newPattern);
}
export function deletePattern(plugin: FolderNotesPlugin, pattern: WhitelistedPattern) {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter((folder) => folder.id !== pattern.id || folder.type === 'folder');
plugin.saveSettings(true);
export async function deletePattern(
plugin: FolderNotesPlugin,
pattern: WhitelistedPattern,
): Promise<void> {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter(
(folder) => folder.id !== pattern.id || folder.type === 'folder',
);
await plugin.saveSettings(true);
resyncArray(plugin);
}
export function getWhitelistedFolderByPattern(plugin: FolderNotesPlugin, folderName: string) {
return plugin.settings.whitelistFolders.filter((s) => s.type === 'pattern').find((pattern) => {
if (!pattern.string) { return false; }
const string = pattern.string.trim();
if (!string.startsWith('{regex}') && !(string.startsWith('*') || string.endsWith('*'))) { return false; }
const regex = string.replace('{regex}', '').trim();
if (string.startsWith('{regex}') && regex === '') { return false; }
if (regex !== undefined && string.startsWith('{regex}')) {
const match = new RegExp(regex).exec(folderName);
if (match) {
return true;
}
} else if (string.startsWith('*') && string.endsWith('*')) {
if (folderName.includes(string.slice(1, -1))) {
return true;
}
} else if (string.startsWith('*')) {
if (folderName.endsWith(string.slice(1))) {
return true;
}
} else if (string.endsWith('*')) {
if (folderName.startsWith(string.slice(0, -1))) {
return true;
}
}
});
export function getWhitelistedFolderByPattern(
plugin: FolderNotesPlugin,
folderName: string,
): WhitelistedPattern | undefined {
return (
plugin.settings.whitelistFolders
.filter((s) => s.type === 'pattern')
.find((pattern) => matchesPatternSpec(pattern.string, folderName))
) as WhitelistedPattern | undefined;
}
export function getWhitelistedFoldersByPattern(plugin: FolderNotesPlugin, folderName: string) {
return plugin.settings.whitelistFolders.filter((s) => s.type === 'pattern').filter((pattern) => {
if (!pattern.string) { return false; }
const string = pattern.string.trim();
if (!string.startsWith('{regex}') && !(string.startsWith('*') || string.endsWith('*'))) { return false; }
const regex = string.replace('{regex}', '').trim();
if (string.startsWith('{regex}') && regex === '') { return false; }
if (regex !== undefined && string.startsWith('{regex}')) {
const match = new RegExp(regex).exec(folderName);
if (match) {
return true;
}
} else if (string.startsWith('*') && string.endsWith('*')) {
if (folderName.includes(string.slice(1, -1))) {
return true;
}
} else if (string.startsWith('*')) {
if (folderName.endsWith(string.slice(1))) {
return true;
}
} else if (string.endsWith('*')) {
if (folderName.startsWith(string.slice(0, -1))) {
return true;
}
}
});
export function getWhitelistedFoldersByPattern(
plugin: FolderNotesPlugin,
folderName: string,
): WhitelistedPattern[] {
return (
plugin.settings.whitelistFolders
.filter((s) => s.type === 'pattern')
.filter((pattern) => matchesPatternSpec(pattern.string, folderName))
) as WhitelistedPattern[];
}
export function addWhitelistedPatternListItem(settings: SettingsTab, containerEl: HTMLElement, pattern: WhitelistedPattern) {
const plugin: FolderNotesPlugin = settings.plugin;
export function addWhitelistedPatternListItem(
settings: SettingsTab,
containerEl: HTMLElement,
pattern: WhitelistedPattern,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
// @ts-ignore
// @ts-expect-error Obsidian's public types don't expose containerEl on this control
cb.containerEl.addClass('fn-exclude-folder-path');
cb.setPlaceholder('Pattern');
cb.setValue(pattern.string);
cb.onChange((value) => {
if (plugin.settings.whitelistFolders.find((folder) => folder.string === value)) { return; }
const exists = plugin.settings.whitelistFolders.some(
(folder) => folder.string === value,
);
if (exists) { return; }
pattern.string = value;
updateWhitelistedPattern(plugin, pattern, pattern);
});
@ -103,11 +125,17 @@ export function addWhitelistedPatternListItem(settings: SettingsTab, containerEl
if (pattern.position === 0) { return; }
pattern.position -= 1;
updateWhitelistedPattern(plugin, pattern, pattern);
const oldPattern = plugin.settings.whitelistFolders.find((folder) => folder.position === pattern.position);
const oldPattern = plugin.settings.whitelistFolders.find(
(folder) => folder.position === pattern.position,
);
if (oldPattern) {
oldPattern.position += 1;
if (oldPattern.type === 'pattern') {
updateWhitelistedPattern(plugin, oldPattern, oldPattern);
updateWhitelistedPattern(
plugin,
oldPattern as WhitelistedPattern,
oldPattern as WhitelistedPattern,
);
} else {
updateWhitelistedFolder(plugin, oldPattern, oldPattern);
}
@ -126,11 +154,17 @@ export function addWhitelistedPatternListItem(settings: SettingsTab, containerEl
pattern.position += 1;
updateWhitelistedPattern(plugin, pattern, pattern);
const oldPattern = plugin.settings.whitelistFolders.find((folder) => folder.position === pattern.position);
const oldPattern = plugin.settings.whitelistFolders.find(
(folder) => folder.position === pattern.position,
);
if (oldPattern) {
oldPattern.position -= 1;
if (oldPattern.type === 'pattern') {
updateWhitelistedPattern(plugin, oldPattern, oldPattern);
updateWhitelistedPattern(
plugin,
oldPattern as WhitelistedPattern,
oldPattern as WhitelistedPattern,
);
} else {
updateWhitelistedFolder(plugin, oldPattern, oldPattern);
}
@ -143,7 +177,7 @@ export function addWhitelistedPatternListItem(settings: SettingsTab, containerEl
cb.setIcon('trash-2');
cb.setTooltip('Delete pattern');
cb.onClick(() => {
deletePattern(plugin, pattern);
void deletePattern(plugin, pattern);
setting.clear();
setting.settingEl.remove();
});

View file

@ -1,6 +1,6 @@
import { App, Modal, Setting } from 'obsidian';
import FolderNotesPlugin from '../../main';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { updateCSSClassesForFolder } from 'src/functions/styleFunctions';
export default class ExcludedFolderSettings extends Modal {
plugin: FolderNotesPlugin;
@ -12,10 +12,10 @@ export default class ExcludedFolderSettings extends Modal {
this.app = app;
this.excludedFolder = excludedFolder;
}
onOpen() {
onOpen(): void {
this.display();
}
display() {
display(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl('h2', { text: 'Excluded folder settings' });
@ -28,7 +28,7 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.subFolders = value;
await this.plugin.saveSettings(true);
})
}),
);
new Setting(contentEl)
@ -40,7 +40,7 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.disableSync = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -52,7 +52,7 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.excludeFromFolderOverview = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -66,7 +66,7 @@ export default class ExcludedFolderSettings extends Modal {
updateCSSClassesForFolder(this.excludedFolder.path, this.plugin);
await this.plugin.saveSettings();
this.display();
})
}),
);
new Setting(contentEl)
@ -78,7 +78,7 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.disableAutoCreate = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -91,7 +91,7 @@ export default class ExcludedFolderSettings extends Modal {
this.excludedFolder.disableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
})
}),
);
if (!this.excludedFolder.disableFolderNote) {
@ -104,12 +104,12 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.enableCollapsing = value;
await this.plugin.saveSettings();
})
}),
);
}
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,6 +1,6 @@
import { App, Modal, Setting } from 'obsidian';
import FolderNotesPlugin from '../../main';
import { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import { refreshAllFolderStyles } from 'src/functions/styleFunctions';
export default class PatternSettings extends Modal {
@ -13,16 +13,19 @@ export default class PatternSettings extends Modal {
this.app = app;
this.pattern = pattern;
}
onOpen() {
onOpen(): void {
this.display();
}
display() {
display(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl('h2', { text: 'Pattern settings' });
new Setting(contentEl)
.setName('Disable folder name sync')
// eslint-disable-next-line max-len
.setDesc('Choose if the folder name should be renamed when the file name has been changed')
.addToggle((toggle) =>
toggle
@ -30,11 +33,12 @@ export default class PatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.disableSync = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
.setName('Disable auto creation of folder notes in this folder')
// eslint-disable-next-line max-len
.setDesc('Choose if a folder note should be created when a new folder is created that matches this pattern')
.addToggle((toggle) =>
toggle
@ -42,7 +46,7 @@ export default class PatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.disableAutoCreate = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -54,7 +58,7 @@ export default class PatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.excludeFromFolderOverview = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -68,7 +72,7 @@ export default class PatternSettings extends Modal {
await this.plugin.saveSettings();
refreshAllFolderStyles(true, this.plugin);
this.display();
})
}),
);
new Setting(contentEl)
@ -81,7 +85,7 @@ export default class PatternSettings extends Modal {
this.pattern.disableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
})
}),
);
if (!this.pattern.disableFolderNote) {
@ -94,12 +98,12 @@ export default class PatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.enableCollapsing = value;
await this.plugin.saveSettings();
})
}),
);
}
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,6 +1,6 @@
import { App, Modal, Setting } from 'obsidian';
import FolderNotesPlugin from '../../main';
import { WhitelistedFolder } from '../WhitelistFolder';
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { WhitelistedFolder } from '../WhitelistFolder';
export default class WhitelistFolderSettings extends Modal {
plugin: FolderNotesPlugin;
app: App;
@ -11,10 +11,12 @@ export default class WhitelistFolderSettings extends Modal {
this.app = app;
this.whitelistedFolder = whitelistedFolder;
}
onOpen() {
onOpen(): void {
this.display();
}
display() {
display(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl('h2', { text: 'Whitelisted folder settings' });
@ -27,11 +29,12 @@ export default class WhitelistFolderSettings extends Modal {
.onChange(async (value) => {
this.whitelistedFolder.subFolders = value;
await this.plugin.saveSettings(true);
})
}),
);
new Setting(contentEl)
.setName('Enable folder name sync')
// eslint-disable-next-line max-len
.setDesc('Choose if the name of a folder note should be renamed when the folder name is changed')
.addToggle((toggle) =>
toggle
@ -39,7 +42,7 @@ export default class WhitelistFolderSettings extends Modal {
.onChange(async (value) => {
this.whitelistedFolder.enableSync = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -51,7 +54,7 @@ export default class WhitelistFolderSettings extends Modal {
.onChange(async (value) => {
this.whitelistedFolder.showInFolderOverview = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -63,7 +66,7 @@ export default class WhitelistFolderSettings extends Modal {
.onChange(async (value) => {
this.whitelistedFolder.hideInFileExplorer = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -74,7 +77,7 @@ export default class WhitelistFolderSettings extends Modal {
.onChange(async (value) => {
this.whitelistedFolder.enableAutoCreate = value;
await this.plugin.saveSettings();
})
}),
);
@ -88,7 +91,7 @@ export default class WhitelistFolderSettings extends Modal {
this.whitelistedFolder.enableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
})
}),
);
if (this.whitelistedFolder.enableFolderNote) {
@ -101,12 +104,12 @@ export default class WhitelistFolderSettings extends Modal {
.onChange(async (value) => {
this.whitelistedFolder.disableCollapsing = value;
await this.plugin.saveSettings();
})
}),
);
}
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,6 +1,6 @@
import { App, Modal, Setting } from 'obsidian';
import FolderNotesPlugin from '../../main';
import { WhitelistedPattern } from '../WhitelistPattern';
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { WhitelistedPattern } from '../WhitelistPattern';
export default class WhitelistPatternSettings extends Modal {
plugin: FolderNotesPlugin;
@ -12,15 +12,18 @@ export default class WhitelistPatternSettings extends Modal {
this.app = app;
this.pattern = pattern;
}
onOpen() {
onOpen(): void {
this.display();
}
display() {
display(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl('h2', { text: 'Whitelisted pattern settings' });
new Setting(contentEl)
.setName('Enable folder name sync')
// eslint-disable-next-line max-len
.setDesc('Choose if the name of a folder note should be renamed when the folder name is changed')
.addToggle((toggle) =>
toggle
@ -28,7 +31,7 @@ export default class WhitelistPatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.enableSync = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -39,7 +42,7 @@ export default class WhitelistPatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.enableAutoCreate = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
@ -51,7 +54,7 @@ export default class WhitelistPatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.showInFolderOverview = value;
await this.plugin.saveSettings();
})
}),
);
@ -65,7 +68,7 @@ export default class WhitelistPatternSettings extends Modal {
this.pattern.enableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
})
}),
);
if (this.pattern.enableFolderNote) {
@ -78,12 +81,12 @@ export default class WhitelistPatternSettings extends Modal {
.onChange(async (value) => {
this.pattern.disableCollapsing = value;
await this.plugin.saveSettings();
})
}),
);
}
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,8 +1,11 @@
import { App, Modal, Setting } from 'obsidian';
import { SettingsTab } from 'src/settings/SettingsTab';
import FolderNotesPlugin from '../../main';
import { Modal, Setting, type App } from 'obsidian';
import type { SettingsTab } from 'src/settings/SettingsTab';
import type FolderNotesPlugin from '../../main';
import { WhitelistedFolder } from '../WhitelistFolder';
import { addWhitelistFolderListItem, addWhitelistedFolder } from '../functions/whitelistFolderFunctions';
import {
addWhitelistFolderListItem,
addWhitelistedFolder,
} from '../functions/whitelistFolderFunctions';
import { addWhitelistedPatternListItem } from '../functions/whitelistPatternFunctions';
export default class WhitelistedFoldersSettings extends Modal {
@ -15,7 +18,8 @@ export default class WhitelistedFoldersSettings extends Modal {
this.settingsTab = settingsTab;
this.app = settingsTab.app;
}
onOpen() {
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'Manage whitelisted folders' });
@ -28,22 +32,31 @@ export default class WhitelistedFoldersSettings extends Modal {
cb.setClass('add-exclude-folder');
cb.setTooltip('Add whitelisted folder');
cb.onClick(() => {
const whitelistedFolder = new WhitelistedFolder('', this.plugin.settings.whitelistFolders.length, undefined, this.plugin);
addWhitelistFolderListItem(this.plugin.settingsTab, contentEl, whitelistedFolder);
const whitelistedFolder = new WhitelistedFolder(
'', this.plugin.settings.whitelistFolders.length,
undefined, this.plugin,
);
addWhitelistFolderListItem(
this.plugin.settingsTab, contentEl, whitelistedFolder,
);
addWhitelistedFolder(this.plugin, whitelistedFolder);
this.settingsTab.display();
});
});
this.plugin.settings.whitelistFolders.sort((a, b) => a.position - b.position).forEach((whitelistedFolder) => {
if (whitelistedFolder.string?.trim() !== '' && whitelistedFolder.path?.trim() === '') {
addWhitelistedPatternListItem(this.settingsTab, contentEl, whitelistedFolder);
} else {
addWhitelistFolderListItem(this.settingsTab, contentEl, whitelistedFolder);
}
});
this.plugin.settings.whitelistFolders
.sort((a, b) => a.position - b.position)
.forEach((whitelistedFolder) => {
if (whitelistedFolder.string?.trim() !== '' &&
whitelistedFolder.path?.trim() === '') {
addWhitelistedPatternListItem(this.settingsTab, contentEl, whitelistedFolder);
} else {
addWhitelistFolderListItem(this.settingsTab, contentEl, whitelistedFolder);
}
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,20 +1,20 @@
export class CustomEventEmitter {
private events: { [key: string]: Array<(data?: any) => void> } = {};
private events: { [key: string]: Array<(data?: unknown) => void> } = {};
on(event: string, listener: (data?: any) => void) {
on(event: string, listener: (data?: unknown) => void): void {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(listener);
}
off(event: string, listener: (data?: any) => void) {
off(event: string, listener: (data?: unknown) => void): void {
if (!this.events[event]) return;
this.events[event] = this.events[event].filter((l) => l !== listener);
}
emit(event: string, data?: any) {
emit(event: string, data?: unknown): void {
if (!this.events[event]) return;
this.events[event].forEach((listener) => listener(data));

View file

@ -1,7 +1,28 @@
import FolderNotesPlugin from 'src/main';
import { getDefer, Listener, Events, ApiInterface, DeferInterface, ListenerRef, EventDispatcherInterface } from 'front-matter-plugin-api-provider';
import { App, TFile, TFolder } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import {
type Listener,
type Events,
type ApiInterface,
type DeferInterface,
type ListenerRef,
type EventDispatcherInterface,
getDefer,
} from 'front-matter-plugin-api-provider';
import { type App, TFile, TFolder } from 'obsidian';
import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions';
interface UpdateData {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}
interface WrappedUpdateData {
data: UpdateData;
}
export class FrontMatterTitlePluginHandler {
plugin: FolderNotesPlugin;
app: App;
@ -14,7 +35,7 @@ export class FrontMatterTitlePluginHandler {
this.plugin = plugin;
this.app = plugin.app;
(async () => {
(async (): Promise<void> => {
this.deffer = getDefer(this.app);
if (this.deffer.isPluginReady()) {
this.api = this.deffer.getApi();
@ -33,8 +54,8 @@ export class FrontMatterTitlePluginHandler {
}
const event: Listener<Events, 'manager:update'> = {
name: 'manager:update',
cb: (data) => {
this.fmptUpdateFileName(data as any, true);
cb: (data): void => {
this.fmptUpdateFileName(data as unknown as UpdateData, true);
},
};
// Keep ref to remove listener
@ -49,20 +70,18 @@ export class FrontMatterTitlePluginHandler {
}
})();
}
deleteEvent() {
deleteEvent(): void {
if (this.eventRef) {
this.dispatcher.removeListener(this.eventRef);
}
}
async fmptUpdateFileName(data: {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}, isEvent: boolean) {
if ((data as any).data) data = (data as any).data;
const file = this.app.vault.getAbstractFileByPath(data.path);
async fmptUpdateFileName(data: UpdateData, isEvent: boolean): Promise<void> {
const hasNestedData = 'data' in (data as unknown as Record<string, unknown>);
const actualData: UpdateData = hasNestedData
? (data as unknown as WrappedUpdateData).data
: data;
const file = this.app.vault.getAbstractFileByPath(actualData.path);
if (!(file instanceof TFile)) { return; }
const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#');
@ -73,11 +92,11 @@ export class FrontMatterTitlePluginHandler {
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) { return; }
if (folderNote !== file) { return; }
if (!data.pathOnly) {
if (!actualData.pathOnly) {
this.plugin.changeFolderNameInExplorer(folder, newName);
}
const breadcrumb = data.breadcrumb;
const { breadcrumb } = actualData;
if (breadcrumb) {
this.plugin.changeFolderNameInPath(folder, newName, breadcrumb);
}
@ -96,15 +115,12 @@ export class FrontMatterTitlePluginHandler {
}
async fmptUpdateFolderName(data: {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}, replacePath: boolean) {
if ((data as any).data) data = (data as any).data;
const folder = this.app.vault.getAbstractFileByPath(data.path);
async fmptUpdateFolderName(data: UpdateData, _replacePath: boolean): Promise<void> {
const hasNestedData = 'data' in (data as unknown as Record<string, unknown>);
const actualData: UpdateData = hasNestedData
? (data as unknown as WrappedUpdateData).data
: data;
const folder = this.app.vault.getAbstractFileByPath(actualData.path);
if (!(folder instanceof TFolder)) { return; }
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) { return; }
@ -113,11 +129,11 @@ export class FrontMatterTitlePluginHandler {
const newName = resolver?.resolve(folderNote?.path ?? '');
if (!newName) return;
if (!data.pathOnly) {
if (!actualData.pathOnly) {
this.plugin.changeFolderNameInExplorer(folder, newName);
}
const breadcrumb = data.breadcrumb;
const { breadcrumb } = actualData;
if (breadcrumb) {
this.plugin.changeFolderNameInPath(folder, newName, breadcrumb);
}

View file

@ -1,5 +1,5 @@
import { Keymap, Platform } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import type FolderNotesPlugin from 'src/main';
import { getFolderNote } from 'src/functions/folderNoteFunctions';
import { handleViewHeaderClick } from './handleClick';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
@ -7,7 +7,7 @@ import { updateCSSClassesForFolder } from 'src/functions/styleFunctions';
let fileExplorerMutationObserver: MutationObserver | null = null;
export function registerFileExplorerObserver(plugin: FolderNotesPlugin) {
export function registerFileExplorerObserver(plugin: FolderNotesPlugin): void {
// Run once on initial layout
plugin.app.workspace.onLayoutReady(() => {
initializeFolderNoteFeatures(plugin);
@ -26,23 +26,23 @@ export function registerFileExplorerObserver(plugin: FolderNotesPlugin) {
if (!(titleContainer instanceof HTMLElement)) return;
updateFolderNamesInPath(plugin, titleContainer);
})
}),
);
}
export function unregisterFileExplorerObserver() {
export function unregisterFileExplorerObserver(): void {
if (fileExplorerMutationObserver) {
fileExplorerMutationObserver.disconnect();
fileExplorerMutationObserver = null;
}
}
function initializeFolderNoteFeatures(plugin: FolderNotesPlugin) {
function initializeFolderNoteFeatures(plugin: FolderNotesPlugin): void {
initializeAllFolderTitles(plugin);
observeFolderTitleMutations(plugin);
}
function initializeBreadcrumbs(plugin: FolderNotesPlugin) {
function initializeBreadcrumbs(plugin: FolderNotesPlugin): void {
const titleContainers = document.querySelectorAll('.view-header-title-container');
if (!titleContainers.length) return;
titleContainers.forEach((container) => {
@ -55,7 +55,7 @@ function initializeBreadcrumbs(plugin: FolderNotesPlugin) {
* Observes the File Explorer for newly added folder elements and applies plugin logic (e.g., styles, event listeners)
* automatically when folders are created, expanded, or when the File Explorer view is reopened.
*/
function observeFolderTitleMutations(plugin: FolderNotesPlugin) {
function observeFolderTitleMutations(plugin: FolderNotesPlugin): void {
if (fileExplorerMutationObserver) {
fileExplorerMutationObserver.disconnect();
}
@ -71,7 +71,7 @@ function observeFolderTitleMutations(plugin: FolderNotesPlugin) {
fileExplorerMutationObserver.observe(document, { childList: true, subtree: true });
}
function initializeAllFolderTitles(plugin: FolderNotesPlugin) {
function initializeAllFolderTitles(plugin: FolderNotesPlugin): void {
const allTitles = document.querySelectorAll('.nav-folder-title-content');
for (const title of Array.from(allTitles)) {
const folderTitle = title as HTMLElement;
@ -83,7 +83,7 @@ function initializeAllFolderTitles(plugin: FolderNotesPlugin) {
}
}
function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin) {
function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin): void {
const titles: HTMLElement[] = [];
if (node.matches('.nav-folder-title-content')) {
titles.push(node);
@ -95,6 +95,7 @@ function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin) {
titles.forEach((folderTitle) => {
const folderEl = folderTitle.closest('.nav-folder-title');
const folderPath = folderEl?.getAttribute('data-path') || '';
const RETRY_TIMEOUT = 50;
if (!folderEl || !folderPath) {
setTimeout(() => {
const retryFolderEl = folderTitle.closest('.nav-folder-title');
@ -102,14 +103,18 @@ function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin) {
if (retryFolderEl && retryFolderPath) {
setupFolderTitle(folderTitle, plugin, retryFolderPath);
}
}, 50);
}, RETRY_TIMEOUT);
return;
}
setupFolderTitle(folderTitle, plugin, folderPath);
});
}
async function setupFolderTitle(folderTitle: HTMLElement, plugin: FolderNotesPlugin, folderPath: string) {
async function setupFolderTitle(
folderTitle: HTMLElement,
plugin: FolderNotesPlugin,
folderPath: string,
): Promise<void> {
if (folderTitle.dataset.initialized === 'true') return;
if (!folderPath) return;
@ -117,7 +122,10 @@ async function setupFolderTitle(folderTitle: HTMLElement, plugin: FolderNotesPlu
await updateCSSClassesForFolder(folderPath, plugin);
if (plugin.settings.frontMatterTitle.enabled) {
plugin.fmtpHandler?.fmptUpdateFolderName({ id: '', result: false, path: folderPath, pathOnly: false }, false);
plugin.fmtpHandler?.fmptUpdateFolderName(
{ id: '', result: false, path: folderPath, pathOnly: false },
false,
);
}
if (Platform.isMobile && plugin.settings.disableOpenFolderNoteOnClick) return;
@ -150,21 +158,24 @@ async function setupFolderTitle(folderTitle: HTMLElement, plugin: FolderNotesPlu
});
}
async function updateFolderNamesInPath(plugin: FolderNotesPlugin, titleContainer: HTMLElement) {
async function updateFolderNamesInPath(
plugin: FolderNotesPlugin,
titleContainer: HTMLElement,
): Promise<void> {
const headers = titleContainer.querySelectorAll('span.view-header-breadcrumb');
let path = '';
const TRAILING_SLASH_LENGTH = 1;
headers.forEach(async (breadcrumb: HTMLElement) => {
path += breadcrumb.getAttribute('old-name') ?? (breadcrumb as HTMLElement).innerText.trim();
path += '/';
const folderPath = path.slice(0, -1);
const folderPath = path.slice(0, -TRAILING_SLASH_LENGTH);
const excludedFolder = getExcludedFolder(plugin, folderPath, true);
if (excludedFolder?.disableFolderNote) return;
const folderNote = getFolderNote(plugin, folderPath);
if (!folderNote) return;
if (folderNote) breadcrumb.classList.add('has-folder-note');
breadcrumb?.setAttribute('data-path', path.slice(0, -1));
breadcrumb?.setAttribute('data-path', path.slice(0, -TRAILING_SLASH_LENGTH));
if (!breadcrumb.onclick) {
breadcrumb.addEventListener('click', (e) => {
handleViewHeaderClick(e as MouseEvent, plugin);
@ -172,7 +183,10 @@ async function updateFolderNamesInPath(plugin: FolderNotesPlugin, titleContainer
}
if (plugin.settings.frontMatterTitle.enabled) {
plugin.fmtpHandler?.fmptUpdateFolderName({ id: '', result: false, path: folderPath, pathOnly: true, breadcrumb: breadcrumb }, true);
plugin.fmtpHandler?.fmptUpdateFolderName(
{ id: '', result: false, path: folderPath, pathOnly: true, breadcrumb: breadcrumb },
true,
);
}
});
}
@ -180,10 +194,14 @@ async function updateFolderNamesInPath(plugin: FolderNotesPlugin, titleContainer
// Schedules a callback to run when the browser is idle, or after a timeout as a fallback.
// - callback: The function to execute when idle or after the timeout.
// - options: Optional object with a 'timeout' property (in milliseconds).
function scheduleIdle(callback: () => void, options?: { timeout: number }) {
function scheduleIdle(callback: () => void, options?: { timeout: number }): void {
const DEFAULT_IDLE_TIMEOUT = 200;
if ('requestIdleCallback' in window) {
(window as any).requestIdleCallback(callback, options);
const windowWithIdle = window as Window & {
requestIdleCallback: (callback: () => void, options?: { timeout: number }) => void
};
windowWithIdle.requestIdleCallback(callback, options);
} else {
setTimeout(callback, options?.timeout || 200);
setTimeout(callback, options?.timeout || DEFAULT_IDLE_TIMEOUT);
}
}

View file

@ -1,5 +1,5 @@
import FolderNotesPlugin from 'src/main';
import { App, EditableFileView, TFolder } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { EditableFileView, TFolder, type App } from 'obsidian';
import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions';
export class TabManager {
plugin: FolderNotesPlugin;
@ -9,19 +9,18 @@ export class TabManager {
this.app = plugin.app;
}
resetTabs() {
resetTabs(): void {
if (!this.isEnabled()) return;
this.app.workspace.iterateAllLeaves((leaf) => {
if (!(leaf.view instanceof EditableFileView)) return;
const file = leaf.view?.file;
if (!file) return;
// @ts-ignore
leaf.tabHeaderInnerTitleEl.setText(file.basename);
});
}
updateTabs() {
updateTabs(): void {
if (!this.isEnabled()) return;
this.app.workspace.iterateAllLeaves((leaf) => {
if (!(leaf.view instanceof EditableFileView)) return;
@ -29,12 +28,11 @@ export class TabManager {
if (!file) return;
const folder = getFolder(this.plugin, file);
if (!folder) return;
// @ts-ignore
leaf.tabHeaderInnerTitleEl.setText(folder.name);
});
}
updateTab(folderPath: string) {
updateTab(folderPath: string): void {
if (!this.isEnabled()) return;
const folder = this.app.vault.getAbstractFileByPath(folderPath);
@ -48,13 +46,12 @@ export class TabManager {
const file = leaf.view?.file;
if (!file) return;
if (file.path === folderNote.path) {
// @ts-ignore
leaf.tabHeaderInnerTitleEl.setText(folder.name);
}
});
}
isEnabled() {
isEnabled(): boolean {
if (this.plugin.settings.folderNoteName === '{{folder_name}}') return false;
return this.plugin.settings.tabManagerEnabled;
}

View file

@ -1,10 +1,18 @@
import { Keymap, Platform } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { Keymap, Platform, type TFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { openFolderNote, createFolderNote, getFolderNote } from 'src/functions/folderNoteFunctions';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import { addCSSClassToFileExplorerEl, removeCSSClassFromFileExplorerEL } from 'src/functions/styleFunctions';
import {
addCSSClassToFileExplorerEl,
removeCSSClassFromFileExplorerEL,
} from 'src/functions/styleFunctions';
export async function handleViewHeaderClick(event: MouseEvent, plugin: FolderNotesPlugin) {
export async function handleViewHeaderClick(
event: MouseEvent,
plugin: FolderNotesPlugin,
): Promise<void> {
if (!plugin.settings.openFolderNoteOnClickInPath) return;
event.stopImmediatePropagation();
event.preventDefault();
@ -13,37 +21,61 @@ export async function handleViewHeaderClick(event: MouseEvent, plugin: FolderNot
const folderPath = event.target.getAttribute('data-path');
if (!folderPath) { return; }
const excludedFolder = getExcludedFolder(plugin, folderPath, true);
if (excludedFolder?.disableFolderNote) {
event.target.onclick = null;
event.target.click();
return;
} else if (excludedFolder?.enableCollapsing || plugin.settings.enableCollapsing) {
event.target.onclick = null;
event.target.click();
}
if (await isExcludedFolder(event, plugin, folderPath)) return;
const folderNote = getFolderNote(plugin, folderPath);
if (folderNote) {
await openFolderNote(plugin, folderNote, event).then(async () => {
// @ts-ignore
const fileExplorerPlugin = plugin.app.internalPlugins.getEnabledPluginById('file-explorer');
if (fileExplorerPlugin && Platform.isMobile && plugin.settings.openSidebar.mobile) {
setTimeout(() => { fileExplorerPlugin.revealInFolder(folderNote); }, 200);
} else if (fileExplorerPlugin && Platform.isDesktop && plugin.settings.openSidebar.desktop) {
fileExplorerPlugin.revealInFolder(folderNote);
}
});
await openFolderNote(plugin, folderNote, event).then(() =>
handleFolderNoteReveal(plugin, folderNote),
);
return;
} else if (event.altKey || Keymap.isModEvent(event) === 'tab') {
const usedCtrl = Platform.isMacOS ? event.metaKey : event.ctrlKey;
if ((plugin.settings.altKey && event.altKey) || (usedCtrl && Keymap.isModEvent(event) === 'tab')) {
await createFolderNote(plugin, folderPath, true, undefined, true);
addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, plugin);
return;
}
if (await handleFolderNoteCreation(event, plugin, folderPath)) return;
}
event.target.onclick = null;
event.target.click();
(event.target as HTMLElement).onclick = null;
(event.target as HTMLElement).click();
}
async function isExcludedFolder(
event: MouseEvent,
plugin: FolderNotesPlugin,
folderPath: string,
): Promise<boolean> {
const excludedFolder = getExcludedFolder(plugin, folderPath, true);
if (excludedFolder?.disableFolderNote) {
(event.target as HTMLElement).onclick = null;
(event.target as HTMLElement).click();
return true;
} else if (excludedFolder?.enableCollapsing || plugin.settings.enableCollapsing) {
(event.target as HTMLElement).onclick = null;
(event.target as HTMLElement).click();
}
return false;
}
async function handleFolderNoteReveal(plugin: FolderNotesPlugin, folderNote: TFile): Promise<void> {
const fileExplorerPlugin = plugin.app.internalPlugins.getEnabledPluginById('file-explorer');
if (fileExplorerPlugin && Platform.isMobile && plugin.settings.openSidebar.mobile) {
const OPEN_SIDEBAR_DELAY = 200;
setTimeout(() => { fileExplorerPlugin.revealInFolder(folderNote); }, OPEN_SIDEBAR_DELAY);
} else if (fileExplorerPlugin && Platform.isDesktop && plugin.settings.openSidebar.desktop) {
fileExplorerPlugin.revealInFolder(folderNote);
}
}
async function handleFolderNoteCreation(
event: MouseEvent,
plugin: FolderNotesPlugin,
folderPath: string,
): Promise<boolean> {
const usedCtrl = Platform.isMacOS ? event.metaKey : event.ctrlKey;
if ((plugin.settings.altKey && event.altKey) ||
(usedCtrl && Keymap.isModEvent(event) === 'tab')) {
await createFolderNote(plugin, folderPath, true, undefined, true);
addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, plugin);
return true;
}
return false;
}

View file

@ -1,10 +1,18 @@
import { TAbstractFile, TFolder, TFile } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { createFolderNote, getFolder, getFolderNote, turnIntoFolderNote } from 'src/functions/folderNoteFunctions';
import { TFolder, TFile, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import {
createFolderNote,
getFolder,
getFolderNote,
turnIntoFolderNote,
} from 'src/functions/folderNoteFunctions';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl } from 'src/functions/styleFunctions';
import {
removeCSSClassFromFileExplorerEL,
addCSSClassToFileExplorerEl,
} from 'src/functions/styleFunctions';
export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin) {
export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin): Promise<void> {
if (!plugin.app.workspace.layoutReady) return;
const folder = file.parent;
@ -23,7 +31,7 @@ export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugi
}
}
async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin) {
async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin): Promise<void> {
const folder = getFolder(plugin, file);
if (!(folder instanceof TFolder) && plugin.settings.autoCreateForFiles) {
@ -50,7 +58,7 @@ async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin) {
}
}
async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin) {
async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin): Promise<void> {
let openFile = plugin.settings.autoCreateFocusFiles;
const attachmentFolderPath = plugin.app.vault.getConfig('attachmentFolderPath') as string;

View file

@ -1,10 +1,14 @@
import { TAbstractFile, TFolder, TFile } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { TFolder, TFile, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { getFolderNote, getFolder, deleteFolderNote } from 'src/functions/folderNoteFunctions';
import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl, hideFolderNoteInFileExplorer } from 'src/functions/styleFunctions';
import {
removeCSSClassFromFileExplorerEL,
addCSSClassToFileExplorerEl,
hideFolderNoteInFileExplorer,
} from 'src/functions/styleFunctions';
import { getFolderPathFromString } from 'src/functions/utils';
export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin) {
export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin): void {
const folder = plugin.app.vault.getAbstractFileByPath(getFolderPathFromString(file.path));
if (folder instanceof TFolder) {
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
@ -15,13 +19,15 @@ export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin) {
}
if (file instanceof TFile) {
const folder = getFolder(plugin, file);
if (!folder) { return; }
const folderNote = getFolderNote(plugin, folder.path);
const folderNoteFolder = getFolder(plugin, file);
if (!folderNoteFolder) { return; }
const folderNote = getFolderNote(plugin, folderNoteFolder.path);
if (folderNote) { return; }
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
hideFolderNoteInFileExplorer(folder.path, plugin);
removeCSSClassFromFileExplorerEL(folderNoteFolder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(
folderNoteFolder.path, 'only-has-folder-note', true, plugin,
);
hideFolderNoteInFileExplorer(folderNoteFolder.path, plugin);
}
if (!(file instanceof TFolder)) { return; }

View file

@ -1,13 +1,29 @@
import { TFile, TFolder, TAbstractFile, Notice } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { extractFolderName, getFolderNote, getFolderNoteFolder } from '../functions/folderNoteFunctions';
import { getExcludedFolder, addExcludedFolder, updateExcludedFolder, deleteExcludedFolder, getDetachedFolder } from '../ExcludeFolders/functions/folderFunctions';
import { TFile, TFolder, Notice, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import {
extractFolderName, getFolderNote, getFolderNoteFolder,
} from '../functions/folderNoteFunctions';
import {
getExcludedFolder, addExcludedFolder,
updateExcludedFolder, deleteExcludedFolder, getDetachedFolder,
} from '../ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl, markFileAsFolderNote, unmarkFileAsFolderNote, unmarkFolderAsFolderNote, markFolderWithFolderNoteClasses, hideFolderNoteInFileExplorer, removeActiveFolder, setActiveFolder } from 'src/functions/styleFunctions';
import { getFolderPathFromString, removeExtension, getFileNameFromPathString } from 'src/functions/utils';
import {
removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl,
markFileAsFolderNote, unmarkFileAsFolderNote,
unmarkFolderAsFolderNote, markFolderWithFolderNoteClasses,
hideFolderNoteInFileExplorer, removeActiveFolder, setActiveFolder,
} from 'src/functions/styleFunctions';
import {
getFolderPathFromString, removeExtension, getFileNameFromPathString,
} from 'src/functions/utils';
export function handleRename(file: TAbstractFile, oldPath: string, plugin: FolderNotesPlugin) {
const folder = file.parent;
export function handleRename(
file: TAbstractFile,
oldPath: string,
plugin: FolderNotesPlugin,
): void {
let folder = file.parent;
const oldFolder = plugin.app.vault.getAbstractFileByPath(getFolderPathFromString(oldPath));
if (folder instanceof TFolder) {
@ -27,20 +43,23 @@ export function handleRename(file: TAbstractFile, oldPath: string, plugin: Folde
}
if (file instanceof TFolder) {
const folder = file;
folder = file;
plugin.tabManager.updateTab(folder.path);
updateExcludedFolderPath(folder, oldPath, plugin);
if (isFolderRename(folder, oldPath)) {
return handleFolderRename(folder, oldPath, plugin);
} else {
return handleFolderMove(folder, oldPath, plugin);
handleFolderRename(folder, oldPath, plugin);
return;
}
return handleFolderMove(folder, oldPath, plugin);
} else if (file instanceof TFile) {
if (isFileRename(file, oldPath)) {
return fmptUpdateFileName(file, oldPath, plugin);
} else {
return handleFileMove(file, oldPath, plugin);
handleFileRename(file, oldPath, plugin);
return;
}
handleFileMove(file, oldPath, plugin);
return;
}
}
@ -63,7 +82,7 @@ function isFolderRename(folder: TFolder, oldPath: string): boolean {
return oldParent === newParent && oldName !== newName;
}
export function handleFolderMove(file: TFolder, oldPath: string, plugin: FolderNotesPlugin) {
export function handleFolderMove(file: TFolder, oldPath: string, plugin: FolderNotesPlugin): void {
if (plugin.settings.storageLocation === 'insideFolder') { return; }
if (!plugin.settings.syncMove) { return; }
const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation);
@ -81,60 +100,43 @@ export function handleFolderMove(file: TFolder, oldPath: string, plugin: FolderN
plugin.app.fileManager.renameFile(folderNote, newPath);
}
export async function handleFileMove(file: TFile, oldPath: string, plugin: FolderNotesPlugin) {
const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename) || file.basename;
const oldFileName = removeExtension(getFileNameFromPathString(oldPath));
const newFolder = getFolderNoteFolder(plugin, file, file.basename);
let excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true);
const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName);
const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file);
// eslint-disable-next-line complexity
export async function handleFileMove(
file: TFile,
oldPath: string,
plugin: FolderNotesPlugin,
): Promise<void> {
const { folderName, oldFileName, newFolder, excludedFolder, oldFolder, folderNote } = getArgs(
plugin, file, oldPath,
);
const isFileNowFolderNoteInNewFolder = folderName === newFolder?.name;
const isFileMovedFromOldFolderNote = oldFolder && oldFolder.name === oldFileName && newFolder?.path !== oldFolder.path;
const isFolderNoteInNewFolder = folderName === newFolder?.name;
const fileMovedFromOldFolderNote = oldFolder && oldFolder.name === oldFileName
&& newFolder?.path !== oldFolder.path;
// this is for turning files into folder notes for folders that already have a folder note
// e.g. Turn into folder note for "Folder name"
const isFileNowFolderNoteWithExistingNote = folderName === newFolder?.name && folderNote;
const isFileWithExistingNote = folderName === newFolder?.name && folderNote;
if (isFileNowFolderNoteWithExistingNote) {
let excludedFolderExisted = true;
let disabledSync = false;
if (!excludedFolder) {
excludedFolderExisted = false;
excludedFolder = new ExcludedFolder(oldFolder?.path || '', plugin.settings.excludeFolders.length, undefined, plugin);
addExcludedFolder(plugin, excludedFolder);
} else if (!excludedFolder.disableSync) {
disabledSync = false;
excludedFolder.disableSync = true;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
return plugin.app.fileManager.renameFile(file, oldPath).then(() => {
if (!excludedFolder) { return; }
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
} else if (isFileNowFolderNoteInNewFolder) {
if (!excludedFolder?.disableFolderNote) {
markFileAsFolderNote(file, plugin);
if (newFolder instanceof TFolder) {
markFolderWithFolderNoteClasses(newFolder, plugin);
if (plugin.app.workspace.getActiveFile()?.path === file.path) {
removeActiveFolder(plugin);
setActiveFolder(newFolder.path, plugin);
}
}
if (oldFolder instanceof TFolder) {
hideFolderNoteInFileExplorer(oldFolder.path, plugin);
unmarkFolderAsFolderNote(oldFolder, plugin);
if (isFileWithExistingNote) {
renameExistingFolderNote(
file, oldPath, plugin, excludedFolder, oldFolder,
);
} else if (isFolderNoteInNewFolder) {
if (excludedFolder?.disableFolderNote) { return; }
markFileAsFolderNote(file, plugin);
if (newFolder instanceof TFolder) {
markFolderWithFolderNoteClasses(newFolder, plugin);
if (plugin.app.workspace.getActiveFile()?.path === file.path) {
removeActiveFolder(plugin);
setActiveFolder(newFolder.path, plugin);
}
}
} else if (isFileMovedFromOldFolderNote) {
if (oldFolder instanceof TFolder) {
hideFolderNoteInFileExplorer(oldFolder.path, plugin);
unmarkFolderAsFolderNote(oldFolder, plugin);
}
} else if (fileMovedFromOldFolderNote) {
unmarkFileAsFolderNote(file, plugin);
if (oldFolder instanceof TFolder) {
removeActiveFolder(plugin);
@ -144,9 +146,75 @@ export async function handleFileMove(file: TFile, oldPath: string, plugin: Folde
}
}
export async function handleFolderRename(file: TFolder, oldPath: string, plugin: FolderNotesPlugin) {
function getArgs(plugin: FolderNotesPlugin, file: TFile, oldPath: string): {
folderName: string;
oldFileName: string;
newFolder: TAbstractFile | null;
excludedFolder: ExcludedFolder | undefined;
oldFolder: TAbstractFile | null;
folderNote: TFile | null | undefined;
} {
const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename)
|| file.basename;
const oldFileName = removeExtension(getFileNameFromPathString(oldPath));
const newFolder = getFolderNoteFolder(plugin, file, file.basename);
let excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true);
const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName);
const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file);
return {
folderName,
oldFileName,
newFolder,
excludedFolder,
oldFolder,
folderNote,
};
}
function renameExistingFolderNote(
file: TFile, oldPath: string, plugin: FolderNotesPlugin,
excludedFolder: ExcludedFolder | undefined,
oldFolder: TAbstractFile | null,
): void {
let excludedFolderExisted = true;
let disabledSync = false;
if (!excludedFolder) {
excludedFolderExisted = false;
excludedFolder = new ExcludedFolder(
oldFolder?.path || '',
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
addExcludedFolder(plugin, excludedFolder);
} else if (!excludedFolder.disableSync) {
disabledSync = false;
excludedFolder.disableSync = true;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
plugin.app.fileManager.renameFile(file, oldPath).then(() => {
if (!excludedFolder) { return; }
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
}
export async function handleFolderRename(
file: TFolder, oldPath: string, plugin: FolderNotesPlugin,
): Promise<void> {
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', file.name);
const oldFileName = plugin.settings.folderNoteName.replace('{{folder_name}}', getFileNameFromPathString(oldPath));
const oldFileName = plugin.settings.folderNoteName
.replace('{{folder_name}}', getFileNameFromPathString(oldPath));
if (fileName === oldFileName) { return; }
@ -182,50 +250,62 @@ export async function handleFolderRename(file: TFolder, oldPath: string, plugin:
plugin.app.fileManager.renameFile(folderNote, newPath);
}
export async function fmptUpdateFileName(file: TFile, oldPath: string, plugin: FolderNotesPlugin) {
// eslint-disable-next-line complexity
export async function handleFileRename(
file: TFile,
oldPath: string,
plugin: FolderNotesPlugin,
): Promise<void> {
const oldFileName = removeExtension(getFileNameFromPathString(oldPath));
const newFileName = file.basename;
if (oldFileName === newFileName) { return; }
const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName);
const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename) || file.basename;
const oldFolderName = extractFolderName(plugin.settings.folderNoteName, oldFileName) || oldFileName;
const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename)
|| file.basename;
const oldFolderName = extractFolderName(plugin.settings.folderNoteName, oldFileName)
|| oldFileName;
const newFolder = getFolderNoteFolder(plugin, file, file.basename);
const excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true);
const detachedExcludedFolder = getDetachedFolder(plugin, newFolder?.path || '');
const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file);
if (!excludedFolder?.disableFolderNote && folderName === newFolder?.name && !detachedExcludedFolder) {
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin);
return;
} else if (excludedFolder?.disableFolderNote || (folderName !== newFolder?.name)) {
removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(newFolder?.path || '', 'has-folder-note', false, plugin);
}
if (excludedFolder?.disableSync || !plugin.settings.syncFolderName) { return; }
if (folderName === newFolder?.name) {
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(oldFolder?.path, 'has-folder-note', false, plugin);
addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin);
// Handle folder note creation
if (shouldCreateFolderNote(excludedFolder, folderName, newFolder, detachedExcludedFolder)) {
if (newFolder) {
handleFolderNoteCreation(file, newFolder, plugin);
}
return;
}
// file matched folder name before rename
// file hasnt moved just renamed
// Need to rename the folder
if (!oldFolder) return;
if (oldFolderName === oldFolder.name && newFolder?.path === oldFolder.path) {
return renameFolderOnFileRename(file, oldPath, oldFolder, plugin);
} else if (folderNote && oldFolderName === oldFolder.name) {
return renameFolderOnFileRename(file, oldPath, oldFolder, plugin);
// Handle folder note removal
if (shouldRemoveFolderNoteClasses(excludedFolder, folderName, newFolder)) {
handleFolderNoteRemoval(file, newFolder, plugin);
}
// Early return if sync is disabled
if (excludedFolder?.disableSync || !plugin.settings.syncFolderName) {
return;
}
// Handle same folder rename
if (folderName === newFolder?.name && newFolder) {
handleSameFolderRename(file, newFolder, oldFolder, plugin);
return;
}
// Handle folder rename on file rename
if (shouldRenameFolderOnFileRename(oldFolderName, oldFolder, newFolder, folderNote)) {
return renameFolderOnFileRename(file, oldPath, oldFolder!, plugin);
}
}
async function renameFolderOnFileRename(file: TFile, oldPath: string, oldFolder: TAbstractFile, plugin: FolderNotesPlugin) {
async function renameFolderOnFileRename(
file: TFile,
oldPath: string,
oldFolder: TAbstractFile,
plugin: FolderNotesPlugin,
): Promise<void> {
const newFolderName = extractFolderName(plugin.settings.folderNoteName, file.basename);
if (!newFolderName) {
removeCSSClassFromFileExplorerEL(oldFolder.path, 'has-folder-note', false, plugin);
@ -255,14 +335,19 @@ async function renameFolderOnFileRename(file: TFile, oldPath: string, oldFolder:
if (plugin.app.vault.getAbstractFileByPath(newFolderPath)) {
await plugin.app.fileManager.renameFile(file, oldPath);
return new Notice('A folder with the same name already exists');
new Notice('A folder with the same name already exists');
return;
}
plugin.app.fileManager.renameFile(oldFolder, newFolderPath);
}
function updateExcludedFolderPath(folder: TFolder, oldPath: string, plugin: FolderNotesPlugin) {
function updateExcludedFolderPath(
folder: TFolder,
oldPath: string,
plugin: FolderNotesPlugin,
): void {
const excludedFolders = plugin.settings.excludeFolders.filter(
(excludedFolder) => excludedFolder.path?.includes(oldPath)
(excludedFolder) => excludedFolder.path?.includes(oldPath),
);
excludedFolders.forEach((excludedFolder) => {
@ -281,3 +366,68 @@ function updateExcludedFolderPath(folder: TFolder, oldPath: string, plugin: Fold
});
plugin.saveSettings();
}
function shouldCreateFolderNote(
excludedFolder: ExcludedFolder | undefined,
folderName: string,
newFolder: TAbstractFile | null,
detachedExcludedFolder: ExcludedFolder | undefined,
): boolean {
return !excludedFolder?.disableFolderNote
&& folderName === (newFolder as TFolder)?.name
&& !detachedExcludedFolder;
}
function shouldRemoveFolderNoteClasses(
excludedFolder: ExcludedFolder | undefined,
folderName: string,
newFolder: TAbstractFile | null,
): boolean {
return excludedFolder?.disableFolderNote || (folderName !== (newFolder as TFolder)?.name);
}
function handleFolderNoteCreation(
file: TFile,
newFolder: TAbstractFile,
plugin: FolderNotesPlugin,
): void {
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin);
}
function handleFolderNoteRemoval(
file: TFile,
newFolder: TAbstractFile | null,
plugin: FolderNotesPlugin,
): void {
removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(newFolder?.path || '', 'has-folder-note', false, plugin);
}
function handleSameFolderRename(
file: TFile,
newFolder: TAbstractFile,
oldFolder: TAbstractFile | null,
plugin: FolderNotesPlugin,
): void {
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(oldFolder?.path, 'has-folder-note', false, plugin);
addCSSClassToFileExplorerEl(newFolder.path, 'has-folder-note', false, plugin);
}
function shouldRenameFolderOnFileRename(
oldFolderName: string,
oldFolder: TAbstractFile | null,
newFolder: TAbstractFile | null,
folderNote: TFile | null | undefined,
): boolean {
if (!oldFolder) return false;
const oldFolderAsFolder = oldFolder as TFolder;
const newFolderAsFolder = newFolder as TFolder;
return (oldFolderName === oldFolderAsFolder.name
&& newFolderAsFolder?.path === oldFolderAsFolder.path)
|| (folderNote !== null && oldFolderName === oldFolderAsFolder.name);
}

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 { WorkspaceLeaf, App } from 'obsidian';
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,14 +1,39 @@
import FolderNotesPlugin from '../main';
/* eslint-disable complexity */
/* eslint-disable max-len */
import type FolderNotesPlugin from '../main';
import ExistingFolderNoteModal from '../modals/ExistingNote';
import { applyTemplate } from '../template';
import { TFolder, TFile, TAbstractFile, 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 = `---
@ -26,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') {
@ -60,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) {
@ -88,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);
}
@ -130,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);
}
@ -145,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) {
const extension = file.extension;
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) {
@ -194,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) {
@ -213,8 +360,12 @@ export async function tempDisableSync(plugin: FolderNotesPlugin, folder: TFolder
return [excludedFolder, excludedFolderExisted, disabledSync];
}
export async function openFolderNote(plugin: FolderNotesPlugin, file: TAbstractFile, evt?: MouseEvent) {
const path = file.path;
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;
@ -229,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;
}
@ -239,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();
}
@ -271,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;
@ -287,58 +443,69 @@ 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.folderNoteType;
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;
} else {
const supportedFileTypes = plugin.settings.supportedFileTypes.filter((type) => type !== plugin.settings.folderNoteType.replace('.', ''));
for (let type of supportedFileTypes) {
if (type === 'excalidraw' || type === '.excalidraw') {
type = '.md';
}
if (!type.startsWith('.')) {
type = '.' + type;
}
folderNote = plugin.app.vault.getAbstractFileByPath(path + type);
if (folderNote instanceof TFile) {
return folderNote;
}
}
const supportedFileTypes = plugin.settings.supportedFileTypes.filter(
(type) => type !== primaryType.replace('.', ''),
);
for (let type of supportedFileTypes) {
if (type === 'excalidraw' || type === '.excalidraw') {
type = '.md';
}
if (!type.startsWith('.')) {
type = '.' + type;
}
folderNote = plugin.app.vault.getAbstractFileByPath(path + type);
if (folderNote instanceof TFile) {
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;
@ -350,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 {
@ -369,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') {
@ -398,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,15 +1,19 @@
import { TFile, TFolder } from 'obsidian';
import FolderNotesPlugin from '../main';
import { getDetachedFolder, getExcludedFolder, addExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import type FolderNotesPlugin from '../main';
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';
import FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
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) => {
@ -156,20 +189,28 @@ export function removeCSSClassFromFileExplorerEL(path: string | undefined, cssCl
parentElement.removeClass(cssClass);
}
return;
} else {
fileExplorerItem.removeClass(cssClass);
}
fileExplorerItem.removeClass(cssClass);
}
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

@ -1,20 +1,21 @@
import { TFolder, TFile, View } from 'obsidian';
import { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import type { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import { getFolderNote } from './folderNoteFunctions';
import FolderNotesPlugin from 'src/main';
import { FileExplorerLeaf } from 'obsidian-typings';
import FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
import type FolderNotesPlugin from 'src/main';
import type { FileExplorerLeaf, FileTreeItem, TreeNode } from 'obsidian-typings';
import type FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
export function getFileNameFromPathString(path: string): string {
return path.substring(path.lastIndexOf('/') >= 0 ? path.lastIndexOf('/') + 1 : 0);
}
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];
} else {
return path.split('/').slice(-1)[0];
return path.split('/').slice(PARENT_FOLDER_INDEX)[0];
}
return path.split('/').slice(LAST_FOLDER_INDEX)[0];
}
export function removeExtension(name: string): string {
@ -30,26 +31,30 @@ export function getFolderPathFromString(path: string): string {
const folderPath = path.substring(0, subString);
if (folderPath === '') {
return '/';
} else {
return folderPath;
}
return folderPath;
}
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;
const focusedItem = fileExplorer.view.tree.focusedItem;
export function getFocusedItem(plugin: FolderNotesPlugin): TreeNode<FileTreeItem> | null {
const fileExplorer = getFileExplorer(plugin) as unknown as FileExplorerLeaf;
const { focusedItem } = fileExplorer.view.tree;
return focusedItem;
}

4
src/globals.d.ts vendored
View file

@ -1,4 +1,4 @@
import { TAbstractFile, TFile, TFolder, View, WorkspaceLeaf } from 'obsidian';
import type { TAbstractFile, TFile, TFolder, View, WorkspaceLeaf } from 'obsidian';
declare module 'obsidian' {
interface Setting {
@ -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,11 +1,23 @@
import { Plugin, TFile, TFolder, TAbstractFile, MarkdownPostProcessorContext, parseYaml, Notice, Keymap, WorkspaceLeaf, requireApiVersion, Platform, debounce } from 'obsidian';
import { DEFAULT_SETTINGS, FolderNotesSettings, 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 { FileExplorerWorkspaceLeaf } from './globals';
import { registerFileExplorerObserver, unregisterFileExplorerObserver } from './events/MutationObserver';
import type { FileExplorerWorkspaceLeaf } from './globals';
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';
@ -13,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 { FileExplorerView, InternalPlugin } from 'obsidian-typings';
// import { getFocusedItem } from './functions/utils';
import type { FileExplorerView, InternalPlugin } from 'obsidian-typings';
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';
@ -24,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;
@ -41,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);
@ -52,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');
}
@ -72,24 +97,12 @@ 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.hoveredElement;
const { hoveredElement } = this;
if (this.hoverLinkTriggered) return;
if (!hoveredElement) return;
if (!Keymap.isModEvent(event)) return;
@ -141,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;
}
@ -173,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) {
@ -184,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;
}
}
@ -199,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;
// eslint-disable-next-line
// @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[]) {
const dragManager = this.app.dragManager;
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;
}
}
@ -226,8 +247,8 @@ export default class FolderNotesPlugin extends Plugin {
return originalHandleDragOver.call(this, evt, ...args);
};
clipboardProto.handleDrop = function (evt: DragEvent, ...args: any[]) {
const dragManager = this.app.dragManager;
clipboardProto.handleDrop = function (evt: DragEvent, ...args: unknown[]): void {
const { dragManager } = this.app;
const draggable = dragManager?.draggable;
if (draggable?.file instanceof TFolder) {
@ -246,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();
@ -306,7 +305,59 @@ export default class FolderNotesPlugin extends Plugin {
openFolderNote(this, folderNote, evt);
}
handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
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);
if (editButton) {
@ -314,7 +365,14 @@ export default class FolderNotesPlugin extends Plugin {
e.stopImmediatePropagation();
e.preventDefault();
e.stopPropagation();
new FolderOverviewSettings(this.app, this, parseYaml(source), ctx, el, this.settings.defaultOverview).open();
new FolderOverviewSettings(
this.app,
this,
parseYaml(source),
ctx,
el,
this.settings.defaultOverview,
).open();
}, { capture: true });
}
});
@ -326,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);
folderOverview.create(this, parseYaml(source), el, ctx);
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);
folderOverview.create(this, parseYaml(source), el, ctx);
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;
@ -357,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);
@ -374,29 +447,39 @@ 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();
}
}
return folder.children.length <= threshold + 1;
} else {
return false;
}
return false;
}
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;
@ -405,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;
@ -424,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);
@ -444,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');
@ -456,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) {
@ -474,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,6 +1,6 @@
import { App, Modal, Setting, Notice, SettingTab } from 'obsidian';
import FolderNotesPlugin from '../main';
import { ListComponent } from 'src/functions/ListComponent';
import { Modal, Setting, Notice, type App, type SettingTab } from 'obsidian';
import type FolderNotesPlugin from '../main';
import type { ListComponent } from 'src/functions/ListComponent';
export default class AddSupportedFileModal extends Modal {
plugin: FolderNotesPlugin;
@ -16,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) => {
@ -34,10 +35,10 @@ export default class AddSupportedFileModal extends Modal {
if (value.trim() !== '') {
this.name = value.trim();
}
})
}),
);
}
async onClose() {
async onClose(): Promise<void> {
if (this.name.toLocaleLowerCase() === 'markdown') {
this.name = 'md';
}
@ -46,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,5 @@
import { FuzzySuggestModal, TFile } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { FuzzySuggestModal, type TFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { createFolderNote } from 'src/functions/folderNoteFunctions';
export class AskForExtensionModal extends FuzzySuggestModal<string> {
plugin: FolderNotesPlugin;
@ -8,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;
@ -20,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,5 @@
import { App, Modal, TFile, Platform } from 'obsidian';
import FolderNotesPlugin from '../main';
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 {
plugin: FolderNotesPlugin;
@ -11,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;
}
@ -46,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();
@ -55,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,13 @@
import { App, Modal, Setting, TFile, Platform, TFolder, TAbstractFile } from 'obsidian';
import FolderNotesPlugin from '../main';
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 {
plugin: FolderNotesPlugin;
@ -7,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;
@ -15,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;
@ -63,7 +81,7 @@ export default class ExistingFolderNoteModal extends Modal {
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,5 +1,5 @@
import { App, Modal, Setting, TFolder } from 'obsidian';
import FolderNotesPlugin from '../main';
import { Modal, Setting, type App, type TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
export default class FolderNameModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
@ -10,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) => {
@ -26,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,5 @@
import { App, Modal, TFolder } from 'obsidian';
import FolderNotesPlugin from '../main';
import { Modal, type App, type TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
export default class NewFolderNameModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
@ -10,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) => {
@ -36,7 +37,7 @@ export default class NewFolderNameModal extends Modal {
},
});
textarea.addEventListener('focus', function() {
textarea.addEventListener('focus', function () {
this.select();
});
@ -49,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);
}
}
}

@ -1 +1 @@
Subproject commit f1d24952999f23e2912c7e6c2da4bd53325d2402
Subproject commit 37008ed555811b06e5e9c3547079368e0cce8113

View file

@ -1,14 +1,17 @@
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';
import { SettingsTab } from './SettingsTab';
import type { SettingsTab } from './SettingsTab';
import ExcludedFolderSettings from 'src/ExcludeFolders/modals/ExcludeFolderSettings';
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 { SettingsTab } from './SettingsTab';
export async function renderFileExplorer(settingsTab: SettingsTab) {
import type { SettingsTab } from './SettingsTab';
export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
@ -18,7 +19,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('hide-folder-note');
}
settingsTab.display();
})
}),
);
const setting2 = new Setting(containerEl)
@ -30,11 +31,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.disableOpenFolderNoteOnClick = value;
await settingsTab.plugin.saveSettings();
})
}),
);
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')
@ -50,7 +52,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
}
settingsTab.plugin.settings.stopWhitespaceCollapsing = !value;
await settingsTab.plugin.saveSettings();
})
}),
);
const disableSetting = new Setting(containerEl);
@ -62,10 +64,11 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.enableCollapsing = !value;
await settingsTab.plugin.saveSettings();
})
}),
);
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')
@ -77,7 +80,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
settingsTab.plugin.settings.useSubmenus = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
if (settingsTab.plugin.settings.frontMatterTitle.enabled) {
@ -91,9 +94,17 @@ 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,
);
});
})
}),
);
}
@ -113,7 +124,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('disable-folder-highlight');
}
await settingsTab.plugin.saveSettings();
})
}),
);
new Setting(containerEl)
@ -131,7 +142,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('fn-hide-collapse-icon');
}
settingsTab.display();
})
}),
);
new Setting(containerEl)
@ -149,7 +160,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('fn-hide-empty-collapse-icon');
}
settingsTab.display();
}
},
));
if (settingsTab.plugin.settings.hideCollapsingIcon) {
@ -161,7 +172,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.ignoreAttachmentFolder = value;
await settingsTab.plugin.saveSettings();
})
}),
);
}
@ -179,7 +190,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('folder-note-underline');
}
await settingsTab.plugin.saveSettings();
})
}),
);
new Setting(containerEl)
@ -196,7 +207,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('folder-note-bold');
}
await settingsTab.plugin.saveSettings();
})
}),
);
new Setting(containerEl)
@ -213,7 +224,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab) {
document.body.classList.remove('folder-note-cursive');
}
await settingsTab.plugin.saveSettings();
})
}),
);
}

View file

@ -1,8 +1,8 @@
import { Setting } from 'obsidian';
import { SettingsTab } from './SettingsTab';
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
@ -22,7 +23,7 @@ export async function renderFolderOverview(settingsTab: SettingsTab) {
} else {
plugin.fvIndexDB.active = false;
}
})
}),
);
containerEl.createEl('h3', { text: 'Overviews default settings' });
@ -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,5 +1,6 @@
/* eslint-disable max-len */
import { Setting, Platform } from 'obsidian';
import { SettingsTab } from './SettingsTab';
import type { SettingsTab } from './SettingsTab';
import { ListComponent } from '../functions/ListComponent';
import AddSupportedFileModal from '../modals/AddSupportedFileType';
import { FrontMatterTitlePluginHandler } from '../events/FrontMatterTitle';
@ -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,8 +40,8 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.showFolderNameInTabTitleSetting = false;
}
}
}, 2000);
})
}, FOLDER_NOTE_NAME_DEBOUNCE_MS);
}),
)
.addButton((button) =>
button
@ -52,7 +55,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.renameFolderNotes,
[])
.open();
})
}),
);
nameSetting.infoEl.appendText('Requires a restart to take effect');
nameSetting.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -74,7 +77,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.tabManagerEnabled = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
}
@ -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;
}
@ -115,17 +129,25 @@ export async function renderGeneral(settingsTab: SettingsTab) {
setting0.setName('Supported file types');
const desc0 = document.createDocumentFragment();
desc0.append(
'Specify which file types are allowed as folder notes. Applies to both new and existing folders. Adding many types may affect performance.'
'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,8 +184,13 @@ 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 = '';
@ -195,7 +231,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
await settingsTab.plugin.saveSettings();
settingsTab.display();
refreshAllFolderStyles(undefined, settingsTab.plugin);
})
}),
)
.addButton((button) =>
button
@ -213,9 +249,9 @@ export async function renderGeneral(settingsTab: SettingsTab) {
'Switch storage location',
'When you click on "Confirm" all folder notes will be moved to the new storage location.',
settingsTab.switchStorageLocation,
[oldStorageLocation]
[oldStorageLocation],
).open();
})
}),
);
storageLocation.infoEl.appendText('Requires a restart to take effect');
storageLocation.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -230,8 +266,8 @@ export async function renderGeneral(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.syncDelete = value;
await settingsTab.plugin.saveSettings();
}
)
},
),
);
new Setting(containerEl)
.setName('Move folder notes when moving the folder')
@ -242,7 +278,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.syncMove = value;
await settingsTab.plugin.saveSettings();
})
}),
);
}
if (Platform.isDesktopApp) {
@ -309,7 +345,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.showDeleteConfirmation = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
new Setting(containerEl)
@ -338,7 +374,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.openInNewTab = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
setting3.infoEl.appendText('Requires a restart to take effect');
setting3.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -355,7 +391,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.focusExistingTab = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
}
@ -369,7 +405,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.syncFolderName = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
settingsTab.settingsPage.createEl('h4', { text: 'Automation settings' });
@ -395,7 +431,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.autoCreate = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
if (settingsTab.plugin.settings.autoCreate) {
@ -409,7 +445,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.autoCreateFocusFiles = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
new Setting(containerEl)
@ -422,7 +458,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.autoCreateForAttachmentFolder = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
}
@ -436,7 +472,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.autoCreateForFiles = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
settingsTab.settingsPage.createEl('h3', { text: 'Integration & Compatibility' });
@ -464,19 +500,29 @@ 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();
})
}),
);
fmtpSetting.infoEl.appendText('Requires a restart to take effect');
fmtpSetting.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -493,7 +539,7 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.persistentSettingsTab.afterRestart = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
new Setting(containerEl)
@ -506,6 +552,6 @@ export async function renderGeneral(settingsTab: SettingsTab) {
settingsTab.plugin.settings.persistentSettingsTab.afterChangingTab = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
}

View file

@ -1,6 +1,7 @@
/* eslint-disable max-len */
import { Setting } from 'obsidian';
import { SettingsTab } from './SettingsTab';
export async function renderPath(settingsTab: SettingsTab) {
import type { SettingsTab } from './SettingsTab';
export async function renderPath(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
.setName('Open folder note through path')
@ -12,7 +13,7 @@ export async function renderPath(settingsTab: SettingsTab) {
settingsTab.plugin.settings.openFolderNoteOnClickInPath = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
})
}),
);
if (settingsTab.plugin.settings.openFolderNoteOnClickInPath) {
@ -25,7 +26,7 @@ export async function renderPath(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.openSidebar.mobile = value;
await settingsTab.plugin.saveSettings();
})
}),
);
new Setting(containerEl)
@ -37,7 +38,7 @@ export async function renderPath(settingsTab: SettingsTab) {
.onChange(async (value) => {
settingsTab.plugin.settings.openSidebar.desktop = value;
await settingsTab.plugin.saveSettings();
})
}),
);
}
@ -56,7 +57,7 @@ export async function renderPath(settingsTab: SettingsTab) {
} else {
settingsTab.plugin.updateAllBreadcrumbs(true);
}
})
}),
);
}
@ -76,7 +77,7 @@ export async function renderPath(settingsTab: SettingsTab) {
document.body.classList.remove('folder-note-underline-path');
}
await settingsTab.plugin.saveSettings();
})
}),
);
new Setting(containerEl)
@ -93,7 +94,7 @@ export async function renderPath(settingsTab: SettingsTab) {
document.body.classList.remove('folder-note-bold-path');
}
await settingsTab.plugin.saveSettings();
})
}),
);
new Setting(containerEl)
@ -110,6 +111,6 @@ export async function renderPath(settingsTab: SettingsTab) {
document.body.classList.remove('folder-note-cursive-path');
}
await settingsTab.plugin.saveSettings();
})
}),
);
}

View file

@ -1,17 +1,20 @@
import { App, Notice, PluginSettingTab, TFile, TFolder, MarkdownPostProcessorContext } from 'obsidian';
import FolderNotesPlugin from '../main';
import { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
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';
import { extractFolderName, getFolderNote } from '../functions/folderNoteFunctions';
import { defaultOverviewSettings } from '../obsidian-folder-overview/src/FolderOverview';
import type { defaultOverviewSettings } from '../obsidian-folder-overview/src/FolderOverview';
import { renderGeneral } from './GeneralSettings';
import { renderFileExplorer } from './FileExplorerSettings';
import { renderPath } from './PathSettings';
import { renderFolderOverview } from './FolderOverviewSettings';
import { renderExcludeFolders } from './ExcludedFoldersSettings';
import { getFolderPathFromString } from '../functions/utils';
import { WhitelistedFolder } from 'src/ExcludeFolders/WhitelistFolder';
import { WhitelistedPattern } from 'src/ExcludeFolders/WhitelistPattern';
import type { WhitelistedFolder } from 'src/ExcludeFolders/WhitelistFolder';
import type { WhitelistedPattern } from 'src/ExcludeFolders/WhitelistPattern';
export interface FolderNotesSettings {
syncFolderName: boolean;
@ -235,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:
@ -256,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;
@ -273,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();
@ -299,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') {
@ -331,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) {
@ -348,10 +374,10 @@ export class SettingsTab extends PluginSettingTab {
} else if (this.plugin.settings.storageLocation === 'insideFolder') {
if (getFolderPathFromString(folderNote.path) === file.path) {
return;
} else {
const newPath = `${file.path}/${folderNote.name}`;
this.plugin.app.fileManager.renameFile(folderNote, newPath);
}
const newPath = `${file.path}/${folderNote.name}`;
this.plugin.app.fileManager.renameFile(folderNote, newPath);
}
}
}

View file

@ -1,14 +1,20 @@
import { Modal, ButtonComponent } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import type FolderNotesPlugin from 'src/main';
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,5 @@
import { App, ButtonComponent, Modal, Setting, TFolder, Notice } from 'obsidian';
import FolderNotesPlugin from '../../main';
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';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
@ -14,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);
@ -22,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.')
@ -46,7 +57,7 @@ export default class ConfirmationModal extends Modal {
cb.onChange(async (value) => {
this.extension = value;
});
}
},
);
}
new Setting(contentEl)
@ -55,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);
@ -82,7 +100,8 @@ export default class ConfirmationModal extends Modal {
});
});
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -1,9 +1,15 @@
import BackupWarningModal from './BackupWarning';
import FolderNotesPlugin from 'src/main';
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,17 +17,19 @@ 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')
.setValue(this.plugin.settings.oldFolderNoteName || '')
.onChange(async (value) => {
this.plugin.settings.oldFolderNoteName = value;
})
}),
);
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')
@ -29,7 +37,7 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
.onChange(async (value) => {
this.plugin.settings.folderNoteName = value;
this.plugin.settingsTab.display();
})
}),
);
}
}

View file

@ -1,8 +1,6 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { TAbstractFile, TFile } from 'obsidian';
import { type TAbstractFile, TFile } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import FolderNotesPlugin from '../main';
import type FolderNotesPlugin from '../main';
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
@ -11,7 +9,7 @@ export enum FileSuggestMode {
export class FileSuggest extends TextInputSuggest<TFile> {
constructor(
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin
plugin: FolderNotesPlugin,
) {
super(inputEl, plugin);
}

View file

@ -1,8 +1,8 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { TAbstractFile, TFolder } from 'obsidian';
import { TFolder, type TAbstractFile } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import FolderNotesPlugin from '../main';
import type FolderNotesPlugin from '../main';
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
@ -35,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,9 +1,8 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { ISuggestOwner, Scope } from 'obsidian';
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
import FolderNotesPlugin from 'src/main';
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 => {
return ((value % size) + size) % size;
@ -20,7 +19,7 @@ class Suggest<T> {
constructor(
owner: ISuggestOwner<T>,
containerEl: HTMLElement,
scope: Scope
scope: Scope,
) {
this.owner = owner;
this.containerEl = containerEl;
@ -28,12 +27,12 @@ class Suggest<T> {
containerEl.on(
'click',
'.suggestion-item',
this.onSuggestionClick.bind(this)
this.onSuggestionClick.bind(this),
);
containerEl.on(
'mousemove',
'.suggestion-item',
this.onSuggestionMouseover.bind(this)
this.onSuggestionMouseover.bind(this),
);
scope.register([], 'ArrowUp', (event) => {
@ -71,7 +70,7 @@ class Suggest<T> {
this.setSelectedItem(item, false);
}
setSuggestions(values: T[]) {
setSuggestions(values: T[]): void {
this.containerEl.empty();
const suggestionEls: HTMLDivElement[] = [];
@ -86,17 +85,17 @@ 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
this.suggestions.length,
);
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
const selectedSuggestion = this.suggestions[normalizedIndex];
@ -140,7 +139,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
'.suggestion-container',
(event: MouseEvent) => {
event.preventDefault();
}
},
);
}
@ -155,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();
@ -163,7 +162,7 @@ export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
}
open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.plugin.app.keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
@ -173,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,5 @@
import { TAbstractFile, TFile, TFolder, Vault, AbstractInputSuggest } from 'obsidian';
import FolderNotesPlugin from '../main';
import { TFile, TFolder, Vault, AbstractInputSuggest, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { getTemplatePlugins } from 'src/template';
export enum FileSuggestMode {
TemplateFiles,
@ -9,7 +9,7 @@ export enum FileSuggestMode {
export class TemplateSuggest extends AbstractInputSuggest<TFile> {
constructor(
public inputEl: HTMLInputElement,
public plugin: FolderNotesPlugin
public plugin: FolderNotesPlugin,
) {
super(plugin.app, inputEl);
}
@ -32,18 +32,27 @@ export class TemplateSuggest extends AbstractInputSuggest<TFile> {
if ((!templateFolder || templateFolder.trim() === '') && !templaterPlugin) {
files = this.plugin.app.vault.getFiles().filter((file) =>
file.path.toLowerCase().includes(lower_input_str)
file.path.toLowerCase().includes(lower_input_str),
);
} else {
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, App, WorkspaceLeaf } from 'obsidian';
import FolderNotesPlugin from './main';
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
) {
templatePath?: string,
): Promise<void> {
const fileContent = await plugin.app.vault.read(file).catch((err) => {
console.error(`Error reading file ${file.path}:`, err);
});
@ -25,21 +54,22 @@ 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);
}
return await templatesPlugin.instance.insertTemplate(templateFile);
} else {
await plugin.app.vault.modify(file, templateContent);
}
await plugin.app.vault.modify(file, templateContent);
} catch (e) {
console.error(e);
@ -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,