Compare commits

..

No commits in common. "main" and "1.8.2" have entirely different histories.
main ... 1.8.2

66 changed files with 3546 additions and 13153 deletions

7
.eslintignore Normal file
View file

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

150
.eslintrc Normal file
View file

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

View file

@ -1,34 +1,26 @@
# https://github.com/SilentVoid13/Templater/blob/master/.github/workflows/release.yml
name: Plugin release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
permissions:
id-token: write
contents: write
attestations: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
with:
submodules: recursive
fetch-depth: 0
- name: Fetch all branches
run: git fetch --all
- name: Extract branch name
run: |
BRANCH_NAME=$(git name-rev --name-only $GITHUB_SHA | sed 's|^remotes/origin/||' | sed 's|^origin/||')
echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV
id: extract_branch
run: echo "BRANCH_NAME=$(git branch -r --contains ${GITHUB_SHA} | grep -v 'detached' | sed 's|origin/||' | xargs)" >> $GITHUB_ENV
# Checkout the main branch to update manifest.json
- name: Checkout main branch to update manifest
@ -47,31 +39,20 @@ jobs:
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add manifest.json
git commit -m "Update manifest.json to version $VERSION" || echo "No changes to commit"
git commit -m "Update manifest.json to version $VERSION"
git push origin main
- name: Ensure HEAD is at updated main commit
# Switch back to the branch that triggered the workflow
- name: Checkout original branch
run: |
git fetch origin
git checkout main
git reset --hard origin/main
git checkout ${{ env.BRANCH_NAME }}
git pull origin ${{ env.BRANCH_NAME }}
- name: npm build
run: |
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

