Compare commits

...

39 commits
1.8.18 ... main

Author SHA1 Message Date
Paul
494d159107
Merge pull request #333 from kyan001/patch-1
Bug Fix #330 & #332
2026-06-26 14:16:55 +02:00
Kyan
0532186cb2
Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-26 11:52:15 +08:00
Kyan
f6a28dab67
Bug Fix #330 & #332
In the breadcrumb path processing loop, the function exits too early when it encounters a level without a folder note, so later levels are never checked. Because of this early return, clicking a deeper path segment does not open its folder note unless all ancestor folders also have folder notes.
2026-06-26 11:38:34 +08:00
Paul
85cd981d8d Update main.ts 2026-06-15 11:51:21 +02:00
Paul
f172b8508f Update manifest.json 2026-06-05 10:06:51 +02:00
Paul
4a71e3d795 Fix #328 2026-06-05 10:06:39 +02:00
Paul
101758a52b Fix plugin not loading on some devices 2026-06-03 10:02:40 +02:00
Paul
a493120095 Update manifest.json 2026-06-02 16:51:48 +02:00
Paul
8248de14d9 Update obsidian-folder-overview 2026-06-02 16:51:36 +02:00
Paul
961914c4af Update manifest.json 2026-06-02 16:48:23 +02:00
Paul
71e3c79078 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2026-06-02 16:48:02 +02:00
Paul
432ecc6072 Fix build issues 2026-06-02 16:47:46 +02:00
Paul
71c2a309b7
Update manifest.json 2026-06-01 20:53:42 +02:00
github-actions[bot]
3bdc358643 Update manifest.json to version 2026-06-01 16:43:00 +00:00
Paul
960fa91b52 Update manifest.json 2026-06-01 18:42:38 +02:00
Paul
644c5ce099 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2026-06-01 18:40:51 +02:00
Paul
937e5939bc Update manifest.json 2026-06-01 18:40:30 +02:00
Paul
4215b22559
Update release.yaml 2026-06-01 18:39:31 +02:00
Paul
c6c334bd16 Update manifest.json 2026-06-01 18:35:16 +02:00
Paul
32ce9f55ed Fix reveal folder function 2026-06-01 18:33:44 +02:00
Paul
b41cc7641d Update obsidian-folder-overview 2026-06-01 17:39:36 +02:00
Paul
ec73ad869f Fix unwanted auto create of folder for unsupported file types 2026-06-01 17:36:48 +02:00
Paul
681320e2f2 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2026-06-01 16:42:17 +02:00
Paul
a4951dbd86 Fix some more eslint issues 2026-06-01 16:21:03 +02:00
Paul
3a744e06b8
Merge pull request #325 from Semptic/fix/274-hide-folder-note-scrolling
fix: increase infinity scroll buffer to show hidden folder notes
2026-06-01 16:05:40 +02:00
Paul
aae916aafa Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2026-05-15 15:40:31 +02:00
Paul
1e99850592 Fix eslint issues 1/2 2026-05-15 15:40:29 +02:00
Paul
b296a3d7e6
Update release.yaml 2026-05-15 13:03:10 +02:00
Stefan Kuhn
9c1b4aeba2 fix: increase infinity scroll buffer to show hidden folder notes
When Hide Folder Note is enabled, Obsidian's virtual scroll buffer
(rootMargin) becomes too small to pre-render items below the many
hidden folder notes, causing items to disappear during scrolling.

Increase infinityScroll.rootMargin to 1.5x viewport height to ensure
items are pre-rendered even with many hidden items above them.

Closes #274

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 09:31:58 +02:00
github-actions[bot]
3fce80d6fb Update manifest-beta.json to version 1.8.20-1-beta in main branch 2026-04-12 14:04:25 +00:00
Paul
2683ee1a1a Fix function for getting the folder of a folder note 2026-03-26 15:22:04 +01:00
Paul
86bd49d873
Merge pull request #319 from LostPaul/dependabot/npm_and_yarn/multi-770cfcd984
Bump minimatch
2026-03-23 10:44:39 +01:00
dependabot[bot]
89f5db7bf0
Bump minimatch
Bumps  and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together.

Updates `minimatch` from 3.1.2 to 3.1.5
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

Updates `minimatch` from 9.0.5 to 9.0.9
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.1.2...v3.1.5)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 3.1.5
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 9.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 09:40:17 +00:00
Paul
5ed694654c Update manifest.json 2026-03-22 20:44:24 +01:00
Paul
1ace668c8f Fix #312 2026-03-22 20:44:06 +01:00
Paul
3daea7efd3 Fix #306 2026-03-12 15:26:27 +01:00
Paul
347c409619 Fix #284 2026-03-12 14:34:36 +01:00
Paul
8fbf999df4 Fix #304 2026-03-10 15:56:52 +01:00
Paul
8344490906 Hide duplicated folder note name in path 2026-03-10 15:49:50 +01:00
51 changed files with 10420 additions and 1831 deletions

View file

@ -6,6 +6,11 @@ on:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
permissions:
id-token: write
contents: write
attestations: write
jobs:
build:
runs-on: ubuntu-latest
@ -57,6 +62,16 @@ jobs:
npm install
npm run fn-build --if-present
- name: Generate artifact attestation for main.js
uses: actions/attest@v4
with:
subject-path: 'main.js'
- name: Generate artifact attestation for styles.css
uses: actions/attest@v4
with:
subject-path: 'styles.css'
- name: Create Plugin release
uses: ncipollo/release-action@v1.12.0
with:

View file

@ -31,7 +31,6 @@ esbuild.build({
'@lezer/common',
'@lezer/highlight',
'@lezer/lr',
"f:/Obsidian/test/.obsidian/plugins/obsidian-folder-notes/node_modules/obsidian",
...builtins],
format: 'cjs',
watch: !prod,

View file

@ -1,3 +1,4 @@
import obsidianmd from "eslint-plugin-obsidianmd";
import tseslint from "typescript-eslint";
export default [
@ -14,9 +15,22 @@ export default [
"**/dist/**",
],
plugins: {
"@typescript-eslint": tseslint.plugin
"@typescript-eslint": tseslint.plugin,
obsidianmd
},
rules: {
"obsidianmd/no-tfile-tfolder-cast": "error",
"obsidianmd/rule-custom-message": [
"error",
{
"no-console": {
messages: {
"Unexpected console statement. Only these console methods are allowed: warn, error, debug.": "Avoid unnecessary logging to console. See https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines#Avoid+unnecessary+logging+to+console",
},
options: [{ allow: ["warn", "error", "debug"] }],
}
}
],
"no-unused-vars": "off",
"quotes": [
"error",
@ -143,7 +157,7 @@ export default [
],
'max-len': [
'warn', {
code: 100,
code: 110,
ignoreComments: true,
}
],
@ -160,4 +174,4 @@ export default [
],
}
}
];
];

View file

