2025-08-11 12:39:33 +00:00
|
|
|
import {
|
|
|
|
|
TFolder,
|
|
|
|
|
Notice,
|
|
|
|
|
TFile,
|
|
|
|
|
Platform,
|
|
|
|
|
type App,
|
|
|
|
|
type Menu,
|
|
|
|
|
type TAbstractFile,
|
|
|
|
|
type Editor,
|
|
|
|
|
type MarkdownView,
|
2026-05-15 13:40:29 +00:00
|
|
|
type MarkdownFileInfo,
|
2025-08-11 12:39:33 +00:00
|
|
|
} from 'obsidian';
|
2025-07-27 09:33:21 +00:00
|
|
|
import type FolderNotesPlugin from './main';
|
2025-08-11 12:39:33 +00:00
|
|
|
import {
|
|
|
|
|
getFolderNote,
|
|
|
|
|
createFolderNote,
|
|
|
|
|
deleteFolderNote,
|
|
|
|
|
turnIntoFolderNote,
|
|
|
|
|
openFolderNote,
|
|
|
|
|
extractFolderName,
|
|
|
|
|
detachFolderNote,
|
|
|
|
|
} from './functions/folderNoteFunctions';
|
2024-03-19 21:33:29 +00:00
|
|
|
import { ExcludedFolder } from './ExcludeFolders/ExcludeFolder';
|
2024-10-22 10:34:18 +00:00
|
|
|
import { getFolderPathFromString, getFileExplorerActiveFolder } from './functions/utils';
|
2025-08-11 12:39:33 +00:00
|
|
|
import {
|
|
|
|
|
deleteExcludedFolder,
|
|
|
|
|
getDetachedFolder,
|
|
|
|
|
getExcludedFolder,
|
|
|
|
|
} from './ExcludeFolders/functions/folderFunctions';
|
|
|
|
|
import {
|
|
|
|
|
hideFolderNoteInFileExplorer,
|
|
|
|
|
showFolderNoteInFileExplorer,
|
|
|
|
|
} from './functions/styleFunctions';
|
2024-10-21 20:17:32 +00:00
|
|
|
|
2026-05-15 13:40:29 +00:00
|
|
|
type MarkdownEditorContext = MarkdownView | MarkdownFileInfo;
|
2024-02-03 15:38:15 +00:00
|
|
|
|
|
|
|
|
|
2023-03-15 20:02:00 +00:00
|
|
|
export class Commands {
|
2023-03-19 22:11:00 +00:00
|
|
|
plugin: FolderNotesPlugin;
|
|
|
|
|
app: App;
|
|
|
|
|
constructor(app: App, plugin: FolderNotesPlugin) {
|
|
|
|
|
this.plugin = plugin;
|
|
|
|
|
this.app = app;
|
|
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
registerCommands(): void {
|
2023-08-10 19:54:22 +00:00
|
|
|
this.editorCommands();
|
|
|
|
|
this.fileCommands();
|
|
|
|
|
this.regularCommands();
|
|
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
|
|
|
|
|
regularCommands(): void {
|
2023-08-10 19:54:22 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'turn-into-folder-note',
|
2025-06-12 16:35:34 +00:00
|
|
|
name: 'Use this file as the folder note for its parent folder',
|
2025-06-13 08:19:21 +00:00
|
|
|
checkCallback: (checking: boolean) => {
|
2023-08-10 19:54:22 +00:00
|
|
|
const file = this.app.workspace.getActiveFile();
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(file instanceof TFile)) return false;
|
2023-08-10 19:54:22 +00:00
|
|
|
const folder = file.parent;
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!folder || !(folder instanceof TFolder)) return false;
|
|
|
|
|
// Only show if file is NOT in the root folder
|
|
|
|
|
if (folder.path === '' || folder.path === '/') return false;
|
2023-08-10 19:54:22 +00:00
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
2025-06-13 08:19:21 +00:00
|
|
|
if (folderNote instanceof TFile && folderNote === file) return false;
|
|
|
|
|
if (checking) return true;
|
2026-05-15 13:40:29 +00:00
|
|
|
void turnIntoFolderNote(this.plugin, file, folder, folderNote);
|
2025-02-25 17:57:23 +00:00
|
|
|
},
|
2023-08-10 19:54:22 +00:00
|
|
|
});
|
2024-09-05 15:05:58 +00:00
|
|
|
|
2023-08-12 19:32:29 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'create-folder-note',
|
2025-06-12 19:07:27 +00:00
|
|
|
name: 'Make a folder with this file as its folder note',
|
2023-08-12 19:32:29 +00:00
|
|
|
callback: async () => {
|
|
|
|
|
const file = this.app.workspace.getActiveFile();
|
|
|
|
|
if (!(file instanceof TFile)) return;
|
|
|
|
|
let newPath = file.parent?.path + '/' + file.basename;
|
|
|
|
|
if (file.parent?.path === '' || file.parent?.path === '/') {
|
|
|
|
|
newPath = file.basename;
|
|
|
|
|
}
|
|
|
|
|
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
|
|
|
|
|
return new Notice('Folder already exists');
|
|
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
const automaticallyCreateFolderNote =
|
|
|
|
|
this.plugin.settings.autoCreate;
|
2023-11-02 16:56:57 +00:00
|
|
|
this.plugin.settings.autoCreate = false;
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.saveSettings();
|
2023-08-12 19:32:29 +00:00
|
|
|
await this.plugin.app.vault.createFolder(newPath);
|
|
|
|
|
const folder = this.plugin.app.vault.getAbstractFileByPath(newPath);
|
|
|
|
|
if (!(folder instanceof TFolder)) return;
|
2026-05-15 13:40:29 +00:00
|
|
|
await createFolderNote(this.plugin, folder.path, true, '.' + file.extension, false, file);
|
2023-11-02 16:56:57 +00:00
|
|
|
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.saveSettings();
|
2025-02-25 17:57:23 +00:00
|
|
|
},
|
|
|
|
|
});
|
2024-09-05 15:05:58 +00:00
|
|
|
|
2023-08-12 19:32:29 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'create-folder-note-for-current-folder',
|
2026-05-15 13:40:29 +00:00
|
|
|
name: 'Create Markdown folder note for this folder',
|
2025-06-13 08:19:21 +00:00
|
|
|
checkCallback: (checking) => {
|
2023-08-12 19:32:29 +00:00
|
|
|
const file = this.app.workspace.getActiveFile();
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(file instanceof TFile)) return false;
|
2023-08-12 19:32:29 +00:00
|
|
|
const folder = file.parent;
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(folder instanceof TFolder)) return false;
|
|
|
|
|
if (folder.path === '' || folder.path === '/') return false;
|
|
|
|
|
if (checking) return true;
|
2026-05-15 13:40:29 +00:00
|
|
|
void createFolderNote(this.plugin, folder.path, true, '.md', false);
|
2025-02-25 17:57:23 +00:00
|
|
|
},
|
2023-08-12 19:32:29 +00:00
|
|
|
});
|
2023-11-04 17:28:52 +00:00
|
|
|
|
|
|
|
|
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
|
|
|
|
|
if (fileType === 'md') return;
|
|
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: `create-${fileType}-folder-note-for-current-folder`,
|
2025-06-12 16:35:34 +00:00
|
|
|
name: `Create ${fileType} folder note for this folder`,
|
2025-06-13 08:19:21 +00:00
|
|
|
checkCallback: (checking) => {
|
2023-11-04 17:28:52 +00:00
|
|
|
const file = this.app.workspace.getActiveFile();
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(file instanceof TFile)) return false;
|
2023-11-04 17:28:52 +00:00
|
|
|
const folder = file.parent;
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(folder instanceof TFolder)) return false;
|
|
|
|
|
if (folder.path === '' || folder.path === '/') return false;
|
|
|
|
|
if (checking) return true;
|
2026-05-15 13:40:29 +00:00
|
|
|
void createFolderNote(this.plugin, folder.path, true, '.' + fileType, false);
|
2025-02-25 17:57:23 +00:00
|
|
|
},
|
2023-11-04 17:28:52 +00:00
|
|
|
});
|
|
|
|
|
});
|
2024-10-22 10:34:18 +00:00
|
|
|
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
|
2025-05-05 09:57:53 +00:00
|
|
|
const type = fileType === 'md' ? 'markdown' : fileType;
|
2024-10-22 10:34:18 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
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) => {
|
2026-06-01 15:36:48 +00:00
|
|
|
const folder = getFileExplorerActiveFolder(this.plugin);
|
2025-05-05 09:57:53 +00:00
|
|
|
if (!folder) return false;
|
|
|
|
|
// Is there already a folder note for the active folder?
|
|
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
|
|
|
|
if (folderNote instanceof TFile) return false;
|
|
|
|
|
if (checking) return true;
|
2024-10-22 10:34:18 +00:00
|
|
|
|
2025-05-05 09:57:53 +00:00
|
|
|
// Everything is fine and not checking, let's create the folder note.
|
|
|
|
|
const ext = '.' + fileType;
|
2025-07-27 09:33:21 +00:00
|
|
|
const { path } = folder;
|
2026-05-15 13:40:29 +00:00
|
|
|
void createFolderNote(this.plugin, path, true, ext, false);
|
2025-05-05 09:57:53 +00:00
|
|
|
},
|
2024-10-22 10:34:18 +00:00
|
|
|
});
|
|
|
|
|
});
|
2023-11-04 17:28:52 +00:00
|
|
|
|
2023-08-12 19:32:29 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'delete-folder-note-for-current-folder',
|
2025-06-12 16:35:34 +00:00
|
|
|
name: 'Delete this folder\'s linked note',
|
2025-06-13 08:19:21 +00:00
|
|
|
checkCallback: (checking) => {
|
2023-08-12 19:32:29 +00:00
|
|
|
const file = this.app.workspace.getActiveFile();
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(file instanceof TFile)) return false;
|
2023-08-12 19:32:29 +00:00
|
|
|
const folder = file.parent;
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(folder instanceof TFolder)) return false;
|
2023-08-12 19:32:29 +00:00
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(folderNote instanceof TFile)) return false;
|
|
|
|
|
if (checking) return true;
|
2026-05-15 13:40:29 +00:00
|
|
|
void deleteFolderNote(this.plugin, folderNote, true);
|
2025-02-25 17:57:23 +00:00
|
|
|
},
|
2023-08-12 19:32:29 +00:00
|
|
|
});
|
2025-01-08 11:43:29 +00:00
|
|
|
|
2024-10-22 10:34:18 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'delete-folder-note-of-active-file-explorer-folder',
|
|
|
|
|
name: 'Delete folder note of current active folder in file explorer',
|
|
|
|
|
checkCallback: (checking: boolean) => {
|
2026-06-01 15:36:48 +00:00
|
|
|
const folder = getFileExplorerActiveFolder(this.plugin);
|
2025-05-05 09:57:53 +00:00
|
|
|
if (!folder) return false;
|
|
|
|
|
// Is there any folder note for the active folder?
|
2024-10-22 10:34:18 +00:00
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
|
|
|
|
if (!(folderNote instanceof TFile)) return false;
|
2025-05-05 09:57:53 +00:00
|
|
|
if (checking) return true;
|
2024-10-22 10:34:18 +00:00
|
|
|
|
2025-05-05 09:57:53 +00:00
|
|
|
// Everything is fine and not checking, let's delete the folder note.
|
2026-05-15 13:40:29 +00:00
|
|
|
void deleteFolderNote(this.plugin, folderNote, true);
|
2025-05-05 09:57:53 +00:00
|
|
|
},
|
2024-10-22 10:34:18 +00:00
|
|
|
});
|
2023-08-12 19:32:29 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'open-folder-note-for-current-folder',
|
2025-06-12 16:35:34 +00:00
|
|
|
name: 'Open this folder\'s linked note',
|
2025-06-13 08:19:21 +00:00
|
|
|
checkCallback: (checking) => {
|
2023-08-12 19:32:29 +00:00
|
|
|
const file = this.app.workspace.getActiveFile();
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(file instanceof TFile)) return false;
|
2023-08-12 19:32:29 +00:00
|
|
|
const folder = file.parent;
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(folder instanceof TFolder)) return false;
|
2023-08-12 19:32:29 +00:00
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
2025-06-13 08:19:21 +00:00
|
|
|
if (!(folderNote instanceof TFile)) return false;
|
|
|
|
|
if (checking) return true;
|
2026-05-15 13:40:29 +00:00
|
|
|
void openFolderNote(this.plugin, folderNote);
|
2025-05-05 09:57:53 +00:00
|
|
|
},
|
2023-08-12 19:32:29 +00:00
|
|
|
});
|
2024-10-21 20:17:32 +00:00
|
|
|
this.plugin.addCommand({
|
2024-10-22 10:34:18 +00:00
|
|
|
id: 'open-folder-note-of-active-file-explorer-folder',
|
2024-10-21 20:17:32 +00:00
|
|
|
name: 'Open folder note of current active folder in file explorer',
|
|
|
|
|
checkCallback: (checking: boolean) => {
|
2026-06-01 15:36:48 +00:00
|
|
|
const folder = getFileExplorerActiveFolder(this.plugin);
|
2025-05-05 09:57:53 +00:00
|
|
|
if (!folder) return false;
|
|
|
|
|
// Is there any folder note for the active folder?
|
2024-10-21 20:17:32 +00:00
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
2024-10-22 10:34:18 +00:00
|
|
|
if (!(folderNote instanceof TFile)) return false;
|
2025-05-05 09:57:53 +00:00
|
|
|
if (checking) return true;
|
2024-10-22 10:34:18 +00:00
|
|
|
|
2025-05-05 09:57:53 +00:00
|
|
|
// Everything is fine and not checking, let's open the folder note.
|
2026-05-15 13:40:29 +00:00
|
|
|
void openFolderNote(this.plugin, folderNote);
|
2025-05-05 09:57:53 +00:00
|
|
|
},
|
2024-10-21 20:17:32 +00:00
|
|
|
});
|
2025-01-11 21:08:54 +00:00
|
|
|
|
2023-10-03 14:52:57 +00:00
|
|
|
this.plugin.addCommand({
|
|
|
|
|
id: 'create-folder-note-from-selected-text',
|
2025-06-12 16:35:34 +00:00
|
|
|
name: 'Create folder note from selection',
|
2026-05-15 13:40:29 +00:00
|
|
|
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownEditorContext) => {
|
2023-10-03 14:52:57 +00:00
|
|
|
const text = editor.getSelection().trim();
|
2025-07-27 09:33:21 +00:00
|
|
|
const { file } = view;
|
2023-10-03 14:52:57 +00:00
|
|
|
if (!(file instanceof TFile)) return false;
|
|
|
|
|
if (text && text.trim() !== '') {
|
2023-11-02 16:56:57 +00:00
|
|
|
if (checking) { return true; }
|
2023-10-03 14:52:57 +00:00
|
|
|
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
|
|
|
|
|
for (const char of blacklist) {
|
|
|
|
|
if (text.includes(char)) {
|
2025-08-11 12:39:33 +00:00
|
|
|
// eslint-disable-next-line max-len
|
2023-10-03 14:52:57 +00:00
|
|
|
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (text.endsWith('.')) {
|
|
|
|
|
new Notice('File name cannot end with a dot');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let folder: TAbstractFile | null;
|
2024-02-03 15:38:15 +00:00
|
|
|
const folderPath = getFolderPathFromString(file.path);
|
2023-10-03 14:52:57 +00:00
|
|
|
if (folderPath === '') {
|
|
|
|
|
folder = this.plugin.app.vault.getAbstractFileByPath(text);
|
|
|
|
|
if (folder instanceof TFolder) {
|
|
|
|
|
new Notice('Folder note already exists');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.app.vault.createFolder(text);
|
|
|
|
|
void createFolderNote(this.plugin, text, false);
|
2025-07-27 09:33:21 +00:00
|
|
|
|
2023-10-03 14:52:57 +00:00
|
|
|
} else {
|
2025-08-11 12:39:33 +00:00
|
|
|
const folderFullPath = folderPath + '/' + text;
|
|
|
|
|
folder = this.plugin.app.vault.getAbstractFileByPath(folderFullPath);
|
2023-10-03 14:52:57 +00:00
|
|
|
if (folder instanceof TFolder) {
|
|
|
|
|
new Notice('Folder note already exists');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
if (this.plugin.settings.storageLocation === 'parentFolder') {
|
2025-08-11 12:39:33 +00:00
|
|
|
if (
|
|
|
|
|
this.app.vault.getAbstractFileByPath(
|
|
|
|
|
folderPath + '/' + text + this.plugin.settings.folderNoteType,
|
|
|
|
|
)
|
|
|
|
|
) {
|
2023-10-03 14:52:57 +00:00
|
|
|
new Notice('File already exists');
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
|
|
|
|
|
void createFolderNote(this.plugin, folderPath + '/' + text, false);
|
2023-10-03 14:52:57 +00:00
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
|
|
|
|
|
const { folderNoteName } = this.plugin.settings;
|
|
|
|
|
const fileName = folderNoteName.replace('{{folder_name}}', text);
|
2023-10-03 14:52:57 +00:00
|
|
|
if (fileName !== text) {
|
|
|
|
|
editor.replaceSelection(`[[${fileName}]]`);
|
|
|
|
|
} else {
|
|
|
|
|
editor.replaceSelection(`[[${fileName}|${text}]]`);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
},
|
2025-02-25 17:57:23 +00:00
|
|
|
});
|
2023-08-10 19:54:22 +00:00
|
|
|
}
|
2024-02-03 15:38:15 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
fileCommands(): void {
|
|
|
|
|
this.plugin.registerEvent(
|
2025-08-18 07:53:19 +00:00
|
|
|
// eslint-disable-next-line complexity
|
2025-08-11 12:39:33 +00:00
|
|
|
this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
|
|
|
|
|
let folder: TAbstractFile | TFolder | null = file.parent;
|
|
|
|
|
if (file instanceof TFile) {
|
|
|
|
|
if (this.plugin.settings.storageLocation === 'insideFolder') {
|
|
|
|
|
folder = file.parent;
|
|
|
|
|
} else {
|
|
|
|
|
const { folderNoteName } = this.plugin.settings;
|
|
|
|
|
const fileName = extractFolderName(folderNoteName, file.basename);
|
|
|
|
|
if (fileName) {
|
|
|
|
|
if (file.parent?.path === '' || file.parent?.path === '/') {
|
|
|
|
|
folder = this.plugin.app.vault.getAbstractFileByPath(fileName);
|
|
|
|
|
} else {
|
|
|
|
|
folder = this.plugin.app.vault.getAbstractFileByPath(
|
|
|
|
|
file.parent?.path + '/' + fileName,
|
|
|
|
|
);
|
|
|
|
|
}
|
2023-07-01 13:10:57 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-06-15 16:23:34 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (folder instanceof TFolder) {
|
|
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
|
|
|
|
const excludedFolder = getExcludedFolder(this.plugin, folder.path, true);
|
|
|
|
|
if (folderNote?.path === file.path && !excludedFolder?.detached) { return; }
|
|
|
|
|
} else if (file.parent instanceof TFolder) {
|
|
|
|
|
folder = file.parent;
|
|
|
|
|
}
|
2023-07-01 13:10:57 +00:00
|
|
|
}
|
2024-06-15 16:23:34 +00:00
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
// eslint-disable-next-line complexity
|
|
|
|
|
const addFolderNoteActions = (folderMenu: Menu): void => {
|
2025-08-18 07:28:23 +00:00
|
|
|
if (file instanceof TFile) {
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Create folder note');
|
|
|
|
|
item.setIcon('edit');
|
|
|
|
|
item.onClick(async () => {
|
2025-08-18 07:28:23 +00:00
|
|
|
if (!folder) return;
|
|
|
|
|
let newPath = folder.path + '/' + file.basename;
|
|
|
|
|
if (folder.path === '' || folder.path === '/') {
|
|
|
|
|
newPath = file.basename;
|
|
|
|
|
}
|
|
|
|
|
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
|
|
|
|
|
return new Notice('Folder already exists');
|
|
|
|
|
}
|
|
|
|
|
const automaticallyCreateFolderNote =
|
|
|
|
|
this.plugin.settings.autoCreate;
|
|
|
|
|
this.plugin.settings.autoCreate = false;
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.saveSettings();
|
2025-08-18 07:28:23 +00:00
|
|
|
await this.plugin.app.vault.createFolder(newPath);
|
|
|
|
|
const newFolder = this.plugin.app.vault
|
|
|
|
|
.getAbstractFileByPath(newPath);
|
|
|
|
|
if (!(newFolder instanceof TFolder)) return;
|
|
|
|
|
await createFolderNote(
|
|
|
|
|
this.plugin,
|
|
|
|
|
newFolder.path,
|
|
|
|
|
true,
|
|
|
|
|
'.' + file.extension,
|
|
|
|
|
false,
|
|
|
|
|
file,
|
|
|
|
|
);
|
|
|
|
|
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.saveSettings();
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (getFolderPathFromString(file.path) === '') return;
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:28:23 +00:00
|
|
|
if (!(folder instanceof TFolder)) return;
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:28:23 +00:00
|
|
|
if (folder.path === '' || folder.path === '/') return;
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle(`Turn into folder note for ${folder?.name}`);
|
|
|
|
|
item.setIcon('edit');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
if (!folder || !(folder instanceof TFolder)) return;
|
|
|
|
|
const folderNote = getFolderNote(this.plugin, folder.path);
|
2026-05-15 13:40:29 +00:00
|
|
|
void turnIntoFolderNote(this.plugin, file, folder, folderNote);
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
|
|
|
|
}
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (!(file instanceof TFolder)) return;
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
const excludedFolder = getExcludedFolder(this.plugin, file.path, false);
|
|
|
|
|
const detachedExcludedFolder = getDetachedFolder(this.plugin, file.path);
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:28:23 +00:00
|
|
|
if (excludedFolder && !excludedFolder.hideInSettings) {
|
2025-08-11 12:39:33 +00:00
|
|
|
// I'm not sure if I'm ever going to add this because of the possibility that a folder got more than one excluded
|
2025-08-18 07:21:10 +00:00
|
|
|
// menu.addItem((item) => {
|
|
|
|
|
// item.setTitle('Manage excluded folder');
|
|
|
|
|
// item.setIcon('settings-2');
|
|
|
|
|
// item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
// if (excludedFolder instanceof ExcludedFolder) {
|
|
|
|
|
// new ExcludedFolderSettings(this.plugin.app, this.plugin, excludedFolder).open();
|
|
|
|
|
// } else if (excludedFolder instanceof ExcludePattern) {
|
|
|
|
|
// new PatternSettings(this.plugin.app, this.plugin, excludedFolder).open();
|
|
|
|
|
// }
|
|
|
|
|
// });
|
2025-08-18 07:21:10 +00:00
|
|
|
// });
|
|
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Remove folder from excluded folders');
|
|
|
|
|
item.setIcon('trash');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
this.plugin.settings.excludeFolders =
|
|
|
|
|
this.plugin.settings.excludeFolders.filter(
|
|
|
|
|
(excluded) =>
|
|
|
|
|
(excluded.path !== file.path) || excluded.detached,
|
|
|
|
|
);
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.saveSettings(true);
|
2025-08-18 07:28:23 +00:00
|
|
|
new Notice('Successfully removed folder from excluded folders');
|
|
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
return;
|
|
|
|
|
}
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (detachedExcludedFolder) {
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Remove folder from detached folders');
|
|
|
|
|
item.setIcon('trash');
|
|
|
|
|
item.onClick(() => {
|
2026-05-15 13:40:29 +00:00
|
|
|
void deleteExcludedFolder(this.plugin, detachedExcludedFolder);
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
|
|
|
|
}
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (detachedExcludedFolder) { return; }
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Exclude folder from folder notes');
|
|
|
|
|
item.setIcon('x-circle');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
const newExcludedFolder = new ExcludedFolder(
|
|
|
|
|
file.path,
|
|
|
|
|
this.plugin.settings.excludeFolders.length,
|
|
|
|
|
undefined,
|
|
|
|
|
this.plugin,
|
|
|
|
|
);
|
|
|
|
|
this.plugin.settings.excludeFolders.push(newExcludedFolder);
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.saveSettings(true);
|
2025-08-18 07:28:23 +00:00
|
|
|
new Notice('Successfully excluded folder from folder notes');
|
|
|
|
|
});
|
2023-06-26 17:05:27 +00:00
|
|
|
});
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (!(file instanceof TFolder)) return;
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:28:23 +00:00
|
|
|
const folderNote = getFolderNote(this.plugin, file.path);
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:28:23 +00:00
|
|
|
if (folderNote instanceof TFile && !detachedExcludedFolder) {
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Delete folder note');
|
|
|
|
|
item.setIcon('trash');
|
|
|
|
|
item.onClick(() => {
|
2026-05-15 13:40:29 +00:00
|
|
|
void deleteFolderNote(this.plugin, folderNote, true);
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2023-03-19 22:11:00 +00:00
|
|
|
});
|
2024-02-03 15:38:15 +00:00
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Open folder note');
|
|
|
|
|
item.setIcon('chevron-right-square');
|
|
|
|
|
item.onClick(() => {
|
2026-05-15 13:40:29 +00:00
|
|
|
void openFolderNote(this.plugin, folderNote);
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
2024-01-13 20:18:32 +00:00
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Detach folder note');
|
|
|
|
|
item.setIcon('unlink');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
detachFolderNote(this.plugin, folderNote);
|
|
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
2024-06-15 16:23:34 +00:00
|
|
|
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Copy Obsidian URL');
|
|
|
|
|
item.setIcon('link');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
this.app.copyObsidianUrl(folderNote);
|
|
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
2024-02-03 15:38:15 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
if (this.plugin.settings.hideFolderNote) {
|
|
|
|
|
if (excludedFolder?.showFolderNote) {
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Hide folder note in explorer');
|
|
|
|
|
item.setIcon('eye-off');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
hideFolderNoteInFileExplorer(file.path, this.plugin);
|
|
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
|
|
|
|
} else {
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle('Show folder note in explorer');
|
|
|
|
|
item.setIcon('eye');
|
|
|
|
|
item.onClick(() => {
|
2025-08-18 07:28:23 +00:00
|
|
|
showFolderNoteInFileExplorer(file.path, this.plugin);
|
|
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
|
|
|
|
}
|
2024-09-05 15:05:58 +00:00
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
} else {
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2026-05-15 13:40:29 +00:00
|
|
|
item.setTitle('Create Markdown folder note');
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setIcon('edit');
|
|
|
|
|
item.onClick(() => {
|
2026-05-15 13:40:29 +00:00
|
|
|
void createFolderNote(this.plugin, file.path, true, '.md');
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2023-11-04 17:28:52 +00:00
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
|
|
|
|
|
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
|
|
|
|
|
if (fileType === 'md') return;
|
2025-08-18 07:53:19 +00:00
|
|
|
folderMenu.addItem((item) => {
|
2025-08-18 07:21:10 +00:00
|
|
|
item.setTitle(`Create ${fileType} folder note`);
|
|
|
|
|
item.setIcon('edit');
|
|
|
|
|
item.onClick(() => {
|
2026-05-15 13:40:29 +00:00
|
|
|
void createFolderNote(this.plugin, file.path, true, '.' + fileType);
|
2025-08-18 07:28:23 +00:00
|
|
|
});
|
2025-08-11 12:39:33 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
2025-08-18 07:28:23 +00:00
|
|
|
};
|
2025-08-18 07:21:10 +00:00
|
|
|
|
2025-08-18 07:28:23 +00:00
|
|
|
if (
|
2025-08-18 07:53:19 +00:00
|
|
|
Platform.isDesktop &&
|
|
|
|
|
!Platform.isTablet &&
|
|
|
|
|
this.plugin.settings.useSubmenus
|
2025-08-18 07:28:23 +00:00
|
|
|
) {
|
|
|
|
|
menu.addItem(async (item) => {
|
2026-05-15 13:40:29 +00:00
|
|
|
item.setTitle('Folder note commands').setIcon('folder-edit');
|
|
|
|
|
let subMenu: Menu = item.setSubmenu();
|
2025-08-18 07:28:23 +00:00
|
|
|
addFolderNoteActions(subMenu);
|
2025-08-18 07:53:19 +00:00
|
|
|
});
|
2025-08-18 07:28:23 +00:00
|
|
|
} else {
|
|
|
|
|
addFolderNoteActions(menu);
|
|
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
}));
|
2023-03-19 22:11:00 +00:00
|
|
|
}
|
2024-02-03 15:38:15 +00:00
|
|
|
|
2025-08-11 12:39:33 +00:00
|
|
|
editorCommands(): void {
|
|
|
|
|
// eslint-disable-next-line max-len
|
2026-05-15 13:40:29 +00:00
|
|
|
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownEditorContext) => {
|
2023-08-10 19:54:22 +00:00
|
|
|
const text = editor.getSelection().trim();
|
|
|
|
|
if (!text || text.trim() === '') return;
|
|
|
|
|
menu.addItem((item) => {
|
|
|
|
|
item.setTitle('Create folder note')
|
|
|
|
|
.setIcon('edit')
|
|
|
|
|
.onClick(() => {
|
2025-07-27 09:33:21 +00:00
|
|
|
const { file } = view;
|
2023-08-10 19:54:22 +00:00
|
|
|
if (!(file instanceof TFile)) return;
|
|
|
|
|
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
|
|
|
|
|
for (const char of blacklist) {
|
|
|
|
|
if (text.includes(char)) {
|
2025-08-11 12:39:33 +00:00
|
|
|
// eslint-disable-next-line max-len
|
2023-08-10 19:54:22 +00:00
|
|
|
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (text.endsWith('.')) {
|
|
|
|
|
new Notice('File name cannot end with a dot');
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-08-11 12:39:33 +00:00
|
|
|
|
2023-08-10 19:54:22 +00:00
|
|
|
let folder: TAbstractFile | null;
|
2024-02-03 15:38:15 +00:00
|
|
|
const folderPath = getFolderPathFromString(file.path);
|
2025-08-11 12:39:33 +00:00
|
|
|
const { folderNoteName } = this.plugin.settings;
|
|
|
|
|
const fileName = folderNoteName.replace('{{folder_name}}', text);
|
2023-08-10 19:54:22 +00:00
|
|
|
if (folderPath === '') {
|
|
|
|
|
folder = this.plugin.app.vault.getAbstractFileByPath(text);
|
|
|
|
|
if (folder instanceof TFolder) {
|
|
|
|
|
return new Notice('Folder note already exists');
|
|
|
|
|
}
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.app.vault.createFolder(text);
|
|
|
|
|
void createFolderNote(this.plugin, text, false);
|
2025-07-27 09:33:21 +00:00
|
|
|
|
2023-08-10 19:54:22 +00:00
|
|
|
} else {
|
2025-08-11 12:39:33 +00:00
|
|
|
folder = this.plugin.app.vault.getAbstractFileByPath(
|
|
|
|
|
folderPath + '/' + text,
|
|
|
|
|
);
|
2023-08-10 19:54:22 +00:00
|
|
|
if (folder instanceof TFolder) {
|
|
|
|
|
return new Notice('Folder note already exists');
|
|
|
|
|
}
|
|
|
|
|
if (this.plugin.settings.storageLocation === 'parentFolder') {
|
2025-08-11 12:39:33 +00:00
|
|
|
if (
|
|
|
|
|
this.app.vault.getAbstractFileByPath(
|
|
|
|
|
folderPath +
|
|
|
|
|
'/' +
|
|
|
|
|
fileName +
|
|
|
|
|
this.plugin.settings.folderNoteType,
|
|
|
|
|
)
|
|
|
|
|
) {
|
2023-08-10 19:54:22 +00:00
|
|
|
return new Notice('File already exists');
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-05-15 13:40:29 +00:00
|
|
|
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
|
|
|
|
|
void createFolderNote(this.plugin, folderPath + '/' + text, false);
|
2023-08-10 19:54:22 +00:00
|
|
|
}
|
2023-10-03 14:52:57 +00:00
|
|
|
if (fileName !== text) {
|
2023-08-10 19:54:22 +00:00
|
|
|
editor.replaceSelection(`[[${fileName}]]`);
|
|
|
|
|
} else {
|
|
|
|
|
editor.replaceSelection(`[[${fileName}|${text}]]`);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}));
|
|
|
|
|
}
|
2023-03-19 22:11:00 +00:00
|
|
|
}
|