@ -13,7 +13,5 @@ The plugin can be downloaded by clicking on https://obsidian.md/plugins?id=folde
The easiest option is to install the [BRAT plugin](https://obsidian.md/plugins?id=obsidian42-brat) and then to follow the following guide: https://tfthacker.com/brat-quick-guide & use this link https://github.com/LostPaul/obsidian-folder-notes to install the beta version.
Join the Discord server to chat about the beta and to also get the beta user role.
## Discord server
[For regular updates on Folder Notes and my other plugins, join the Discord server to get notified and participate in discussions.](https://discord.gg/4UQEDfQmuH)

View file

@ -1,6 +1,5 @@
# Folder overview
[The folder overview feature can be installed as a standalone plugin and works without the folder notes plugin by clicking on this.](https://obsidian.md/plugins?id=folder-overview)
**Don't install "folder notes" & the "folder overview" plugin instead only install the folder notes plugin otherwise you can't enable both and there can be other bugs.**
This feature/plugin creates a [code block](https://help.obsidian.md/Editing+and+formatting/Basic+formatting+syntax#Code+blocks) that provides an overview of your entire vault or specific folders. Instructions for creating or editing a folder overview, along with additional guidance, can be found further down the page.
Currently there are two styles available: **file explorer** & **list style**, with more coming soon. The **overview updates** itself **automatically** when you create, delete or modify a file. You can also integrate it into **templates**, as explained further down below this page.
@ -10,71 +9,23 @@ Additionally, you can **customize** the overview by selecting **specific file ty
There are two options to create a folder overview in a note
- Use the command "Insert folder overview" from the [command palette](https://help.obsidian.md/Plugins/Command+palette)
- On desktop right click and click on the option "Insert folder overview"
- On desktop right click and click on the option "Create folder overview"
## Edit overview
If the overview shows no files or folders yet you can just click on the button that says "Edit overview". But if there are already files/folders you can use the command "Edit folder overview" from the [command palette](https://help.obsidian.md/Plugins/Command+palette) or click on the button as showed in the video below.
![type:video](../assets/n5AGi3VCxF5JcNx2Wm5O.mp4)
## Default settings
To edit the default values for new folder overviews you can either use the command "Edit folder overview" or open the plugins settings tab and then the folder overview tab to the edit the default settings. This won't change the settings of already existing folder overviews.
To edit the default values for new folder overviews you can either use the command "Edit folder overview" or open the plugins settings tab and then the folder overview tab to the edit the default settings.
![Screenshot](../assets/screenshots/b4QOtkzJs0.png)
(On the left is the plugin settings tab and the right the view you see when you use the "Edit folder overview" command)
## FAQ
### How can I use this in a template?
## How can I use this in a template?
Just create the folder overview as normal in a template and then change the settings to be what you want to use as a default for the files you apply the templates to. It also doesn't matter that the id of the overview in the template will be the same as the id of the overview that gets created when the template has been used.
### What's the id in the code block for?
## What's the id in the code block for?
They're there to differentiate the overviews from each other so that you can use "Edit folder overview" command to edit the overviews from one file (there can be multiple overviews in one file). It's especially useful on mobile because the button to edit the code block on mobile is quite small.
### How can I make the links appear in the graph view?
[Edit the folder overview](#edit-overview) and then enable "Use actual links" to let the links appear in the graph view. The file will be then edited under the code block and there will be another list added which is hidden by default. You can choose to either hide the code block or the list under it in the folder overview settings. The list of links under the code block will also be visible in other apps that support markdown.
![Screenshot](../assets/screenshots/Obsidian_nAqAIrlZFW.png)
![Screenshot](../assets/screenshots/P7yvNZmF5e.png)
## Settings
### Auto sync
When this is enabled and you rename/create/delete a file/folder which is a children of the selected folder the overview will automatically update.
### Title
The title is above the overview when you enable "Show the title". You can use variables like this: {{variableName}} which will be replaced with something and the list of variables you can use is below this text.
| Variable | Explanation | Example |
| ------------------ | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| folderName | The name of the folder depending on what folder you choose to display | Folder1 |
| folderPath | The path of the choosen folder | Folder1/Folder2 |
| fileName | The name of the file where the overview is located in | File1 |
| filePath | The path of the file where the overview is located in | Folder1/File1 |
| fmtpFileName | The changed file name using the [front matter title plugin](https://github.com/snezhig/obsidian-front-matter-title) | Real file name: 1234, changed name: File1 |
| properties.<name\> | Choose any property from a file | properties.name => File1<br>properties.date => e.g. 01.01.2001 |
### Folder path
The overview will show the children of the selected folder.
When you leave the path empty it will use the parent folder of where the overview is located in. The same is true for "File's parent folder path". If you're using the folder notes plugin and have the storage location set to parent folder you'll need to use "Path of folder linked to the file" to show the children of the folder which is linked to the file. Otherwise if you would for example have the overview located in "File1" which is linked to "Folder1" it would choose all the files/folders of the vault.
### Use actual links
This allows the links of the overview to show up in the grap view, see [How can I make the links appear in the graph view?](#how-can-i-make-the-links-appear-in-the-graph-view?).
#### Hide link list/folder overview
Either hide the code block or the list under it. When you hide the link list every list item will have a span item added to it. It looks like on the image at [How can I make the links appear in the graph view?](#how-can-i-make-the-links-appear-in-the-graph-view?).
### Include types
Only the file types which are selected here will show up in the overview.
### Show folder notes
Show the file itself which is linked to one folder.
### File depth
Limit the depth of files/folders which are shown in the overview. When it is at depth one only the first level files/folders will be shown. When you can't see the childrens of folder the name won't be shown except when you enable ["Show folder names of folders that appear empty in the folder overview"](#show-folder-names-of-folders-that-appear-empty-in-the-folder-overview) or the folder has a file linked to it.
### Sort files by
Choose the order of files and folders.
### Show folder names of folders that appear empty in the folder overview
When folders don't have any childrens they don't show up until you enable this setting but they also don't show up until you set the "File depth" setting until the level of the children of a folder. For example you can see "Folder1" at level 2 and their children at level 3 and the folder name gets shown at level 3 or when you enable this setting.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

View file

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

View file

@ -1,7 +1,7 @@
{
"id": "folder-notes",
"name": "Folder notes beta",
"version": "1.8.20-1-beta",
"version": "1.8.5-3-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.26",
"minAppVersion": "1.4.10",
"version": "1.8.1",
"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",
"authorUrl": "https://github.com/LostPaul",

11409
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -10,28 +10,23 @@
"fv-build": "tsc -noEmit -skipLibCheck && node ./src/obsidian-folder-overview/esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json",
"folder-overview": "node ./src/obsidian-folder-overview/esbuild.config.mjs",
"fv-dev": "npm run folder-overview"
},
"keywords": [],
"author": "Lost Paul",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@eslint/js": "^8.57.1",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.0",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.14.47",
"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": "^6.0.3",
"typescript-eslint": "^8.38.0"
"typescript": "4.7.4"
},
"dependencies": {
"@popperjs/core": "^2.11.6"

View file

@ -1,38 +1,11 @@
import {
TFolder,
Notice,
TFile,
Platform,
type App,
type Menu,
type TAbstractFile,
type Editor,
type MarkdownView,
type MarkdownFileInfo,
} from 'obsidian';
import type FolderNotesPlugin from './main';
import {
getFolderNote,
createFolderNote,
deleteFolderNote,
turnIntoFolderNote,
openFolderNote,
extractFolderName,
detachFolderNote,
} from './functions/folderNoteFunctions';
import { App, TFolder, Menu, TAbstractFile, Notice, TFile, Editor, MarkdownView, Platform } from 'obsidian';
import FolderNotesPlugin from './main';
import { getFolderNote, createFolderNote, deleteFolderNote, turnIntoFolderNote, openFolderNote, extractFolderName, detachFolderNote } from './functions/folderNoteFunctions';
import { ExcludedFolder } from './ExcludeFolders/ExcludeFolder';
import { getFolderPathFromString, getFileExplorerActiveFolder } from './functions/utils';
import {
deleteExcludedFolder,
getDetachedFolder,
getExcludedFolder,
} from './ExcludeFolders/functions/folderFunctions';
import {
hideFolderNoteInFileExplorer,
showFolderNoteInFileExplorer,
} from './functions/styleFunctions';
import { deleteExcludedFolder, getDetachedFolder, getExcludedFolder } from './ExcludeFolders/functions/folderFunctions';
import { hideFolderNoteInFileExplorer, showFolderNoteInFileExplorer } from './functions/styleFunctions';
type MarkdownEditorContext = MarkdownView | MarkdownFileInfo;
export class Commands {
@ -42,13 +15,12 @@ export class Commands {
this.plugin = plugin;
this.app = app;
}
registerCommands(): void {
registerCommands() {
this.editorCommands();
this.fileCommands();
this.regularCommands();
}
regularCommands(): void {
regularCommands() {
this.plugin.addCommand({
id: 'turn-into-folder-note',
name: 'Use this file as the folder note for its parent folder',
@ -62,7 +34,7 @@ export class Commands {
const folderNote = getFolderNote(this.plugin, folder.path);
if (folderNote instanceof TFile && folderNote === file) return false;
if (checking) return true;
void turnIntoFolderNote(this.plugin, file, folder, folderNote);
turnIntoFolderNote(this.plugin, file, folder, folderNote);
},
});
@ -79,22 +51,21 @@ export class Commands {
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
const automaticallyCreateFolderNote = this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
void this.plugin.saveSettings();
this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const folder = this.plugin.app.vault.getAbstractFileByPath(newPath);
if (!(folder instanceof TFolder)) return;
await createFolderNote(this.plugin, folder.path, true, '.' + file.extension, false, file);
createFolderNote(this.plugin, folder.path, true, '.' + file.extension, false, file);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
void this.plugin.saveSettings();
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;
@ -102,7 +73,7 @@ export class Commands {
if (!(folder instanceof TFolder)) return false;
if (folder.path === '' || folder.path === '/') return false;
if (checking) return true;
void createFolderNote(this.plugin, folder.path, true, '.md', false);
createFolderNote(this.plugin, folder.path, true, '.md', false);
},
});
@ -118,7 +89,7 @@ export class Commands {
if (!(folder instanceof TFolder)) return false;
if (folder.path === '' || folder.path === '/') return false;
if (checking) return true;
void createFolderNote(this.plugin, folder.path, true, '.' + fileType, false);
createFolderNote(this.plugin, folder.path, true, '.' + fileType, false);
},
});
});
@ -128,7 +99,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(this.plugin);
const folder = getFileExplorerActiveFolder();
if (!folder) return false;
// Is there already a folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
@ -137,8 +108,8 @@ export class Commands {
// Everything is fine and not checking, let's create the folder note.
const ext = '.' + fileType;
const { path } = folder;
void createFolderNote(this.plugin, path, true, ext, false);
const path = folder.path;
createFolderNote(this.plugin, path, true, ext, false);
},
});
});
@ -154,7 +125,7 @@ export class Commands {
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
void deleteFolderNote(this.plugin, folderNote, true);
deleteFolderNote(this.plugin, folderNote, true);
},
});
@ -162,7 +133,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(this.plugin);
const folder = getFileExplorerActiveFolder();
if (!folder) return false;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
@ -170,7 +141,7 @@ export class Commands {
if (checking) return true;
// Everything is fine and not checking, let's delete the folder note.
void deleteFolderNote(this.plugin, folderNote, true);
deleteFolderNote(this.plugin, folderNote, true);
},
});
this.plugin.addCommand({
@ -184,14 +155,14 @@ export class Commands {
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
void openFolderNote(this.plugin, folderNote);
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(this.plugin);
const folder = getFileExplorerActiveFolder();
if (!folder) return false;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
@ -199,23 +170,22 @@ export class Commands {
if (checking) return true;
// Everything is fine and not checking, let's open the folder note.
void openFolderNote(this.plugin, folderNote);
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: MarkdownEditorContext) => {
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownView) => {
const text = editor.getSelection().trim();
const { file } = view;
const file = view.file;
if (!(file instanceof TFile)) return false;
if (text && text.trim() !== '') {
if (checking) { return true; }
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
for (const char of blacklist) {
if (text.includes(char)) {
// eslint-disable-next-line max-len
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
return false;
}
@ -231,33 +201,26 @@ export class Commands {
if (folder instanceof TFolder) {
new Notice('Folder note already exists');
return false;
} else {
this.plugin.app.vault.createFolder(text);
createFolderNote(this.plugin, text, false);
}
void this.plugin.app.vault.createFolder(text);
void createFolderNote(this.plugin, text, false);
} else {
const folderFullPath = folderPath + '/' + text;
folder = this.plugin.app.vault.getAbstractFileByPath(folderFullPath);
folder = this.plugin.app.vault.getAbstractFileByPath(folderPath + '/' + text);
if (folder instanceof TFolder) {
new Notice('Folder note already exists');
return false;
}
if (this.plugin.settings.storageLocation === 'parentFolder') {
if (
this.app.vault.getAbstractFileByPath(
folderPath + '/' + text + this.plugin.settings.folderNoteType,
)
) {
if (this.app.vault.getAbstractFileByPath(folderPath + '/' + text + this.plugin.settings.folderNoteType)) {
new Notice('File already exists');
return false;
}
}
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
void createFolderNote(this.plugin, folderPath + '/' + text, false);
this.plugin.app.vault.createFolder(folderPath + '/' + text);
createFolderNote(this.plugin, folderPath + '/' + text, false);
}
const { folderNoteName } = this.plugin.settings;
const fileName = folderNoteName.replace('{{folder_name}}', text);
const fileName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', text);
if (fileName !== text) {
editor.replaceSelection(`[[${fileName}]]`);
} else {
@ -270,44 +233,52 @@ export class Commands {
});
}
fileCommands(): void {
this.plugin.registerEvent(
// eslint-disable-next-line complexity
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,
);
}
fileCommands() {
this.plugin.registerEvent(this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
let folder: TAbstractFile | TFolder | null = file.parent;
if (file instanceof TFile) {
if (this.plugin.settings.storageLocation === 'insideFolder') {
folder = file.parent;
} else {
const fileName = extractFolderName(this.plugin.settings.folderNoteName, file.basename);
if (fileName) {
if (file.parent?.path === '' || file.parent?.path === '/') {
folder = this.plugin.app.vault.getAbstractFileByPath(fileName);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(file.parent?.path + '/' + fileName);
}
}
if (folder instanceof TFolder) {
const folderNote = getFolderNote(this.plugin, folder.path);
const excludedFolder = getExcludedFolder(this.plugin, folder.path, true);
if (folderNote?.path === file.path && !excludedFolder?.detached) { return; }
} else if (file.parent instanceof TFolder) {
folder = file.parent;
}
}
// eslint-disable-next-line complexity
const addFolderNoteActions = (folderMenu: Menu): void => {
if (file instanceof TFile) {
folderMenu.addItem((item) => {
item.setTitle('Create folder note');
item.setIcon('edit');
item.onClick(async () => {
if (folder instanceof TFolder) {
const folderNote = getFolderNote(this.plugin, folder.path);
const excludedFolder = getExcludedFolder(this.plugin, folder.path, true);
if (folderNote?.path === file.path && !excludedFolder?.detached) { return; }
} else if (file.parent instanceof TFolder) {
folder = file.parent;
}
}
menu.addItem(async (item) => {
if (Platform.isDesktop && !Platform.isTablet && this.plugin.settings.useSubmenus) {
item
.setTitle('Folder Note Commands')
.setIcon('folder-edit');
}
let subMenu: Menu;
if (!Platform.isDesktopApp || !Platform.isDesktop || Platform.isTablet || !this.plugin.settings.useSubmenus) {
subMenu = menu;
item.setDisabled(true);
} else {
// @ts-ignore
subMenu = item.setSubmenu() as Menu;
}
if (file instanceof TFile) {
// @ts-ignore
subMenu.addItem((item) => {
item.setTitle('Create folder note')
.setIcon('edit')
.onClick(async () => {
if (!folder) return;
let newPath = folder.path + '/' + file.basename;
if (folder.path === '' || folder.path === '/') {
@ -316,217 +287,173 @@ export class Commands {
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
const automaticallyCreateFolderNote = this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
void this.plugin.saveSettings();
this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const newFolder = this.plugin.app.vault
.getAbstractFileByPath(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,
);
await createFolderNote(this.plugin, newFolder.path, true, '.' + file.extension, false, file);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
void this.plugin.saveSettings();
this.plugin.saveSettings();
});
});
if (getFolderPathFromString(file.path) === '') return;
if (!(folder instanceof TFolder)) return;
if (folder.path === '' || folder.path === '/') return;
folderMenu.addItem((item) => {
item.setTitle(`Turn into folder note for ${folder?.name}`);
item.setIcon('edit');
item.onClick(() => {
});
if (getFolderPathFromString(file.path) === '') return;
if (!(folder instanceof TFolder)) return;
if (folder.path === '' || folder.path === '/') return;
subMenu.addItem((item) => {
item.setTitle(`Turn into folder note for ${folder?.name}`)
.setIcon('edit')
.onClick(() => {
if (!folder || !(folder instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, folder.path);
void turnIntoFolderNote(this.plugin, file, folder, folderNote);
turnIntoFolderNote(this.plugin, file, folder, folderNote);
});
});
}
if (!(file instanceof TFolder)) return;
const excludedFolder = getExcludedFolder(this.plugin, file.path, false);
const detachedExcludedFolder = getDetachedFolder(this.plugin, file.path);
if (excludedFolder && !excludedFolder.hideInSettings) {
// I'm not sure if I'm ever going to add this because of the possibility that a folder got more than one excluded
// menu.addItem((item) => {
// item.setTitle('Manage excluded folder');
// item.setIcon('settings-2');
// item.onClick(() => {
// if (excludedFolder instanceof ExcludedFolder) {
// new ExcludedFolderSettings(this.plugin.app, this.plugin, excludedFolder).open();
// } else if (excludedFolder instanceof ExcludePattern) {
// new PatternSettings(this.plugin.app, this.plugin, excludedFolder).open();
// }
// });
// });
folderMenu.addItem((item) => {
item.setTitle('Remove folder from excluded folders');
item.setIcon('trash');
item.onClick(() => {
this.plugin.settings.excludeFolders =
this.plugin.settings.excludeFolders.filter(
(excluded) =>
(excluded.path !== file.path) || excluded.detached,
);
void this.plugin.saveSettings(true);
});
}
if (!(file instanceof TFolder)) return;
const excludedFolder = getExcludedFolder(this.plugin, file.path, false);
const detachedExcludedFolder = getDetachedFolder(this.plugin, file.path);
if (excludedFolder && !excludedFolder.hideInSettings) {
// I'm not sure if I'm ever going to add this because of the possibility that a folder got more than one excluded
// subMenu.addItem((item) => {
// item.setTitle('Manage excluded folder')
// .setIcon('settings-2')
// .onClick(() => {
// console.log('excludedFolder', excludedFolder)
// console.log('2', getExcludedFolder(this.plugin, file.path, false))
// if (excludedFolder instanceof ExcludedFolder) {
// new ExcludedFolderSettings(this.plugin.app, this.plugin, excludedFolder).open();
// } else if (excludedFolder instanceof ExcludePattern) {
// new PatternSettings(this.plugin.app, this.plugin, excludedFolder).open();
// }
// })
// })
subMenu.addItem((item) => {
item.setTitle('Remove folder from excluded folders')
.setIcon('trash')
.onClick(() => {
this.plugin.settings.excludeFolders = this.plugin.settings.excludeFolders.filter(
(folder) => (folder.path !== file.path) || folder.detached);
this.plugin.saveSettings(true);
new Notice('Successfully removed folder from excluded folders');
});
});
return;
}
if (detachedExcludedFolder) {
folderMenu.addItem((item) => {
item.setTitle('Remove folder from detached folders');
item.setIcon('trash');
item.onClick(() => {
void deleteExcludedFolder(this.plugin, detachedExcludedFolder);
});
return;
}
if (detachedExcludedFolder) {
subMenu.addItem((item) => {
item.setTitle('Remove folder from detached folders')
.setIcon('trash')
.onClick(() => {
deleteExcludedFolder(this.plugin, detachedExcludedFolder);
});
});
}
if (detachedExcludedFolder) { return; }
folderMenu.addItem((item) => {
item.setTitle('Exclude folder from folder notes');
item.setIcon('x-circle');
item.onClick(() => {
const newExcludedFolder = new ExcludedFolder(
file.path,
this.plugin.settings.excludeFolders.length,
undefined,
this.plugin,
);
this.plugin.settings.excludeFolders.push(newExcludedFolder);
void this.plugin.saveSettings(true);
});
}
if (detachedExcludedFolder) { return; }
subMenu.addItem((item) => {
item.setTitle('Exclude folder from folder notes')
.setIcon('x-circle')
.onClick(() => {
const excludedFolder = new ExcludedFolder(file.path, this.plugin.settings.excludeFolders.length, undefined, this.plugin);
this.plugin.settings.excludeFolders.push(excludedFolder);
this.plugin.saveSettings(true);
new Notice('Successfully excluded folder from folder notes');
});
});
if (!(file instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, file.path);
if (folderNote instanceof TFile && !detachedExcludedFolder) {
subMenu.addItem((item) => {
item.setTitle('Delete folder note')
.setIcon('trash')
.onClick(() => {
deleteFolderNote(this.plugin, folderNote, true);
});
});
if (!(file instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, file.path);
if (folderNote instanceof TFile && !detachedExcludedFolder) {
folderMenu.addItem((item) => {
item.setTitle('Delete folder note');
item.setIcon('trash');
item.onClick(() => {
void deleteFolderNote(this.plugin, folderNote, true);
subMenu.addItem((item) => {
item.setTitle('Open folder note')
.setIcon('chevron-right-square')
.onClick(() => {
openFolderNote(this.plugin, folderNote);
});
});
});
folderMenu.addItem((item) => {
item.setTitle('Open folder note');
item.setIcon('chevron-right-square');
item.onClick(() => {
void openFolderNote(this.plugin, folderNote);
});
});
folderMenu.addItem((item) => {
item.setTitle('Detach folder note');
item.setIcon('unlink');
item.onClick(() => {
subMenu.addItem((item) => {
item.setTitle('Detach folder note')
.setIcon('unlink')
.onClick(() => {
detachFolderNote(this.plugin, folderNote);
});
});
});
folderMenu.addItem((item) => {
item.setTitle('Copy Obsidian URL');
item.setIcon('link');
item.onClick(() => {
subMenu.addItem((item) => {
item.setTitle('Copy Obsidian URL')
.setIcon('link')
.onClick(() => {
// @ts-ignore
this.app.copyObsidianUrl(folderNote);
});
});
});
if (this.plugin.settings.hideFolderNote) {
if (excludedFolder?.showFolderNote) {
folderMenu.addItem((item) => {
item.setTitle('Hide folder note in explorer');
item.setIcon('eye-off');
item.onClick(() => {
if (this.plugin.settings.hideFolderNote) {
if (excludedFolder?.showFolderNote) {
subMenu.addItem((item) => {
item.setTitle('Hide folder note in explorer')
.setIcon('eye-off')
.onClick(() => {
hideFolderNoteInFileExplorer(file.path, this.plugin);
});
});
} else {
folderMenu.addItem((item) => {
item.setTitle('Show folder note in explorer');
item.setIcon('eye');
item.onClick(() => {
});
} else {
subMenu.addItem((item) => {
item.setTitle('Show folder note in explorer')
.setIcon('eye')
.onClick(() => {
showFolderNoteInFileExplorer(file.path, this.plugin);
});
});
}
});
}
} else {
folderMenu.addItem((item) => {
item.setTitle('Create Markdown folder note');
item.setIcon('edit');
item.onClick(() => {
void createFolderNote(this.plugin, file.path, true, '.md');
});
});
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
if (fileType === 'md') return;
folderMenu.addItem((item) => {
item.setTitle(`Create ${fileType} folder note`);
item.setIcon('edit');
item.onClick(() => {
void createFolderNote(this.plugin, file.path, true, '.' + fileType);
});
});
});
}
};
if (
Platform.isDesktop &&
!Platform.isTablet &&
this.plugin.settings.useSubmenus
) {
menu.addItem(async (item) => {
item.setTitle('Folder note commands').setIcon('folder-edit');
let subMenu: Menu = item.setSubmenu();
addFolderNoteActions(subMenu);
});
} else {
addFolderNoteActions(menu);
subMenu.addItem((item) => {
item.setTitle('Create markdown folder note')
.setIcon('edit')
.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.md');
});
});
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
if (fileType === 'md') return;
subMenu.addItem((item) => {
item.setTitle(`Create ${fileType} folder note`)
.setIcon('edit')
.onClick(() => {
createFolderNote(this.plugin, file.path, true, '.' + fileType);
});
});
});
}
}));
});
}));
}
editorCommands(): void {
// eslint-disable-next-line max-len
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownEditorContext) => {
editorCommands() {
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownView) => {
const text = editor.getSelection().trim();
if (!text || text.trim() === '') return;
menu.addItem((item) => {
item.setTitle('Create folder note')
.setIcon('edit')
.onClick(() => {
const { file } = view;
const file = view.file;
if (!(file instanceof TFile)) return;
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
for (const char of blacklist) {
if (text.includes(char)) {
// eslint-disable-next-line max-len
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
return;
}
@ -535,40 +462,29 @@ export class Commands {
new Notice('File name cannot end with a dot');
return;
}
let folder: TAbstractFile | null;
const folderPath = getFolderPathFromString(file.path);
const { folderNoteName } = this.plugin.settings;
const fileName = folderNoteName.replace('{{folder_name}}', text);
const fileName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', text);
if (folderPath === '') {
folder = this.plugin.app.vault.getAbstractFileByPath(text);
if (folder instanceof TFolder) {
return new Notice('Folder note already exists');
} else {
this.plugin.app.vault.createFolder(text);
createFolderNote(this.plugin, text, false);
}
void this.plugin.app.vault.createFolder(text);
void createFolderNote(this.plugin, text, false);
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(
folderPath + '/' + text,
);
folder = this.plugin.app.vault.getAbstractFileByPath(folderPath + '/' + text);
if (folder instanceof TFolder) {
return new Notice('Folder note already exists');
}
if (this.plugin.settings.storageLocation === 'parentFolder') {
if (
this.app.vault.getAbstractFileByPath(
folderPath +
'/' +
fileName +
this.plugin.settings.folderNoteType,
)
) {
if (this.app.vault.getAbstractFileByPath(folderPath + '/' + fileName + this.plugin.settings.folderNoteType)) {
return new Notice('File already exists');
}
}
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
void createFolderNote(this.plugin, folderPath + '/' + text, false);
this.plugin.app.vault.createFolder(folderPath + '/' + text);
createFolderNote(this.plugin, folderPath + '/' + text, false);
}
if (fileName !== text) {
editor.replaceSelection(`[[${fileName}]]`);

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
import type FolderNotesPlugin from '../main';
import FolderNotesPlugin from '../main';
export class WhitelistedPattern {
type: string;
id: string;
@ -6,19 +6,14 @@ export class WhitelistedPattern {
path: string;
position: number;
subFolders: 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,
id: string | undefined,
plugin: FolderNotesPlugin,
) {
enableSync: boolean;
enableAutoCreate: boolean;
enableFolderNote: boolean;
disableCollapsing: boolean;
showInFolderOverview: boolean;
hideInFileExplorer: boolean;
hideInSettings: boolean;
constructor(pattern: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {
this.type = 'pattern';
this.id = id || crypto.randomUUID();
this.subFolders = plugin.settings.excludePatternDefaultSettings.subFolders;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,41 +1,20 @@
import type FolderNotesPlugin from 'src/main';
import {
type Listener,
type Events,
type ApiInterface,
type DeferInterface,
type ListenerRef,
type EventDispatcherInterface,
getDefer,
} from 'front-matter-plugin-api-provider';
import { type App, TFile, TFolder } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { getDefer, Listener, Events, ApiInterface, DeferInterface, ListenerRef, EventDispatcherInterface } from 'front-matter-plugin-api-provider';
import { App, TFile, TFolder } from 'obsidian';
import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions';
interface UpdateData {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}
interface WrappedUpdateData {
data: UpdateData;
}
export class FrontMatterTitlePluginHandler {
plugin: FolderNotesPlugin;
app!: App;
app: App;
api: ApiInterface | null = null;
deffer: DeferInterface | null = null;
modifiedFolders: Map<string, TFolder> = new Map();
eventRef: ListenerRef<'manager:update'> | null = null;
dispatcher: EventDispatcherInterface<Events> | null = null;
eventRef: ListenerRef<'manager:update'>;
dispatcher: EventDispatcherInterface<Events>;
constructor(plugin: FolderNotesPlugin) {
this.plugin = plugin;
this.app = plugin.app;
(async (): Promise<void> => {
(async () => {
this.deffer = getDefer(this.app);
if (this.deffer.isPluginReady()) {
this.api = this.deffer.getApi();
@ -47,41 +26,41 @@ export class FrontMatterTitlePluginHandler {
await this.deffer.awaitFeatures();
}
}
if (plugin.settings.frontMatterTitle.enabled) {
const dispatcher = this.api?.getEventDispatcher();
if (dispatcher) {
this.dispatcher = dispatcher;
}
const event: Listener<Events, 'manager:update'> = {
name: 'manager:update',
cb: (data): void => {
this.fmptUpdateFileName(data as unknown as UpdateData, true);
},
};
// Keep ref to remove listener
const ref = dispatcher?.addListener(event);
if (ref) {
this.eventRef = ref;
}
// this.plugin.app.vault.getFiles().forEach((file) => {
// this.handleRename({ id: '', result: false, path: file.path }, false);
// });
plugin.updateAllBreadcrumbs();
const dispatcher = this.api?.getEventDispatcher();
if (dispatcher) {
this.dispatcher = dispatcher;
}
const event: Listener<Events, 'manager:update'> = {
name: 'manager:update',
cb: (data) => {
this.fmptUpdateFileName(data as any, true);
},
};
// Keep ref to remove listener
const ref = dispatcher?.addListener(event);
if (ref) {
this.eventRef = ref;
}
// this.plugin.app.vault.getFiles().forEach((file) => {
// this.handleRename({ id: '', result: false, path: file.path }, false);
// });
this.plugin.updateAllBreadcrumbs();
})();
}
deleteEvent(): void {
if (this.eventRef && this.dispatcher) {
deleteEvent() {
if (this.eventRef) {
this.dispatcher.removeListener(this.eventRef);
}
}
async fmptUpdateFileName(data: UpdateData, isEvent: boolean): Promise<void> {
const hasNestedData = 'data' in (data as unknown as Record<string, unknown>);
const actualData: UpdateData = hasNestedData
? (data as unknown as WrappedUpdateData).data
: data;
const file = this.app.vault.getAbstractFileByPath(actualData.path);
async fmptUpdateFileName(data: {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}, isEvent: boolean) {
if ((data as any).data) data = (data as any).data;
const file = this.app.vault.getAbstractFileByPath(data.path);
if (!(file instanceof TFile)) { return; }
const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#');
@ -92,11 +71,11 @@ export class FrontMatterTitlePluginHandler {
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) { return; }
if (folderNote !== file) { return; }
if (!actualData.pathOnly) {
if (!data.pathOnly) {
this.plugin.changeFolderNameInExplorer(folder, newName);
}
const { breadcrumb } = actualData;
const breadcrumb = data.breadcrumb;
if (breadcrumb) {
this.plugin.changeFolderNameInPath(folder, newName, breadcrumb);
}
@ -115,12 +94,15 @@ export class FrontMatterTitlePluginHandler {
}
async fmptUpdateFolderName(data: UpdateData, _replacePath: boolean): Promise<void> {
const hasNestedData = 'data' in (data as unknown as Record<string, unknown>);
const actualData: UpdateData = hasNestedData
? (data as unknown as WrappedUpdateData).data
: data;
const folder = this.app.vault.getAbstractFileByPath(actualData.path);
async fmptUpdateFolderName(data: {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}, replacePath: boolean) {
if ((data as any).data) data = (data as any).data;
const folder = this.app.vault.getAbstractFileByPath(data.path);
if (!(folder instanceof TFolder)) { return; }
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) { return; }
@ -129,11 +111,11 @@ export class FrontMatterTitlePluginHandler {
const newName = resolver?.resolve(folderNote?.path ?? '');
if (!newName) return;
if (!actualData.pathOnly) {
if (!data.pathOnly) {
this.plugin.changeFolderNameInExplorer(folder, newName);
}
const { breadcrumb } = actualData;
const breadcrumb = data.breadcrumb;
if (breadcrumb) {
this.plugin.changeFolderNameInPath(folder, newName, breadcrumb);
}
@ -141,23 +123,4 @@ export class FrontMatterTitlePluginHandler {
folder.newName = newName;
this.modifiedFolders.set(folder.path, folder);
}
async getNewFolderName(folder: TFolder): Promise<string | null> {
if (this.modifiedFolders.has(folder.path)) {
const modifiedFolder = this.modifiedFolders.get(folder.path);
if (modifiedFolder) {
return modifiedFolder.newName;
}
}
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) return null;
const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#');
return resolver?.resolve(folderNote?.path ?? '') ?? null;
}
async getNewFileName(file: TFile): Promise<string | null> {
const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#');
const changedName = resolver?.resolve(file?.path ?? '');
return changedName ?? null;
}
}

View file

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

View file

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

View file

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

View file

@ -1,19 +1,10 @@
import { TFolder, TFile, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import {
createFolderNote,
getFolder,
getFolderNote,
turnIntoFolderNote,
} from 'src/functions/folderNoteFunctions';
import { TAbstractFile, TFolder, TFile } from 'obsidian';
import FolderNotesPlugin from 'src/main';
import { createFolderNote, getFolder, getFolderNote, turnIntoFolderNote } from 'src/functions/folderNoteFunctions';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import {
removeCSSClassFromFileExplorerEL,
addCSSClassToFileExplorerEl,
} from 'src/functions/styleFunctions';
import { isFileInAttachmentFolder } from '@app/functions/utils';
import { removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl } from 'src/functions/styleFunctions';
export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin): Promise<void> {
export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin) {
if (!plugin.app.workspace.layoutReady) return;
const folder = file.parent;
@ -32,7 +23,7 @@ export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugi
}
}
async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin): Promise<void> {
async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin) {
const folder = getFolder(plugin, file);
if (!(folder instanceof TFolder) && plugin.settings.autoCreateForFiles) {
@ -51,8 +42,7 @@ 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 && !isFileInAttachmentFolder(plugin, file)) {
if (!plugin.settings.supportedFileTypes.includes(file.extension)) { return; }
} else if (plugin.settings.autoCreateForFiles) {
if (!file.parent) { return; }
const newFolder = await plugin.app.fileManager.createNewFolder(file.parent);
turnIntoFolderNote(plugin, file, newFolder);
@ -60,7 +50,7 @@ async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin): Promi
}
}
async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin): Promise<void> {
async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin) {
let openFile = plugin.settings.autoCreateFocusFiles;
const attachmentFolderPath = plugin.app.vault.getConfig('attachmentFolderPath') as string;
@ -80,6 +70,6 @@ async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin):
const folderNote = getFolderNote(plugin, folder.path);
if (folderNote) return;
void createFolderNote(plugin, folder.path, openFile, undefined, true);
createFolderNote(plugin, folder.path, openFile, undefined, true);
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
}

View file

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

View file

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

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();
@ -18,19 +18,19 @@ export class ListComponent {
this.defaultValues = defaultValues;
}
on(event: string, listener: (data?: unknown) => void): void {
on(event: string, listener: (data?: any) => void) {
this.emitter.on(event, listener);
}
off(event: string, listener: (data?: unknown) => void): void {
off(event: string, listener: (data?: any) => void) {
this.emitter.off(event, listener);
}
private emit(event: string, data?: unknown): void {
private emit(event: string, data?: any) {
this.emitter.emit(event, data);
}
setValues(values: string[]): void {
setValues(values: string[]) {
this.removeElements();
this.values = values;
if (values.length !== 0) {
@ -41,63 +41,47 @@ export class ListComponent {
this.emit('update', this.values);
}
removeElements(): void {
removeElements() {
this.listEl.empty();
}
addElement(value: string): void {
addElement(value: string) {
this.listEl.createSpan('setting-hotkey', (span) => {
if (value.toLocaleLowerCase() === 'md') {
const markdown = 'markdown';
span.innerText = markdown;
span.innerText = 'markdown';
} else {
span.innerText = value;
}
span.setAttribute('extension', value);
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' });
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(() => {
svgElement.innerHTML = svg;
removeSpan.onClickEvent((e) => {
this.removeValue(value);
span.remove();
});
});
}
async addValue(value: string): Promise<void> {
async addValue(value: string) {
this.values.push(value);
this.addElement(value);
this.emit('add', value);
this.emit('update', this.values);
}
addResetButton(): this {
// eslint-disable-next-line max-len
addResetButton() {
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>';
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(() => {
resetButton.innerHTML = svg;
resetButton.onClickEvent((e) => {
this.setValues(this.defaultValues);
});
return this;
}
removeValue(value: string): void {
removeValue(value: string) {
this.values = this.values.filter((v) => v !== value);
this.listEl.find(`[extension='${value}']`).remove();
this.emit('remove', value);

View file

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

View file

@ -1,72 +1,48 @@
/* eslint-disable complexity */
/* eslint-disable max-len */
import type FolderNotesPlugin from '../main';
import FolderNotesPlugin from '../main';
import ExistingFolderNoteModal from '../modals/ExistingNote';
import { applyTemplate } from '../template';
import {
TFolder,
TFile,
Keymap,
type TAbstractFile,
type MarkdownView,
type WorkspaceLeaf,
} from 'obsidian';
import { TFolder, TFile, TAbstractFile, Keymap } from 'obsidian';
import DeleteConfirmationModal from '../modals/DeleteConfirmation';
import {
addExcludedFolder,
deleteExcludedFolder,
getDetachedFolder,
getExcludedFolder,
updateExcludedFolder,
} from '../ExcludeFolders/functions/folderFunctions';
import { addExcludedFolder, deleteExcludedFolder, getDetachedFolder, getExcludedFolder, updateExcludedFolder } from '../ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from '../ExcludeFolders/ExcludeFolder';
import { getDefaultTemplate, openExcalidrawView } from './excalidraw';
import { openExcalidrawView } from './excalidraw';
import { AskForExtensionModal } from 'src/modals/AskForExtension';
import {
addCSSClassToFileExplorerEl,
removeCSSClassFromFileExplorerEL,
removeActiveFolder,
setActiveFolder,
updateCSSClassesForFolderNote,
} from 'src/functions/styleFunctions';
import {
getFolderNameFromPathString,
getFolderPathFromString,
removeExtension,
} from 'src/functions/utils';
import { addCSSClassToFileExplorerEl, removeCSSClassFromFileExplorerEL, removeActiveFolder, setActiveFolder } from 'src/functions/styleFunctions';
import { getFolderNameFromPathString, getFolderPathFromString, removeExtension } from 'src/functions/utils';
const defaultExcalidrawTemplate = `---
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,
folderPath: string,
openFile: boolean,
extension?: string,
displayModal?: boolean,
preexistingNote?: TFile,
): Promise<void> {
let {
leaf,
fileName,
folderNote,
folderNoteType,
detachedFolder,
path,
} = getArgs(plugin, folderPath, extension, preexistingNote);
export async function createFolderNote(plugin: FolderNotesPlugin, folderPath: string, openFile: boolean, extension?: string, displayModal?: boolean, preexistingNote?: TFile) {
const leaf = plugin.app.workspace.getLeaf(false);
const folderName = getFolderNameFromPathString(folderPath);
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
let folderNote = getFolderNote(plugin, folderPath);
if (preexistingNote) {
folderNote = preexistingNote;
}
let folderNoteType = extension ?? plugin.settings.folderNoteType;
const detachedFolder = getDetachedFolder(plugin, folderPath);
let path = '';
if (folderNoteType === '.excalidraw') {
folderNoteType = '.md';
extension = '.excalidraw';
} else if (folderNoteType === '.ask') {
if (plugin.askModalCurrentlyOpen) return;
return new AskForExtensionModal(
plugin,
folderPath,
openFile,
folderNoteType,
displayModal,
preexistingNote,
).open();
return new AskForExtensionModal(plugin, folderPath, openFile, folderNoteType, displayModal, preexistingNote).open();
}
if (plugin.settings.storageLocation === 'parentFolder') {
@ -83,7 +59,27 @@ export async function createFolderNote(
}
if (detachedFolder && folderNote?.extension !== extension && folderNote) {
await handleTurnNoteIntoFolderNote(plugin, folderNote, detachedFolder, folderPath, fileName);
deleteExcludedFolder(plugin, detachedFolder);
removeCSSClassFromFileExplorerEL(folderNote?.path, 'is-folder-note', false, plugin);
const folder = plugin.app.vault.getAbstractFileByPath(folderPath) as TFolder;
if (!folderNote || folderNote.basename !== fileName) return;
let count = 1;
let newName = removeExtension(folderNote.path) + ` (${count}).${folderNote.path.split('.').pop()}`;
while (count < 100 && plugin.app.vault.getAbstractFileByPath(newName)) {
count++;
newName = removeExtension(folderNote.path) + ` (${count}).${folderNote.path.split('.').pop()}`;
}
const [excludedFolder, excludedFolderExisted, disabledSync] = await tempDisableSync(plugin, folder);
await plugin.app.fileManager.renameFile(folderNote, newName).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
}
if (!extension) {
@ -91,7 +87,33 @@ export async function createFolderNote(
}
if (!folderNote) {
folderNote = await handleCreateFolderNote(plugin, folderNoteType, openFile, leaf, folderNote, path, extension);
let content = '';
if (extension !== '.md' && extension) {
if (plugin.settings.templatePath && folderNoteType.split('.').pop() === plugin.settings.templatePath.split('.').pop()) {
const templateFile = plugin.app.vault.getAbstractFileByPath(plugin.settings.templatePath);
if (templateFile instanceof TFile) {
if (['md', 'canvas', 'txt'].includes(templateFile.extension)) {
content = await plugin.app.vault.read(templateFile);
if (extension === '.excalidraw' && !content.includes('==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠==')) {
content = defaultExcalidrawTemplate;
}
} else {
return plugin.app.vault.readBinary(templateFile).then(async (data) => {
folderNote = await plugin.app.vault.createBinary(path, data);
if (openFile) {
await leaf.openFile(folderNote);
}
});
}
}
} else if (plugin.settings.folderNoteType === '.excalidraw' || extension === '.excalidraw') {
content = defaultExcalidrawTemplate;
} else if (plugin.settings.folderNoteType === '.canvas') {
content = '{}';
}
}
folderNote = await plugin.app.vault.create(path, content);
} else {
await plugin.app.fileManager.renameFile(folderNote, path);
}
@ -107,13 +129,11 @@ export async function createFolderNote(
}
await leaf.openFile(folderNote);
if (plugin.settings.folderNoteType === '.excalidraw' || extension === '.excalidraw') {
openExcalidrawView(plugin.app, folderNote);
openExcalidrawView(leaf);
}
}
const matchingExtension =
extension?.split('.').pop() ===
plugin.settings.templatePath.split('.').pop();
const matchingExtension = extension?.split('.').pop() === plugin.settings.templatePath.split('.').pop();
if (folderNote && matchingExtension && plugin.settings.folderNoteType !== '.excalidraw') {
applyTemplate(plugin, folderNote, leaf, plugin.settings.templatePath);
}
@ -124,160 +144,19 @@ export async function createFolderNote(
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
}
function getArgs(
plugin: FolderNotesPlugin,
folderPath: string,
extension?: string,
preexistingNote?: TFile,
): {
leaf: WorkspaceLeaf;
fileName: string;
folderNote: TFile | null | undefined;
folderNoteType: string;
detachedFolder: ExcludedFolder | undefined;
path: string | '';
} {
const leaf = plugin.app.workspace.getLeaf(false);
const folderName = getFolderNameFromPathString(folderPath);
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
let folderNote = getFolderNote(plugin, folderPath);
if (preexistingNote) {
folderNote = preexistingNote;
}
let folderNoteType = extension ?? plugin.settings.folderNoteType;
const detachedFolder = getDetachedFolder(plugin, folderPath);
let path = '';
return {
leaf,
fileName,
folderNote,
folderNoteType,
detachedFolder,
path,
};
}
async function handleCreateFolderNote(plugin: FolderNotesPlugin, folderNoteType: string, openFile: boolean, leaf: WorkspaceLeaf, folderNote: TFile | null | undefined, path: string, extension?: string): Promise<TFile> {
let content = '';
if (extension !== '.md' && extension) {
if (
plugin.settings.templatePath &&
folderNoteType.split('.').pop() ===
plugin.settings.templatePath.split('.').pop()
) {
const templateFile = plugin.app.vault.getAbstractFileByPath(
plugin.settings.templatePath,
);
if (templateFile instanceof TFile) {
if (['md', 'canvas', 'txt'].includes(templateFile.extension)) {
content = await plugin.app.vault.read(templateFile);
if (
extension === '.excalidraw' &&
!content.includes(
'==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this activeDocument. ⚠==',
)
) {
content = await getDefaultTemplate(plugin.app);
}
} else {
plugin.app.vault.readBinary(templateFile).then(async (data) => {
folderNote = await plugin.app.vault.createBinary(path, data);
if (openFile) {
await leaf.openFile(folderNote);
}
return folderNote;
});
}
}
} else if (
plugin.settings.folderNoteType === '.excalidraw' ||
extension === '.excalidraw'
) {
content = await getDefaultTemplate(plugin.app);
} else if (plugin.settings.folderNoteType === '.canvas') {
content = '{}';
}
}
folderNote = await plugin.app.vault.create(path, content);
return folderNote;
}
async function handleTurnNoteIntoFolderNote(
plugin: FolderNotesPlugin,
folderNote: TFile,
detachedFolder: ExcludedFolder,
folderPath: string,
fileName: string,
): Promise<void> {
deleteExcludedFolder(plugin, detachedFolder);
removeCSSClassFromFileExplorerEL(folderNote?.path, 'is-folder-note', false, plugin);
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
if (!(folder instanceof TFolder)) return;
if (!folderNote || folderNote.basename !== fileName) return;
let count = 1;
const baseName = removeExtension(folderNote.path);
const ext = folderNote.path.split('.').pop();
let newName = `${baseName} (${count}).${ext}`;
const MAX_FOLDER_NOTE_RENAME_ATTEMPTS = 100;
while (
count < MAX_FOLDER_NOTE_RENAME_ATTEMPTS &&
plugin.app.vault.getAbstractFileByPath(newName)
) {
count++;
newName = `${baseName} (${count}).${ext}`;
}
const [
excludedFolder,
excludedFolderExisted,
disabledSync,
] = await tempDisableSync(plugin, folder);
await plugin.app.fileManager.renameFile(folderNote, newName).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
}
export async function turnIntoFolderNote(
plugin: FolderNotesPlugin,
file: TFile,
folder: TFolder,
folderNote?: TFile | null | TAbstractFile,
skipConfirmation?: boolean,
): Promise<void> {
const { extension } = file;
export async function turnIntoFolderNote(plugin: FolderNotesPlugin, file: TFile, folder: TFolder, folderNote?: TFile | null | TAbstractFile, skipConfirmation?: boolean) {
const extension = file.extension;
const detachedExcludedFolder = getDetachedFolder(plugin, folder.path);
if (folderNote) {
if (
plugin.settings.showRenameConfirmation &&
!skipConfirmation &&
!detachedExcludedFolder
) {
if (plugin.settings.showRenameConfirmation && !skipConfirmation && !detachedExcludedFolder) {
return new ExistingFolderNoteModal(plugin.app, plugin, file, folder, folderNote).open();
}
removeCSSClassFromFileExplorerEL(folderNote.path, 'is-folder-note', false, plugin);
const [
excludedFolder,
excludedFolderExisted,
disabledSync,
] = await tempDisableSync(plugin, folder);
const CTIME_SLICE_START = 10;
const RANDOM_SUFFIX_MAX = 1000;
const randomSuffix = Math.floor(Math.random() * RANDOM_SUFFIX_MAX);
const ctimeSuffix = file.stat.ctime.toString().slice(CTIME_SLICE_START);
const newPath = `${folder.path}/${folder.name} (${ctimeSuffix}${randomSuffix}).${extension}`;
const [excludedFolder, excludedFolderExisted, disabledSync] = await tempDisableSync(plugin, folder);
const newPath = `${folder.path}/${folder.name} (${file.stat.ctime.toString().slice(10) + Math.floor(Math.random() * 1000)}).${extension}`;
plugin.app.fileManager.renameFile(folderNote, newPath).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
@ -303,39 +182,25 @@ export async function turnIntoFolderNote(
}
if (detachedExcludedFolder) {
void deleteExcludedFolder(plugin, detachedExcludedFolder);
deleteExcludedFolder(plugin, detachedExcludedFolder);
}
await plugin.app.fileManager.renameFile(file, path);
void addCSSClassToFileExplorerEl(path, 'is-folder-note', false, plugin, true);
void addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
addCSSClassToFileExplorerEl(path, 'is-folder-note', false, plugin, true);
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
removeActiveFolder(plugin);
setActiveFolder(folder.path, plugin);
}
export async function tempDisableSync(
plugin: FolderNotesPlugin,
folder: TFolder,
): Promise<
[
excludedFolder: ExcludedFolder | undefined,
excludedFolderExisted: boolean,
disabledSync: boolean
]
> {
export async function tempDisableSync(plugin: FolderNotesPlugin, folder: TFolder): Promise<[excludedFolder: ExcludedFolder | undefined, excludedFolderExisted: boolean, disabledSync: boolean]> {
let excludedFolder = getExcludedFolder(plugin, folder.path, false);
let excludedFolderExisted = true;
let disabledSync = false;
if (!excludedFolder) {
excludedFolderExisted = false;
excludedFolder = new ExcludedFolder(
folder.path,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
excludedFolder = new ExcludedFolder(folder.path, plugin.settings.excludeFolders.length, undefined, plugin);
excludedFolder.disableSync = true;
addExcludedFolder(plugin, excludedFolder);
} else if (!excludedFolder.disableSync) {
@ -347,12 +212,8 @@ export async function tempDisableSync(
return [excludedFolder, excludedFolderExisted, disabledSync];
}
export async function openFolderNote(
plugin: FolderNotesPlugin,
file: TAbstractFile,
evt?: MouseEvent,
): Promise<void> {
const { path } = file;
export async function openFolderNote(plugin: FolderNotesPlugin, file: TAbstractFile, evt?: MouseEvent) {
const path = file.path;
const focusExistingTab = plugin.settings.focusExistingTab && plugin.settings.openInNewTab;
const activeFilePath = plugin.app.workspace.getActiveFile()?.path;
@ -367,7 +228,7 @@ export async function openFolderNote(
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (
leaf.getViewState().type === 'markdown' &&
(leaf.view as MarkdownView).file?.path === path
(leaf.view as import('obsidian').MarkdownView).file?.path === path
) {
foundLeaf = leaf;
}
@ -377,19 +238,14 @@ export async function openFolderNote(
if (foundLeaf) {
plugin.app.workspace.setActiveLeaf(foundLeaf, { focus: true });
} else {
const shouldOpenInNewTab = Keymap.isModEvent(evt) || plugin.settings.openInNewTab;
const leaf = plugin.app.workspace.getLeaf(shouldOpenInNewTab);
const leaf = plugin.app.workspace.getLeaf(Keymap.isModEvent(evt) || plugin.settings.openInNewTab);
if (file instanceof TFile) {
await leaf.openFile(file);
}
}
}
export async function deleteFolderNote(
plugin: FolderNotesPlugin,
file: TFile,
displayModal: boolean,
): Promise<void> {
export async function deleteFolderNote(plugin: FolderNotesPlugin, file: TFile, displayModal: boolean) {
if (plugin.settings.showDeleteConfirmation && displayModal) {
return new DeleteConfirmationModal(plugin.app, plugin, file).open();
}
@ -397,7 +253,7 @@ export async function deleteFolderNote(
if (!folder) return;
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(excludedFolder) => (excludedFolder.path !== folder.path) || !excludedFolder.showFolderNote);
(excludedFolder) => (excludedFolder.path !== folder.path) && excludedFolder.showFolderNote);
plugin.saveSettings(false);
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
@ -414,7 +270,7 @@ export async function deleteFolderNote(
}
}
export function extractFolderName(template: string, changedFileName: string): string | null {
export function extractFolderName(template: string, changedFileName: string) {
const [prefix, suffix] = template.split('{{folder_name}}');
if (prefix.trim() === '' && suffix.trim() === '') {
return changedFileName;
@ -430,70 +286,58 @@ export function extractFolderName(template: string, changedFileName: string): st
return null;
}
function findFolderNoteFile(
plugin: FolderNotesPlugin,
path: string,
primaryType: string,
): TFile | null {
let folderNote = plugin.app.vault.getAbstractFileByPath(path + primaryType);
if (
folderNote instanceof TFile &&
plugin.settings.supportedFileTypes.includes(primaryType.replace('.', ''))
) {
return folderNote;
export function getFolderNote(plugin: FolderNotesPlugin, folderPath: string, storageLocation?: string, file?: TFile, oldFolderNoteName?: string) {
if (!folderPath) return null;
const folder = {
path: folderPath,
name: getFolderNameFromPathString(folderPath),
};
const folderNoteName = oldFolderNoteName ?? plugin.settings.folderNoteName;
let fileName = folderNoteName.replace('{{folder_name}}', folder.name);
if (file) {
fileName = folderNoteName.replace('{{folder_name}}', file.basename);
}
const supportedFileTypes = plugin.settings.supportedFileTypes.filter(
(type) => type !== primaryType.replace('.', ''),
);
for (let type of supportedFileTypes) {
if (type === 'excalidraw' || type === '.excalidraw') {
type = '.md';
}
if (!type.startsWith('.')) {
type = '.' + type;
}
folderNote = plugin.app.vault.getAbstractFileByPath(path + type);
if (folderNote instanceof TFile) {
return folderNote;
}
}
return null;
}
export function getFolderNote(
plugin: FolderNotesPlugin,
folderPath: string,
storageLocation?: string,
file?: TFile,
oldFolderNoteName?: string,
): TFile | null | undefined {
const folder = getFolderInfo(folderPath);
if (!folder) return null;
let fileName = resolveFileName(plugin, folder, file, oldFolderNoteName);
if (!fileName) return null;
adjustFolderPathForStorage(folder, folderPath, plugin, storageLocation);
if ((plugin.settings.storageLocation === 'parentFolder' || storageLocation === 'parentFolder') && storageLocation !== 'insideFolder') {
folder.path = getFolderPathFromString(folderPath);
}
const path = buildFullPath(folder, fileName);
const primaryType = normalizeFolderNoteType(plugin.settings.folderNoteType);
return findFolderNoteFile(plugin, path, primaryType);
let path = `${folder.path}/${fileName}`;
folder.path === '/' ? path = fileName : path = `${folder.path}/${fileName}`;
let folderNoteType = plugin.settings.folderNoteType;
if (folderNoteType === '.excalidraw') {
folderNoteType = '.md';
}
let folderNote = plugin.app.vault.getAbstractFileByPath(path + folderNoteType);
if (folderNote instanceof TFile && plugin.settings.supportedFileTypes.includes(plugin.settings.folderNoteType.replace('.', ''))) {
return folderNote;
} else {
const supportedFileTypes = plugin.settings.supportedFileTypes.filter((type) => type !== plugin.settings.folderNoteType.replace('.', ''));
for (let type of supportedFileTypes) {
if (type === 'excalidraw' || type === '.excalidraw') {
type = '.md';
}
if (!type.startsWith('.')) {
type = '.' + type;
}
folderNote = plugin.app.vault.getAbstractFileByPath(path + type);
if (folderNote instanceof TFile) {
return folderNote;
}
}
}
}
export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile): void {
export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile) {
const folder = getFolder(plugin, file);
if (!folder) return;
const excludedFolder = new ExcludedFolder(
folder.path,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
excludedFolder.showFolderNote = true;
const excludedFolder = new ExcludedFolder(folder.path, plugin.settings.excludeFolders.length, undefined, plugin);
excludedFolder.hideInSettings = true;
excludedFolder.disableFolderNote = true;
excludedFolder.disableSync = true;
@ -502,32 +346,19 @@ export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile): void {
excludedFolder.detached = true;
excludedFolder.detachedFilePath = file.path;
addExcludedFolder(plugin, excludedFolder);
void updateCSSClassesForFolderNote(file.path, plugin);
}
export function getFolder(
plugin: FolderNotesPlugin,
file: TFile,
storageLocation?: string,
): TFolder | TAbstractFile | null {
export function getFolder(plugin: FolderNotesPlugin, file: TFile, storageLocation?: string) {
if (!file) return null;
let folderName = extractFolderName(plugin.settings.folderNoteName, file.basename);
if (
plugin.settings.folderNoteName === file.basename &&
plugin.settings.storageLocation === 'insideFolder'
) {
if (plugin.settings.folderNoteName === file.basename && plugin.settings.storageLocation === 'insideFolder') {
folderName = file.parent?.name ?? '';
}
if (!folderName) return null;
let folderPath = getFolderPathFromString(file.path);
let folder: TFolder | TAbstractFile | null = null;
if (
(plugin.settings.storageLocation === 'parentFolder' ||
storageLocation === 'parentFolder') &&
storageLocation !== 'insideFolder'
) {
if ((plugin.settings.storageLocation === 'parentFolder' || storageLocation === 'parentFolder') && storageLocation !== 'insideFolder') {
if (folderPath.trim() === '' || folderPath === '/') {
folderPath = folderName;
} else {
@ -537,16 +368,11 @@ export function getFolder(
} else {
folder = plugin.app.vault.getAbstractFileByPath(folderPath);
}
if (!folder) { return null; }
return folder;
}
export function getFolderNoteFolder(
plugin: FolderNotesPlugin,
folderNote: TFile | string,
fileName: string,
): TFolder | TAbstractFile | null {
export function getFolderNoteFolder(plugin: FolderNotesPlugin, folderNote: TFile | string, fileName: string) {
if (!folderNote) return null;
let filePath = '';
if (typeof folderNote === 'string') {
@ -556,13 +382,6 @@ 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') {
@ -578,46 +397,3 @@ export function getFolderNoteFolder(
if (!folder) { return null; }
return folder;
}
function getFolderInfo(folderPath: string): { path: string; name: string } | null {
if (!folderPath) return null;
return {
path: folderPath,
name: getFolderNameFromPathString(folderPath),
};
}
function resolveFileName(
plugin: FolderNotesPlugin,
folder: { path: string; name: string },
file?: TFile,
oldFolderNoteName?: string,
): string | null {
const templateName = oldFolderNoteName ?? plugin.settings.folderNoteName;
if (!templateName) return null;
const nameSource = file ? file.basename : folder.name;
return templateName.replace('{{folder_name}}', nameSource);
}
function adjustFolderPathForStorage(
folder: { path: string; name: string },
folderPath: string,
plugin: FolderNotesPlugin,
storageLocation?: string,
): void {
if (
(plugin.settings.storageLocation === 'parentFolder' ||
storageLocation === 'parentFolder') &&
storageLocation !== 'insideFolder'
) {
folder.path = getFolderPathFromString(folderPath);
}
}
function buildFullPath(folder: { path: string }, fileName: string): string {
return folder.path === '/' ? fileName : `${folder.path}/${fileName}`;
}
function normalizeFolderNoteType(type: string): string {
return type === '.excalidraw' ? '.md' : type;
}

View file

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

View file

@ -1,26 +1,20 @@
import { TFolder, TFile, View, type TAbstractFile } from 'obsidian';
import type { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import { TFolder, TFile, View } from 'obsidian';
import { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import { getFolderNote } from './folderNoteFunctions';
import type FolderNotesPlugin from 'src/main';
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';
}
import FolderNotesPlugin from 'src/main';
import { FileExplorerLeaf } from 'obsidian-typings';
import FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
export function getFileNameFromPathString(path: string): string {
return path.substring(path.lastIndexOf('/') >= 0 ? path.lastIndexOf('/') + 1 : 0);
}
export function getFolderNameFromPathString(path: string): string {
const PARENT_FOLDER_INDEX = -2;
const LAST_FOLDER_INDEX = -1;
if (path.endsWith('.md') || path.endsWith('.canvas')) {
return path.split('/').slice(PARENT_FOLDER_INDEX)[0];
return path.split('/').slice(-2)[0];
} else {
return path.split('/').slice(-1)[0];
}
return path.split('/').slice(LAST_FOLDER_INDEX)[0];
}
export function removeExtension(name: string): string {
@ -36,76 +30,46 @@ export function getFolderPathFromString(path: string): string {
const folderPath = path.substring(0, subString);
if (folderPath === '') {
return '/';
} else {
return folderPath;
}
return folderPath;
}
export function getParentFolderPath(path: string): string {
return getFolderPathFromString(getFolderPathFromString(path));
return this.getFolderPathFromString(this.getFolderPathFromString(path));
}
export function getFileExplorer(
plugin: FolderNotesPlugin | FolderOverviewPlugin,
): FileExplorerWorkspaceLeaf | undefined {
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') {
plugin.app.workspace.iterateAllLeaves((x) => {
if (isFileExplorerWorkspaceLeaf(x)) {
leaf = x;
}
});
}
return isFileExplorerWorkspaceLeaf(leaf) ? leaf : undefined;
export function getFileExplorer(plugin: FolderNotesPlugin | FolderOverviewPlugin) {
return plugin.app.workspace.getLeavesOfType('file-explorer')[0] as any as FileExplorerWorkspaceLeaf;
}
export function getFileExplorerView(plugin: FolderNotesPlugin): FileExplorerView | undefined {
return getFileExplorer(plugin)?.view;
export function getFileExplorerView(plugin: FolderNotesPlugin) {
return getFileExplorer(plugin).view;
}
export function getFocusedItem(plugin: FolderNotesPlugin): FileTreeItem | null {
const fileExplorer = getFileExplorer(plugin);
if (!fileExplorer) { return null; }
const { focusedItem } = fileExplorer.view.tree;
export function getFocusedItem(plugin: FolderNotesPlugin) {
const fileExplorer = getFileExplorer(plugin) as any as FileExplorerLeaf;
const focusedItem = fileExplorer.view.tree.focusedItem;
return focusedItem;
}
export function getFileExplorerActiveFolder(plugin: FolderNotesPlugin): TFolder | null {
export function getFileExplorerActiveFolder(): TFolder | null {
// Check if the active view is a file explorer.
const view = plugin.app.workspace.getActiveViewOfType(View);
const view = this.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;
return activeFileOrFolder as TFolder;
}
export function getFileExplorerActiveFolderNote(plugin: FolderNotesPlugin): TFile | null {
const folder = getFileExplorerActiveFolder(plugin);
export function getFileExplorerActiveFolderNote(): TFile | null {
const folder = getFileExplorerActiveFolder();
if (!folder) return null;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(plugin, folder.path);
const folderNote = getFolderNote(this.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 + '/');
}

4
src/globals.d.ts vendored
View file

@ -1,4 +1,4 @@
import type { TAbstractFile, TFile, TFolder, View, WorkspaceLeaf } from 'obsidian';
import { TAbstractFile, TFile, TFolder, View, WorkspaceLeaf } from 'obsidian';
declare module 'obsidian' {
interface Setting {
@ -40,6 +40,7 @@ interface FileItem {
el: HTMLDivElement;
file: TFile;
fileExplorer: FileExplorerView;
info: any;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
}
@ -47,6 +48,7 @@ interface FileItem {
interface FolderItem {
el: HTMLDivElement;
fileExplorer: FileExplorerView;
info: any;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
file: TFolder;

View file

@ -1,116 +1,88 @@
import {
type App, type TAbstractFile,
type MarkdownPostProcessorContext,
type WorkspaceLeaf,
Plugin, TFile, TFolder,
parseYaml, Notice, Keymap,
requireApiVersion, Platform, debounce,
} from 'obsidian';
import {
type FolderNotesSettings, DEFAULT_SETTINGS, SettingsTab,
} from './settings/SettingsTab';
import { FolderOverview, type defaultOverviewSettings } from './obsidian-folder-overview/src/FolderOverview';
import { Plugin, TFile, TFolder, TAbstractFile, MarkdownPostProcessorContext, parseYaml, Notice, Keymap, WorkspaceLeaf, requireApiVersion, Platform } from 'obsidian';
import { DEFAULT_SETTINGS, FolderNotesSettings, SettingsTab } from './settings/SettingsTab';
import { Commands } from './Commands';
import type { FileExplorerWorkspaceLeaf } from './globals';
import {
registerFileExplorerObserver, unregisterFileExplorerObserver,
} from './events/MutationObserver';
import { FileExplorerWorkspaceLeaf } from './globals';
import { registerFileExplorerObserver, unregisterFileExplorerObserver } from './events/MutationObserver';
import { handleRename } from './events/handleRename';
import {
getFolderNote, getFolder, openFolderNote, createFolderNote,
} from './functions/folderNoteFunctions';
import { getFolderNote, getFolder, openFolderNote, createFolderNote } from './functions/folderNoteFunctions';
import { handleCreate } from './events/handleCreate';
import { FrontMatterTitlePluginHandler } from './events/FrontMatterTitle';
import { FolderOverviewSettings } from './obsidian-folder-overview/src/modals/Settings';
import { FolderOverview } from './obsidian-folder-overview/src/FolderOverview';
import { TabManager } from './events/TabManager';
import './functions/ListComponent';
import { handleDelete } from './events/handleDelete';
import {
addCSSClassToFileExplorerEl, getFileExplorerElement, removeCSSClassFromFileExplorerEL,
refreshAllFolderStyles, setActiveFolder, removeActiveFolder,
} from './functions/styleFunctions';
import { addCSSClassToFileExplorerEl, getFileExplorerElement, removeCSSClassFromFileExplorerEL, refreshAllFolderStyles, setActiveFolder, removeActiveFolder } from './functions/styleFunctions';
import { getExcludedFolder } from './ExcludeFolders/functions/folderFunctions';
import { getFileExplorer } from './functions/utils';
import { FileExplorerView, InternalPlugin } from 'obsidian-typings';
import { getFocusedItem } from './functions/utils';
import { FOLDER_OVERVIEW_VIEW, FolderOverviewView } from './obsidian-folder-overview/src/view';
import { registerOverviewCommands } from './obsidian-folder-overview/src/Commands';
import { updateOverviewView, updateViewDropdown } from './obsidian-folder-overview/src/main';
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;
observer: MutationObserver;
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;
async onload(): Promise<void> {
console.debug('loading folder notes plugin');
private fileExplorerPlugin!: InternalPlugin;
private fileExplorerView!: FileExplorerView;
async onload() {
console.log('loading folder notes plugin');
await this.loadSettings();
this.settingsTab = new SettingsTab(this.app, this);
this.addSettingTab(this.settingsTab);
await this.saveSettings();
this.fvIndexDB = new FvIndexDB(this);
this.saveSettings();
// Add CSS Classes
this.addSettingCssClasses();
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.highlightFolder) { document.body.classList.add('disable-folder-highlight'); }
if (requireApiVersion('1.7.2')) {
document.body.classList.add('version-1-7-2');
}
new Commands(this.app, this).registerCommands();
registerOverviewCommands(this);
this.app.workspace.onLayoutReady(() => this.onLayoutReady());
this.app.workspace.onLayoutReady(this.onLayoutReady.bind(this));
// this.observer.observe(document.body, {
// childList: true,
// subtree: true,
// });
if (!this.settings.persistentSettingsTab.afterRestart) {
this.settings.settingsTab = 'general';
}
this.registerDomEvent(window, 'keydown', (event: KeyboardEvent) => {
const { hoveredElement } = this;
if (event.key === 'Enter') {
const folderNote = getFolderNote(this, getFocusedItem(this)?.file?.path || '');
if (!folderNote) return;
openFolderNote(this, folderNote);
}
const hoveredElement = this.hoveredElement;
if (this.hoverLinkTriggered) return;
if (!hoveredElement) return;
if (!Keymap.isModEvent(event)) return;
@ -132,7 +104,11 @@ export default class FolderNotesPlugin extends Plugin {
this.hoverLinkTriggered = true;
});
this.registerEvent(this.app.workspace.on('file-open', (openFile: TFile | null) => {
this.registerEvent(this.app.vault.on('create', (file: TAbstractFile) => {
handleCreate(file, this);
}));
this.registerEvent(this.app.workspace.on('file-open', async (openFile: TFile | null) => {
removeActiveFolder(this);
if (!openFile || !openFile.basename) { return; }
@ -147,143 +123,69 @@ export default class FolderNotesPlugin extends Plugin {
setActiveFolder(folder.path, this);
}));
this.registerEvent(this.app.vault.on('create', (file: TAbstractFile) => {
handleCreate(file, this).catch((err) => {
console.error(err); new Notice('Error handling file creation');
});
this.handleVaultChange();
}));
this.registerEvent(this.app.vault.on('rename', (file: TAbstractFile, oldPath: string) => {
handleRename(file, oldPath, this);
this.handleVaultChange();
}));
this.registerEvent(this.app.vault.on('delete', (file: TAbstractFile) => {
handleDelete(file, this);
this.handleVaultChange();
}));
this.registerMarkdownCodeBlockProcessor(
'folder-overview',
(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
this.handleOverviewBlock(source, el, ctx);
},
this.registerMarkdownCodeBlockProcessor('folder-overview', (source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
this.handleOverviewBlock(source, el, ctx);
});
this.registerEvent(
this.app.workspace.on('layout-change', () => {
const fileExplorerLeaf = this.app.workspace.getLeavesOfType('file-explorer')[0];
if (!fileExplorerLeaf) return;
const container = fileExplorerLeaf.view.containerEl;
if (container) {
this.registerDomEvent(container, 'click', (evt: MouseEvent) => {
this.handleFileExplorerClick(evt);
}, true);
}
})
);
}
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;
}
onLayoutReady() {
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);
});
this.app.workspace.on('layout-change', () => {
this.tabManager.updateTabs();
});
if (this.app.plugins.getPlugin('obsidian-front-matter-title-plugin')) {
if (this.settings.frontMatterTitle.enabled) {
this.fmtpHandler = new FrontMatterTitlePluginHandler(this);
}
this.tabManager = new TabManager(this);
this.tabManager.updateTabs();
this.registerDomEvent(activeDocument, 'click', (evt: MouseEvent) => {
this.handleFileExplorerClick(evt);
}, true);
// Handle middle mouse button clicks
this.registerDomEvent(activeDocument, 'auxclick', (evt: MouseEvent) => {
const rightClick = 2;
if (evt.button === rightClick) return;
this.handleFileExplorerClick(evt);
}, true);
const fileExplorerLeaf = this.app.workspace.getLeavesOfType('file-explorer')[0];
if (fileExplorerLeaf) {
const container = fileExplorerLeaf.view.containerEl;
if (container) {
this.registerDomEvent(container, 'click', (evt: MouseEvent) => {
this.handleFileExplorerClick(evt);
}, true);
}
}
const fileExplorerPlugin = this.app.internalPlugins.getEnabledPluginById('file-explorer');
if (fileExplorerPlugin) {
const fileExplorer = fileExplorerPlugin as unknown as FileExplorerPluginLike;
const originalRevealInFolder =
(fileExplorer.revealInFolder as unknown as FileExplorerPluginLike['revealInFolder'])
.bind(fileExplorer);
fileExplorer.revealInFolder = (file: TAbstractFile): void => {
const originalRevealInFolder = fileExplorerPlugin.revealInFolder.bind(fileExplorerPlugin);
fileExplorerPlugin.revealInFolder = (file: TAbstractFile) => {
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) {
originalRevealInFolder(file);
return;
}
activeDocument.body.classList.remove('hide-folder-note');
originalRevealInFolder(folder);
const FOLDER_REVEAL_DELAY = 100;
window.setTimeout(() => {
activeDocument.body.classList.add('hide-folder-note');
}, FOLDER_REVEAL_DELAY);
document.body.classList.remove('hide-folder-note');
originalRevealInFolder.call(fileExplorerPlugin, folder);
setTimeout(() => {
document.body.classList.add('hide-folder-note');
}, 100);
return;
}
}
if (file instanceof TFolder || file instanceof TFile) {
originalRevealInFolder(file);
return;
}
return originalRevealInFolder.call(fileExplorerPlugin, file);
};
}
@ -292,31 +194,26 @@ export default class FolderNotesPlugin extends Plugin {
if (!view) { return; }
const viewWithEditModes = view as ViewWithEditModes;
const activeEditor = this.app.workspace.activeEditor as ActiveEditorLike | undefined;
const editMode = viewWithEditModes.editMode ?? viewWithEditModes.sourceMode
?? activeEditor?.editMode;
// @ts-ignore
const editMode = view.editMode ?? view.sourceMode ?? this.app.workspace.activeEditor?.editMode;
// eslint-disable-next-line
const plugin = this;
if (!editMode) { return; }
const { clipboardManager } = editMode;
const clipboardProto = Object.getPrototypeOf(clipboardManager) as ClipboardManagerLike;
// @ts-ignore
const clipboardProto = editMode.clipboardManager.constructor.prototype;
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 as ClipboardManagerLike).app;
const originalHandleDragOver = clipboardProto.handleDragOver;
const originalHandleDrop = clipboardProto.handleDrop;
clipboardProto.handleDragOver = function (evt: DragEvent, ...args: any[]) {
const dragManager = this.app.dragManager;
const draggable = dragManager?.draggable;
if (draggable?.file instanceof TFolder) {
const folderNote = getFolderNote(folderNotePlugin, draggable.file.path);
const folderNote = getFolderNote(plugin, draggable.file.path);
if (folderNote) {
dragManager.setAction(
window.i18next.t('interface.drag-and-drop.insert-link-here'),
);
dragManager.setAction(window.i18next.t('interface.drag-and-drop.insert-link-here'));
return;
}
}
@ -324,12 +221,12 @@ export default class FolderNotesPlugin extends Plugin {
return originalHandleDragOver.call(this, evt, ...args);
};
clipboardProto.handleDrop = function (evt: DragEvent, ...args: unknown[]): void {
const { dragManager } = (this as ClipboardManagerLike).app;
clipboardProto.handleDrop = function (evt: DragEvent, ...args: any[]) {
const dragManager = this.app.dragManager;
const draggable = dragManager?.draggable;
if (draggable?.file instanceof TFolder) {
const folderNote = getFolderNote(folderNotePlugin, draggable.file.path);
const folderNote = getFolderNote(plugin, draggable.file.path);
if (folderNote) {
draggable.file = folderNote;
draggable.type = 'file';
@ -338,106 +235,53 @@ export default class FolderNotesPlugin extends Plugin {
return originalHandleDrop.call(this, evt, ...args);
};
if (this.settings.fvGlobalSettings.autoUpdateLinks) {
this.fvIndexDB.init(false);
}
}
handleVaultChange(): void {
if (!this.settings.fvGlobalSettings.autoUpdateLinks) return;
const DEBOUNCE_DELAY = 2000;
debounce(() => {
void updateAllOverviews(this);
}, DEBOUNCE_DELAY, true)();
}
handleFileExplorerClick(evt: MouseEvent): void {
handleFileExplorerClick(evt: MouseEvent) {
const target = evt.target as HTMLElement;
if (evt.shiftKey) return;
if (this.isMobileClickDisabled()) return;
const { folderTitleEl, onlyClickedOnFolderTitle } = this.getFolderTitleInfo(target);
if (Platform.isMobile && this.settings.disableOpenFolderNoteOnClick) return;
// Check if the click is on a folder
const folderTitleEl = target.closest('.nav-folder-title') as HTMLElement;
if (!folderTitleEl) return;
if (this.shouldIgnoreClickByWhitespaceOrCollapse(target, onlyClickedOnFolderTitle)) return;
const folderPath = this.getValidFolderPath(folderTitleEl);
const onlyClickedOnFolderTitle = !!target.closest('.nav-folder-title-content');
if (!this.settings.stopWhitespaceCollapsing && !onlyClickedOnFolderTitle) return;
// Ignore clicks on the collapse icon
if (target.closest('.collapse-icon')) return;
const folderPath = folderTitleEl.getAttribute('data-path');
if (!folderPath) return;
const usedCtrl = this.isCtrlUsed(evt);
const folderNote = getFolderNote(this, folderPath);
const excludedFolder = getExcludedFolder(this, folderPath, true);
if (excludedFolder?.disableFolderNote) return;
if (!folderNote && this.shouldCreateNote(evt, usedCtrl)) {
this.createNoteAndMark(folderPath);
return;
const folderNote = getFolderNote(this, folderPath);
if (!folderNote && (evt.altKey || Keymap.isModEvent(evt) === 'tab')) {
if ((this.settings.altKey && evt.altKey) || (this.settings.ctrlKey && Keymap.isModEvent(evt) === 'tab')) {
createFolderNote(this, folderPath, true, undefined, true);
addCSSClassToFileExplorerEl(folderPath, 'has-folder-note', false, this);
removeCSSClassFromFileExplorerEL(folderPath, 'has-not-folder-note', false, this);
return;
}
}
if (!(folderNote instanceof TFile)) return;
if (!this.shouldOpenNote(usedCtrl, evt)) return;
if (!this.settings.enableCollapsing || usedCtrl) {
if (this.settings.openWithCtrl && !evt.ctrlKey) return;
if (this.settings.openWithAlt && !evt.altKey) return;
if (!this.settings.enableCollapsing) {
evt.preventDefault();
evt.stopImmediatePropagation();
}
void openFolderNote(this, folderNote, evt);
openFolderNote(this, folderNote, evt);
}
private isMobileClickDisabled(): boolean {
return Platform.isMobile && this.settings.disableOpenFolderNoteOnClick;
}
private getFolderTitleInfo(target: HTMLElement): {
folderTitleEl: HTMLElement | null;
onlyClickedOnFolderTitle: boolean;
} {
const folderTitleEl = target.closest('.nav-folder-title');
const onlyClickedOnFolderTitle = !!target.closest('.nav-folder-title-content');
return {
folderTitleEl: folderTitleEl instanceof HTMLElement ? folderTitleEl : null,
onlyClickedOnFolderTitle,
};
}
private shouldIgnoreClickByWhitespaceOrCollapse(
target: HTMLElement,
onlyClickedOnFolderTitle: boolean,
): boolean {
if (!this.settings.stopWhitespaceCollapsing && !onlyClickedOnFolderTitle) return true;
if (target.closest('.collapse-icon')) return true;
return false;
}
private getValidFolderPath(folderTitleEl: HTMLElement): string | null {
const folderPath = folderTitleEl.getAttribute('data-path');
if (!folderPath) return null;
const excludedFolder = getExcludedFolder(this, folderPath, true);
if (excludedFolder?.disableFolderNote) return null;
return folderPath;
}
private isCtrlUsed(evt: MouseEvent): boolean {
return Platform.isMacOS ? evt.metaKey : evt.ctrlKey;
}
private shouldCreateNote(evt: MouseEvent, usedCtrl: boolean): boolean {
const isTabMod = Keymap.isModEvent(evt) === 'tab';
if (!(evt.altKey || isTabMod)) return false;
return (this.settings.altKey && evt.altKey) || (usedCtrl && this.settings.ctrlKey);
}
private createNoteAndMark(folderPath: string): void {
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 {
if (this.settings.openWithCtrl && !usedCtrl) return false;
if (this.settings.openWithAlt && !evt.altKey) return false;
return true;
}
handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext): void {
handleOverviewBlock(source: string, el: HTMLElement, ctx: MarkdownPostProcessorContext) {
const observer = new MutationObserver(() => {
const editButton = el.parentElement?.childNodes.item(1);
if (editButton) {
@ -445,14 +289,7 @@ export default class FolderNotesPlugin extends Plugin {
e.stopImmediatePropagation();
e.preventDefault();
e.stopPropagation();
new FolderOverviewSettings(
this.app,
this,
parseYaml(source) as defaultOverviewSettings,
ctx,
el,
this.settings.defaultOverview,
).open();
new FolderOverviewSettings(this.app, this, parseYaml(source), ctx, el, this.settings.defaultOverview).open();
}, { capture: true });
}
});
@ -464,35 +301,21 @@ export default class FolderNotesPlugin extends Plugin {
try {
if (this.app.workspace.layoutReady) {
const { defaultOverview } = this.settings;
const folderOverview = new FolderOverview(
this,
ctx,
source,
el,
defaultOverview,
);
void folderOverview.create(this, el, ctx);
const folderOverview = new FolderOverview(this, ctx, source, el, this.settings.defaultOverview);
folderOverview.create(this, parseYaml(source), el, ctx);
} else {
this.app.workspace.onLayoutReady(() => {
const folderOverview = new FolderOverview(
this,
ctx,
source,
el,
this.settings.defaultOverview,
);
void folderOverview.create(this, el, ctx);
const folderOverview = new FolderOverview(this, ctx, source, el, this.settings.defaultOverview);
folderOverview.create(this, parseYaml(source), el, ctx);
});
}
} catch (e) {
// eslint-disable-next-line max-len
new Notice('Error creating folder overview (folder notes plugin) - check console for more details');
console.error(e);
}
}
async activateOverviewView(): Promise<void> {
async activateOverviewView() {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
@ -506,69 +329,49 @@ export default class FolderNotesPlugin extends Plugin {
}
if (!leaf) return;
void workspace.revealLeaf(leaf);
workspace.revealLeaf(leaf);
}
updateOverviewView: typeof updateOverviewView = updateOverviewView;
updateViewDropdown: typeof updateViewDropdown = updateViewDropdown;
updateOverviewView = updateOverviewView;
updateViewDropdown = updateViewDropdown;
isEmptyFolderNoteFolder(folder: TFolder): boolean {
let attachmentFolderPath = this.app.vault.getConfig('attachmentFolderPath') as string;
const attachmentFolderPath = this.app.vault.getConfig('attachmentFolderPath') as string;
const cleanAttachmentFolderPath = attachmentFolderPath?.replace('./', '') || '';
const attachmentsAreInRootFolder = attachmentFolderPath === './'
|| attachmentFolderPath === '';
const attachmentsAreInRootFolder = attachmentFolderPath === './' || attachmentFolderPath === '';
const threshold = this.settings.storageLocation === 'insideFolder' ? 1 : 0;
if (folder.children.length === 0) {
void addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
}
attachmentFolderPath = `${folder.path}/${cleanAttachmentFolderPath}`;
if (folder.children.length === threshold) {
void addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, this);
return true;
} else if (folder.children.length > threshold) {
if (attachmentsAreInRootFolder) {
return false;
} else if (
this.app.vault.getAbstractFileByPath(attachmentFolderPath) instanceof TFolder
) {
const attachmentFolder = this.app.vault.getAbstractFileByPath(attachmentFolderPath);
if (
attachmentFolder instanceof TFolder &&
folder.children.length <= threshold + 1
) {
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,
);
} else if (this.settings.ignoreAttachmentFolder && this.app.vault.getAbstractFileByPath(`${folder.path}/${cleanAttachmentFolderPath}`)) {
const folderPath = `${folder.path}/${cleanAttachmentFolderPath}`;
const attachmentFolder = this.app.vault.getAbstractFileByPath(folderPath);
if (attachmentFolder instanceof TFolder && folder.children.length <= threshold + 1) {
if (!folder.collapsed) {
getFileExplorerElement(folder.path, this)?.click();
}
}
return folder.children.length <= threshold + 1;
} else {
return false;
}
return false;
}
return true;
}
async changeFolderNameInExplorer(
folder: TFolder,
newName: string | null | undefined,
waitForCreate = false,
count = 0,
): Promise<void> {
const MAX_RETRY_COUNT = 5;
const RETRY_DELAY_MS = 500;
async changeFolderNameInExplorer(folder: TFolder, newName: string | null | undefined, waitForCreate = false, count = 0) {
if (!newName) newName = folder.name;
let fileExplorerItem = getFileExplorerElement(folder.path, this);
if (!fileExplorerItem) {
if (waitForCreate && count < MAX_RETRY_COUNT) {
await new Promise<void>((resolve) => window.setTimeout(resolve, RETRY_DELAY_MS));
void this.changeFolderNameInExplorer(folder, newName, waitForCreate, count + 1);
if (waitForCreate && count < 5) {
await new Promise((r) => setTimeout(r, 500));
this.changeFolderNameInExplorer(folder, newName, waitForCreate, count + 1);
return;
}
return;
@ -577,19 +380,15 @@ export default class FolderNotesPlugin extends Plugin {
fileExplorerItem = fileExplorerItem?.querySelector('div.nav-folder-title-content');
if (!fileExplorerItem) { return; }
if (this.settings.frontMatterTitle.explorer && this.settings.frontMatterTitle.enabled) {
(fileExplorerItem).innerText = newName;
(fileExplorerItem).setAttribute('old-name', folder.name);
fileExplorerItem.innerText = newName;
fileExplorerItem.setAttribute('old-name', folder.name);
} else {
(fileExplorerItem).innerText = folder.name;
(fileExplorerItem).removeAttribute('old-name');
fileExplorerItem.innerText = folder.name;
fileExplorerItem.removeAttribute('old-name');
}
}
async changeFolderNameInPath(
folder: TFolder,
newName: string | null | undefined,
breadcrumb: HTMLElement,
): Promise<void> {
async changeFolderNameInPath(folder: TFolder, newName: string | null | undefined, breadcrumb: HTMLElement) {
if (!newName) newName = folder.name;
breadcrumb.textContent = folder.newName || folder.name;
@ -600,9 +399,9 @@ export default class FolderNotesPlugin extends Plugin {
/**
* Updates all folder names in the path above the note editor
*/
updateAllBreadcrumbs(remove?: boolean): void {
updateAllBreadcrumbs(remove?: boolean) {
if (!this.settings.frontMatterTitle.path && !remove) { return; }
const viewHeaderItems = activeDocument.querySelectorAll('span.view-header-breadcrumb');
const viewHeaderItems = document.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; }
@ -620,20 +419,20 @@ export default class FolderNotesPlugin extends Plugin {
});
}
onunload(): void {
onunload() {
unregisterFileExplorerObserver();
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');
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');
removeActiveFolder(this);
if (this.fmtpHandler) {
this.fmtpHandler.deleteEvent();
}
}
async loadSettings(): Promise<void> {
const data = await this.loadData() as LegacySettingsData | null;
async loadSettings() {
const data = await this.loadData();
if (data) {
if (data.allowWhitespaceCollapsing === true) {
data.stopWhitespaceCollapsing = false;
@ -650,14 +449,12 @@ export default class FolderNotesPlugin extends Plugin {
}
if (!data) { return; }
const overview = data.defaultOverview;
const overview = (data as any).defaultOverview;
if (!overview) { return; }
this.settings.defaultOverview = Object.assign(
{}, DEFAULT_SETTINGS.defaultOverview, overview,
);
this.settings.defaultOverview = Object.assign({}, DEFAULT_SETTINGS.defaultOverview, overview);
}
async saveSettings(reloadStyles?: boolean): Promise<void> {
async saveSettings(reloadStyles?: boolean) {
await this.saveData(this.settings);
// cleanup any css if we need too
if ((!this.settingsOpened || reloadStyles === true) && reloadStyles !== false) {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

@ -1 +1 @@
Subproject commit f047859cc9a7f1c04ad35ecdc440a31a300bc6c2
Subproject commit 3e48a5c20e8069cef1b36fb973c3ea182ca13f33

View file

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

View file

@ -1,7 +1,6 @@
/* eslint-disable max-len */
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void> {
import { SettingsTab } from './SettingsTab';
export async function renderFileExplorer(settingsTab: SettingsTab) {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
@ -14,12 +13,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
settingsTab.plugin.settings.hideFolderNote = value;
await settingsTab.plugin.saveSettings();
if (value) {
activeDocument.body.classList.add('hide-folder-note');
document.body.classList.add('hide-folder-note');
} else {
activeDocument.body.classList.remove('hide-folder-note');
document.body.classList.remove('hide-folder-note');
}
settingsTab.display();
}),
})
);
const setting2 = new Setting(containerEl)
@ -31,12 +30,11 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.disableOpenFolderNoteOnClick = value;
await settingsTab.plugin.saveSettings();
}),
})
);
setting2.infoEl.appendText('Requires a restart to take effect');
const setting2AccentColor = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
setting2.infoEl.style.color = setting2AccentColor;
setting2.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
new Setting(containerEl)
.setName('Open folder notes by only clicking directly on the folder name')
@ -46,13 +44,13 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.setValue(!settingsTab.plugin.settings.stopWhitespaceCollapsing)
.onChange(async (value) => {
if (!value) {
activeDocument.body.classList.add('fn-whitespace-stop-collapsing');
document.body.classList.add('fn-whitespace-stop-collapsing');
} else {
activeDocument.body.classList.remove('fn-whitespace-stop-collapsing');
document.body.classList.remove('fn-whitespace-stop-collapsing');
}
settingsTab.plugin.settings.stopWhitespaceCollapsing = !value;
await settingsTab.plugin.saveSettings();
}),
})
);
const disableSetting = new Setting(containerEl);
@ -64,11 +62,10 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.enableCollapsing = !value;
await settingsTab.plugin.saveSettings();
}),
})
);
disableSetting.infoEl.appendText('Requires a restart to take effect');
const accentColor = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
disableSetting.infoEl.style.color = accentColor;
disableSetting.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
new Setting(containerEl)
.setName('Use submenus')
@ -80,7 +77,7 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
settingsTab.plugin.settings.useSubmenus = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
if (settingsTab.plugin.settings.frontMatterTitle.enabled) {
@ -94,17 +91,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
settingsTab.plugin.settings.frontMatterTitle.explorer = value;
await settingsTab.plugin.saveSettings();
settingsTab.plugin.app.vault.getFiles().forEach((file) => {
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName(
{
id: '',
result: false,
path: file.path,
pathOnly: false,
},
false,
);
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName({ id: '', result: false, path: file.path, pathOnly: false }, false);
});
}),
})
);
}
@ -119,12 +108,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.highlightFolder = value;
if (!value) {
activeDocument.body.classList.add('disable-folder-highlight');
document.body.classList.add('disable-folder-highlight');
} else {
activeDocument.body.classList.remove('disable-folder-highlight');
document.body.classList.remove('disable-folder-highlight');
}
await settingsTab.plugin.saveSettings();
}),
})
);
new Setting(containerEl)
@ -135,14 +124,14 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.setValue(settingsTab.plugin.settings.hideCollapsingIcon)
.onChange(async (value) => {
settingsTab.plugin.settings.hideCollapsingIcon = value;
if (value) {
activeDocument.body.classList.add('fn-hide-collapse-icon');
} else {
activeDocument.body.classList.remove('fn-hide-collapse-icon');
}
await settingsTab.plugin.saveSettings();
if (value) {
document.body.classList.add('fn-hide-collapse-icon');
} else {
document.body.classList.remove('fn-hide-collapse-icon');
}
settingsTab.display();
}),
})
);
new Setting(containerEl)
@ -155,12 +144,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
settingsTab.plugin.settings.hideCollapsingIconForEmptyFolders = value;
await settingsTab.plugin.saveSettings();
if (value) {
activeDocument.body.classList.add('fn-hide-empty-collapse-icon');
document.body.classList.add('fn-hide-empty-collapse-icon');
} else {
activeDocument.body.classList.remove('fn-hide-empty-collapse-icon');
document.body.classList.remove('fn-hide-empty-collapse-icon');
}
settingsTab.display();
},
}
));
if (settingsTab.plugin.settings.hideCollapsingIcon) {
@ -170,14 +159,9 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
toggle
.setValue(settingsTab.plugin.settings.ignoreAttachmentFolder)
.onChange(async (value) => {
if (value) {
activeDocument.body.classList.add('fn-ignore-attachment-folder');
} else {
activeDocument.body.classList.remove('fn-ignore-attachment-folder');
}
settingsTab.plugin.settings.ignoreAttachmentFolder = value;
await settingsTab.plugin.saveSettings();
}),
})
);
}
@ -190,12 +174,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.underlineFolder = value;
if (value) {
activeDocument.body.classList.add('folder-note-underline');
document.body.classList.add('folder-note-underline');
} else {
activeDocument.body.classList.remove('folder-note-underline');
document.body.classList.remove('folder-note-underline');
}
await settingsTab.plugin.saveSettings();
}),
})
);
new Setting(containerEl)
@ -207,12 +191,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.boldName = value;
if (value) {
activeDocument.body.classList.add('folder-note-bold');
document.body.classList.add('folder-note-bold');
} else {
activeDocument.body.classList.remove('folder-note-bold');
document.body.classList.remove('folder-note-bold');
}
await settingsTab.plugin.saveSettings();
}),
})
);
new Setting(containerEl)
@ -224,12 +208,12 @@ export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void
.onChange(async (value) => {
settingsTab.plugin.settings.cursiveName = value;
if (value) {
activeDocument.body.classList.add('folder-note-cursive');
document.body.classList.add('folder-note-cursive');
} else {
activeDocument.body.classList.remove('folder-note-cursive');
document.body.classList.remove('folder-note-cursive');
}
await settingsTab.plugin.saveSettings();
}),
})
);
}

View file

@ -1,51 +1,11 @@
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
import { SettingsTab } from './SettingsTab';
import { createOverviewSettings } from 'src/obsidian-folder-overview/src/settings';
export async function renderFolderOverview(settingsTab: SettingsTab): Promise<void> {
export async function renderFolderOverview(settingsTab: SettingsTab) {
const { plugin } = settingsTab;
const defaultOverviewSettings = plugin.settings.defaultOverview;
const overviewSettings = plugin.settings.defaultOverview;
const containerEl = settingsTab.settingsPage;
containerEl.createEl('p', { text: 'Edit the default settings for folder overviews', cls: 'setting-item-description' });
containerEl.createEl('h3', { text: 'Global settings' });
new Setting(containerEl)
.setName('Auto-update links without opening the overview')
// eslint-disable-next-line max-len
.setDesc('If enabled, the links that appear in the graph view will be updated even when you don\'t have the overview open somewhere.')
.addToggle((toggle) =>
toggle
.setValue(plugin.settings.fvGlobalSettings.autoUpdateLinks)
.onChange(async (value) => {
plugin.settings.fvGlobalSettings.autoUpdateLinks = value;
await plugin.saveSettings();
if (value) {
plugin.fvIndexDB.init(true);
} else {
plugin.fvIndexDB.active = false;
}
}),
);
containerEl.createEl('h3', { text: 'Overviews default settings' });
const pEl = containerEl.createEl('p', {
text: 'Edit the default settings for new folder overviews, ',
cls: 'setting-item-description',
});
const span = createSpan({ text: "this won't apply to already existing overviews.", cls: '' });
const accentColor = (settingsTab.app.vault.getConfig('accentColor') as string) || '#7d5bed';
span.setAttr('style', `color: ${accentColor};`);
pEl.appendChild(span);
void createOverviewSettings(
containerEl,
defaultOverviewSettings,
plugin,
plugin.settings.defaultOverview,
// eslint-disable-next-line @typescript-eslint/unbound-method
settingsTab.display,
undefined,
undefined,
undefined,
settingsTab,
);
createOverviewSettings(containerEl, overviewSettings, plugin, plugin.settings.defaultOverview, settingsTab.display, undefined, undefined, undefined, settingsTab);
}

View file

@ -1,7 +1,5 @@
/* eslint-disable max-len */
import { Setting, Platform } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
import { SettingsTab } from './SettingsTab';
import { ListComponent } from '../functions/ListComponent';
import AddSupportedFileModal from '../modals/AddSupportedFileType';
import { FrontMatterTitlePluginHandler } from '../events/FrontMatterTitle';
@ -11,10 +9,9 @@ import { refreshAllFolderStyles } from '../functions/styleFunctions';
import BackupWarningModal from './modals/BackupWarning';
import RenameFolderNotesModal from './modals/RenameFns';
let debounceTimer: number | undefined;
let debounceTimer: NodeJS.Timeout;
// eslint-disable-next-line complexity
export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
export async function renderGeneral(settingsTab: SettingsTab) {
const containerEl = settingsTab.settingsPage;
const nameSetting = new Setting(containerEl)
.setName('Folder note name template')
@ -27,9 +24,8 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.folderNoteName = value;
await settingsTab.plugin.saveSettings();
window.clearTimeout(debounceTimer);
const FOLDER_NOTE_NAME_DEBOUNCE_MS = 2000;
debounceTimer = window.setTimeout(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
if (!value.includes('{{folder_name}}')) {
if (!settingsTab.showFolderNameInTabTitleSetting) {
settingsTab.display();
@ -41,8 +37,8 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.showFolderNameInTabTitleSetting = false;
}
}
}, FOLDER_NOTE_NAME_DEBOUNCE_MS);
}),
}, 2000);
})
)
.addButton((button) =>
button
@ -54,17 +50,17 @@ 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();
}),
})
);
nameSetting.infoEl.appendText('Requires a restart to take effect');
nameSetting.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
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)
@ -78,51 +74,40 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.tabManagerEnabled = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
}
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);
}
});
if (
!settingsTab.plugin.settings.supportedFileTypes.includes(
settingsTab.plugin.settings.folderNoteType.replace('.', ''),
) &&
settingsTab.plugin.settings.folderNoteType !== '.ask'
) {
if (!settingsTab.plugin.settings.supportedFileTypes.includes(settingsTab.plugin.settings.folderNoteType.replace('.', '')) && settingsTab.plugin.settings.folderNoteType !== '.ask') {
settingsTab.plugin.settings.folderNoteType = '.md';
void settingsTab.plugin.saveSettings();
settingsTab.plugin.saveSettings();
}
let defaultType = settingsTab.plugin.settings.folderNoteType.startsWith('.')
? settingsTab.plugin.settings.folderNoteType
: '.' + settingsTab.plugin.settings.folderNoteType;
if (
!settingsTab.plugin.settings.supportedFileTypes.includes(
defaultType.replace('.', ''),
)
) {
let defaultType = settingsTab.plugin.settings.folderNoteType.startsWith('.') ? settingsTab.plugin.settings.folderNoteType : '.' + settingsTab.plugin.settings.folderNoteType;
if (!settingsTab.plugin.settings.supportedFileTypes.includes(defaultType.replace('.', ''))) {
defaultType = '.ask';
settingsTab.plugin.settings.folderNoteType = defaultType;
}
dropdown
.setValue(defaultType)
.onChange(async (value: string) => {
settingsTab.plugin.settings.folderNoteType = value as '.md' | '.canvas' | '.ask';
void settingsTab.plugin.saveSettings();
void settingsTab.display();
.onChange(async (value: '.md' | '.canvas') => {
settingsTab.plugin.settings.folderNoteType = value;
settingsTab.plugin.saveSettings();
settingsTab.display();
});
});
@ -130,31 +115,22 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
setting0.setName('Supported file types');
const desc0 = document.createDocumentFragment();
desc0.append(
'Specify which file types are allowed as folder notes. Applies to both new and existing folders. Adding many types may affect performance.',
'Specify which file types are allowed as folder notes. Applies to both new and existing folders. Adding many types may affect performance.'
);
setting0.setDesc(desc0);
const list = new ListComponent(
setting0.settingEl,
settingsTab.plugin.settings.supportedFileTypes || [],
['md', 'canvas'],
);
list.on('update', (values: unknown) => {
settingsTab.plugin.settings.supportedFileTypes = values as string[];
void settingsTab.plugin.saveSettings();
void settingsTab.display();
const list = new ListComponent(setting0.settingEl, settingsTab.plugin.settings.supportedFileTypes || [], ['md', 'canvas']);
list.on('update', async (values: string[]) => {
settingsTab.plugin.settings.supportedFileTypes = values;
await settingsTab.plugin.saveSettings();
settingsTab.display();
});
if (
!settingsTab.plugin.settings.supportedFileTypes.includes('md') ||
!settingsTab.plugin.settings.supportedFileTypes.includes('canvas') ||
!settingsTab.plugin.settings.supportedFileTypes.includes('excalidraw')
) {
if (!settingsTab.plugin.settings.supportedFileTypes.includes('md') || !settingsTab.plugin.settings.supportedFileTypes.includes('canvas') || !settingsTab.plugin.settings.supportedFileTypes.includes('excalidraw')) {
setting0.addDropdown((dropdown) => {
const options = [
{ value: 'md', label: 'Markdown' },
{ value: 'canvas', label: 'Canvas' },
{ value: 'base', label: 'Bases' },
{ value: 'excalidraw', label: 'Excalidraw' },
{ value: 'excalidraw', label: 'excalidraw' },
{ value: 'custom', label: 'Custom extension' },
];
@ -167,16 +143,11 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
dropdown.setValue('+');
dropdown.onChange(async (value) => {
if (value === 'custom') {
return new AddSupportedFileModal(
settingsTab.app,
settingsTab.plugin,
settingsTab,
list,
).open();
return new AddSupportedFileModal(settingsTab.app, settingsTab.plugin, settingsTab, list as ListComponent).open();
}
await list.addValue(value.toLowerCase());
void settingsTab.display();
void settingsTab.plugin.saveSettings();
settingsTab.display();
settingsTab.plugin.saveSettings();
});
});
} else {
@ -185,13 +156,8 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
.setButtonText('Add custom file type')
.setCta()
.onClick(async () => {
new AddSupportedFileModal(
settingsTab.app,
settingsTab.plugin,
settingsTab,
list,
).open();
}),
new AddSupportedFileModal(settingsTab.app, settingsTab.plugin, settingsTab, list as ListComponent).open();
})
);
}
@ -202,11 +168,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
.addSearch((cb) => {
new TemplateSuggest(cb.inputEl, settingsTab.plugin);
cb.setPlaceholder('Template path');
const templateFile = settingsTab.plugin.app.vault.getAbstractFileByPath(
settingsTab.plugin.settings.templatePath,
);
const templateName = templateFile?.name.replace('.md', '') || '';
cb.setValue(templateName);
cb.setValue(settingsTab.plugin.app.vault.getAbstractFileByPath(settingsTab.plugin.settings.templatePath)?.name.replace('.md', '') || '');
cb.onChange(async (value) => {
if (value.trim() === '') {
settingsTab.plugin.settings.templatePath = '';
@ -227,15 +189,12 @@ 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: string) => {
if (value !== 'insideFolder' && value !== 'parentFolder' && value !== 'vaultFolder') {
return;
}
.onChange(async (value: 'insideFolder' | 'parentFolder' | 'vaultFolder') => {
settingsTab.plugin.settings.storageLocation = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
refreshAllFolderStyles(undefined, settingsTab.plugin);
}),
})
)
.addButton((button) =>
button
@ -253,9 +212,9 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
'Switch storage location',
'When you click on "Confirm" all folder notes will be moved to the new storage location.',
settingsTab.switchStorageLocation,
[oldStorageLocation],
[oldStorageLocation]
).open();
}),
})
);
storageLocation.infoEl.appendText('Requires a restart to take effect');
storageLocation.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -270,23 +229,23 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.syncDelete = value;
await settingsTab.plugin.saveSettings();
},
),
}
)
);
new Setting(containerEl)
.setName('Move folder notes when moving the folder')
.setDesc('Move the folder note file along with the folder when it is moved')
.setDesc('Move the folder note when moving the folder')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.syncMove)
.onChange(async (value) => {
settingsTab.plugin.settings.syncMove = value;
await settingsTab.plugin.saveSettings();
}),
})
);
}
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')
@ -294,11 +253,10 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
.addDropdown((dropdown) => {
if (!Platform.isMacOS) {
dropdown.addOption('ctrl', 'Ctrl + Click');
dropdown.addOption('alt', 'Alt + Click');
} else {
dropdown.addOption('ctrl', 'Cmd + Click');
dropdown.addOption('alt', 'Option + Click');
}
dropdown.addOption('alt', 'Alt + Click');
dropdown.setValue(settingsTab.plugin.settings.ctrlKey ? 'ctrl' : 'alt');
dropdown.onChange(async (value) => {
settingsTab.plugin.settings.ctrlKey = value === 'ctrl';
@ -312,14 +270,13 @@ 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');
} else {
dropdown.addOption('ctrl', 'Cmd + Click');
dropdown.addOption('alt', 'Option + Click');
}
dropdown.addOption('alt', 'Alt + Click');
if (settingsTab.plugin.settings.openByClick) {
dropdown.setValue('click');
} else if (settingsTab.plugin.settings.openWithCtrl) {
@ -349,7 +306,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.showDeleteConfirmation = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
new Setting(containerEl)
@ -360,9 +317,8 @@ 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) => {
const v = value as 'trash' | 'delete' | 'obsidianTrash';
settingsTab.plugin.settings.deleteFilesAction = v;
dropdown.onChange(async (value: 'trash' | 'delete' | 'obsidianTrash') => {
settingsTab.plugin.settings.deleteFilesAction = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
});
@ -379,7 +335,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.openInNewTab = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
setting3.infoEl.appendText('Requires a restart to take effect');
setting3.infoEl.style.color = settingsTab.app.vault.getConfig('accentColor') as string || '#7d5bed';
@ -396,7 +352,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.focusExistingTab = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
}
@ -410,7 +366,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.syncFolderName = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
settingsTab.settingsPage.createEl('h4', { text: 'Automation settings' });
@ -436,7 +392,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.autoCreate = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
if (settingsTab.plugin.settings.autoCreate) {
@ -450,12 +406,12 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.autoCreateFocusFiles = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
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)
@ -463,7 +419,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.autoCreateForAttachmentFolder = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
}
@ -477,16 +433,16 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.autoCreateForFiles = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
settingsTab.settingsPage.createEl('h3', { text: 'Integration & compatibility' });
settingsTab.settingsPage.createEl('h3', { text: 'Integration & Compatibility' });
const desc1 = document.createDocumentFragment();
const link = activeDocument.createElement('a');
const link = document.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(
@ -505,34 +461,24 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.frontMatterTitle.enabled = value;
await settingsTab.plugin.saveSettings();
if (value) {
settingsTab.plugin.fmtpHandler =
new FrontMatterTitlePluginHandler(settingsTab.plugin);
settingsTab.plugin.fmtpHandler = new FrontMatterTitlePluginHandler(settingsTab.plugin);
} else {
if (settingsTab.plugin.fmtpHandler) {
settingsTab.plugin.updateAllBreadcrumbs(true);
}
settingsTab.plugin.app.vault.getFiles().forEach((file) => {
void settingsTab.plugin.fmtpHandler?.fmptUpdateFileName(
{
id: '',
result: false,
path: file.path,
pathOnly: false,
},
false,
);
settingsTab.plugin.fmtpHandler?.fmptUpdateFileName({ id: '', result: false, path: file.path, pathOnly: false }, false);
});
settingsTab.plugin.fmtpHandler?.deleteEvent();
settingsTab.plugin.fmtpHandler =
new FrontMatterTitlePluginHandler(settingsTab.plugin);
settingsTab.plugin.fmtpHandler = null;
}
settingsTab.display();
}),
})
);
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')
@ -544,7 +490,7 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.persistentSettingsTab.afterRestart = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
new Setting(containerEl)
@ -557,6 +503,6 @@ export async function renderGeneral(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.persistentSettingsTab.afterChangingTab = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
}

View file

@ -1,7 +1,6 @@
/* eslint-disable max-len */
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
export async function renderPath(settingsTab: SettingsTab): Promise<void> {
import { SettingsTab } from './SettingsTab';
export async function renderPath(settingsTab: SettingsTab) {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
.setName('Open folder note through path')
@ -13,34 +12,32 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
settingsTab.plugin.settings.openFolderNoteOnClickInPath = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
})
);
if (settingsTab.plugin.settings.openFolderNoteOnClickInPath) {
new Setting(containerEl)
.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
.setValue(settingsTab.plugin.settings.openSidebar.mobile)
.onChange(async (value) => {
settingsTab.plugin.settings.openSidebar.mobile = value;
await settingsTab.plugin.saveSettings();
}),
);
new Setting(containerEl)
.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
.setValue(settingsTab.plugin.settings.openSidebar.mobile)
.onChange(async (value) => {
settingsTab.plugin.settings.openSidebar.mobile = value;
await settingsTab.plugin.saveSettings();
})
);
new Setting(containerEl)
.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
.setValue(settingsTab.plugin.settings.openSidebar.desktop)
.onChange(async (value) => {
settingsTab.plugin.settings.openSidebar.desktop = value;
await settingsTab.plugin.saveSettings();
}),
);
}
new Setting(containerEl)
.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
.setValue(settingsTab.plugin.settings.openSidebar.desktop)
.onChange(async (value) => {
settingsTab.plugin.settings.openSidebar.desktop = value;
await settingsTab.plugin.saveSettings();
})
);
if (settingsTab.plugin.settings.frontMatterTitle.enabled) {
new Setting(containerEl)
@ -57,7 +54,7 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
} else {
settingsTab.plugin.updateAllBreadcrumbs(true);
}
}),
})
);
}
@ -72,12 +69,12 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.underlineFolderInPath = value;
if (value) {
activeDocument.body.classList.add('folder-note-underline-path');
document.body.classList.add('folder-note-underline-path');
} else {
activeDocument.body.classList.remove('folder-note-underline-path');
document.body.classList.remove('folder-note-underline-path');
}
await settingsTab.plugin.saveSettings();
}),
})
);
new Setting(containerEl)
@ -89,12 +86,12 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.boldNameInPath = value;
if (value) {
activeDocument.body.classList.add('folder-note-bold-path');
document.body.classList.add('folder-note-bold-path');
} else {
activeDocument.body.classList.remove('folder-note-bold-path');
document.body.classList.remove('folder-note-bold-path');
}
await settingsTab.plugin.saveSettings();
}),
})
);
new Setting(containerEl)
@ -106,24 +103,11 @@ export async function renderPath(settingsTab: SettingsTab): Promise<void> {
.onChange(async (value) => {
settingsTab.plugin.settings.cursiveNameInPath = value;
if (value) {
activeDocument.body.classList.add('folder-note-cursive-path');
document.body.classList.add('folder-note-cursive-path');
} else {
activeDocument.body.classList.remove('folder-note-cursive-path');
document.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

@ -1,20 +1,17 @@
import {
Notice, PluginSettingTab, TFile,
TFolder, type App, type MarkdownPostProcessorContext,
} from 'obsidian';
import type FolderNotesPlugin from '../main';
import type { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import type { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { App, Notice, PluginSettingTab, TFile, TFolder, MarkdownPostProcessorContext } from 'obsidian';
import FolderNotesPlugin from '../main';
import { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { extractFolderName, getFolderNote } from '../functions/folderNoteFunctions';
import type { defaultOverviewSettings } from '../obsidian-folder-overview/src/FolderOverview';
import { overviewSettings } from '../obsidian-folder-overview/src/FolderOverview';
import { renderGeneral } from './GeneralSettings';
import { renderFileExplorer } from './FileExplorerSettings';
import { renderPath } from './PathSettings';
import { renderFolderOverview } from './FolderOverviewSettings';
import { renderExcludeFolders } from './ExcludedFoldersSettings';
import { getFolderPathFromString } from '../functions/utils';
import type { WhitelistedFolder } from 'src/ExcludeFolders/WhitelistFolder';
import type { WhitelistedPattern } from 'src/ExcludeFolders/WhitelistPattern';
import { WhitelistedFolder } from 'src/ExcludeFolders/WhitelistFolder';
import { WhitelistedPattern } from 'src/ExcludeFolders/WhitelistPattern';
export interface FolderNotesSettings {
syncFolderName: boolean;
@ -44,7 +41,7 @@ export interface FolderNotesSettings {
disableFolderHighlighting: boolean;
storageLocation: 'insideFolder' | 'parentFolder' | 'vaultFolder';
syncDelete: boolean;
defaultOverview: defaultOverviewSettings;
defaultOverview: overviewSettings;
useSubmenus: boolean;
syncMove: boolean;
frontMatterTitle: {
@ -77,12 +74,7 @@ export interface FolderNotesSettings {
persistentSettingsTab: {
afterRestart: boolean;
afterChangingTab: boolean;
},
firstTimeInsertOverview: boolean;
fvGlobalSettings: {
autoUpdateLinks: boolean;
}
hideFolderNoteNameInPath: boolean;
}
export const DEFAULT_SETTINGS: FolderNotesSettings = {
@ -132,13 +124,6 @@ export const DEFAULT_SETTINGS: FolderNotesSettings = {
alwaysCollapse: false,
autoSync: true,
allowDragAndDrop: true,
hideLinkList: true,
hideFolderOverview: false,
useActualLinks: false,
fmtpIntegration: false,
titleSize: 1,
isInCallout: false,
useWikilinks: true,
},
useSubmenus: true,
syncMove: true,
@ -148,7 +133,7 @@ export const DEFAULT_SETTINGS: FolderNotesSettings = {
path: true,
},
settingsTab: 'general',
supportedFileTypes: ['md', 'canvas', 'base'],
supportedFileTypes: ['md', 'canvas'],
boldName: false,
boldNameInPath: false,
cursiveName: false,
@ -203,19 +188,14 @@ export const DEFAULT_SETTINGS: FolderNotesSettings = {
afterRestart: true,
afterChangingTab: true,
},
firstTimeInsertOverview: true,
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);
}
@ -241,39 +221,29 @@ export class SettingsTab extends PluginSettingTab {
id: 'path',
},
};
renderSettingsPage(tabId: string): void {
renderSettingsPage(tabId: string) {
this.settingsPage.empty();
switch (tabId.toLocaleLowerCase()) {
case this.TABS.GENERAL.id:
void renderGeneral(this);
renderGeneral(this);
break;
case this.TABS.FOLDER_OVERVIEW.id:
void renderFolderOverview(this);
renderFolderOverview(this);
break;
case this.TABS.EXCLUDE_FOLDERS.id:
void renderExcludeFolders(this);
renderExcludeFolders(this);
break;
case this.TABS.FILE_EXPLORER.id:
void renderFileExplorer(this);
renderFileExplorer(this);
break;
case this.TABS.PATH.id:
void renderPath(this);
renderPath(this);
break;
}
}
display(
contentEl?: HTMLElement,
yaml?: defaultOverviewSettings,
plugin?: FolderNotesPlugin,
defaultSettings?: boolean,
display?: CallableFunction,
el?: HTMLElement,
ctx?: MarkdownPostProcessorContext,
file?: TFile | null,
settingsTab?: this,
): void {
display(contentEl?: HTMLElement, yaml?: overviewSettings, plugin?: FolderNotesPlugin, defaultSettings?: boolean, display?: CallableFunction, el?: HTMLElement, ctx?: MarkdownPostProcessorContext, file?: TFile | null, settingsTab?: this) {
plugin = this?.plugin ?? plugin;
if (plugin) {
plugin.settingsOpened = true;
@ -290,19 +260,16 @@ export class SettingsTab extends PluginSettingTab {
for (const [tabId, tabInfo] of Object.entries(settingsTab.TABS)) {
const tabEl = tabBar.createEl('div', { cls: 'fn-settings-tab' });
tabEl.createEl('div', { cls: 'fn-settings-tab-name', text: tabInfo.name });
if (
plugin &&
plugin.settings.settingsTab.toLocaleLowerCase() ===
tabId.toLocaleLowerCase()
) {
if (plugin && plugin.settings.settingsTab.toLocaleLowerCase() === tabId.toLocaleLowerCase()) {
tabEl.addClass('fn-settings-tab-active');
}
tabEl.addEventListener('click', () => {
for (const child of Array.from(tabBar.children)) {
(child as HTMLElement).classList.remove('fn-settings-tab-active');
// @ts-ignore
for (const tabEl of tabBar.children) {
tabEl.removeClass('fn-settings-tab-active');
if (!plugin) { return; }
plugin.settings.settingsTab = tabId.toLocaleLowerCase();
void plugin.saveSettings();
plugin.saveSettings();
}
tabEl.addClass('fn-settings-tab-active');
if (!settingsTab) { return; }
@ -319,47 +286,39 @@ export class SettingsTab extends PluginSettingTab {
}
}
renameFolderNotes(): void {
renameFolderNotes() {
new Notice('Starting to update folder notes...');
const oldTemplate = this.plugin.settings.oldFolderNoteName ?? '{{folder_name}}';
for (const folder of this.app.vault.getAllLoadedFiles()) {
if (folder instanceof TFolder) {
const folderNote = getFolderNote(
this.plugin,
folder.path,
undefined,
undefined,
oldTemplate,
);
const folderNote = getFolderNote(this.plugin, folder.path, undefined, undefined, oldTemplate);
if (!(folderNote instanceof TFile)) { continue; }
const folderName = extractFolderName(oldTemplate, folderNote.basename) ?? '';
const newFolderNoteName = this.plugin.settings.folderNoteName
.replace('{{folder_name}}', folderName);
const newFolderNoteName = this.plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
let newPath = '';
if (this.plugin.settings.storageLocation === 'parentFolder') {
if (getFolderPathFromString(folder.path).trim() === '/') {
newPath = `${newFolderNoteName}.${folderNote.extension}`;
} else {
newPath = `${folderNote.parent?.path}/${newFolderNoteName}.${folderNote.extension}`;
}
} else if (this.plugin.settings.storageLocation === 'insideFolder') {
newPath = `${folder.path}/${newFolderNoteName}.${folderNote.extension}`;
}
void this.app.fileManager.renameFile(folderNote, newPath);
this.app.fileManager.renameFile(folderNote, newPath);
}
}
this.plugin.settings.oldFolderNoteName = this.plugin.settings.folderNoteName;
void this.plugin.saveSettings();
this.plugin.saveSettings();
new Notice('Finished updating folder notes');
}
switchStorageLocation(oldMethod: string): void {
switchStorageLocation(oldMethod: string) {
new Notice('Starting to switch storage location...');
this.app.vault.getAllLoadedFiles().forEach((file) => {
if (file instanceof TFolder) {
@ -372,14 +331,14 @@ export class SettingsTab extends PluginSettingTab {
} else {
newPath = `${getFolderPathFromString(file.path)}/${folderNote.name}`;
}
void this.plugin.app.fileManager.renameFile(folderNote, newPath);
this.plugin.app.fileManager.renameFile(folderNote, newPath);
} else if (this.plugin.settings.storageLocation === 'insideFolder') {
if (getFolderPathFromString(folderNote.path) === file.path) {
return;
} else {
const newPath = `${file.path}/${folderNote.name}`;
this.plugin.app.fileManager.renameFile(folderNote, newPath);
}
const newPath = `${file.path}/${folderNote.name}`;
void this.plugin.app.fileManager.renameFile(folderNote, newPath);
}
}
}

View file

@ -1,20 +1,14 @@
import { Modal, ButtonComponent } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import FolderNotesPlugin from 'src/main';
export default class BackupWarningModal extends Modal {
plugin: FolderNotesPlugin;
title: string;
desc: string;
callback: (oldMethod: string) => void;
args: [string];
callback: (...args: any[]) => void;
args: any[];
constructor(
plugin: FolderNotesPlugin,
title: string,
description: string,
callback: (oldMethod: string) => void,
args: [string],
) {
constructor(plugin: FolderNotesPlugin, title: string, description: string, callback: (...args: any[]) => void, args: any[] = []) {
super(plugin.app);
this.plugin = plugin;
this.title = title;
@ -23,7 +17,7 @@ export default class BackupWarningModal extends Modal {
this.desc = description;
}
onOpen(): void {
onOpen() {
this.modalEl.addClass('fn-backup-warning-modal');
const { contentEl } = this;
@ -31,8 +25,9 @@ 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.' }).addClass('fn-warning-text');
this.insertCustomHtml();
contentEl.createEl('p', { text: 'Make sure to backup your vault before using this feature.' }).style.color = '#fb464c';
const buttonContainer = contentEl.createDiv({ cls: 'fn-modal-button-container' });
const confirmButton = new ButtonComponent(buttonContainer);
@ -50,7 +45,11 @@ export default class BackupWarningModal extends Modal {
});
}
onClose(): void {
insertCustomHtml(): void {
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}

View file

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

View file

@ -1,33 +1,27 @@
import BackupWarningModal from './BackupWarning';
import type FolderNotesPlugin from 'src/main';
import FolderNotesPlugin from 'src/main';
import { Setting } from 'obsidian';
export default class RenameFolderNotesModal extends BackupWarningModal {
constructor(
plugin: FolderNotesPlugin,
title: string,
description: string,
callback: (oldMethod: string) => void,
args: [string],
) {
constructor(plugin: FolderNotesPlugin, title: string, description: string, callback: (...args: any[]) => void, args: any[] = []) {
super(plugin, title, description, callback, args);
}
insertCustomHtml(): void {
const { contentEl } = this;
new Setting(contentEl)
.setName('Old folder note name')
.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')
.setValue(this.plugin.settings.oldFolderNoteName || '')
.onChange(async (value) => {
this.plugin.settings.oldFolderNoteName = value;
}),
})
);
new Setting(contentEl)
.setName('New folder note name')
.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')
@ -35,7 +29,7 @@ export default class RenameFolderNotesModal extends BackupWarningModal {
.onChange(async (value) => {
this.plugin.settings.folderNoteName = value;
this.plugin.settingsTab.display();
}),
})
);
}
}

View file

@ -1,21 +1,22 @@
import { AbstractInputSuggest, type TAbstractFile, TFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { TAbstractFile, TFile } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import FolderNotesPlugin from '../main';
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
}
export class FileSuggest extends AbstractInputSuggest<TFile> {
plugin: FolderNotesPlugin;
export class FileSuggest extends TextInputSuggest<TFile> {
constructor(
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin
) {
super(plugin.app, inputEl);
this.plugin = plugin;
super(inputEl, plugin);
}
get_error_msg(mode: FileSuggestMode): string {
switch (mode) {
case FileSuggestMode.TemplateFiles:

View file

@ -1,23 +1,21 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { AbstractInputSuggest, TFolder, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { TAbstractFile, TFolder } from 'obsidian';
import { TextInputSuggest } from './Suggest';
import FolderNotesPlugin from '../main';
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
}
export class FolderSuggest extends AbstractInputSuggest<TFolder> {
plugin: FolderNotesPlugin;
export class FolderSuggest extends TextInputSuggest<TFolder> {
constructor(
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
public inputEl: HTMLInputElement,
plugin: FolderNotesPlugin,
private whitelistSuggester: boolean,
public folder?: TFolder,
) {
super(plugin.app, inputEl);
this.plugin = plugin;
super(inputEl, plugin);
}
@ -37,18 +35,13 @@ export class FolderSuggest extends AbstractInputSuggest<TFolder> {
if (this.folder) {
files = this.folder.children;
} else {
const MAX_FILE_SUGGESTIONS = 100;
files = this.plugin.app.vault.getAllLoadedFiles().slice(0, MAX_FILE_SUGGESTIONS);
files = this.plugin.app.vault.getAllLoadedFiles().slice(0,100);
}
files.forEach((folder: TAbstractFile) => {
if (
folder instanceof TFolder &&
folder.path.toLowerCase().contains(lower_input_str) &&
(
!this.plugin.settings.excludeFolders.find(
(f) => f.path === folder.path,
) || this.whitelistSuggester
)
folder.path.toLowerCase().contains(lower_input_str) &&
(!this.plugin.settings.excludeFolders.find((f) => f.path === folder.path) || this.whitelistSuggester)
) {
folders.push(folder);
}

206
src/suggesters/Suggest.ts Normal file
View file

@ -0,0 +1,206 @@
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes and https://github.com/SilentVoid13/Templater
import { ISuggestOwner, Scope } from 'obsidian';
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
import FolderNotesPlugin from 'src/main';
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[]) {
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) {
const currentValue = this.values[this.selectedItem];
if (currentValue) {
this.owner.selectSuggestion(currentValue, event);
}
}
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
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-ignore
this.open(app.dom.appContainerEl, this.inputEl);
} else {
this.close();
}
}
open(container: HTMLElement, inputEl: HTMLElement): void {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
this.plugin.app.keymap.pushScope(this.scope);
container.appendChild(this.suggestEl);
this.popper = createPopper(inputEl, this.suggestEl, {
placement: 'bottom-start',
modifiers: [
{
name: 'sameWidth',
enabled: true,
fn: ({ state, instance }) => {
// 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,24 +1,15 @@
import { TFile, TFolder, Vault, AbstractInputSuggest, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { TAbstractFile, TFile, TFolder, Vault, AbstractInputSuggest } from 'obsidian';
import FolderNotesPlugin from '../main';
import { getTemplatePlugins } from 'src/template';
interface TemplateSuggestion {
path: string;
name: string;
parent?: {
path: string;
} | null;
}
export enum FileSuggestMode {
TemplateFiles,
ScriptFiles,
TemplateFiles,
ScriptFiles,
}
export class TemplateSuggest extends AbstractInputSuggest<TemplateSuggestion> {
export class TemplateSuggest extends AbstractInputSuggest<TFile> {
constructor(
public inputEl: HTMLInputElement,
public plugin: FolderNotesPlugin,
public inputEl: HTMLInputElement,
public plugin: FolderNotesPlugin
) {
super(plugin.app, inputEl);
}
@ -33,39 +24,24 @@ export class TemplateSuggest extends AbstractInputSuggest<TemplateSuggestion> {
}
}
getSuggestions(input_str: string): TemplateSuggestion[] {
getSuggestions(input_str: string): TFile[] {
const { templateFolder, templaterPlugin } = getTemplatePlugins(this.app);
let files: TemplateSuggestion[] = [];
let files: TFile[] = [];
const lower_input_str = input_str.toLowerCase();
if ((!templateFolder || templateFolder.trim() === '') && !templaterPlugin) {
files = this.plugin.app.vault.getFiles().filter((file) =>
file.path.toLowerCase().includes(lower_input_str),
file.path.toLowerCase().includes(lower_input_str)
);
} else {
let folder: TFolder | TAbstractFile | null = null;
let folder: TFolder;
if (templaterPlugin) {
folder = this.plugin.app.vault.getAbstractFileByPath(
(templaterPlugin as unknown as {
plugin?: { settings?: { templates_folder?: string } }
}).plugin?.settings?.templates_folder as string,
);
if (!(folder instanceof TFolder)) {
return [
{
path: '',
name:
'You need to set the Templates folder in the Templater settings first.',
},
];
}
} else if (templateFolder) {
folder = this.plugin.app.vault.getAbstractFileByPath(templateFolder);
}
if (!(folder instanceof TFolder)) {
return [];
templaterPlugin.plugin?.settings?.templates_folder as string
) as TFolder;
} else {
folder = this.plugin.app.vault.getAbstractFileByPath(templateFolder) as TFolder;
}
Vault.recurseChildren(folder, (file: TAbstractFile) => {
@ -79,7 +55,7 @@ export class TemplateSuggest extends AbstractInputSuggest<TemplateSuggestion> {
}
renderSuggestion(file: TemplateSuggestion, el: HTMLElement): void {
renderSuggestion(file: TFile, el: HTMLElement): void {
const { templateFolder, templaterPlugin } = getTemplatePlugins(this.app);
if ((!templateFolder || templateFolder.trim() === '') && !templaterPlugin) {
@ -90,11 +66,11 @@ export class TemplateSuggest extends AbstractInputSuggest<TemplateSuggestion> {
}
selectSuggestion(file: TemplateSuggestion): void {
selectSuggestion(file: TFile): void {
this.inputEl.value = file.name.replace('.md', '');
this.inputEl.trigger('input');
this.plugin.settings.templatePath = file.path;
void this.plugin.saveSettings();
this.plugin.saveSettings();
this.close();
}
}

View file

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

View file

@ -8,12 +8,6 @@
display: none;
}
/* make.md plugin integration */
.hide-folder-note .mk-tree-node > .mk-tree-wrapper > .dropzone > .mk-tree-item.is-folder-note {
opacity: 40%;
display: flex;
}
.pointer-cursor,
.has-folder-note .nav-folder-title-content:hover,
.has-folder-note.view-header-breadcrumb:hover,
@ -23,14 +17,6 @@
cursor: pointer !important;
}
.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
@ -133,18 +119,8 @@ body:not(.disable-folder-highlight) .tree-item-self.fn-is-active {
display: none;
}
.cm-line:has(.fv-link-list-item),
li:has(.fv-link-list-item),
.el-ul:has(.fv-link-list-item),
.cm-line:has(.fv-link-list-start),
.cm-line:has(.fv-link-list-end),
.fv-hide-overview {
display: none !important;
}
.folder-overview-list {
margin-top: 0 !important;
margin-top: 0 !important;
margin-bottom: 0 !important;
padding-bottom: 1.200 !important;
padding-top: 1.200 !important;
@ -227,10 +203,6 @@ 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 */
@ -241,15 +213,12 @@ 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-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 {
.fn-hide-empty-collapse-icon .fn-empty-folder .tree-item-icon,
.only-has-folder-note:not(.is-collapsed):not(.show-folder-note-in-explorer) > .nav-folder-children {
display: none;
}
/* ==========================================================================
Settings Tabs
========================================================================== */
@ -356,4 +325,4 @@ body.fn-hide-collapse-icon.only-has-folder-note:not(.is-collapsed):not(.show-fol
.is-phone .fn-overview-folder-path .setting-item-control {
display: block;
}
}
}

View file

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

View file

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