@ -1,7 +1,7 @@
{
"id": "folder-notes",
"name": "Folder notes beta",
"version": "1.8.17-5-beta",
"version": "1.8.20-1-beta",
"minAppVersion": "0.15.0",
"description": "Create notes within folders that can be accessed without collapsing the folder, similar to the functionality offered in Notion.",
"author": "Lost Paul",

View file

@ -1,8 +1,8 @@
{
"id": "folder-notes",
"name": "Folder notes",
"version": "1.8.18",
"minAppVersion": "0.15.0",
"version": "1.8.26",
"minAppVersion": "1.4.10",
"description": "Create notes within folders that can be accessed without collapsing the folder, similar to the functionality offered in Notion.",
"author": "Lost Paul",
"authorUrl": "https://github.com/LostPaul",

11051
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -18,18 +18,19 @@
"devDependencies": {
"@eslint/js": "^8.57.1",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^8.38.0",
"@typescript-eslint/parser": "^8.38.0",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.0",
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"eslint": "^9.32.0",
"eslint": "^10.4.1",
"eslint-plugin-obsidianmd": "^0.3.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.8.4",
"typescript": "^6.0.3",
"typescript-eslint": "^8.38.0"
},
"dependencies": {

View file

@ -8,6 +8,7 @@ import {
type TAbstractFile,
type Editor,
type MarkdownView,
type MarkdownFileInfo,
} from 'obsidian';
import type FolderNotesPlugin from './main';
import {
@ -31,6 +32,7 @@ import {
showFolderNoteInFileExplorer,
} from './functions/styleFunctions';
type MarkdownEditorContext = MarkdownView | MarkdownFileInfo;
export class Commands {
@ -60,7 +62,7 @@ export class Commands {
const folderNote = getFolderNote(this.plugin, folder.path);
if (folderNote instanceof TFile && folderNote === file) return false;
if (checking) return true;
turnIntoFolderNote(this.plugin, file, folder, folderNote);
void turnIntoFolderNote(this.plugin, file, folder, folderNote);
},
});
@ -80,19 +82,19 @@ export class Commands {
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
this.plugin.saveSettings();
void this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const folder = this.plugin.app.vault.getAbstractFileByPath(newPath);
if (!(folder instanceof TFolder)) return;
createFolderNote(this.plugin, folder.path, true, '.' + file.extension, false, file);
await createFolderNote(this.plugin, folder.path, true, '.' + file.extension, false, file);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
this.plugin.saveSettings();
void this.plugin.saveSettings();
},
});
this.plugin.addCommand({
id: 'create-folder-note-for-current-folder',
name: 'Create markdown folder note for this folder',
name: 'Create Markdown folder note for this folder',
checkCallback: (checking) => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return false;
@ -100,7 +102,7 @@ export class Commands {
if (!(folder instanceof TFolder)) return false;
if (folder.path === '' || folder.path === '/') return false;
if (checking) return true;
createFolderNote(this.plugin, folder.path, true, '.md', false);
void createFolderNote(this.plugin, folder.path, true, '.md', false);
},
});
@ -116,7 +118,7 @@ export class Commands {
if (!(folder instanceof TFolder)) return false;
if (folder.path === '' || folder.path === '/') return false;
if (checking) return true;
createFolderNote(this.plugin, folder.path, true, '.' + fileType, false);
void createFolderNote(this.plugin, folder.path, true, '.' + fileType, false);
},
});
});
@ -126,7 +128,7 @@ export class Commands {
id: `create-${type}-folder-note-for-active-file-explorer-folder`,
name: `Create ${type} folder note for current active folder in file explorer`,
checkCallback: (checking: boolean) => {
const folder = getFileExplorerActiveFolder();
const folder = getFileExplorerActiveFolder(this.plugin);
if (!folder) return false;
// Is there already a folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
@ -136,7 +138,7 @@ export class Commands {
// Everything is fine and not checking, let's create the folder note.
const ext = '.' + fileType;
const { path } = folder;
createFolderNote(this.plugin, path, true, ext, false);
void createFolderNote(this.plugin, path, true, ext, false);
},
});
});
@ -152,7 +154,7 @@ export class Commands {
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
deleteFolderNote(this.plugin, folderNote, true);
void deleteFolderNote(this.plugin, folderNote, true);
},
});
@ -160,7 +162,7 @@ export class Commands {
id: 'delete-folder-note-of-active-file-explorer-folder',
name: 'Delete folder note of current active folder in file explorer',
checkCallback: (checking: boolean) => {
const folder = getFileExplorerActiveFolder();
const folder = getFileExplorerActiveFolder(this.plugin);
if (!folder) return false;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
@ -168,7 +170,7 @@ export class Commands {
if (checking) return true;
// Everything is fine and not checking, let's delete the folder note.
deleteFolderNote(this.plugin, folderNote, true);
void deleteFolderNote(this.plugin, folderNote, true);
},
});
this.plugin.addCommand({
@ -182,14 +184,14 @@ export class Commands {
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
openFolderNote(this.plugin, folderNote);
void openFolderNote(this.plugin, folderNote);
},
});
this.plugin.addCommand({
id: 'open-folder-note-of-active-file-explorer-folder',
name: 'Open folder note of current active folder in file explorer',
checkCallback: (checking: boolean) => {
const folder = getFileExplorerActiveFolder();
const folder = getFileExplorerActiveFolder(this.plugin);
if (!folder) return false;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
@ -197,14 +199,14 @@ export class Commands {
if (checking) return true;
// Everything is fine and not checking, let's open the folder note.
openFolderNote(this.plugin, folderNote);
void openFolderNote(this.plugin, folderNote);
},
});
this.plugin.addCommand({
id: 'create-folder-note-from-selected-text',
name: 'Create folder note from selection',
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownView) => {
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownEditorContext) => {
const text = editor.getSelection().trim();
const { file } = view;
if (!(file instanceof TFile)) return false;
@ -230,8 +232,8 @@ export class Commands {
new Notice('Folder note already exists');
return false;
}
this.plugin.app.vault.createFolder(text);
createFolderNote(this.plugin, text, false);
void this.plugin.app.vault.createFolder(text);
void createFolderNote(this.plugin, text, false);
} else {
const folderFullPath = folderPath + '/' + text;
@ -250,8 +252,8 @@ export class Commands {
return false;
}
}
this.plugin.app.vault.createFolder(folderPath + '/' + text);
createFolderNote(this.plugin, folderPath + '/' + text, false);
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
void createFolderNote(this.plugin, folderPath + '/' + text, false);
}
const { folderNoteName } = this.plugin.settings;
@ -317,7 +319,7 @@ export class Commands {
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
this.plugin.saveSettings();
void this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const newFolder = this.plugin.app.vault
.getAbstractFileByPath(newPath);
@ -331,7 +333,7 @@ export class Commands {
file,
);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
this.plugin.saveSettings();
void this.plugin.saveSettings();
});
});
@ -347,7 +349,7 @@ export class Commands {
item.onClick(() => {
if (!folder || !(folder instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, folder.path);
turnIntoFolderNote(this.plugin, file, folder, folderNote);
void turnIntoFolderNote(this.plugin, file, folder, folderNote);
});
});
}
@ -380,7 +382,7 @@ export class Commands {
(excluded) =>
(excluded.path !== file.path) || excluded.detached,
);
this.plugin.saveSettings(true);
void this.plugin.saveSettings(true);
new Notice('Successfully removed folder from excluded folders');
});
});
@ -393,7 +395,7 @@ export class Commands {
item.setTitle('Remove folder from detached folders');
item.setIcon('trash');
item.onClick(() => {
deleteExcludedFolder(this.plugin, detachedExcludedFolder);
void deleteExcludedFolder(this.plugin, detachedExcludedFolder);
});
});
}
@ -411,7 +413,7 @@ export class Commands {
this.plugin,
);
this.plugin.settings.excludeFolders.push(newExcludedFolder);
this.plugin.saveSettings(true);
void this.plugin.saveSettings(true);
new Notice('Successfully excluded folder from folder notes');
});
});
@ -425,7 +427,7 @@ export class Commands {
item.setTitle('Delete folder note');
item.setIcon('trash');
item.onClick(() => {
deleteFolderNote(this.plugin, folderNote, true);
void deleteFolderNote(this.plugin, folderNote, true);
});
});
@ -433,7 +435,7 @@ export class Commands {
item.setTitle('Open folder note');
item.setIcon('chevron-right-square');
item.onClick(() => {
openFolderNote(this.plugin, folderNote);
void openFolderNote(this.plugin, folderNote);
});
});
@ -474,10 +476,10 @@ export class Commands {
}
} else {
folderMenu.addItem((item) => {
item.setTitle('Create markdown folder note');
item.setTitle('Create Markdown folder note');
item.setIcon('edit');
item.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.md');
void createFolderNote(this.plugin, file.path, true, '.md');
});
});
@ -487,7 +489,7 @@ export class Commands {
item.setTitle(`Create ${fileType} folder note`);
item.setIcon('edit');
item.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.' + fileType);
void createFolderNote(this.plugin, file.path, true, '.' + fileType);
});
});
});
@ -500,8 +502,8 @@ export class Commands {
this.plugin.settings.useSubmenus
) {
menu.addItem(async (item) => {
item.setTitle('Folder Note Commands').setIcon('folder-edit');
let subMenu: Menu = item.setSubmenu() as Menu;
item.setTitle('Folder note commands').setIcon('folder-edit');
let subMenu: Menu = item.setSubmenu();
addFolderNoteActions(subMenu);
});
} else {
@ -512,7 +514,7 @@ export class Commands {
editorCommands(): void {
// eslint-disable-next-line max-len
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => {
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownEditorContext) => {
const text = editor.getSelection().trim();
if (!text || text.trim() === '') return;
menu.addItem((item) => {
@ -543,8 +545,8 @@ export class Commands {
if (folder instanceof TFolder) {
return new Notice('Folder note already exists');
}
this.plugin.app.vault.createFolder(text);
createFolderNote(this.plugin, text, false);
void this.plugin.app.vault.createFolder(text);
void createFolderNote(this.plugin, text, false);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(
@ -565,8 +567,8 @@ export class Commands {
return new Notice('File already exists');
}
}
this.plugin.app.vault.createFolder(folderPath + '/' + text);
createFolderNote(this.plugin, folderPath + '/' + text, false);
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
void createFolderNote(this.plugin, folderPath + '/' + text, false);
}
if (fileName !== text) {
editor.replaceSelection(`[[${fileName}]]`);

View file

@ -12,7 +12,7 @@ export class ExcludedFolder {
position: number;
excludeFromFolderOverview: boolean;
hideInSettings: boolean;
detached: boolean;
detached: boolean = false;
detachedFilePath?: string;
showFolderNote: boolean;
constructor(path: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {

View file

@ -12,7 +12,7 @@ export class ExcludePattern {
enableCollapsing: boolean;
excludeFromFolderOverview: boolean;
hideInSettings: boolean;
detached: boolean;
detached: boolean = false;
detachedFilePath?: string;
showFolderNote: boolean;
constructor(

View file

@ -5,14 +5,14 @@ export class WhitelistedFolder {
path: string;
string: string;
subFolders: boolean;
enableSync: boolean;
enableAutoCreate: boolean;
enableFolderNote: boolean;
disableCollapsing: boolean;
showInFolderOverview: boolean;
hideInFileExplorer: boolean;
enableSync: boolean = false;
enableAutoCreate: boolean = false;
enableFolderNote: boolean = false;
disableCollapsing: boolean = false;
showInFolderOverview: boolean = false;
hideInFileExplorer: boolean = false;
position: number;
hideInSettings: boolean;
hideInSettings: boolean = false;
constructor(path: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {
this.type = 'folder';
this.id = id || crypto.randomUUID();

View file

@ -6,13 +6,13 @@ export class WhitelistedPattern {
path: string;
position: number;
subFolders: boolean;
enableSync: boolean;
enableAutoCreate: boolean;
enableFolderNote: boolean;
disableCollapsing: boolean;
showInFolderOverview: boolean;
hideInFileExplorer: boolean;
hideInSettings: boolean;
enableSync: boolean = false;
enableAutoCreate: boolean = false;
enableFolderNote: boolean = false;
disableCollapsing: boolean = false;
showInFolderOverview: boolean = false;
hideInFileExplorer: boolean = false;
hideInSettings: boolean = false;
constructor(
pattern: string,
position: number,

View file

@ -49,9 +49,9 @@ function aggregateFlags(
for (const property of propertiesToCopy) {
const value = (matchedFolder as Partial<ExcludedFolder>)[property];
if (value === true) {
(result as Partial<ExcludedFolder>)[property] = true as never;
(result)[property] = true as never;
} else if (!value) {
(result as Partial<ExcludedFolder>)[property] = false as never;
(result)[property] = false as never;
}
}
}
@ -117,7 +117,7 @@ export function getExcludedFolder(
const whitelist = getWhitelistedFolder(
plugin,
path,
) as WhitelistedFolder | WhitelistedPattern | undefined;
);
let skipWhitelist = ignoreWhitelist ?? false;
if (excluded?.detached) skipWhitelist = true;
@ -235,7 +235,6 @@ export function addExcludeFolderListItem(
plugin,
false,
);
// @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 || '');

View file

@ -95,7 +95,6 @@ export function addExcludePatternListItem(
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
// @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);

View file

@ -132,7 +132,6 @@ export function addWhitelistFolderListItem(
plugin,
true,
);
// @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);

View file

@ -97,7 +97,6 @@ export function addWhitelistedPatternListItem(
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
// @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);

View file

@ -25,7 +25,7 @@ export default class PatternSettings extends Modal {
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

View file

@ -34,7 +34,7 @@ export default class WhitelistFolderSettings extends Modal {
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

View file

@ -23,7 +23,7 @@ export default class WhitelistPatternSettings extends Modal {
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

View file

@ -25,12 +25,12 @@ interface WrappedUpdateData {
export class FrontMatterTitlePluginHandler {
plugin: FolderNotesPlugin;
app: App;
app!: App;
api: ApiInterface | null = null;
deffer: DeferInterface | null = null;
modifiedFolders: Map<string, TFolder> = new Map();
eventRef: ListenerRef<'manager:update'>;
dispatcher: EventDispatcherInterface<Events>;
eventRef: ListenerRef<'manager:update'> | null = null;
dispatcher: EventDispatcherInterface<Events> | null = null;
constructor(plugin: FolderNotesPlugin) {
this.plugin = plugin;
this.app = plugin.app;
@ -66,13 +66,13 @@ export class FrontMatterTitlePluginHandler {
// this.plugin.app.vault.getFiles().forEach((file) => {
// this.handleRename({ id: '', result: false, path: file.path }, false);
// });
this.plugin.updateAllBreadcrumbs();
plugin.updateAllBreadcrumbs();
}
})();
}
deleteEvent(): void {
if (this.eventRef) {
if (this.eventRef && this.dispatcher) {
this.dispatcher.removeListener(this.eventRef);
}
}

View file

@ -1,6 +1,6 @@
import { Keymap, Platform } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { getFolderNote } from 'src/functions/folderNoteFunctions';
import { getFolderNote, getFolderNoteFolder } from 'src/functions/folderNoteFunctions';
import { handleViewHeaderClick } from './handleClick';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import { updateCSSClassesForFolder } from 'src/functions/styleFunctions';
@ -43,10 +43,10 @@ function initializeFolderNoteFeatures(plugin: FolderNotesPlugin): void {
}
function initializeBreadcrumbs(plugin: FolderNotesPlugin): void {
const titleContainers = document.querySelectorAll('.view-header-title-container');
const titleContainers = activeDocument.querySelectorAll('.view-header-title-container');
if (!titleContainers.length) return;
titleContainers.forEach((container) => {
if (!(container instanceof HTMLElement)) return;
if (!(container.instanceOf(HTMLElement))) return;
scheduleIdle(() => updateFolderNamesInPath(plugin, container), { timeout: 1000 });
});
}
@ -62,7 +62,7 @@ function observeFolderTitleMutations(plugin: FolderNotesPlugin): void {
fileExplorerMutationObserver = new MutationObserver((mutations) => {
for (const mutation of mutations) {
for (const node of Array.from(mutation.addedNodes)) {
if (!(node instanceof HTMLElement)) continue;
if (!(node.instanceOf(HTMLElement))) continue;
processAddedFolders(node, plugin);
}
}
@ -72,7 +72,7 @@ function observeFolderTitleMutations(plugin: FolderNotesPlugin): void {
}
function initializeAllFolderTitles(plugin: FolderNotesPlugin): void {
const allTitles = document.querySelectorAll('.nav-folder-title-content');
const allTitles = activeDocument.querySelectorAll('.nav-folder-title-content');
for (const title of Array.from(allTitles)) {
const folderTitle = title as HTMLElement;
const folderEl = folderTitle.closest('.nav-folder-title');
@ -97,7 +97,7 @@ function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin): void
const folderPath = folderEl?.getAttribute('data-path') || '';
const RETRY_TIMEOUT = 50;
if (!folderEl || !folderPath) {
setTimeout(() => {
window.setTimeout(() => {
const retryFolderEl = folderTitle.closest('.nav-folder-title');
const retryFolderPath = retryFolderEl?.getAttribute('data-path') || '';
if (retryFolderEl && retryFolderPath) {
@ -158,27 +158,60 @@ async function setupFolderTitle(
});
}
// eslint-disable-next-line complexity
async function updateFolderNamesInPath(
plugin: FolderNotesPlugin,
titleContainer: HTMLElement,
): Promise<void> {
const headers = titleContainer.querySelectorAll('span.view-header-breadcrumb');
const titleParent = titleContainer.querySelector('.view-header-title-parent');
let path = '';
const TRAILING_SLASH_LENGTH = 1;
headers.forEach(async (breadcrumb: HTMLElement) => {
path += breadcrumb.getAttribute('old-name') ?? (breadcrumb as HTMLElement).innerText.trim();
if (titleParent?.childNodes.length === 0) {
titleContainer.classList.remove('hide-folder-note-title-in-path');
}
for (const breadcrumb of Array.from(titleParent?.childNodes ?? [])) {
if (!(breadcrumb instanceof HTMLElement)) continue;
if (breadcrumb.classList.contains('view-header-breadcrumb-separator')) {
if (breadcrumb.nextSibling === null) {
breadcrumb.classList.add('is-last-separator');
}
continue;
}
path += breadcrumb.getAttribute('old-name') ?? (breadcrumb).innerText.trim();
path += '/';
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');
const viewHeaderTitle = titleContainer.querySelector('.view-header-title');
if (viewHeaderTitle && folderNote) {
const filePath = path + (viewHeaderTitle as HTMLElement).innerText.trim() + '.md';
const file = plugin.app.vault.getAbstractFileByPath(
filePath);
const folder = getFolderNoteFolder(plugin, folderNote, file?.name ?? '');
if (folder && file && file.path === folderNote?.path && file.parent?.path !== '/') {
viewHeaderTitle.parentElement?.classList.add('hide-folder-note-title-in-path');
viewHeaderTitle.classList.add('path-is-folder-note');
} else {
viewHeaderTitle.parentElement?.classList.remove('hide-folder-note-title-in-path');
viewHeaderTitle.classList.remove('path-is-folder-note');
}
}
if (!folderNote) {
breadcrumb.classList.remove('has-folder-note');
breadcrumb.removeAttribute('data-path');
continue;
}
breadcrumb.classList.add('has-folder-note');
breadcrumb?.setAttribute('data-path', path.slice(0, -TRAILING_SLASH_LENGTH));
if (!breadcrumb.onclick) {
breadcrumb.addEventListener('click', (e) => {
handleViewHeaderClick(e as MouseEvent, plugin);
handleViewHeaderClick(e, plugin);
}, { capture: true });
}
@ -188,7 +221,7 @@ async function updateFolderNamesInPath(
true,
);
}
});
}
}
// Schedules a callback to run when the browser is idle, or after a timeout as a fallback.
@ -202,6 +235,6 @@ function scheduleIdle(callback: () => void, options?: { timeout: number }): void
};
windowWithIdle.requestIdleCallback(callback, options);
} else {
setTimeout(callback, options?.timeout || DEFAULT_IDLE_TIMEOUT);
globalThis.setTimeout(callback, options?.timeout || DEFAULT_IDLE_TIMEOUT);
}
}

View file

@ -33,8 +33,8 @@ export async function handleViewHeaderClick(
} else if (event.altKey || Keymap.isModEvent(event) === 'tab') {
if (await handleFolderNoteCreation(event, plugin, folderPath)) return;
}
(event.target as HTMLElement).onclick = null;
(event.target as HTMLElement).click();
(event.target).onclick = null;
(event.target).click();
}
async function isExcludedFolder(
@ -58,7 +58,7 @@ async function handleFolderNoteReveal(plugin: FolderNotesPlugin, folderNote: TFi
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);
window.setTimeout(() => { fileExplorerPlugin.revealInFolder(folderNote); }, OPEN_SIDEBAR_DELAY);
} else if (fileExplorerPlugin && Platform.isDesktop && plugin.settings.openSidebar.desktop) {
fileExplorerPlugin.revealInFolder(folderNote);
}

View file

@ -11,6 +11,7 @@ import {
removeCSSClassFromFileExplorerEL,
addCSSClassToFileExplorerEl,
} from 'src/functions/styleFunctions';
import { isFileInAttachmentFolder } from '@app/functions/utils';
export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin): Promise<void> {
if (!plugin.app.workspace.layoutReady) return;
@ -50,7 +51,8 @@ async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin): Promi
if (folderNote && folderNote.path === file.path) {
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
} else if (plugin.settings.autoCreateForFiles) {
} else if (plugin.settings.autoCreateForFiles && !isFileInAttachmentFolder(plugin, file)) {
if (!plugin.settings.supportedFileTypes.includes(file.extension)) { return; }
if (!file.parent) { return; }
const newFolder = await plugin.app.fileManager.createNewFolder(file.parent);
turnIntoFolderNote(plugin, file, newFolder);
@ -78,6 +80,6 @@ async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin):
const folderNote = getFolderNote(plugin, folder.path);
if (folderNote) return;
createFolderNote(plugin, folder.path, openFile, undefined, true);
void createFolderNote(plugin, folder.path, openFile, undefined, true);
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
}

View file

@ -375,7 +375,7 @@ function shouldCreateFolderNote(
detachedExcludedFolder: ExcludedFolder | undefined,
): boolean {
return !excludedFolder?.disableFolderNote
&& folderName === (newFolder as TFolder)?.name
&& folderName === (newFolder)?.name
&& !detachedExcludedFolder;
}
@ -384,7 +384,7 @@ function shouldRemoveFolderNoteClasses(
folderName: string,
newFolder: TAbstractFile | null,
): boolean {
return excludedFolder?.disableFolderNote || (folderName !== (newFolder as TFolder)?.name);
return excludedFolder?.disableFolderNote || (folderName !== (newFolder)?.name);
}
function handleFolderNoteCreation(
@ -424,8 +424,8 @@ function shouldRenameFolderOnFileRename(
): boolean {
if (!oldFolder) return false;
const oldFolderAsFolder = oldFolder as TFolder;
const newFolderAsFolder = newFolder as TFolder;
const oldFolderAsFolder = oldFolder;
const newFolderAsFolder = newFolder;
return (oldFolderName === oldFolderAsFolder.name
&& newFolderAsFolder?.path === oldFolderAsFolder.path)

View file

@ -4,9 +4,9 @@ export class ListComponent {
emitter: CustomEventEmitter;
containerEl: HTMLElement;
controlEl: HTMLElement;
emptyStateEl: HTMLElement;
emptyStateEl!: HTMLElement;
listEl: HTMLElement;
values: string[];
values!: string[];
defaultValues: string[];
constructor(containerEl: HTMLElement, values: string[] = [], defaultValues: string[] = []) {
this.emitter = new CustomEventEmitter();
@ -48,17 +48,23 @@ export class ListComponent {
addElement(value: string): void {
this.listEl.createSpan('setting-hotkey', (span) => {
if (value.toLocaleLowerCase() === 'md') {
span.innerText = 'markdown';
const markdown = 'markdown';
span.innerText = markdown;
} else {
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;
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svg, 'image/svg+xml');
const svgNode = svgDoc.documentElement;
if (svgNode) {
svgElement.appendChild(activeDocument.importNode(svgNode, true));
}
removeSpan.onClickEvent(() => {
this.removeValue(value);
span.remove();
@ -78,7 +84,13 @@ export class ListComponent {
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;
const svgElement = resetButton.createEl('span', { cls: 'ofn-list-item-remove-icon' });
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svg, 'image/svg+xml');
const svgNode = svgDoc.documentElement;
if (svgNode) {
svgElement.appendChild(activeDocument.importNode(svgNode, true));
}
resetButton.onClickEvent(() => {
this.setValues(this.defaultValues);
});

View file

@ -1,19 +1,29 @@
import type { WorkspaceLeaf, App } from 'obsidian';
import type { WorkspaceLeaf, App, TFile } from 'obsidian';
interface ExcalidrawPlugin {
setExcalidrawView(leaf: WorkspaceLeaf): void;
openDrawing(file: TFile): void;
getBlankDrawing(): Promise<string>;
}
export async function openExcalidrawView(
app: App,
leaf: WorkspaceLeaf,
file: TFile,
): Promise<void> {
const { excalidraw, excalidrawEnabled } = await getExcalidrawPlugin(app);
if (excalidrawEnabled && excalidraw) {
excalidraw.setExcalidrawView(leaf);
excalidraw.openDrawing(file);
}
}
export async function getDefaultTemplate(app: App): Promise<string> {
const { excalidraw, excalidrawEnabled } = await getExcalidrawPlugin(app);
if (excalidrawEnabled && excalidraw) {
return excalidraw.getBlankDrawing();
}
return '';
}
export async function getExcalidrawPlugin(
app: App,
): Promise<{ excalidraw: ExcalidrawPlugin | null; excalidrawEnabled: boolean }> {

View file

@ -20,13 +20,14 @@ import {
updateExcludedFolder,
} from '../ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from '../ExcludeFolders/ExcludeFolder';
import { openExcalidrawView } from './excalidraw';
import { getDefaultTemplate, openExcalidrawView } from './excalidraw';
import { AskForExtensionModal } from 'src/modals/AskForExtension';
import {
addCSSClassToFileExplorerEl,
removeCSSClassFromFileExplorerEL,
removeActiveFolder,
setActiveFolder,
updateCSSClassesForFolderNote,
} from 'src/functions/styleFunctions';
import {
getFolderNameFromPathString,
@ -35,21 +36,6 @@ import {
} from 'src/functions/utils';
const defaultExcalidrawTemplate = `---
excalidraw-plugin: parsed
tags: [excalidraw]
---
== Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ==
%%
# Drawing
\`\`\`json
{'type":"excalidraw","version":2,"source":"https://github.com/zsviczian/obsidian-excalidraw-plugin/releases/tag/1.9.20","elements":[],"appState":{"gridSize":null,"viewBackgroundColor":"#ffffff'}}
\`\`\`
%%`;
export async function createFolderNote(
plugin: FolderNotesPlugin,
@ -121,7 +107,7 @@ export async function createFolderNote(
}
await leaf.openFile(folderNote);
if (plugin.settings.folderNoteType === '.excalidraw' || extension === '.excalidraw') {
openExcalidrawView(plugin.app, leaf);
openExcalidrawView(plugin.app, folderNote);
}
}
@ -189,10 +175,10 @@ async function handleCreateFolderNote(plugin: FolderNotesPlugin, folderNoteType:
if (
extension === '.excalidraw' &&
!content.includes(
'==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==',
'==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this activeDocument. ⚠==',
)
) {
content = defaultExcalidrawTemplate;
content = await getDefaultTemplate(plugin.app);
}
} else {
plugin.app.vault.readBinary(templateFile).then(async (data) => {
@ -208,7 +194,7 @@ async function handleCreateFolderNote(plugin: FolderNotesPlugin, folderNoteType:
plugin.settings.folderNoteType === '.excalidraw' ||
extension === '.excalidraw'
) {
content = defaultExcalidrawTemplate;
content = await getDefaultTemplate(plugin.app);
} else if (plugin.settings.folderNoteType === '.canvas') {
content = '{}';
}
@ -227,7 +213,8 @@ async function handleTurnNoteIntoFolderNote(
): Promise<void> {
deleteExcludedFolder(plugin, detachedFolder);
removeCSSClassFromFileExplorerEL(folderNote?.path, 'is-folder-note', false, plugin);
const folder = plugin.app.vault.getAbstractFileByPath(folderPath) as TFolder;
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
if (!(folder instanceof TFolder)) return;
if (!folderNote || folderNote.basename !== fileName) return;
let count = 1;
const baseName = removeExtension(folderNote.path);
@ -316,12 +303,12 @@ export async function turnIntoFolderNote(
}
if (detachedExcludedFolder) {
deleteExcludedFolder(plugin, detachedExcludedFolder);
void deleteExcludedFolder(plugin, detachedExcludedFolder);
}
await plugin.app.fileManager.renameFile(file, path);
addCSSClassToFileExplorerEl(path, 'is-folder-note', false, plugin, true);
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
void addCSSClassToFileExplorerEl(path, 'is-folder-note', false, plugin, true);
void addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
removeActiveFolder(plugin);
setActiveFolder(folder.path, plugin);
@ -506,6 +493,7 @@ export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile): void {
undefined,
plugin,
);
excludedFolder.showFolderNote = true;
excludedFolder.hideInSettings = true;
excludedFolder.disableFolderNote = true;
excludedFolder.disableSync = true;
@ -514,6 +502,7 @@ export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile): void {
excludedFolder.detached = true;
excludedFolder.detachedFilePath = file.path;
addExcludedFolder(plugin, excludedFolder);
void updateCSSClassesForFolderNote(file.path, plugin);
}
@ -567,6 +556,13 @@ export function getFolderNoteFolder(
filePath = folderNote.path;
}
const folderName = extractFolderName(plugin.settings.folderNoteName, fileName);
if (!plugin.settings.folderNoteName.includes('{{folder_name}}') && plugin.settings.storageLocation === 'insideFolder') {
if (folderNote instanceof TFile) {
return folderNote.parent;
}
const file = plugin.app.vault.getAbstractFileByPath(filePath);
return file instanceof TFile ? file.parent : null;
}
if (!folderName) return null;
let folderPath = getFolderPathFromString(filePath);
if (plugin.settings.storageLocation === 'parentFolder') {

View file

@ -28,6 +28,7 @@ export function refreshAllFolderStyles(forceReload = false, plugin: FolderNotesP
/**
* @description Updates the CSS classes for a specific folder in the file explorer.
*/
// eslint-disable-next-line complexity
export async function updateCSSClassesForFolder(
folderPath: string,
plugin: FolderNotesPlugin,
@ -45,6 +46,7 @@ export async function updateCSSClassesForFolder(
if (!folderNote || detachedFolderNote) {
removeCSSClassFromFileExplorerEL(folder?.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder?.path, 'only-has-folder-note', true, plugin);
removeCSSClassFromFileExplorerEL(folderNote?.path, 'is-folder-note', false, plugin);
return;
}
@ -149,7 +151,7 @@ export async function addCSSClassToFileExplorerEl(
if (!fileExplorerItem) {
if (waitForCreate && count < MAX_RETRIES) {
await new Promise((r) => setTimeout(r, RETRY_DELAY));
await new Promise((r) => window.setTimeout(r, RETRY_DELAY));
addCSSClassToFileExplorerEl(path, cssClass, parent, plugin, waitForCreate, count + 1);
return;
}
@ -162,7 +164,7 @@ export async function addCSSClassToFileExplorerEl(
}
} else {
fileExplorerItem.addClass(cssClass);
document.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
activeDocument.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
item.addClass(cssClass);
});
}
@ -181,7 +183,7 @@ export function removeCSSClassFromFileExplorerEL(
): void {
if (!path) return;
const fileExplorerItem = getFileExplorerElement(path, plugin);
document.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
activeDocument.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
item.removeClass(cssClass);
});
if (!fileExplorerItem) { return; }

View file

@ -1,10 +1,15 @@
import { TFolder, TFile, View } from 'obsidian';
import { TFolder, TFile, View, type TAbstractFile } from 'obsidian';
import type { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import { getFolderNote } from './folderNoteFunctions';
import type FolderNotesPlugin from 'src/main';
import type { FileExplorerLeaf, FileTreeItem, TreeNode } from 'obsidian-typings';
import type { FileTreeItem } from 'obsidian-typings';
import type FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
function isFileExplorerWorkspaceLeaf(leaf: unknown): leaf is FileExplorerWorkspaceLeaf {
const tabHeaderEl = (leaf as { tabHeaderEl?: HTMLElement | null } | null)?.tabHeaderEl;
return tabHeaderEl?.dataset?.type === 'file-explorer';
}
export function getFileNameFromPathString(path: string): string {
return path.substring(path.lastIndexOf('/') >= 0 ? path.lastIndexOf('/') + 1 : 0);
}
@ -37,57 +42,70 @@ export function getFolderPathFromString(path: string): string {
}
export function getParentFolderPath(path: string): string {
return this.getFolderPathFromString(this.getFolderPathFromString(path));
return getFolderPathFromString(getFolderPathFromString(path));
}
export function getFileExplorer(
plugin: FolderNotesPlugin | FolderOverviewPlugin,
): FileExplorerWorkspaceLeaf | undefined {
// eslint-disable-next-line max-len
let leaf = plugin.app.workspace.getLeavesOfType('file-explorer')[0] as unknown as FileExplorerWorkspaceLeaf;
let leaf = plugin.app.workspace.getLeavesOfType('file-explorer')[0];
if (!leaf) { return undefined; }
/* make.md plugin integration */
if ((leaf.containerEl?.lastChild as HTMLElement)?.dataset?.type == 'mk-path-view') {
if ((leaf.containerEl?.lastChild as HTMLElement)?.dataset?.type === 'mk-path-view') {
plugin.app.workspace.iterateAllLeaves((x) => {
if ((x as FileExplorerWorkspaceLeaf).tabHeaderEl?.dataset?.type == 'file-explorer') {
leaf = x as FileExplorerWorkspaceLeaf;
if (isFileExplorerWorkspaceLeaf(x)) {
leaf = x;
}
});
}
return leaf;
return isFileExplorerWorkspaceLeaf(leaf) ? leaf : undefined;
}
export function getFileExplorerView(plugin: FolderNotesPlugin): FileExplorerView | undefined {
return getFileExplorer(plugin)?.view;
}
export function getFocusedItem(plugin: FolderNotesPlugin): TreeNode<FileTreeItem> | null {
const fileExplorer = getFileExplorer(plugin) as unknown as FileExplorerLeaf | undefined;
export function getFocusedItem(plugin: FolderNotesPlugin): FileTreeItem | null {
const fileExplorer = getFileExplorer(plugin);
if (!fileExplorer) { return null; }
const { focusedItem } = fileExplorer.view.tree;
return focusedItem;
}
export function getFileExplorerActiveFolder(): TFolder | null {
export function getFileExplorerActiveFolder(plugin: FolderNotesPlugin): TFolder | null {
// Check if the active view is a file explorer.
const view = this.app.workspace.getActiveViewOfType(View);
const view = plugin.app.workspace.getActiveViewOfType(View);
if (view?.getViewType() !== 'file-explorer') return null;
// Check if there is a focused or active item in the file explorer.
const fe = view as FileExplorerView;
const activeFileOrFolder =
fe.tree.focusedItem?.file ?? fe.activeDom?.file;
if (!(activeFileOrFolder instanceof TFolder)) return null;
return activeFileOrFolder as TFolder;
return activeFileOrFolder;
}
export function getFileExplorerActiveFolderNote(): TFile | null {
const folder = getFileExplorerActiveFolder();
export function getFileExplorerActiveFolderNote(plugin: FolderNotesPlugin): TFile | null {
const folder = getFileExplorerActiveFolder(plugin);
if (!folder) return null;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
const folderNote = getFolderNote(plugin, folder.path);
if (!(folderNote instanceof TFile)) return null;
return folderNote;
}
export function getAttachmentFolderPath(plugin: FolderNotesPlugin): string {
const attachmentFolderPath = plugin.app.vault.getConfig('attachmentFolderPath') as string;
const cleanAttachmentFolderPath = attachmentFolderPath?.replace('./', '') || '';
return cleanAttachmentFolderPath;
}
export function isFileInAttachmentFolder(plugin: FolderNotesPlugin, file: TAbstractFile): boolean {
const attachmentFolderPath = getAttachmentFolderPath(plugin);
if (!attachmentFolderPath) return false;
return file.path.startsWith(attachmentFolderPath + '/')
|| file.path.includes('/' + attachmentFolderPath + '/');
}

View file

@ -1,5 +1,5 @@
import {
type TAbstractFile,
type App, type TAbstractFile,
type MarkdownPostProcessorContext,
type WorkspaceLeaf,
Plugin, TFile, TFolder,
@ -9,6 +9,7 @@ import {
import {
type FolderNotesSettings, DEFAULT_SETTINGS, SettingsTab,
} from './settings/SettingsTab';
import { FolderOverview, type defaultOverviewSettings } from './obsidian-folder-overview/src/FolderOverview';
import { Commands } from './Commands';
import type { FileExplorerWorkspaceLeaf } from './globals';
import {
@ -21,7 +22,6 @@ import {
import { handleCreate } from './events/handleCreate';
import { FrontMatterTitlePluginHandler } from './events/FrontMatterTitle';
import { FolderOverviewSettings } from './obsidian-folder-overview/src/modals/Settings';
import { FolderOverview } from './obsidian-folder-overview/src/FolderOverview';
import { TabManager } from './events/TabManager';
import './functions/ListComponent';
import { handleDelete } from './events/handleDelete';
@ -30,71 +30,80 @@ import {
refreshAllFolderStyles, setActiveFolder, removeActiveFolder,
} from './functions/styleFunctions';
import { getExcludedFolder } from './ExcludeFolders/functions/folderFunctions';
import { getFileExplorer } from './functions/utils';
import { FOLDER_OVERVIEW_VIEW, FolderOverviewView } from './obsidian-folder-overview/src/view';
import { registerOverviewCommands } from './obsidian-folder-overview/src/Commands';
import { updateOverviewView, updateViewDropdown } from './obsidian-folder-overview/src/main';
import { FvIndexDB } from './obsidian-folder-overview/src/utils/IndexDB';
import { updateAllOverviews } from './obsidian-folder-overview/src/utils/functions';
interface FileExplorerPluginLike extends Plugin {
revealInFolder: (file: TAbstractFile) => void;
}
interface DragManagerLike {
draggable?: {
file?: TAbstractFile;
type?: string;
} | null;
setAction(action: string): void;
}
interface ClipboardManagerLike {
app: App & {
dragManager?: DragManagerLike;
};
handleDragOver: (evt: DragEvent, ...args: unknown[]) => void;
handleDrop: (evt: DragEvent, ...args: unknown[]) => void;
}
interface EditModeLike {
clipboardManager: ClipboardManagerLike;
}
interface ViewWithEditModes {
editMode?: EditModeLike;
sourceMode?: EditModeLike;
}
interface ActiveEditorLike {
editMode?: EditModeLike;
}
type LegacySettingsData = Partial<FolderNotesSettings> & {
allowWhitespaceCollapsing?: boolean;
defaultOverview?: defaultOverviewSettings;
};
export default class FolderNotesPlugin extends Plugin {
settings: FolderNotesSettings;
settingsTab: SettingsTab;
activeFolderDom: HTMLElement | null;
activeFileExplorer: FileExplorerWorkspaceLeaf;
settings!: FolderNotesSettings;
settingsTab!: SettingsTab;
activeFolderDom!: HTMLElement | null;
activeFileExplorer!: FileExplorerWorkspaceLeaf;
fmtpHandler: FrontMatterTitlePluginHandler | null = null;
hoveredElement: HTMLElement | null = null;
mouseEvent: MouseEvent | null = null;
hoverLinkTriggered = false;
tabManager: TabManager;
tabManager!: TabManager;
settingsOpened = false;
askModalCurrentlyOpen = false;
fvIndexDB: FvIndexDB;
fvIndexDB!: FvIndexDB;
async onload(): Promise<void> {
console.log('loading folder notes plugin');
console.debug('loading folder notes plugin');
await this.loadSettings();
this.settingsTab = new SettingsTab(this.app, this);
this.addSettingTab(this.settingsTab);
this.saveSettings();
await this.saveSettings();
this.fvIndexDB = new FvIndexDB(this);
// 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.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.ignoreAttachmentFolder) {
document.body.classList.add('fn-ignore-attachment-folder');
}
if (!this.settings.highlightFolder) {
document.body.classList.add('disable-folder-highlight');
}
if (requireApiVersion('1.7.2')) {
document.body.classList.add('version-1-7-2');
}
this.addSettingCssClasses();
new Commands(this.app, this).registerCommands();
registerOverviewCommands(this);
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
this.app.workspace.onLayoutReady(() => this.onLayoutReady());
if (!this.settings.persistentSettingsTab.afterRestart) {
this.settings.settingsTab = 'general';
@ -123,7 +132,7 @@ export default class FolderNotesPlugin extends Plugin {
this.hoverLinkTriggered = true;
});
this.registerEvent(this.app.workspace.on('file-open', async (openFile: TFile | null) => {
this.registerEvent(this.app.workspace.on('file-open', (openFile: TFile | null) => {
removeActiveFolder(this);
if (!openFile || !openFile.basename) { return; }
@ -139,7 +148,9 @@ export default class FolderNotesPlugin extends Plugin {
}));
this.registerEvent(this.app.vault.on('create', (file: TAbstractFile) => {
handleCreate(file, this);
handleCreate(file, this).catch((err) => {
console.error(err); new Notice('Error handling file creation');
});
this.handleVaultChange();
}));
@ -161,12 +172,64 @@ export default class FolderNotesPlugin extends Plugin {
);
}
addSettingCssClasses(): void {
activeDocument.body.classList.add('folder-notes-plugin');
if (this.settings.hideFolderNote) { activeDocument.body.classList.add('hide-folder-note'); }
if (this.settings.hideCollapsingIconForEmptyFolders) {
activeDocument.body.classList.add('fn-hide-empty-collapse-icon');
}
if (this.settings.hideFolderNoteNameInPath) {
activeDocument.body.classList.add('folder-note-hide-name-path');
}
if (this.settings.underlineFolder) {
activeDocument.body.classList.add('folder-note-underline');
}
if (this.settings.boldName) { activeDocument.body.classList.add('folder-note-bold'); }
if (this.settings.cursiveName) { activeDocument.body.classList.add('folder-note-cursive'); }
if (this.settings.boldNameInPath) {
activeDocument.body.classList.add('folder-note-bold-path');
}
if (this.settings.cursiveNameInPath) {
activeDocument.body.classList.add('folder-note-cursive-path');
}
if (this.settings.underlineFolderInPath) {
activeDocument.body.classList.add('folder-note-underline-path');
}
if (this.settings.stopWhitespaceCollapsing) {
activeDocument.body.classList.add('fn-whitespace-stop-collapsing');
}
if (this.settings.hideCollapsingIcon) {
activeDocument.body.classList.add('fn-hide-collapse-icon');
}
if (this.settings.ignoreAttachmentFolder) {
activeDocument.body.classList.add('fn-ignore-attachment-folder');
}
if (!this.settings.highlightFolder) {
activeDocument.body.classList.add('disable-folder-highlight');
}
if (requireApiVersion('1.7.2')) {
activeDocument.body.classList.add('version-1-7-2');
}
}
onLayoutReady(): void {
if (!this._loaded) {
return;
}
registerFileExplorerObserver(this);
const fileExplorer = getFileExplorer(this);
// @ts-expect-error use internal API
const infinityScroll = fileExplorer?.view?.tree?.infinityScroll;
if (infinityScroll) {
// increase infinity scroll buffer to show hidden folder notes
// Issue: https://github.com/LostPaul/obsidian-folder-notes/issues/274
infinityScroll.rootMargin = 1.5;
}
this.registerView(FOLDER_OVERVIEW_VIEW, (leaf: WorkspaceLeaf) => {
return new FolderOverviewView(leaf, this);
});
@ -181,37 +244,46 @@ export default class FolderNotesPlugin extends Plugin {
this.tabManager = new TabManager(this);
this.tabManager.updateTabs();
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
this.registerDomEvent(activeDocument, 'click', (evt: MouseEvent) => {
this.handleFileExplorerClick(evt);
}, true);
// Handle middle mouse button clicks
this.registerDomEvent(document, 'auxclick', (evt: MouseEvent) => {
this.registerDomEvent(activeDocument, 'auxclick', (evt: MouseEvent) => {
const rightClick = 2;
if (evt.button === rightClick) return;
this.handleFileExplorerClick(evt);
}, true);
const fileExplorerPlugin = this.app.internalPlugins.getEnabledPluginById('file-explorer');
if (fileExplorerPlugin) {
const originalRevealInFolder = fileExplorerPlugin.revealInFolder
.bind(fileExplorerPlugin);
fileExplorerPlugin.revealInFolder = (file: TAbstractFile): void => {
const fileExplorer = fileExplorerPlugin as unknown as FileExplorerPluginLike;
const originalRevealInFolder =
(fileExplorer.revealInFolder as unknown as FileExplorerPluginLike['revealInFolder'])
.bind(fileExplorer);
fileExplorer.revealInFolder = (file: TAbstractFile): void => {
if (file instanceof TFile) {
const folder = getFolder(this, file);
if (folder instanceof TFolder) {
const folderNote = getFolderNote(this, folder.path);
if (!folderNote || folderNote.path !== file.path) {
return originalRevealInFolder.call(fileExplorerPlugin, file);
originalRevealInFolder(file);
return;
}
document.body.classList.remove('hide-folder-note');
originalRevealInFolder.call(fileExplorerPlugin, folder);
activeDocument.body.classList.remove('hide-folder-note');
originalRevealInFolder(folder);
const FOLDER_REVEAL_DELAY = 100;
setTimeout(() => {
document.body.classList.add('hide-folder-note');
window.setTimeout(() => {
activeDocument.body.classList.add('hide-folder-note');
}, FOLDER_REVEAL_DELAY);
return;
}
}
return originalRevealInFolder.call(fileExplorerPlugin, file);
if (file instanceof TFolder || file instanceof TFile) {
originalRevealInFolder(file);
return;
}
};
}
@ -220,25 +292,27 @@ export default class FolderNotesPlugin extends Plugin {
if (!view) { return; }
// @ts-expect-error use internal API
const editMode = view.editMode ?? view.sourceMode
// @ts-expect-error use internal API
?? this.app.workspace.activeEditor?.editMode;
const viewWithEditModes = view as ViewWithEditModes;
const activeEditor = this.app.workspace.activeEditor as ActiveEditorLike | undefined;
const editMode = viewWithEditModes.editMode ?? viewWithEditModes.sourceMode
?? activeEditor?.editMode;
const plugin = this;
if (!editMode) { return; }
const clipboardProto = editMode.clipboardManager.constructor.prototype;
const originalHandleDragOver = clipboardProto.handleDragOver;
const originalHandleDrop = clipboardProto.handleDrop;
const { clipboardManager } = editMode;
const clipboardProto = Object.getPrototypeOf(clipboardManager) as ClipboardManagerLike;
const originalHandleDragOver =
clipboardProto.handleDragOver as unknown as ClipboardManagerLike['handleDragOver'];
const originalHandleDrop =
clipboardProto.handleDrop as unknown as ClipboardManagerLike['handleDrop'];
const folderNotePlugin = this;
clipboardProto.handleDragOver = function (evt: DragEvent, ...args: unknown[]): void {
const { dragManager } = this.app;
const { dragManager } = (this as ClipboardManagerLike).app;
const draggable = dragManager?.draggable;
if (draggable?.file instanceof TFolder) {
const folderNote = getFolderNote(plugin, draggable.file.path);
const folderNote = getFolderNote(folderNotePlugin, draggable.file.path);
if (folderNote) {
dragManager.setAction(
window.i18next.t('interface.drag-and-drop.insert-link-here'),
@ -251,11 +325,11 @@ export default class FolderNotesPlugin extends Plugin {
};
clipboardProto.handleDrop = function (evt: DragEvent, ...args: unknown[]): void {
const { dragManager } = this.app;
const { dragManager } = (this as ClipboardManagerLike).app;
const draggable = dragManager?.draggable;
if (draggable?.file instanceof TFolder) {
const folderNote = getFolderNote(plugin, draggable.file.path);
const folderNote = getFolderNote(folderNotePlugin, draggable.file.path);
if (folderNote) {
draggable.file = folderNote;
draggable.type = 'file';
@ -274,7 +348,7 @@ export default class FolderNotesPlugin extends Plugin {
if (!this.settings.fvGlobalSettings.autoUpdateLinks) return;
const DEBOUNCE_DELAY = 2000;
debounce(() => {
updateAllOverviews(this);
void updateAllOverviews(this);
}, DEBOUNCE_DELAY, true)();
}
@ -305,7 +379,7 @@ export default class FolderNotesPlugin extends Plugin {
evt.stopImmediatePropagation();
}
openFolderNote(this, folderNote, evt);
void openFolderNote(this, folderNote, evt);
}
private isMobileClickDisabled(): boolean {
@ -316,9 +390,12 @@ export default class FolderNotesPlugin extends Plugin {
folderTitleEl: HTMLElement | null;
onlyClickedOnFolderTitle: boolean;
} {
const folderTitleEl = target.closest('.nav-folder-title') as HTMLElement | null;
const folderTitleEl = target.closest('.nav-folder-title');
const onlyClickedOnFolderTitle = !!target.closest('.nav-folder-title-content');
return { folderTitleEl, onlyClickedOnFolderTitle };
return {
folderTitleEl: folderTitleEl instanceof HTMLElement ? folderTitleEl : null,
onlyClickedOnFolderTitle,
};
}
private shouldIgnoreClickByWhitespaceOrCollapse(
@ -349,9 +426,9 @@ export default class FolderNotesPlugin extends Plugin {
}
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);
void createFolderNote(this, folderPath, true, undefined, true);
void addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, this);
void removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, this);
}
private shouldOpenNote(usedCtrl: boolean, evt: MouseEvent): boolean {
@ -371,7 +448,7 @@ export default class FolderNotesPlugin extends Plugin {
new FolderOverviewSettings(
this.app,
this,
parseYaml(source),
parseYaml(source) as defaultOverviewSettings,
ctx,
el,
this.settings.defaultOverview,
@ -395,7 +472,7 @@ export default class FolderNotesPlugin extends Plugin {
el,
defaultOverview,
);
folderOverview.create(this, el, ctx);
void folderOverview.create(this, el, ctx);
} else {
this.app.workspace.onLayoutReady(() => {
const folderOverview = new FolderOverview(
@ -405,7 +482,7 @@ export default class FolderNotesPlugin extends Plugin {
el,
this.settings.defaultOverview,
);
folderOverview.create(this, el, ctx);
void folderOverview.create(this, el, ctx);
});
}
} catch (e) {
@ -429,7 +506,7 @@ export default class FolderNotesPlugin extends Plugin {
}
if (!leaf) return;
workspace.revealLeaf(leaf);
void workspace.revealLeaf(leaf);
}
updateOverviewView: typeof updateOverviewView = updateOverviewView;
@ -442,12 +519,12 @@ export default class FolderNotesPlugin extends Plugin {
|| attachmentFolderPath === '';
const threshold = this.settings.storageLocation === 'insideFolder' ? 1 : 0;
if (folder.children.length === 0) {
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
void addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
}
attachmentFolderPath = `${folder.path}/${cleanAttachmentFolderPath}`;
if (folder.children.length === threshold) {
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
void addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
return true;
} else if (folder.children.length > threshold) {
if (attachmentsAreInRootFolder) {
@ -460,11 +537,15 @@ export default class FolderNotesPlugin extends Plugin {
attachmentFolder instanceof TFolder &&
folder.children.length <= threshold + 1
) {
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
addCSSClassToFileExplorerEl(
void addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
void addCSSClassToFileExplorerEl(
folder.path, 'fn-has-attachment-folder',
false, this,
);
void addCSSClassToFileExplorerEl(
folder.path, 'fn-has-attachment-folder',
true, this,
);
}
return folder.children.length <= threshold + 1;
}
@ -486,7 +567,7 @@ export default class FolderNotesPlugin extends Plugin {
let fileExplorerItem = getFileExplorerElement(folder.path, this);
if (!fileExplorerItem) {
if (waitForCreate && count < MAX_RETRY_COUNT) {
await new Promise<void>((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
await new Promise<void>((resolve) => window.setTimeout(resolve, RETRY_DELAY_MS));
void this.changeFolderNameInExplorer(folder, newName, waitForCreate, count + 1);
return;
}
@ -496,11 +577,11 @@ 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 as HTMLElement).innerText = newName;
(fileExplorerItem as HTMLElement).setAttribute('old-name', folder.name);
(fileExplorerItem).innerText = newName;
(fileExplorerItem).setAttribute('old-name', folder.name);
} else {
(fileExplorerItem as HTMLElement).innerText = folder.name;
(fileExplorerItem as HTMLElement).removeAttribute('old-name');
(fileExplorerItem).innerText = folder.name;
(fileExplorerItem).removeAttribute('old-name');
}
}
@ -521,7 +602,7 @@ export default class FolderNotesPlugin extends Plugin {
*/
updateAllBreadcrumbs(remove?: boolean): void {
if (!this.settings.frontMatterTitle.path && !remove) { return; }
const viewHeaderItems = document.querySelectorAll('span.view-header-breadcrumb');
const viewHeaderItems = activeDocument.querySelectorAll('span.view-header-breadcrumb');
const files = this.app.vault.getAllLoadedFiles().filter((file) => file instanceof TFolder);
viewHeaderItems.forEach((item) => {
if (!item.hasAttribute('data-path')) { return; }
@ -541,10 +622,10 @@ export default class FolderNotesPlugin extends Plugin {
onunload(): void {
unregisterFileExplorerObserver();
document.body.classList.remove('folder-notes-plugin');
document.body.classList.remove('folder-note-underline');
document.body.classList.remove('hide-folder-note');
document.body.classList.remove('fn-whitespace-stop-collapsing');
activeDocument.body.classList.remove('folder-notes-plugin');
activeDocument.body.classList.remove('folder-note-underline');
activeDocument.body.classList.remove('hide-folder-note');
activeDocument.body.classList.remove('fn-whitespace-stop-collapsing');
removeActiveFolder(this);
if (this.fmtpHandler) {
this.fmtpHandler.deleteEvent();
@ -552,7 +633,7 @@ export default class FolderNotesPlugin extends Plugin {
}
async loadSettings(): Promise<void> {
const data = await this.loadData();
const data = await this.loadData() as LegacySettingsData | null;
if (data) {
if (data.allowWhitespaceCollapsing === true) {
data.stopWhitespaceCollapsing = false;

View file

@ -38,7 +38,7 @@ export default class AddSupportedFileModal extends Modal {
}),
);
}
async onClose(): Promise<void> {
onClose(): void {
if (this.name.toLocaleLowerCase() === 'markdown') {
this.name = 'md';
}
@ -50,10 +50,13 @@ export default class AddSupportedFileModal extends Modal {
new Notice('This extension is already supported');
return;
} else {
await this.list.addValue(this.name.toLowerCase());
this.settingsTab.display();
this.plugin.saveSettings();
contentEl.empty();
// Run async operations without returning a Promise from onClose
void (async (): Promise<void> => {
await this.list.addValue(this.name.toLowerCase());
this.settingsTab.display();
await this.plugin.saveSettings();
contentEl.empty();
})();
}
}
}

View file

@ -39,7 +39,7 @@ export class AskForExtensionModal extends FuzzySuggestModal<string> {
onChooseItem(item: string, _evt: MouseEvent | KeyboardEvent): void {
this.plugin.askModalCurrentlyOpen = false;
this.extension = '.' + item;
createFolderNote(
void createFolderNote(
this.plugin,
this.folderPath,
this.openFile,

View file

@ -46,9 +46,9 @@ export default class ExistingFolderNoteModal extends Modal {
text: 'Rename and don\'t ask again',
});
confirmButton.classList.add('mod-warning', 'fn-confirmation-modal-button');
confirmButton.addEventListener('click', async () => {
confirmButton.addEventListener('click', () => {
this.plugin.settings.showRenameConfirmation = false;
this.plugin.saveSettings();
void this.plugin.saveSettings();
this.close();
turnIntoFolderNote(this.plugin, this.file, this.folder, this.folderNote, true);
});
@ -69,14 +69,14 @@ export default class ExistingFolderNoteModal extends Modal {
}
const button = buttonContainer.createEl('button', { text: 'Rename' });
button.classList.add('mod-warning', 'fn-confirmation-modal-button');
button.addEventListener('click', async () => {
this.plugin.saveSettings();
button.addEventListener('click', () => {
void this.plugin.saveSettings();
this.close();
turnIntoFolderNote(this.plugin, this.file, this.folder, this.folderNote, true);
});
button.focus();
const cancelButton = buttonContainer.createEl('button', { text: 'Cancel' });
cancelButton.addEventListener('click', async () => {
cancelButton.addEventListener('click', () => {
this.close();
});

@ -1 +1 @@
Subproject commit c5986efabab87155c1ab652920deebbc75b883af
Subproject commit f047859cc9a7f1c04ad35ecdc440a31a300bc6c2

View file

@ -28,12 +28,12 @@ export async function renderExcludeFolders(settingsTab: SettingsTab): Promise<vo
'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';

View file

@ -14,9 +14,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
settingsTab.plugin.settings.hideFolderNote = value;
await settingsTab.plugin.saveSettings();
if (value) {
document.body.classList.add('hide-folder-note');
activeDocument.body.classList.add('hide-folder-note');
} else {
document.body.classList.remove('hide-folder-note');
activeDocument.body.classList.remove('hide-folder-note');
}
settingsTab.display();
}),
@ -46,9 +46,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.setValue(!settingsTab.plugin.settings.stopWhitespaceCollapsing)
.onChange(async (value) => {
if (!value) {
document.body.classList.add('fn-whitespace-stop-collapsing');
activeDocument.body.classList.add('fn-whitespace-stop-collapsing');
} else {
document.body.classList.remove('fn-whitespace-stop-collapsing');
activeDocument.body.classList.remove('fn-whitespace-stop-collapsing');
}
settingsTab.plugin.settings.stopWhitespaceCollapsing = !value;
await settingsTab.plugin.saveSettings();
@ -119,9 +119,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.highlightFolder = value;
if (!value) {
document.body.classList.add('disable-folder-highlight');
activeDocument.body.classList.add('disable-folder-highlight');
} else {
document.body.classList.remove('disable-folder-highlight');
activeDocument.body.classList.remove('disable-folder-highlight');
}
await settingsTab.plugin.saveSettings();
}),
@ -136,9 +136,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.hideCollapsingIcon = value;
if (value) {
document.body.classList.add('fn-hide-collapse-icon');
activeDocument.body.classList.add('fn-hide-collapse-icon');
} else {
document.body.classList.remove('fn-hide-collapse-icon');
activeDocument.body.classList.remove('fn-hide-collapse-icon');
}
await settingsTab.plugin.saveSettings();
settingsTab.display();
@ -155,9 +155,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
settingsTab.plugin.settings.hideCollapsingIconForEmptyFolders = value;
await settingsTab.plugin.saveSettings();
if (value) {
document.body.classList.add('fn-hide-empty-collapse-icon');
activeDocument.body.classList.add('fn-hide-empty-collapse-icon');
} else {
document.body.classList.remove('fn-hide-empty-collapse-icon');
activeDocument.body.classList.remove('fn-hide-empty-collapse-icon');
}
settingsTab.display();
},
@ -171,9 +171,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.setValue(settingsTab.plugin.settings.ignoreAttachmentFolder)
.onChange(async (value) => {
if (value) {
document.body.classList.add('fn-ignore-attachment-folder');
activeDocument.body.classList.add('fn-ignore-attachment-folder');
} else {
document.body.classList.remove('fn-ignore-attachment-folder');
activeDocument.body.classList.remove('fn-ignore-attachment-folder');
}
settingsTab.plugin.settings.ignoreAttachmentFolder = value;
await settingsTab.plugin.saveSettings();
@ -190,9 +190,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.underlineFolder = value;
if (value) {
document.body.classList.add('folder-note-underline');
activeDocument.body.classList.add('folder-note-underline');
} else {
document.body.classList.remove('folder-note-underline');
activeDocument.body.classList.remove('folder-note-underline');
}
await settingsTab.plugin.saveSettings();
}),
@ -207,9 +207,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.boldName = value;
if (value) {
document.body.classList.add('folder-note-bold');
activeDocument.body.classList.add('folder-note-bold');
} else {
document.body.classList.remove('folder-note-bold');
activeDocument.body.classList.remove('folder-note-bold');
}
await settingsTab.plugin.saveSettings();
}),
@ -224,9 +224,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.cursiveName = value;
if (value) {
document.body.classList.add('folder-note-cursive');
activeDocument.body.classList.add('folder-note-cursive');
} else {
document.body.classList.remove('folder-note-cursive');
activeDocument.body.classList.remove('folder-note-cursive');
}
await settingsTab.plugin.saveSettings();
}),

View file

@ -36,11 +36,12 @@ export async function renderFolderOverview(settingsTab: SettingsTab): Promise<vo
span.setAttr('style', `color: ${accentColor};`);
pEl.appendChild(span);
createOverviewSettings(
void createOverviewSettings(
containerEl,
defaultOverviewSettings,
plugin,
plugin.settings.defaultOverview,
// eslint-disable-next-line @typescript-eslint/unbound-method
settingsTab.display,
undefined,
undefined,

View file

@ -1,3 +1,4 @@
/* eslint-disable max-len */
import { Setting, Platform } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
@ -10,7 +11,7 @@ import { refreshAllFolderStyles } from '../functions/styleFunctions';
import BackupWarningModal from './modals/BackupWarning';
import RenameFolderNotesModal from './modals/RenameFns';
let debounceTimer: NodeJS.Timeout;
let debounceTimer: number | undefined;
// eslint-disable-next-line complexity
export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
@ -26,9 +27,9 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.folderNoteName = value;
await settingsTab.plugin.saveSettings();
clearTimeout(debounceTimer);
window.clearTimeout(debounceTimer);
const FOLDER_NOTE_NAME_DEBOUNCE_MS = 2000;
debounceTimer = setTimeout(() => {
debounceTimer = window.setTimeout(() => {
if (!value.includes('{{folder_name}}')) {
if (!settingsTab.showFolderNameInTabTitleSetting) {
settingsTab.display();
@ -53,7 +54,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
'Rename all existing folder notes',
'When you click on "Confirm" all existing folder notes will be renamed to the new folder note name.',
settingsTab.renameFolderNotes,
[])
[settingsTab.plugin.settings.oldFolderNoteName ?? '{{folder_name}}'])
.open();
}),
);
@ -62,8 +63,8 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
if (!settingsTab.plugin.settings.folderNoteName.includes('{{folder_name}}')) {
new Setting(containerEl)
.setName('Display Folder Name in Tab Title')
.setDesc('Use the actual folder name in the tab title instead of the custom folder note name (e.g., "Folder Note").')
.setName('Display folder name in tab title')
.setDesc('Use the actual folder name in the tab title instead of the custom folder note name (e.g., "folder note").')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.tabManagerEnabled)
@ -83,12 +84,12 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
new Setting(containerEl)
.setName('Default file type for new folder notes')
.setDesc('Choose the default file type (canvas, markdown, ...) used when creating new folder notes.')
.setDesc('Choose the default file type (canvas, Markdown, ...) used when creating new folder notes.')
.addDropdown((dropdown) => {
dropdown.addOption('.ask', 'ask for file type');
dropdown.addOption('.ask', 'Ask for file type');
settingsTab.plugin.settings.supportedFileTypes.forEach((type) => {
if (type === '.md' || type === 'md') {
dropdown.addOption('.md', 'markdown');
dropdown.addOption('.md', 'Markdown');
} else {
dropdown.addOption('.' + type, type);
}
@ -101,7 +102,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.folderNoteType !== '.ask'
) {
settingsTab.plugin.settings.folderNoteType = '.md';
settingsTab.plugin.saveSettings();
void settingsTab.plugin.saveSettings();
}
let defaultType = settingsTab.plugin.settings.folderNoteType.startsWith('.')
@ -118,10 +119,10 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
dropdown
.setValue(defaultType)
.onChange(async (value: '.md' | '.canvas') => {
settingsTab.plugin.settings.folderNoteType = value;
settingsTab.plugin.saveSettings();
settingsTab.display();
.onChange(async (value: string) => {
settingsTab.plugin.settings.folderNoteType = value as '.md' | '.canvas' | '.ask';
void settingsTab.plugin.saveSettings();
void settingsTab.display();
});
});
@ -137,10 +138,10 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.supportedFileTypes || [],
['md', 'canvas'],
);
list.on('update', async (values: string[]) => {
settingsTab.plugin.settings.supportedFileTypes = values;
await settingsTab.plugin.saveSettings();
settingsTab.display();
list.on('update', (values: unknown) => {
settingsTab.plugin.settings.supportedFileTypes = values as string[];
void settingsTab.plugin.saveSettings();
void settingsTab.display();
});
if (
@ -170,12 +171,12 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.app,
settingsTab.plugin,
settingsTab,
list as ListComponent,
list,
).open();
}
await list.addValue(value.toLowerCase());
settingsTab.display();
settingsTab.plugin.saveSettings();
void settingsTab.display();
void settingsTab.plugin.saveSettings();
});
});
} else {
@ -188,7 +189,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.app,
settingsTab.plugin,
settingsTab,
list as ListComponent,
list,
).open();
}),
);
@ -226,7 +227,10 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
.addOption('insideFolder', 'Inside the folder')
.addOption('parentFolder', 'In the parent folder')
.setValue(settingsTab.plugin.settings.storageLocation)
.onChange(async (value: 'insideFolder' | 'parentFolder' | 'vaultFolder') => {
.onChange(async (value: string) => {
if (value !== 'insideFolder' && value !== 'parentFolder' && value !== 'vaultFolder') {
return;
}
settingsTab.plugin.settings.storageLocation = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
@ -282,7 +286,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
);
}
if (Platform.isDesktopApp) {
settingsTab.settingsPage.createEl('h3', { text: 'Keyboard Shortcuts' });
settingsTab.settingsPage.createEl('h3', { text: 'Keyboard shortcuts' });
new Setting(containerEl)
.setName('Key for creating folder note')
@ -308,7 +312,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
.setName('Key for opening folder note')
.setDesc('Select the combination to open a folder note')
.addDropdown((dropdown) => {
dropdown.addOption('click', 'Mouse Click');
dropdown.addOption('click', 'Mouse click');
if (!Platform.isMacOS) {
dropdown.addOption('ctrl', 'Ctrl + Click');
dropdown.addOption('alt', 'Alt + Click');
@ -356,8 +360,9 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
dropdown.addOption('obsidianTrash', 'Move to Obsidian trash (.trash folder)');
dropdown.addOption('delete', 'Delete permanently');
dropdown.setValue(settingsTab.plugin.settings.deleteFilesAction);
dropdown.onChange(async (value: 'trash' | 'delete' | 'obsidianTrash') => {
settingsTab.plugin.settings.deleteFilesAction = value;
dropdown.onChange(async (value) => {
const v = value as 'trash' | 'delete' | 'obsidianTrash';
settingsTab.plugin.settings.deleteFilesAction = v;
await settingsTab.plugin.saveSettings();
settingsTab.display();
});
@ -450,7 +455,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
new Setting(containerEl)
.setName('Auto-create for attachment folders')
.setDesc('Also automatically create folder notes for attachment folders (e.g., "Attachments", "Media", etc.).')
.setDesc('Also automatically create folder notes for attachment folders (e.g., "attachments", "media", etc.).')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.autoCreateForAttachmentFolder)
@ -475,13 +480,13 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
}),
);
settingsTab.settingsPage.createEl('h3', { text: 'Integration & Compatibility' });
settingsTab.settingsPage.createEl('h3', { text: 'Integration & compatibility' });
const desc1 = document.createDocumentFragment();
const link = document.createElement('a');
const link = activeDocument.createElement('a');
link.href = 'https://github.com/snezhig/obsidian-front-matter-title';
link.textContent = 'front matter title plugin';
link.textContent = 'Front matter title plugin';
link.target = '_blank';
desc1.append(
@ -507,7 +512,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.updateAllBreadcrumbs(true);
}
settingsTab.plugin.app.vault.getFiles().forEach((file) => {
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName(
void settingsTab.plugin.fmtpHandler?.fmptUpdateFileName(
{
id: '',
result: false,
@ -527,7 +532,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
fmtpSetting.infoEl.appendText('Requires a restart to take effect');
fmtpSetting.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
settingsTab.settingsPage.createEl('h3', { text: 'Session & Persistence' });
settingsTab.settingsPage.createEl('h3', { text: 'Session & persistence' });
new Setting(containerEl)
.setName('Persist tab after restart')

View file

@ -18,7 +18,7 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
if (settingsTab.plugin.settings.openFolderNoteOnClickInPath) {
new Setting(containerEl)
.setName('Open sidebar when opening a folder note through path (Mobile only)')
.setName('Open sidebar when opening a folder note through path (mobile only)')
.setDesc('Open the sidebar when opening a folder note through the path on mobile')
.addToggle((toggle) =>
toggle
@ -30,7 +30,7 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
);
new Setting(containerEl)
.setName('Open sidebar when opening a folder note through path (Desktop only)')
.setName('Open sidebar when opening a folder note through path (desktop only)')
.setDesc('Open the sidebar when opening a folder note through the path on desktop')
.addToggle((toggle) =>
toggle
@ -72,9 +72,9 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.underlineFolderInPath = value;
if (value) {
document.body.classList.add('folder-note-underline-path');
activeDocument.body.classList.add('folder-note-underline-path');
} else {
document.body.classList.remove('folder-note-underline-path');
activeDocument.body.classList.remove('folder-note-underline-path');
}
await settingsTab.plugin.saveSettings();
}),
@ -89,9 +89,9 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.boldNameInPath = value;
if (value) {
document.body.classList.add('folder-note-bold-path');
activeDocument.body.classList.add('folder-note-bold-path');
} else {
document.body.classList.remove('folder-note-bold-path');
activeDocument.body.classList.remove('folder-note-bold-path');
}
await settingsTab.plugin.saveSettings();
}),
@ -106,11 +106,24 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.cursiveNameInPath = value;
if (value) {
document.body.classList.add('folder-note-cursive-path');
activeDocument.body.classList.add('folder-note-cursive-path');
} else {
document.body.classList.remove('folder-note-cursive-path');
activeDocument.body.classList.remove('folder-note-cursive-path');
}
await settingsTab.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName('Hide folder note name in the path')
.setDesc('Only show the folder name in the path and hide the folder note name.')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.hideFolderNoteNameInPath)
.onChange(async (value) => {
activeDocument.body.classList.toggle('folder-note-hide-name-path', value);
settingsTab.plugin.settings.hideFolderNoteNameInPath = value;
await settingsTab.plugin.saveSettings();
}),
);
}

View file

@ -82,6 +82,7 @@ export interface FolderNotesSettings {
fvGlobalSettings: {
autoUpdateLinks: boolean;
}
hideFolderNoteNameInPath: boolean;
}
export const DEFAULT_SETTINGS: FolderNotesSettings = {
@ -206,14 +207,15 @@ export const DEFAULT_SETTINGS: FolderNotesSettings = {
fvGlobalSettings: {
autoUpdateLinks: false,
},
hideFolderNoteNameInPath: false,
};
export class SettingsTab extends PluginSettingTab {
plugin: FolderNotesPlugin;
app: App;
excludeFolders: ExcludedFolder[];
plugin!: FolderNotesPlugin;
app!: App;
excludeFolders!: ExcludedFolder[];
settingsPage!: HTMLElement;
showFolderNameInTabTitleSetting: boolean;
showFolderNameInTabTitleSetting!: boolean;
constructor(app: App, plugin: FolderNotesPlugin) {
super(app, plugin);
}
@ -244,19 +246,19 @@ export class SettingsTab extends PluginSettingTab {
this.settingsPage.empty();
switch (tabId.toLocaleLowerCase()) {
case this.TABS.GENERAL.id:
renderGeneral(this);
void renderGeneral(this);
break;
case this.TABS.FOLDER_OVERVIEW.id:
renderFolderOverview(this);
void renderFolderOverview(this);
break;
case this.TABS.EXCLUDE_FOLDERS.id:
renderExcludeFolders(this);
void renderExcludeFolders(this);
break;
case this.TABS.FILE_EXPLORER.id:
renderFileExplorer(this);
void renderFileExplorer(this);
break;
case this.TABS.PATH.id:
renderPath(this);
void renderPath(this);
break;
}
}
@ -296,12 +298,11 @@ export class SettingsTab extends PluginSettingTab {
tabEl.addClass('fn-settings-tab-active');
}
tabEl.addEventListener('click', () => {
// @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');
for (const child of Array.from(tabBar.children)) {
(child as HTMLElement).classList.remove('fn-settings-tab-active');
if (!plugin) { return; }
plugin.settings.settingsTab = tabId.toLocaleLowerCase();
plugin.saveSettings();
void plugin.saveSettings();
}
tabEl.addClass('fn-settings-tab-active');
if (!settingsTab) { return; }
@ -342,19 +343,19 @@ export class SettingsTab extends PluginSettingTab {
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') {
newPath = `${folder.path}/${newFolderNoteName}.${folderNote.extension}`;
}
this.app.fileManager.renameFile(folderNote, newPath);
void this.app.fileManager.renameFile(folderNote, newPath);
}
}
this.plugin.settings.oldFolderNoteName = this.plugin.settings.folderNoteName;
this.plugin.saveSettings();
void this.plugin.saveSettings();
new Notice('Finished updating folder notes');
}
@ -371,13 +372,13 @@ export class SettingsTab extends PluginSettingTab {
} else {
newPath = `${getFolderPathFromString(file.path)}/${folderNote.name}`;
}
this.plugin.app.fileManager.renameFile(folderNote, newPath);
void this.plugin.app.fileManager.renameFile(folderNote, newPath);
} else if (this.plugin.settings.storageLocation === 'insideFolder') {
if (getFolderPathFromString(folderNote.path) === file.path) {
return;
}
const newPath = `${file.path}/${folderNote.name}`;
this.plugin.app.fileManager.renameFile(folderNote, newPath);
void this.plugin.app.fileManager.renameFile(folderNote, newPath);
}
}

View file

@ -5,15 +5,15 @@ export default class BackupWarningModal extends Modal {
plugin: FolderNotesPlugin;
title: string;
desc: string;
callback: (...args: unknown[]) => void;
args: unknown[];
callback: (oldMethod: string) => void;
args: [string];
constructor(
plugin: FolderNotesPlugin,
title: string,
description: string,
callback: (...args: unknown[]) => void,
args: unknown[] = [],
callback: (oldMethod: string) => void,
args: [string],
) {
super(plugin.app);
this.plugin = plugin;
@ -32,7 +32,7 @@ export default class BackupWarningModal extends Modal {
contentEl.createEl('p', { text: this.desc });
// eslint-disable-next-line max-len
contentEl.createEl('p', { text: 'Make sure to backup your vault before using this feature.' }).style.color = '#fb464c';
contentEl.createEl('p', { text: 'Make sure to backup your vault before using this feature.' }).addClass('fn-warning-text');
const buttonContainer = contentEl.createDiv({ cls: 'fn-modal-button-container' });
const confirmButton = new ButtonComponent(buttonContainer);

View file

@ -6,7 +6,7 @@ import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions'
export default class ConfirmationModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
folder: TFolder;
folder!: TFolder;
extension: string;
constructor(app: App, plugin: FolderNotesPlugin) {
super(app);
@ -36,7 +36,7 @@ export default class ConfirmationModal extends Modal {
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';
setting.infoEl.createEl('p', { text: 'Make sure to backup your vault before using this feature.' }).addClass('fn-warning-text');
// 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

View file

@ -7,8 +7,8 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
plugin: FolderNotesPlugin,
title: string,
description: string,
callback: (...args: unknown[]) => void,
args: unknown[] = [],
callback: (oldMethod: string) => void,
args: [string],
) {
super(plugin, title, description, callback, args);
}
@ -16,8 +16,7 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
insertCustomHtml(): void {
const { contentEl } = this;
new Setting(contentEl)
.setName('Old Folder Note Name')
// eslint-disable-next-line max-len
.setName('Old folder note name')
.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')
@ -28,8 +27,7 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
);
new Setting(contentEl)
.setName('New Folder Note Name')
// eslint-disable-next-line max-len
.setName('New folder note name')
.setDesc('Every folder note that matches the old folder note name will be renamed to this name.')
.addText((text) => text
.setPlaceholder('Enter the new folder note name')

View file

@ -1,17 +1,19 @@
import { type TAbstractFile, TFile } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import { AbstractInputSuggest, type TAbstractFile, TFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
}
export class FileSuggest extends TextInputSuggest<TFile> {
export class FileSuggest extends AbstractInputSuggest<TFile> {
plugin: FolderNotesPlugin;
constructor(
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
) {
super(inputEl, plugin);
super(plugin.app, inputEl);
this.plugin = plugin;
}
get_error_msg(mode: FileSuggestMode): string {

View file

@ -1,21 +1,23 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { TFolder, type TAbstractFile } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import { AbstractInputSuggest, TFolder, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
}
export class FolderSuggest extends TextInputSuggest<TFolder> {
export class FolderSuggest extends AbstractInputSuggest<TFolder> {
plugin: FolderNotesPlugin;
constructor(
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
private whitelistSuggester: boolean,
public folder?: TFolder,
) {
super(inputEl, plugin);
super(plugin.app, inputEl);
this.plugin = plugin;
}

View file

@ -1,205 +0,0 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
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;
};
class Suggest<T> {
private owner: ISuggestOwner<T>;
private values: T[];
private suggestions: HTMLDivElement[];
private selectedItem: number;
private containerEl: HTMLElement;
plugin: FolderNotesPlugin;
constructor(
owner: ISuggestOwner<T>,
containerEl: HTMLElement,
scope: Scope,
) {
this.owner = owner;
this.containerEl = containerEl;
containerEl.on(
'click',
'.suggestion-item',
this.onSuggestionClick.bind(this),
);
containerEl.on(
'mousemove',
'.suggestion-item',
this.onSuggestionMouseover.bind(this),
);
scope.register([], 'ArrowUp', (event) => {
if (!event.isComposing) {
this.setSelectedItem(this.selectedItem - 1, true);
return false;
}
});
scope.register([], 'ArrowDown', (event) => {
if (!event.isComposing) {
this.setSelectedItem(this.selectedItem + 1, true);
return false;
}
});
scope.register([], 'Enter', (event) => {
if (!event.isComposing) {
this.useSelectedItem(event);
return false;
}
});
}
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
event.preventDefault();
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
this.useSelectedItem(event);
}
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void {
const item = this.suggestions.indexOf(el);
this.setSelectedItem(item, false);
}
setSuggestions(values: T[]): void {
this.containerEl.empty();
const suggestionEls: HTMLDivElement[] = [];
values.forEach((value) => {
const suggestionEl = this.containerEl.createDiv('suggestion-item');
this.owner.renderSuggestion(value, suggestionEl);
suggestionEls.push(suggestionEl);
});
this.values = values;
this.suggestions = suggestionEls;
this.setSelectedItem(0, false);
}
useSelectedItem(event: MouseEvent | KeyboardEvent): void {
const currentValue = this.values[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
setSelectedItem(selectedIndex: number, scrollIntoView: boolean): void {
const normalizedIndex = wrapAround(
selectedIndex,
this.suggestions.length,
);
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
const selectedSuggestion = this.suggestions[normalizedIndex];
prevSelectedSuggestion?.removeClass('is-selected');
selectedSuggestion?.addClass('is-selected');
this.selectedItem = normalizedIndex;
if (scrollIntoView) {
selectedSuggestion.scrollIntoView(false);
}
}
}
export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
protected inputEl: HTMLInputElement | HTMLTextAreaElement;
private popper: PopperInstance;
private scope: Scope;
private suggestEl: HTMLElement;
private suggest: Suggest<T>;
plugin: FolderNotesPlugin;
constructor(inputEl: HTMLInputElement | HTMLTextAreaElement, plugin: FolderNotesPlugin) {
this.inputEl = inputEl;
this.plugin = plugin;
this.scope = new Scope();
this.suggestEl = createDiv('suggestion-container');
const suggestion = this.suggestEl.createDiv('suggestion');
this.suggest = new Suggest(this, suggestion, this.scope);
this.scope.register([], 'Escape', this.close.bind(this));
this.inputEl.addEventListener('input', this.onInputChanged.bind(this));
this.inputEl.addEventListener('focus', this.onInputChanged.bind(this));
this.inputEl.addEventListener('blur', this.close.bind(this));
this.suggestEl.on(
'mousedown',
'.suggestion-container',
(event: MouseEvent) => {
event.preventDefault();
},
);
}
onInputChanged(): void {
const inputStr = this.inputEl.value;
const suggestions = this.getSuggestions(inputStr);
if (!suggestions) {
this.close();
return;
}
if (suggestions.length > 0) {
this.suggest.setSuggestions(suggestions);
// @ts-expect-error App may not exist
this.open(app.dom.appContainerEl, this.inputEl);
} else {
this.close();
}
}
open(container: HTMLElement, inputEl: HTMLElement): void {
this.plugin.app.keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, {
placement: 'bottom-start',
modifiers: [
{
name: 'sameWidth',
enabled: true,
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
// we need to early exit to avoid an infinite loop
const targetWidth = `${state.rects.reference.width}px`;
if (state.styles.popper.width === targetWidth) {
return;
}
state.styles.popper.width = targetWidth;
instance.update();
},
phase: 'beforeWrite',
requires: ['computeStyles'],
},
],
});
}
close(): void {
this.plugin.app.keymap.popScope(this.scope);
this.suggest.setSuggestions([]);
if (this.popper) this.popper.destroy();
this.suggestEl.detach();
}
abstract getSuggestions(inputStr: string): T[];
abstract renderSuggestion(item: T, el: HTMLElement): void;
abstract selectSuggestion(item: T): void;
}

View file

@ -1,12 +1,21 @@
import { TFile, TFolder, Vault, AbstractInputSuggest, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { getTemplatePlugins } from 'src/template';
interface TemplateSuggestion {
path: string;
name: string;
parent?: {
path: string;
} | null;
}
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
}
export class TemplateSuggest extends AbstractInputSuggest<TFile> {
export class TemplateSuggest extends AbstractInputSuggest<TemplateSuggestion> {
constructor(
public inputEl: HTMLInputElement,
public plugin: FolderNotesPlugin,
@ -24,10 +33,10 @@ export class TemplateSuggest extends AbstractInputSuggest<TFile> {
}
}
getSuggestions(input_str: string): TFile[] {
getSuggestions(input_str: string): TemplateSuggestion[] {
const { templateFolder, templaterPlugin } = getTemplatePlugins(this.app);
let files: TFile[] = [];
let files: TemplateSuggestion[] = [];
const lower_input_str = input_str.toLowerCase();
if ((!templateFolder || templateFolder.trim() === '') && !templaterPlugin) {
@ -47,13 +56,12 @@ export class TemplateSuggest extends AbstractInputSuggest<TFile> {
{
path: '',
name:
// eslint-disable-next-line max-len
'You need to set the Templates folder in the Templater settings first.',
} as TFile,
},
];
}
} else if (templateFolder) {
folder = this.plugin.app.vault.getAbstractFileByPath(templateFolder) as TFolder;
folder = this.plugin.app.vault.getAbstractFileByPath(templateFolder);
}
if (!(folder instanceof TFolder)) {
@ -71,7 +79,7 @@ export class TemplateSuggest extends AbstractInputSuggest<TFile> {
}
renderSuggestion(file: TFile, el: HTMLElement): void {
renderSuggestion(file: TemplateSuggestion, el: HTMLElement): void {
const { templateFolder, templaterPlugin } = getTemplatePlugins(this.app);
if ((!templateFolder || templateFolder.trim() === '') && !templaterPlugin) {
@ -82,11 +90,11 @@ export class TemplateSuggest extends AbstractInputSuggest<TFile> {
}
selectSuggestion(file: TFile): void {
selectSuggestion(file: TemplateSuggestion): void {
this.inputEl.value = file.name.replace('.md', '');
this.inputEl.trigger('input');
this.plugin.settings.templatePath = file.path;
this.plugin.saveSettings();
void this.plugin.saveSettings();
this.close();
}
}

View file

@ -55,7 +55,7 @@ export async function applyTemplate(
} = 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. ⚠==')) {
if (templateContent.includes('==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this activeDocument. ⚠==')) {
return;
}

View file

@ -23,10 +23,15 @@
cursor: pointer !important;
}
.hide-folder-note :not(.show-folder-note-in-explorer).only-has-folder-note .nav-folder-children {
.hide-folder-note :not(.show-folder-note-in-explorer):not(.fn-has-attachment-folder).only-has-folder-note .nav-folder-children {
display: none !important;
}
.fn-warning-text {
color: #fb464c
}
/* ==========================================================================
Tree Items
========================================================================== */
@ -222,6 +227,10 @@ li:has(.fv-link-list-item),
.folder-note-cursive-path .has-folder-note.view-header-breadcrumb {
font-style: italic;
}
.folder-note-hide-name-path .hide-folder-note-title-in-path .is-last-separator,
.folder-note-hide-name-path .hide-folder-note-title-in-path .path-is-folder-note {
display: none;
}
/* Collapse Icon Handling */
@ -232,7 +241,7 @@ li:has(.fv-link-list-item),
.fn-has-no-files .collapse-icon,
.fn-hide-collapse-icon .has-folder-note.only-has-folder-note .tree-item-icon,
body.fn-ignore-attachment-folder.fn-hide-collapse-icon .only-has-folder-note .fn-empty-folder.fn-has-attachment-folder .tree-item-icon,
body.fn-hide-collapse-icon.fn-ignore-attachment-folder .only-has-folder-note .fn-empty-folder.fn-has-attachment-folder .tree-item-icon,
body.fn-hide-collapse-icon .only-has-folder-note .fn-empty-folder:not(.fn-has-attachment-folder) .tree-item-icon,
body.fn-hide-empty-collapse-icon :not(.only-has-folder-note) > .fn-empty-folder:not(.fn-has-attachment-folder) .tree-item-icon,
body.fn-hide-collapse-icon.only-has-folder-note:not(.is-collapsed):not(.show-folder-note-in-explorer)>.nav-folder-children {
@ -240,6 +249,7 @@ body.fn-hide-collapse-icon.only-has-folder-note:not(.is-collapsed):not(.show-fol
}
/* ==========================================================================
Settings Tabs
========================================================================== */

View file

@ -1,13 +1,14 @@
{
"compilerOptions": {
"ignoreDeprecations": "6.0",
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"module": "node16",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"moduleResolution": "node16",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
@ -17,7 +18,10 @@
"ES6",
"ES7",
"ES2021"
]
],
"paths": {
"@app/*": ["./src/*"]
}
},
"include": [
"**/*.ts"

View file

@ -1,3 +1,4 @@
/* eslint-disable no-undef */
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;