Compare commits

..

776 commits
1.2.1 ... main

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

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

Closes #274

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 09:40:17 +00:00
Paul
5ed694654c Update manifest.json 2026-03-22 20:44:24 +01:00
Paul
1ace668c8f Fix #312 2026-03-22 20:44:06 +01:00
Paul
3daea7efd3 Fix #306 2026-03-12 15:26:27 +01:00
Paul
347c409619 Fix #284 2026-03-12 14:34:36 +01:00
Paul
8fbf999df4 Fix #304 2026-03-10 15:56:52 +01:00
Paul
8344490906 Hide duplicated folder note name in path 2026-03-10 15:49:50 +01:00
Paul
7c41751c34 Update manifest.json 2026-02-01 19:47:00 +01:00
Paul
7ad48ee08f Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2026-01-28 12:27:24 +01:00
Paul
6a83bab773 Fix #296 2026-01-28 12:27:22 +01:00
Paul
5908ff0b79
Merge pull request #301 from mjkerrison/fix/excluded-folders-reset-293
fix: prevent excluded folders list from being reset
2026-01-26 20:51:13 +01:00
mjkerrison
a044e6c2eb fix: startup error and settings tab reset issues
- Handle missing file explorer during early startup to prevent
  "Cannot read properties of undefined" error
- Use renderSettingsPage instead of display when adding excluded/
  whitelisted folders to stay on current settings tab

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 12:22:28 +11:00
mjkerrison
88cb9541f0 fix: prevent excluded folders list from being reset
The filter logic in deleteFolderNote() and hideFolderNoteInFileExplorer()
was using && instead of ||, causing all excluded folders with
showFolderNote=false (the default) to be removed instead of just the
target entry.

Fixes #293

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-08 12:35:49 +11:00
Paul
47c6490ba9
Merge pull request #299 from stdword/stdword-patch-1
make.md plugin integration
2026-01-02 13:01:43 +01:00
Lost Paul
4d30ea696e Fix #300 2026-01-02 12:55:40 +01:00
Sergey Kolesnik
ef59e9998e
fix: make.md plugin integration 2025-12-18 21:00:54 +03:00
Sergey Kolesnik
f4dbd3e93c
fix: make.md plugin integration 2025-12-18 20:58:14 +03:00
Lost Paul
1d7a065b8a Update obsidian-folder-overview 2025-11-29 21:00:38 +01:00
Lost Paul
fdade5d96b Add option to use markdown links in folder overview 2025-11-29 20:28:04 +01:00
Lost Paul
e0613b7d27
Update README.md 2025-08-20 09:53:59 +02:00
Lost Paul
ce4eab2d64 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-08-18 11:37:01 +02:00
Lost Paul
abfab4fc86 Remove console.log 2025-08-18 11:35:20 +02:00
github-actions[bot]
997ffb583b Update manifest.json to version 2025-08-18 09:13:43 +00:00
Lost Paul
5eeb86fa0b
Merge pull request #280 from Nightwish2710/feat-update-command-logic-for-mobile
Remove empty command on mobile
2025-08-18 09:54:18 +02:00
Lost Paul
08b8056df6 Fix linter issues 2025-08-18 09:53:19 +02:00
Nightwish2710
203d9f4119 feat: update command menu for mobile rendering 2025-08-18 14:28:23 +07:00
Nightwish2710
03b6de9db7 feat: update command menu for mobile rendering 2025-08-18 14:21:10 +07:00
github-actions[bot]
e826fe6740 Update manifest-beta.json to version 1.8.17-5-beta in main branch 2025-08-14 08:15:24 +00:00
Lost Paul
765c048fbd Update packages 2025-08-14 10:14:58 +02:00
Lost Paul
5638c3c40d Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-08-13 15:41:26 +02:00
github-actions[bot]
8e2c3376a6 Update manifest-beta.json to version 1.8.17-4-beta in main branch 2025-08-13 13:41:20 +00:00
Lost Paul
b24b25ef34 Update package.json 2025-08-13 15:40:30 +02:00
github-actions[bot]
f670410cf2 Update manifest-beta.json to version 1.8.17-3-beta in main branch 2025-08-13 13:37:12 +00:00
Lost Paul
3e23140bfb Fix # 275 2025-08-13 15:36:40 +02:00
Lost Paul
68d9b0b8d5
Merge pull request #273 from LostPaul/code-style-changes
Code style changes
2025-08-11 15:13:07 +02:00
Lost Paul
e88ccfeb06 Update obsidian-folder-overview 2025-08-11 14:46:07 +02:00
Lost Paul
7c31c7cec5 Every remaining file 2025-08-11 14:39:33 +02:00
Lost Paul
113cf5f40c Excluded folders 2025-08-10 12:21:16 +02:00
Lost Paul
8adf6186ac Fix linter issues for the events folder 2025-08-08 09:37:35 +02:00
Lost Paul
d59f9dc631 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-07-27 11:33:23 +02:00
Lost Paul
d98958c7bd Eslint code style fixes no. 1 2025-07-27 11:33:21 +02:00
github-actions[bot]
b0955915fb Update manifest-beta.json to version 1.8.17-2-beta in main branch 2025-07-26 08:18:48 +00:00
Lost Paul
c5cd9abc79 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-07-26 10:16:29 +02:00
Lost Paul
2504d69c31 Update obsidian-folder-overview 2025-07-26 10:16:21 +02:00
Lost Paul
86288080c8 Update SettingsTab.ts 2025-07-26 10:16:19 +02:00
github-actions[bot]
e87aec28c1 Update manifest-beta.json to version 1.8.17-1-beta in main branch 2025-07-26 08:12:20 +00:00
Lost Paul
247c252ef8 Support editing folder overview in callout 2025-07-26 10:11:55 +02:00
github-actions[bot]
eedbd8fca9 Update manifest-beta.json to version 1.8.17-0-beta in main branch 2025-07-22 08:40:37 +00:00
Lost Paul
d58492f8a4 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-07-22 10:27:38 +02:00
Lost Paul
726476a42c Fix #268 2025-07-22 10:27:36 +02:00
Lost Paul
a66847a38e Update obsidian-folder-overview 2025-07-22 10:10:02 +02:00
Lost Paul
7f59b13788 Fix #267 2025-07-22 09:34:20 +02:00
Lost Paul
c87a8b67d9 Update obsidian-folder-overview 2025-07-21 09:15:22 +02:00
Lost Paul
2dad83ee7f Remove notices 2025-07-21 09:15:06 +02:00
Lost Paul
2fdda5c199 Update obsidian-folder-overview 2025-07-19 16:01:38 +02:00
Lost Paul
f73515245c Update Commands.ts 2025-07-19 11:19:18 +02:00
Lost Paul
4f018a9158 Customize folder overview title size 2025-07-19 11:15:39 +02:00
Lost Paul
b165b46aa7 Supported file types add .base 2025-07-19 09:59:05 +02:00
Lost Paul
ccc3b24da7 Update link list in the background 2025-07-19 09:39:59 +02:00
Lost Paul
ffaa74faaa Update PathSettings.ts 2025-07-19 09:33:21 +02:00
Lost Paul
cc2b15d8db Fix folder highlight 2025-07-19 09:18:14 +02:00
Lost Paul
e46191d137 Update link list in the background 2025-07-18 18:10:48 +02:00
Lost Paul
9a1e5773a0 Update obsidian-folder-overview 2025-07-18 18:01:26 +02:00
github-actions[bot]
ba442d20ce Update manifest.json to version 2025-07-15 11:57:22 +00:00
Lost Paul
f024b44f21 Add notice about templater plugin 2025-07-15 13:36:17 +02:00
Lost Paul
ecc8dc266a Fix #259 2025-07-15 13:14:56 +02:00
Lost Paul
ce9fcb72c1 Fix #253 2025-07-07 11:40:16 +02:00
github-actions[bot]
0b6ecc730d Update manifest.json to version 2025-07-07 09:18:47 +00:00
Lost Paul
4c3251fcea Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-07-07 11:18:23 +02:00
Lost Paul
26ea44a0b3 Update obsidian-folder-overview 2025-07-07 11:17:54 +02:00
github-actions[bot]
c039b942e0 Update manifest.json to version 2025-07-07 09:12:10 +00:00
Lost Paul
f3f835844d Unnecessary click listener 2025-07-07 11:08:27 +02:00
Lost Paul
ae1f67c84b Support frontmatter title plugin for folder overview 2025-07-06 18:35:58 +02:00
Lost Paul
243aa671f1 Update Folder overview.md 2025-07-06 18:26:05 +02:00
Lost Paul
a2aca093ed Update docs for folder overview 2025-07-06 18:22:09 +02:00
Lost Paul
7d61dcf049 Fix click on folder note in folder overview 2025-07-06 13:18:12 +02:00
Lost Paul
b64119268b Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-07-06 12:31:52 +02:00
Lost Paul
6e9a7c7e17 Fix #256 2025-07-06 12:31:49 +02:00
github-actions[bot]
b441347d1f Update manifest.json to version 2025-06-30 14:04:35 +00:00
Lost Paul
4c986d7543 Update obsidian-folder-overview 2025-06-30 16:04:09 +02:00
Lost Paul
ef0dbbd66b Fix #250 2025-06-30 16:01:49 +02:00
Lost Paul
0adebd0042 Update GeneralSettings.ts 2025-06-30 16:01:10 +02:00
Lost Paul
50bedc6ef4 Fix #248 2025-06-30 15:59:30 +02:00
github-actions[bot]
41187bf1fe Update manifest.json to version 2025-06-29 07:25:40 +00:00
Lost Paul
97c9f27988 Fix https://github.com/LostPaul/obsidian-folder-overview/issues/14 2025-06-29 09:24:59 +02:00
github-actions[bot]
1f1be4a563 Update manifest.json to version 2025-06-27 19:00:07 +00:00
Lost Paul
5f2f2b0830 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-06-27 20:59:35 +02:00
Lost Paul
30a2a5f09e Added actual links option for folder overview 2025-06-27 20:59:33 +02:00
github-actions[bot]
13afbd66df Update manifest.json to version 2025-06-27 09:48:27 +00:00
Lost Paul
f8d79fae43 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-06-27 11:47:27 +02:00
Lost Paul
b6df0cf1c7 Fix esbuild 2025-06-27 11:46:53 +02:00
github-actions[bot]
9e5e297ddf Update manifest.json to version 2025-06-27 08:16:06 +00:00
Lost Paul
86dad65c83 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-06-27 10:11:06 +02:00
Lost Paul
b581b30abc Fix #247 2025-06-27 10:10:53 +02:00
Lost Paul
f910338a1b
Merge pull request #246 from LostPaul/dependabot/npm_and_yarn/esbuild-0.25.0 2025-06-26 20:42:31 +02:00
dependabot[bot]
2f41144e53
Bump esbuild from 0.14.47 to 0.25.0
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.14.47 to 0.25.0.
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG-2022.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.14.47...v0.25.0)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.25.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-26 18:38:16 +00:00
Lost Paul
51a32d350e Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-06-26 20:26:30 +02:00
Lost Paul
4d6820dc53 Fix choose folder note type modal 2025-06-26 20:26:25 +02:00
Lost Paul
c8e54037c6
Update release.yaml 2025-06-25 10:41:23 +02:00
github-actions[bot]
2c8c54ee75 Update manifest.json to version 2025-06-25 08:02:03 +00:00
Lost Paul
d050217f5b Fix folder overview path & remove buggy enter event 2025-06-25 10:01:43 +02:00
github-actions[bot]
6c7c732d5b Update manifest.json to version 2025-06-23 13:56:41 +00:00
Lost Paul
d12ffadf4a
Update release.yaml 2025-06-23 15:55:55 +02:00
Lost Paul
b2a490576d
Update release.yaml 2025-06-23 15:47:21 +02:00
Lost Paul
758b87affc Update release.yaml 2025-06-23 15:13:32 +02:00
github-actions[bot]
393fcb9048 Update manifest.json to version 2025-06-23 12:24:33 +00:00
Lost Paul
1141ed8b48
Update release.yaml 2025-06-23 14:23:59 +02:00
github-actions[bot]
05fdaf5162 Update manifest.json to version 2025-06-23 12:20:28 +00:00
Lost Paul
6d60f5b217
Update release.yaml 2025-06-23 14:20:03 +02:00
github-actions[bot]
ac33e80ce9 Update manifest.json to version 2025-06-23 12:15:27 +00:00
Lost Paul
2ac869b50b Update manifest.json 2025-06-23 14:12:18 +02:00
Lost Paul
84bf4e0d99
Update release.yaml 2025-06-23 14:11:42 +02:00
github-actions[bot]
2bc19a09ab Update manifest.json to version 2025-06-23 12:08:31 +00:00
Lost Paul
54f2e33b77 Update obsidian-folder-overview 2025-06-23 14:07:15 +02:00
Lost Paul
9d01829ff2
Merge pull request #242 from LostPaul/beta
Merge beta with main
2025-06-23 13:50:17 +02:00
Lost Paul
bd866bc206
Merge branch 'main' into beta 2025-06-23 13:50:03 +02:00
Lost Paul
3f6be04dc8 Fix videos 2025-06-23 13:45:33 +02:00
Lost Paul
7003d10671 Add troubleshooting guide and update folder overview docs
Added a new Troubleshooting.md with guidance on folder collapsing issues and included a relevant screenshot. Updated Folder overview.md with sections on using overviews in templates and explanation of overview IDs.
2025-06-23 13:37:02 +02:00
Lost Paul
a82126ca55 Expand documentation for general settings
Added detailed explanations for supported file types, template path, storage location, folder note behavior, integration options, and session persistence in the General settings documentation.
2025-06-23 12:38:59 +02:00
Lost Paul
77d74f7993 Fix supported file types 2025-06-22 20:06:37 +02:00
Lost Paul
d1ee82fe1d Add settings documentation and update index page
Added new documentation pages for File Explorer, General, and Path settings under Features/Settings, including a related screenshot. Updated the index.md to improve feature descriptions and clarify plugin capabilities.
2025-06-21 12:48:07 +02:00
Lost Paul
3011bbad82 Update PathSettings.ts 2025-06-21 11:50:11 +02:00
Lost Paul
caa3688c8c Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-06-21 11:27:42 +02:00
Lost Paul
fbe65f8f5d Update mkdocs.yml 2025-06-21 11:27:35 +02:00
Lost Paul
b6102e3124
Update README.md 2025-06-21 10:49:26 +02:00
Lost Paul
75c7fadb8b Update documentation for folder notes features
Revised and clarified command palette, context menu, and exclusion features in the documentation. Added new sections and improved descriptions for folder note commands, exclusion/whitelisting, and folder overview. Included new screenshots and video assets to illustrate features and workflows.
2025-06-21 09:59:46 +02:00
Lost Paul
1f5f383921 Fix excluded folder "show in file explorer" setting
Previously when enabled folder notes wouldn't show in the file explorer
2025-06-21 09:58:15 +02:00
github-actions[bot]
d39214f59f Update manifest-beta.json to version 1.8.5-3-beta in main branch 2025-06-20 15:55:51 +00:00
Lost Paul
c5c001cffc
Merge pull request #240 from LostPaul/fix-css-style-issues
Fix css style issues
2025-06-20 17:52:45 +02:00
Lost Paul
1c437fb6e6 Update folder overview docs 2025-06-20 17:51:18 +02:00
Lost Paul
7c12335581 Update folder overview docs 2025-06-20 17:50:39 +02:00
Lost Paul
f98472457c Update obsidian-folder-overview 2025-06-20 17:48:32 +02:00
Lost Paul
256b254c0a Add missing css code 2025-06-20 11:17:27 +02:00
github-actions[bot]
a59fc25ff9 Update manifest-beta.json to version 1.8.5-2-beta in main branch 2025-06-18 10:30:51 +00:00
Lost Paul
d1e2aa1c64 Fix rename handler & active folder highlight 2025-06-18 12:30:25 +02:00
github-actions[bot]
877aa85fff Update manifest-beta.json to version 1.8.5-1-beta in main branch 2025-06-17 10:48:17 +00:00
Lost Paul
17251a4826 Fix #171 2025-06-17 12:47:54 +02:00
Lost Paul
fd44ed7873 Update obsidian-folder-overview 2025-06-17 12:05:56 +02:00
Lost Paul
415ccdd57f Clearer function names for style functions 2025-06-17 12:02:53 +02:00
github-actions[bot]
33c77530a9 Update manifest-beta.json to version 1.8.5-0-beta in main branch 2025-06-16 13:11:21 +00:00
Lost Paul
b429ec2041 Cleaner & more organized css file 2025-06-16 15:07:07 +02:00
Lost Paul
18e1f4dc45 Stop folder collapsing without touching css styles 2025-06-16 15:03:31 +02:00
Lost Paul
b56055fca4
Update README.md 2025-06-16 12:25:53 +02:00
github-actions[bot]
f13f1be14a Update manifest-beta.json to version 1.8.4-2-beta in main branch 2025-06-13 09:35:15 +00:00
Lost Paul
1d55c4117c Fix #125 2025-06-13 11:34:35 +02:00
github-actions[bot]
6e54dfa907 Update manifest-beta.json to version 1.8.4-1-beta in main branch 2025-06-13 08:19:46 +00:00
Lost Paul
f5b7df6a58 Fix #129 2025-06-13 10:19:21 +02:00
Lost Paul
c0bdf0a6c2 Update Commands.ts 2025-06-12 21:07:27 +02:00
github-actions[bot]
a1c4f2d3c6 Update manifest-beta.json to version 1.8.4-0-beta in main branch 2025-06-12 16:44:08 +00:00
Lost Paul
e7653658fc Fix #112 2025-06-12 18:43:27 +02:00
Lost Paul
86d1abab49 Improve command names 2025-06-12 18:35:34 +02:00
Lost Paul
b613d40679
Update bug_report.md 2025-06-11 10:38:57 +02:00
Lost Paul
53bc204cf4
Create label-beta.yml 2025-06-11 10:37:22 +02:00
Lost Paul
1d77169f54 Update setting descriptions 2025-06-07 14:04:45 +02:00
github-actions[bot]
2a1d71c106 Update manifest-beta.json to version 1.8.3-4-beta in main branch 2025-06-05 19:01:33 +00:00
Lost Paul
3d897a1135 Update MutationObserver.ts 2025-06-05 20:38:41 +02:00
Lost Paul
0a528039b0 Edited setting descriptions 2025-06-05 20:01:12 +02:00
Lost Paul
6f75d505cb Fix #223 and removing collapse icon for empty folders 2025-06-05 19:52:33 +02:00
github-actions[bot]
decb48db85 Update manifest-beta.json to version 1.8.3-1-beta in main branch 2025-06-05 12:33:19 +00:00
Lost Paul
a640c25f84 Update folderNoteFunctions.ts 2025-06-05 13:01:33 +02:00
github-actions[bot]
4770d466be Update manifest-beta.json to version 1.8.3-0-beta in main branch 2025-06-05 10:59:37 +00:00
Lost Paul
4871580e10 Fix bug "disable folder collapsing" is disabled 2025-06-05 11:39:37 +02:00
Lost Paul
8533e30ce5 Update main.ts 2025-06-04 15:18:50 +02:00
Lost Paul
153aeebb13
Merge pull request #221 from LostPaul/Improve-plugin-performance
Improve plugin performance
2025-06-04 15:02:32 +02:00
Lost Paul
1f04660242
Merge branch 'beta-3' into Improve-plugin-performance 2025-06-04 15:02:13 +02:00
Lost Paul
d6cf91b767 Fix changing folder names in the path 2025-06-04 14:54:11 +02:00
github-actions[bot]
d34084a563 Update manifest-beta.json to version 1.8.2-1-beta in main branch 2025-06-03 19:39:23 +00:00
Lost Paul
095e275ef8 Fix fmtp changing names in the explorer 2025-06-03 21:38:57 +02:00
Lost Paul
84d8c608fd Remove unnecessary awaits 2025-06-01 13:11:36 +02:00
Lost Paul
25ae1596f4 Fix dropping folder into note 2025-06-01 12:21:46 +02:00
github-actions[bot]
9833a8ac06 Update manifest-beta.json to version 1.8.2-0-beta in main branch 2025-05-30 18:52:06 +00:00
Lost Paul
27ab512611 Update obsidian-folder-overview 2025-05-30 20:48:44 +02:00
Lost Paul
94ce26c3e7 Fix show folder note in file explorer command 2025-05-30 20:47:50 +02:00
Lost Paul
fe64c66b60 Improve performance of mutation observer 2025-05-30 16:29:00 +02:00
Lost Paul
f53e2b6648 Update MutationObserver.ts 2025-05-30 11:33:30 +02:00
github-actions[bot]
e7adc6f7ca Update manifest-beta.json to version 1.8.1-3-beta in main branch 2025-05-29 14:06:49 +00:00
Lost Paul
7b9b6fabfa Improve setting descriptions & titles
Not done yet and I'll also improve the command names
2025-05-29 16:06:20 +02:00
Lost Paul
04e611fb97 Fix fmtp collapsed folder issue
See #168
2025-05-29 16:05:41 +02:00
github-actions[bot]
a050c2673c Update manifest-beta.json to version 1.8.1-2-beta in main branch 2025-05-29 08:29:26 +00:00
Lost Paul
30dce8bda0
Update beta-release.yaml 2025-05-29 10:28:40 +02:00
Lost Paul
2241a33d41
Update beta-release.yaml 2025-05-29 10:25:45 +02:00
Lost Paul
2692a22f93
Merge pull request #219 from LostPaul/beta-2
Fix #218
2025-05-29 10:20:39 +02:00
Lost Paul
fc80e33c43 Added submodule to correct folder 2025-05-27 20:57:13 +02:00
Lost Paul
8cc82e2967 Cleaned up incorrect submodule setup 2025-05-27 20:57:04 +02:00
github-actions[bot]
7d71087231 Update manifest-beta.json to version 1.8.0-4-beta in main branch 2025-05-27 17:29:39 +00:00
Lost Paul
219647482c Fix #218 2025-05-27 19:03:03 +02:00
Lost Paul
0739e10a8e Improved setting descriptions & titles
Not done yet
2025-05-27 11:02:22 +02:00
Lost Paul
3d5185fe23
Update README.md 2025-05-24 14:07:32 +02:00
github-actions[bot]
65813918d0 Update manifest-beta.json to version 1.8.0-0-beta in main branch 2025-05-07 12:39:11 +00:00
Lost Paul
cffb667940 Update SettingsTab.ts 2025-05-07 14:38:25 +02:00
github-actions[bot]
fed9fad7d7 Update manifest-beta.json to version 1.7.30-30-beta in main branch 2025-05-07 12:34:33 +00:00
Lost Paul
78a28b09b5
Merge pull request #163 from jfsicilia/open_from_file_explorer
Added new command to open folder note of current active folder in file explorer.
2025-05-05 12:15:21 +02:00
Lost Paul
d0aea34ea9 Fix eslint issues 2025-05-05 11:57:53 +02:00
Lost Paul
3924d53360
Merge branch 'main' into open_from_file_explorer 2025-05-01 16:35:57 +02:00
Lost Paul
8c57f7e10f Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-05-01 15:26:08 +02:00
Lost Paul
d7d6562cc4 Option to disable persisting tab 2025-05-01 15:26:05 +02:00
Lost Paul
07dfd5f504
Update issue templates 2025-04-26 15:35:12 +02:00
Lost Paul
7bd36394e2
Update issue templates 2025-04-26 14:58:28 +02:00
Lost Paul
94d3a92181 Update config.yml 2025-04-25 15:47:20 +02:00
Lost Paul
70f1f5559f Update config.yml 2025-04-25 15:40:41 +02:00
Lost Paul
308dff16be Update config.yml 2025-04-25 15:29:02 +02:00
Lost Paul
a933f1c3a6 Create config.yml 2025-04-25 15:25:53 +02:00
Lost Paul
0bc9ebf201 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-04-24 20:31:48 +02:00
Lost Paul
fad9053b08 Fix desc for the front matter title setting 2025-04-24 20:31:45 +02:00
github-actions[bot]
59fcc3b256 Update manifest-beta.json to version 1.7.30-29-beta in main branch 2025-04-22 18:21:22 +00:00
Lost Paul
729c3d2828 Update manifest-beta.json 2025-04-22 20:20:57 +02:00
Lost Paul
8ca22f1b93 Revert "Update manifest-beta.json"
This reverts commit bc48b43b34.
2025-04-22 20:17:19 +02:00
Lost Paul
bc48b43b34 Update manifest-beta.json 2025-04-22 20:14:50 +02:00
Lost Paul
73d7addee6 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-04-22 20:12:52 +02:00
Lost Paul
6349262332 Remove #175 error message
(and hopefully also fix the bug)
2025-04-22 20:12:50 +02:00
Lost Paul
7130e7c9a6
Merge pull request #215 from nonissue/patch-1 2025-04-12 12:21:53 +02:00
Andy Williams
31e357efcb
docs: correct Folder overview.md link in Getting started.md. 2025-04-12 03:37:44 -06:00
github-actions[bot]
3437541940 Update manifest-beta.json to version 1.7.30-26-beta in main branch 2025-03-04 21:06:03 +00:00
Lost Paul
0264b32886 Update obsidian-folder-overview 2025-03-04 22:01:17 +01:00
Lost Paul
4e5a805889 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-03-04 22:00:57 +01:00
Lost Paul
fedddcaa84 Fix templater vars 2025-03-04 22:00:49 +01:00
Lost Paul
06f0138bca
Update README.md 2025-03-04 10:22:54 +01:00
Lost Paul
805fc8ac77 Update obsidian-folder-overview 2025-03-01 14:47:05 +01:00
Lost Paul
8986c5b524 Fix code style issues part two 2025-03-01 14:45:52 +01:00
Lost Paul
8f131c83a8 Fix code style issues 2025-02-25 18:57:23 +01:00
Lost Paul
9c8760ef7d
Update build-and-push.yml 2025-02-24 12:04:54 +01:00
Lost Paul
d4f7050351 Add missing default settings 2025-02-24 11:56:38 +01:00
Lost Paul
a3a306c9cf Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-02-24 09:15:55 +01:00
Lost Paul
1fc237c21b Update obsidian-folder-overview 2025-02-24 09:15:52 +01:00
Lost Paul
029bf36dbf
Update build-and-push.yml 2025-02-24 09:08:37 +01:00
Lost Paul
d5179f5780
Update build-and-push.yml 2025-02-24 08:37:47 +01:00
Lost Paul
7903068cc6
Update and rename create-release-for-fv.yaml to build-and-push.yml 2025-02-24 08:23:28 +01:00
Lost Paul
086c791a82
Update create-release-for-fv.yaml 2025-02-20 11:13:30 +01:00
Lost Paul
0fa791aefb
Update create-release-for-fv.yaml 2025-02-20 10:53:42 +01:00
Lost Paul
3860de6050 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-02-19 16:05:04 +01:00
Lost Paul
efe4db4fdd Update obsidian-folder-overview 2025-02-19 16:05:00 +01:00
Lost Paul
15b9d64b7b
Update create-release-for-fv.yaml 2025-02-18 20:41:07 +01:00
Lost Paul
c836ce9a45
Update deploy-mkdocs.yml 2025-02-18 19:00:20 +01:00
Lost Paul
d83e9ef60e
Update create-release-for-fv.yaml 2025-02-18 18:54:31 +01:00
Lost Paul
283aef2fe0
Update create-release-for-fv.yaml 2025-02-18 18:52:21 +01:00
Lost Paul
6f6c208360 Fix auto folder note create for files 2025-02-06 10:09:23 +01:00
Lost Paul
889688c3a4 Update obsidian-folder-overview 2025-02-04 21:36:33 +01:00
Lost Paul
e09f190985 Fix applying templates & using templates without plugin 2025-02-04 21:36:25 +01:00
Lost Paul
7aad817dfb Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-02-01 21:00:26 +01:00
Lost Paul
be8e17f135 Auto create folder note when creating note option 2025-02-01 21:00:23 +01:00
github-actions[bot]
09c09ee31a Update manifest-beta.json to version 1.7.30-25-beta in main branch 2025-02-01 11:34:23 +00:00
Lost Paul
abf6336514 Fix renaming of folder notes who are in the parent folder 2025-02-01 11:35:51 +01:00
Lost Paul
e3e51062fa Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-31 13:17:14 +01:00
Lost Paul
69235ef3ed Update styles.css 2025-01-31 13:17:10 +01:00
Lost Paul
dad0042aa6
Update create-release-for-fv.yaml 2025-01-31 09:36:33 +01:00
Lost Paul
4c03e553f7
Update create-release-for-fv.yaml 2025-01-31 09:31:26 +01:00
Lost Paul
790fc07f42
Update create-release-for-fv.yaml 2025-01-31 09:14:38 +01:00
Lost Paul
a9da31b6ed
Update create-release-for-fv.yaml 2025-01-31 09:09:15 +01:00
Lost Paul
d59340176c
Update create-release-for-fv.yaml 2025-01-31 08:59:32 +01:00
Lost Paul
5d84788cde
Create create-release-for-fv.yaml 2025-01-31 08:27:01 +01:00
github-actions[bot]
c5454e0de5 Update manifest-beta.json to version 1.7.30-24-beta in main branch 2025-01-28 18:45:43 +00:00
Lost Paul
578e549cb8 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-28 19:38:01 +01:00
Lost Paul
3acfba425e Fix parent folder path storage option on first lvl 2025-01-28 19:37:47 +01:00
github-actions[bot]
2733830531 Update manifest-beta.json to version 1.7.30-23-beta in main branch 2025-01-28 17:07:43 +00:00
github-actions[bot]
17ae3f6461 Update manifest-beta.json to version 1.7.30-22-beta in main branch 2025-01-28 16:52:16 +00:00
Lost Paul
faa86db7d6 Use modal and remove unnecessary setting 2025-01-28 17:51:54 +01:00
Lost Paul
416bf25a91 Add old/folder note name for rename existing folder notes modal 2025-01-28 17:32:01 +01:00
Lost Paul
d9ac3d199c Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-26 13:18:48 +01:00
github-actions[bot]
8e4e522fee Update manifest-beta.json to version 1.7.30-21-beta in main branch 2025-01-26 12:16:02 +00:00
Lost Paul
e7804b7e3d Update ChangeFnName.ts 2025-01-26 13:15:35 +01:00
github-actions[bot]
34615acb46 Update manifest-beta.json to version 1.7.30-20-beta in main branch 2025-01-26 12:13:35 +00:00
Lost Paul
451de100be Fix folder note name feature and add conformation modal 2025-01-26 13:08:09 +01:00
Lost Paul
7d1b03ec17 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-25 21:26:06 +01:00
Lost Paul
a54808da93 Backup warning modal 2025-01-25 21:26:03 +01:00
github-actions[bot]
b0d74adfe6 Update manifest-beta.json to version 1.7.30-19-beta in main branch 2025-01-25 11:58:55 +00:00
Lost Paul
c1d9fbca7f Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-25 12:39:03 +01:00
github-actions[bot]
3370060c80 Update manifest-beta.json to version 1.7.30-18-beta in main branch 2025-01-25 11:39:02 +00:00
Lost Paul
962b5a5b32 Update template path description 2025-01-25 12:38:37 +01:00
Lost Paul
64a417bc04
Update release.yaml 2025-01-25 12:37:07 +01:00
Lost Paul
5486b04f25
Update beta-release.yaml 2025-01-25 12:36:54 +01:00
github-actions[bot]
d197fb9fdb Update manifest-beta.json to version 1.7.30-17-beta in main branch 2025-01-25 11:18:28 +00:00
Lost Paul
85bebc97e8 Remove redundant code 2025-01-25 12:16:22 +01:00
Lost Paul
2338b32d96 Fix detach folder issue 2025-01-25 11:36:37 +01:00
Lost Paul
50461f5e34 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-25 11:23:24 +01:00
Lost Paul
5188d45f5d Update package.json 2025-01-25 11:23:20 +01:00
Lost Paul
0fe07eed04 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-15 10:35:11 +01:00
Lost Paul
fd67bfae84 Update Folder overview.md 2025-01-15 10:35:07 +01:00
Lost Paul
36925bfc39 Update Folder overview.md 2025-01-14 21:39:33 +01:00
github-actions[bot]
8b7a2fad6a Update manifest-beta.json to version 1.7.30-16-beta in main branch 2025-01-14 20:28:41 +00:00
Lost Paul
54285cb8b7 Add styles & functions for the folder overview 2025-01-14 21:28:06 +01:00
Lost Paul
df07661d3b Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-12 21:46:19 +01:00
github-actions[bot]
ddb64526af Update manifest-beta.json to version 1.7.30-15-beta in main branch 2025-01-12 20:46:10 +00:00
Lost Paul
5b75e0988b Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-12 21:46:07 +01:00
Lost Paul
0936bc9bb6 Fix build issues 2025-01-12 21:45:44 +01:00
github-actions[bot]
cdb7d77dcf Update manifest-beta.json to version 1.7.30-14-beta in main branch 2025-01-12 20:40:21 +00:00
Lost Paul
e29833cdf2 Update manifest-beta.json 2025-01-12 21:35:26 +01:00
Lost Paul
b79942f1b9 Fix settings tab 2025-01-12 21:35:09 +01:00
github-actions[bot]
541ff4f9be Update manifest-beta.json to version 1.7.30-13-beta in main branch 2025-01-12 20:26:42 +00:00
Lost Paul
ffe72b2094 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-12 21:26:10 +01:00
github-actions[bot]
a335939483 Update manifest-beta.json to version 1.7.30-12-beta in main branch 2025-01-12 20:22:50 +00:00
Lost Paul
c867127b16 Update manifest-beta.json 2025-01-12 21:22:20 +01:00
github-actions[bot]
18dec14866 Update manifest-beta.json to version 1.7.30-11-beta in main branch 2025-01-12 20:13:51 +00:00
Lost Paul
e2ab165d52 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-12 21:13:04 +01:00
Lost Paul
dd6a59131b Update obsidian-folder-overview 2025-01-12 21:12:55 +01:00
github-actions[bot]
8c8804dc24 Update manifest-beta.json to version 1.7.30-10-beta in main branch 2025-01-12 20:08:13 +00:00
Lost Paul
45d4b3ebb9
Update release.yaml 2025-01-12 21:03:41 +01:00
Lost Paul
04272814d5
Update beta-release.yaml 2025-01-12 21:03:18 +01:00
github-actions[bot]
50f7808dad Update manifest-beta.json to version 1.7.30-9-beta in main branch 2025-01-12 19:53:12 +00:00
Lost Paul
8d68bbd0b4 Fixed some build issues 2025-01-12 20:52:16 +01:00
github-actions[bot]
2f7d6a76f7 Update manifest-beta.json to version 1.7.30-8-beta in main branch 2025-01-12 19:21:04 +00:00
Lost Paul
df7f95252b Update manifest-beta.json 2025-01-12 20:20:39 +01:00
Lost Paul
85cc1a3a23 Update manifest-beta.json 2025-01-12 20:17:31 +01:00
Lost Paul
01e4fc7f66 Update obsidian-folder-overview 2025-01-12 20:16:14 +01:00
Lost Paul
7f26da6ba7 Fix multi select 2025-01-12 20:09:33 +01:00
Lost Paul
8612246987 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-11 22:08:57 +01:00
Lost Paul
07ad2b927b Remove duplicated commands 2025-01-11 22:08:54 +01:00
Lost Paul
a95db26128 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-09 11:43:58 +01:00
Lost Paul
2286c489ed Update manifest.json 2025-01-09 11:43:48 +01:00
github-actions[bot]
cecd3e719f Update manifest.json to version 2025-01-09 10:43:17 +00:00
Lost Paul
fd0819b235 Update package.json 2025-01-09 10:52:37 +01:00
Lost Paul
fd967fc2fc Update package.json 2025-01-09 10:48:19 +01:00
Lost Paul
f268793990 Update obsidian-folder-overview 2025-01-09 10:36:43 +01:00
Lost Paul
7a40e41224 Update styleFunctions.ts 2025-01-09 10:36:40 +01:00
Lost Paul
7b086542d8 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2025-01-08 23:11:25 +01:00
Lost Paul
c98fe4ed4c Update obsidian-folder-overview 2025-01-08 23:11:22 +01:00
Lost Paul
cdb4fa11f7 Update Folder overview.md 2025-01-08 22:48:20 +01:00
github-actions[bot]
fffb10e802 Update manifest.json to version 2025-01-08 21:33:46 +00:00
github-actions[bot]
25c52658e1 Update manifest.json to version 2025-01-08 21:26:31 +00:00
Lost Paul
1bdad7d4f0 Update obsidian-folder-overview 2025-01-08 16:15:03 +01:00
Lost Paul
abc72d3216 Edit text 2025-01-08 16:14:36 +01:00
Lost Paul
61220433e1 Update obsidian-folder-overview 2025-01-08 16:14:10 +01:00
Lost Paul
8ef02fbdb8 Update obsidian-folder-overview 2025-01-08 15:59:24 +01:00
Lost Paul
f67031a7fc Move folder overview 2025-01-08 15:54:38 +01:00
Lost Paul
182839c8de Add folder overview as submodule 2025-01-08 14:08:18 +01:00
Lost Paul
341dde9c74 Change folder overview paths 2025-01-08 12:43:29 +01:00
Lost Paul
b059914c6f Auto update list overview 2025-01-07 21:46:58 +01:00
Lost Paul
2e2d343aed Make folder overview an independent plugin progress 2025-01-07 21:39:34 +01:00
Lost Paul
62c4b5e149 Fix code style issues 2025-01-07 11:54:21 +01:00
Lost Paul
4c3e186d0b Made Folder Overview its own Plugin
Still needs quite a lot of changes in order to work flawless
2025-01-06 21:36:22 +01:00
Lost Paul
b1e03cd36b When there isn't a active file it defaults to the default settings 2025-01-03 22:36:34 +01:00
Lost Paul
81d7236a1e Remove flashing of folder overview settings view 2025-01-03 22:25:25 +01:00
Lost Paul
85d82ed732 Update view.ts 2025-01-03 13:40:50 +01:00
Lost Paul
23a702d9a3 Update view.ts 2025-01-03 13:40:34 +01:00
Lost Paul
b65d624e3b Folder overview settings view 2025-01-03 13:39:51 +01:00
Lost Paul
5c4c20d170 File explorer view now also works when file explorer view isn't open 2025-01-01 13:07:29 +01:00
Lost Paul
5106b6f471 Fix file explorer folder overview depth issue
and also added a tooltip above the depth slider
2024-12-31 23:06:49 +01:00
Lost Paul
b3e0c0c1b3 Fix file depth for folder overview list type 2024-12-29 16:25:46 +01:00
Lost Paul
7829afef3e Improved folder overview yaml 2024-12-29 15:39:10 +01:00
Lost Paul
6119cef5c8 Fixed that files are before folders in the folder overview 2024-12-29 15:14:30 +01:00
Lost Paul
f605ec0cff Fixed list style folder overview only using the source folder 2024-12-28 20:48:24 +01:00
Lost Paul
c1605ae673 Improved sorting code 2024-12-28 18:25:36 +01:00
Lost Paul
1333c28b8d Fix #169
and I also fixed that folders that are lower than depth 1 now show up again the in the list style folder overview
2024-12-28 18:25:06 +01:00
Lost Paul
2bf6c26fb2 Add always to collapse to folder overview settings 2024-12-28 17:55:48 +01:00
Lost Paul
69a1318442 Fix #182 2024-12-27 22:10:30 +01:00
Lost Paul
ba4069fe7d
Merge pull request #185 from xorvoid/folder-overview-collapse
Added feature to always collapse elements in the folder overview expl…
2024-12-27 20:15:08 +01:00
Lost Paul
4673d9db29
Merge pull request #184 from vkatushenok/escape-selectors
Escape folder path in selectors
2024-12-27 20:14:55 +01:00
Lost Paul
cc830920dd Update styleFunctions.ts 2024-12-27 14:12:23 +01:00
xorvoid
76ee4f2bac Added feature to always collapse elements in the folder overview explorer 2024-12-16 16:10:54 -08:00
Vladimir Katushenok
3406e6e489 Escape folder path in selectors 2024-12-14 16:20:03 +11:00
Lost Paul
c1e7567a6e Save changes 2024-11-12 22:10:25 +01:00
jfsicilia
8627c2db54 Added commands for folder note creation and deletion in file explorer. Improved code readability. 2024-10-22 11:34:18 +01:00
jfsicilia
6743340aba Added new command to open folder note of current active folder in file explorer. 2024-10-21 21:17:32 +01:00
Lost Paul
75443255b0 Fix style issue with new version 2024-10-03 17:34:38 +02:00
Lost Paul
1e4ed4b102 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-10-03 17:33:59 +02:00
github-actions[bot]
4b2995ca47 Update manifest.json to version 2024-10-03 12:33:58 +00:00
Lost Paul
1804c4b11a Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-09-29 20:45:47 +02:00
Lost Paul
230060d592 Update MutationObserver.ts 2024-09-29 20:45:44 +02:00
github-actions[bot]
6c1346d513 Update manifest.json to version 2024-09-28 18:20:49 +00:00
Lost Paul
0f5cd8692e Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-09-22 22:24:49 +02:00
Lost Paul
e4f6419687 Save progress 2024-09-22 22:24:46 +02:00
Lost Paul
168ac4fe5b
Merge pull request #149 from LostPaul/dependabot/npm_and_yarn/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3
2024-09-22 11:16:46 +02:00
dependabot[bot]
cfb4bff22a
Bump braces from 3.0.2 to 3.0.3
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-22 09:14:48 +00:00
Lost Paul
1c794c18ea Fix issues with 1.7.2 Obsidian release and some code rewrite 2024-09-21 21:49:34 +02:00
github-actions[bot]
423071e492 Update manifest-beta.json to version 1.7.30-6-beta in main branch 2024-09-20 13:53:13 +00:00
github-actions[bot]
401be90177 Update manifest-beta.json to version 1.7.30-5-beta in main branch 2024-09-17 15:17:06 +00:00
Lost Paul
511a516f20
Update release.yaml
Fix release script that it updates the manifest file when you publish a release from a other branch
2024-09-17 14:30:19 +02:00
Lost Paul
0d9a4ad83b
Update beta-release.yaml
Fix beta release script so that you can publish a release from any branch and it updates the manifest-beta file in the main branch.
2024-09-17 14:29:11 +02:00
github-actions[bot]
17fa0a7e45 Update manifest-beta.json to version 1.7.30-4-beta in main branch 2024-09-17 12:19:28 +00:00
github-actions[bot]
988025d9c5 Update manifest-beta.json to version 1.7.30-3-beta in main branch 2024-09-17 12:14:14 +00:00
github-actions[bot]
c3e2437eae Update manifest-beta.json to version 1.7.30-1-beta 2024-09-17 12:05:41 +00:00
Lost Paul
1a0220709d
Update beta-release.yaml
Make it possible to publish beta versions from every branch
2024-09-17 14:03:46 +02:00
Lost Paul
0b33f170ad
Update manifest.json 2024-09-07 15:38:35 +02:00
github-actions[bot]
6fb731275f Update manifest-beta.json to version 1.7.26-4-beta 2024-09-07 13:23:34 +00:00
github-actions[bot]
62037c60e2 Update manifest-beta.json to version 1.7.26-3-beta 2024-09-07 13:23:09 +00:00
github-actions[bot]
f4408c3acd Update manifest-beta.json to version 1.7.26-2-beta 2024-09-07 13:15:54 +00:00
Lost Paul
446185c51d Fix compiling issues 2024-09-07 14:25:51 +02:00
Lost Paul
62d05b90c5 Merged stashed changes 2024-09-07 13:44:11 +02:00
Lost Paul
4962ac5236 Save progress 2024-09-05 17:05:58 +02:00
Lost Paul
98158cbef9 Update styleFunctions.ts 2024-08-20 13:03:28 +02:00
Lost Paul
db2b0c096a Drop folder notes into not to create an link 2024-08-20 13:03:21 +02:00
Lost Paul
07b9b812e2 Update SettingsTab.ts 2024-08-20 13:02:19 +02:00
Lost Paul
8baef8b7f7 Option to hide collapse icon of empty folders 2024-08-20 13:02:10 +02:00
Lost Paul
bd78c12329 Internal typings package 2024-08-20 12:56:26 +02:00
Lost Paul
5e903abab5 Added obsidian typing package 2024-08-15 14:02:37 +02:00
Lost Paul
6d1c1d0cd4 Drag folder notes into markdown note/canvas 2024-08-15 14:02:18 +02:00
Lost Paul
e25ded832d Update FileExplorer.ts 2024-08-14 13:28:55 +02:00
Lost Paul
cde7cf09ec Fix #128 2024-08-14 13:28:46 +02:00
Lost Paul
84bd8c55c7 Fix missing files in folder overview issue 2024-08-13 15:50:04 +02:00
Lost Paul
52b0905d0d Automatically update folder overview
& remove listeners when closing the file
2024-08-13 11:32:55 +02:00
Lost Paul
5d3a77d711 Update FileExplorer.ts 2024-08-12 22:27:48 +02:00
Lost Paul
78b9838fde File menu context menu for file explorer overview 2024-08-12 22:27:38 +02:00
Lost Paul
ca622350e4 Should fix #42 2024-08-12 15:40:37 +02:00
Lost Paul
5173962ee8 Improved the list setting type 2024-08-11 15:58:05 +02:00
Lost Paul
6b5514d7f2 Merge branch 'main' into Fix-folder-note-delete-issues 2024-08-08 11:02:59 +02:00
Lost Paul
51f2340d8c
Update manifest.json 2024-08-07 12:43:47 +02:00
github-actions[bot]
385a410917 Update manifest.json to version 1.7.29 2024-08-07 10:42:19 +00:00
Lost Paul
435a2587bc Testing the release script 2024-08-07 12:40:36 +02:00
Lost Paul
25b3eeaa2e Update release.yaml 2024-08-07 12:39:05 +02:00
Lost Paul
ad84aa66f1 Testing the release script 2024-08-07 12:34:28 +02:00
Lost Paul
e9c0e0a266 Update release.yaml 2024-08-07 12:33:54 +02:00
Lost Paul
942d779a82 Testing the release script 2024-08-07 12:31:50 +02:00
Lost Paul
eebc0d1d58 Merge branch 'Fix-folder-note-delete-issues' of https://github.com/LostPaul/obsidian-folder-notes into Fix-folder-note-delete-issues 2024-08-07 12:30:48 +02:00
Lost Paul
76ebb1aa27 Update release.yaml 2024-08-07 12:30:46 +02:00
Lost Paul
181cedbcf2
Update manifest.json 2024-08-06 22:28:16 +02:00
github-actions[bot]
2e24920a1e Update manifest.json to version 2024-08-06 11:51:38 +00:00
Lost Paul
8f444cb2a3 Update manifest.json 2024-08-06 13:51:17 +02:00
Lost Paul
f5a832ae68 Update release.yaml 2024-08-06 13:51:03 +02:00
Lost Paul
1b611136d6 Update manifest.json 2024-08-06 13:48:32 +02:00
Lost Paul
04f6e95f93 Update release.yaml 2024-08-06 13:47:35 +02:00
Lost Paul
f2e907d741 Update manifest.json 2024-08-06 13:38:51 +02:00
Lost Paul
6e86172195 Update release.yaml 2024-08-06 13:38:33 +02:00
Lost Paul
7e26d0e246 Option to delete folder note permanently, move to trash, ...
Now you can choose if you want to delete the folder note permanently or just want to move it the system/obsidian trash like it's possible for normal files

This also fixes #142 & #120
2024-08-06 13:37:26 +02:00
Lost Paul
53be512154
Update release.yaml 2024-08-05 15:58:47 +02:00
Lost Paul
1310db6be7
Update beta-release.yaml 2024-08-05 15:32:50 +02:00
github-actions[bot]
b69e5d66f9 Update manifest-beta.json to version 1.7.23-9-beta 2024-08-05 13:23:38 +00:00
github-actions[bot]
1242a9f7cd Update manifest-beta.json to version 2024-08-05 10:58:05 +00:00
Lost Paul
4376dd0670
Update beta-release.yaml 2024-08-05 12:44:46 +02:00
Lost Paul
6e3cf90a1a
Update beta-release.yaml 2024-08-05 12:41:29 +02:00
Lost Paul
7bbfeeef65
Update beta-release.yaml 2024-08-05 11:25:26 +02:00
Lost Paul
6fca9ac1e7 Update manifest-beta.json 2024-06-18 18:54:21 +02:00
Lost Paul
490d5e190b Update SettingsTab.ts 2024-06-18 18:54:18 +02:00
Lost Paul
901174ef25 Update manifest-beta.json 2024-06-18 18:44:54 +02:00
Lost Paul
3d10f1eb41 Commit to save progress 2024-06-16 22:28:31 +02:00
Lost Paul
a26422d0f2 Limit suggested folders amount 2024-06-16 18:07:26 +02:00
Lost Paul
e7a100e9fb Detach folder note command 2024-06-15 18:23:34 +02:00
Lost Paul
f8ac0362ec Remove style from folder in path if excluded 2024-06-15 17:59:27 +02:00
Lost Paul
b705810d3d Update whitelistFolderFunctions.ts 2024-06-15 17:57:31 +02:00
Lost Paul
046773f901 Detach folder note function 2024-06-15 17:56:14 +02:00
Lost Paul
f1b181e772 getDetachedFolder & includeDetached folder option 2024-06-15 17:55:47 +02:00
Lost Paul
df3b30a422 Hide detached folders 2024-06-15 17:53:25 +02:00
Lost Paul
12c03793ed Fix excluded folder path sync, disable sync style bug 2024-06-15 16:07:43 +02:00
Lost Paul
9513713edf Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-06-04 21:11:44 +02:00
Lost Paul
ce4374e99f Update manifest-beta.json 2024-06-04 19:26:21 +02:00
Lost Paul
fe562944fa
Update beta-release.yaml 2024-06-04 19:25:43 +02:00
Lost Paul
b957b2ad64 Update manifest-beta.json 2024-06-04 19:23:50 +02:00
Lost Paul
370c219670
Update beta-release.yaml 2024-06-04 19:20:14 +02:00
Lost Paul
161372754a Update manifest-beta.json 2024-06-04 19:15:45 +02:00
Lost Paul
e7c2ce8262
Update beta-release.yaml 2024-06-04 19:15:12 +02:00
Lost Paul
8043570761 Update manifest-beta.json 2024-06-04 19:07:53 +02:00
Lost Paul
879856308b
Update beta-release.yaml 2024-06-04 19:06:57 +02:00
Lost Paul
63547e2f10 Update manifest-beta.json 2024-06-04 19:02:42 +02:00
Lost Paul
d7f713c918 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-06-04 18:49:55 +02:00
Lost Paul
20bf0daf97 Update manifest-beta.json 2024-06-04 18:49:43 +02:00
Lost Paul
c762deecd5
Update beta-release.yaml 2024-06-04 18:48:30 +02:00
Lost Paul
c92e401b37
Update beta-release.yaml 2024-06-04 18:45:44 +02:00
Lost Paul
9aa01aebbf Update manifest-beta.json 2024-06-04 18:36:22 +02:00
Lost Paul
4fc6980cd5 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-06-04 18:29:28 +02:00
Lost Paul
217a622334 Update manifest-beta.json 2024-06-04 18:29:16 +02:00
Lost Paul
345df3e6fe
Update beta-release.yaml 2024-06-04 18:27:56 +02:00
Lost Paul
e3cf795ab3 Update manifest-beta.json 2024-06-04 12:00:01 +02:00
Lost Paul
c3babf96bd Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-06-04 09:25:55 +02:00
Lost Paul
c0c3b4bf52 Update manifest-beta.json 2024-06-04 09:25:44 +02:00
Lost Paul
e10eb00788
Update beta-release.yaml 2024-06-04 09:24:40 +02:00
Lost Paul
e64f88c742 Update manifest-beta.json 2024-06-04 09:13:21 +02:00
Lost Paul
59dd52f0e6 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-06-04 09:10:06 +02:00
Lost Paul
39efafcbb8 Update manifest-beta.json 2024-06-04 09:09:57 +02:00
Lost Paul
45e21ea0db
Create beta-release.yaml 2024-06-04 09:08:23 +02:00
Lost Paul
3fb7b37aaf
Delete .github/workflows/beta-release.yaml 2024-06-04 09:04:03 +02:00
Lost Paul
c7c7ecfb2e Update manifest-beta.json 2024-06-04 09:01:33 +02:00
Lost Paul
0bc4cea2b1 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-06-04 08:52:51 +02:00
Lost Paul
c1da176baa Update manifest-beta.json 2024-06-04 08:52:32 +02:00
Lost Paul
914c111fa9
Update beta-release.yaml 2024-06-04 08:48:01 +02:00
Lost Paul
b2544f4750
Create beta-release.yaml 2024-06-04 08:46:33 +02:00
Lost Paul
3c77bee0f5 Create manifest-beta.json 2024-06-04 08:40:01 +02:00
Lost Paul
3673e6c0e2 Option to disable folder highlighting 2024-06-02 14:36:07 +02:00
Lost Paul
a975b85469 Fix #115 2024-06-01 18:49:19 +02:00
Lost Paul
504a484c8b Update handleCreate.ts 2024-06-01 18:27:07 +02:00
Lost Paul
e8e80f6ddb Fix #95
Also added a setting to allow the automatic creation of folder notes for the attachment folder
2024-06-01 18:04:34 +02:00
Lost Paul
c72697b81b Option to disable the sidebar on desktop
Added a setting to also disable the sidebar from automatically opening when you click on a folder name in the path on desktop
2024-06-01 16:18:45 +02:00
Lost Paul
65503affd0
Merge pull request #108 from milan-tom/avoid-opening-left-sidebar
Avoid opening left sidebar if closed when folder note clicked in path
2024-05-26 00:02:24 +02:00
Lost Paul
d4e3da3088 Setting to disable sidebar opening
Added a setting to enable/disable the opening of the sidebar on mobile when you click on a folder name in the path

Improved the way it stops the sidebar from opening

Fixed a bug where the sidebar wouldn't open
2024-05-26 00:01:38 +02:00
Lost Paul
1de99078f7 Change titles/descriptions of whitelist modals 2024-05-21 18:46:31 +02:00
Lost Paul
f8a77854ee Override whitelisted folders 2024-05-21 18:17:13 +02:00
Lost Paul
7b52e04d38 Fix for #120 2024-05-21 18:16:27 +02:00
Lost Paul
64ce683a5c Fix small issues 2024-05-21 17:42:40 +02:00
Lost Paul
0978c220af Add id to Exclude Pattern/Folder
To make it easier to delete a pattern/folder and have multiple of them with the same path or string
2024-05-21 17:31:33 +02:00
Lost Paul
f48598d55f Multiple excluded folders/patterns instead only one
Previously the plugin checked if a folder matches a folder path or pattern and stopped searching for other patterns/excluded folder after its first find.
This meant you couldn't "override" an already excluded folder in the settings. So if you had a regex that matches every folder it would just use the settings you set for the regex and ignore everything else that would also match the folder.
2024-05-21 17:30:03 +02:00
Lost Paul
cd38702394 Override excluded folders/patterns with whitelist 2024-05-21 16:58:44 +02:00
Lost Paul
8cd11dd959 Temp beta bug fix 2024-05-19 16:48:13 +02:00
Lost Paul
945c25f97e Update manifest.json 2024-04-20 17:12:02 +02:00
milan-tom
a16f4635e7
Change duplicate event to be MouseEvent 2024-04-20 12:11:29 +01:00
milan-tom
45df35d913
Remove existing event listener(s) for folder note header breadcrumb when open folder in path enabled 2024-04-20 11:51:42 +01:00
Lost Paul
dc2e53c589
Merge pull request #100 from RyotaUshio/main 2024-03-26 13:10:28 +01:00
Ryota Ushio
486350e447
Fix #99: Remove logging code 2024-03-26 21:02:58 +09:00
Lost Paul
065000d28e Build error fix 2024-03-19 22:54:20 +01:00
Lost Paul
e59f3343e2 Update manifest.json 2024-03-19 22:51:33 +01:00
Lost Paul
42d598f62c Update WhitelistPatternSettings.ts 2024-03-19 22:49:47 +01:00
Lost Paul
58c6ed7885 Update manifest.json 2024-03-19 22:48:11 +01:00
Lost Paul
5b5990fd32 Update whitelistFolderFunctions.ts 2024-03-19 22:47:11 +01:00
Lost Paul
582df27c70 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-03-19 22:44:34 +01:00
Lost Paul
271cc47fb1 Update manifest.json 2024-03-19 22:44:09 +01:00
Lost Paul
6a2297e739 Fix excluded folders bug & style reload issues 2024-03-19 22:43:57 +01:00
Lost Paul
215836483b
Create release.yaml 2024-03-19 22:40:47 +01:00
Lost Paul
55113c7998
Delete .github/workflows/release.yaml 2024-03-19 22:40:25 +01:00
Lost Paul
29b0a2d922 Fix excluded folders bug & style reload issues 2024-03-19 22:33:29 +01:00
Lost Paul
63ee47ba7f Update manifest.json 2024-02-10 21:54:19 +01:00
Lost Paul
c991c1220e Fix #84 2024-02-10 21:54:08 +01:00
Lost Paul
8b1804caf2 Fix #84 2024-02-10 21:43:48 +01:00
Lost Paul
068a2babc7 Update manifest.json 2024-02-03 19:36:12 +01:00
Lost Paul
18593d252e Fix handle create event 2024-02-03 19:30:06 +01:00
Lost Paul
404eeac1e8 Hide collapse icon storage location difference 2024-02-03 19:29:42 +01:00
Lost Paul
cfbee6a814 Hide collapse icon storage location difference 2024-02-03 17:27:22 +01:00
Lost Paul
518bf64c29 Fix create note bug 2024-02-03 17:26:41 +01:00
Lost Paul
ca194f7172 Add missing plugin objects 2024-02-03 16:52:51 +01:00
Lost Paul
f37a9d7a83 Add missing plugin objects 2024-02-03 16:49:15 +01:00
Lost Paul
0162af9aff Change code structure & fix handle rename 2024-02-03 16:38:15 +01:00
Lost Paul
48ffcdbe9f Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-02-03 12:17:47 +01:00
Lost Paul
b2fe971c28 Fix #81 2024-02-03 12:17:45 +01:00
Lost Paul
dd583a3079
Update deploy-mkdocs.yml 2024-01-28 17:22:54 +01:00
Lost Paul
f6744f75f8 Video support 2024-01-28 17:01:22 +01:00
Lost Paul
e403d35b7b Update Folder overview.md 2024-01-28 16:45:30 +01:00
Lost Paul
15f4d2844c Update manifest.json 2024-01-27 17:44:37 +01:00
Lost Paul
5fafc8f3d7 Remove setting tab headers and fix #79 2024-01-27 17:32:15 +01:00
Lost Paul
f044351727 Update manifest.json 2024-01-27 13:11:41 +01:00
Lost Paul
841b4e211b Fix active file class getting applied to non folder note folders 2024-01-27 13:10:59 +01:00
Lost Paul
77700797a2 Fix for older users of the plugin 2024-01-27 12:29:53 +01:00
Lost Paul
dec8a6e237 Change default settings 2024-01-27 12:23:56 +01:00
Lost Paul
4371aa56d3 Fixed hide collapse icon bug & more
Fixed that the file explorer always updates when changing a tab in the plugin settings

Changed the setting name for the css class that stops white space collapsing for less confusing with the name
2024-01-27 12:17:27 +01:00
Lost Paul
35e53aa5f5 Finished hide collapse icon feature 2024-01-23 19:35:38 +01:00
Lost Paul
402e28828d Update styles.css 2024-01-21 22:08:16 +01:00
Lost Paul
63fd0d7085 Update main.ts 2024-01-21 22:08:14 +01:00
Lost Paul
b8817a5692 Fix active folder highlight 2024-01-21 18:12:15 +01:00
Lost Paul
45a43c8748 Fix hide collapse icon 2024-01-21 16:53:53 +01:00
Lost Paul
21e733f263 Option to use folder name instead of file name in a tab title 2024-01-21 16:22:53 +01:00
Lost Paul
4fce7adbf0 Fix create folder note bug 2024-01-20 18:31:50 +01:00
Lost Paul
7c455e695c Option to hide collapse indicator when folder only has a folder note and nothing else 2024-01-14 12:52:28 +01:00
Lost Paul
41968bb9b8 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2024-01-13 22:01:16 +01:00
Lost Paul
23f8a05101 Update manifest.json 2024-01-13 22:01:05 +01:00
Lost Paul
671b8416bc Hide collapse icon code 2024-01-13 21:59:05 +01:00
Lost Paul
0bb2dd5a91 Copy folder note Obsidian url 2024-01-13 21:18:32 +01:00
Lost Paul
160ba24d02
Update README.md 2024-01-07 14:05:15 +01:00
Lost Paul
2637e33954 Fix right click 2024-01-07 06:46:51 +01:00
Lost Paul
dd24f3ffa3
Fix #76 2024-01-07 06:36:04 +01:00
Lost Paul
42dc67e2fc Update mkdocs.yml 2024-01-06 18:29:36 +01:00
Lost Paul
2b2f2adde7 Update mkdocs.yml 2024-01-06 18:24:31 +01:00
Lost Paul
1dcb40c65e Update mkdocs.yml 2024-01-06 18:10:37 +01:00
Lost Paul
cb29a307f0 Support for middle mouse click to open in a new tab 2024-01-06 18:08:35 +01:00
Lost Paul
ee1b2c699d Update manifest.json 2024-01-03 18:27:36 +01:00
Lost Paul
bcec6dfca8 Fix missing plugin variable 2024-01-03 18:26:16 +01:00
Lost Paul
9ed663948e Update manifest.json 2024-01-03 18:13:03 +01:00
Lost Paul
63d96c0da3 Update Getting started.md 2024-01-03 17:32:14 +01:00
Lost Paul
b959f0bd0c Update Folder overview.md 2024-01-03 17:22:37 +01:00
Lost Paul
7dd5678621 Update mkdocs.yml 2024-01-03 17:20:43 +01:00
Lost Paul
a6069c1b12 Update mkdocs.yml 2024-01-03 17:15:43 +01:00
Lost Paul
a67f075686 Update Folder context menu commands.md 2024-01-03 17:11:13 +01:00
Lost Paul
8590752582 Update File context menu commands.md 2024-01-03 17:11:11 +01:00
Lost Paul
01aad39f3a Extended the documentation 2024-01-03 17:07:14 +01:00
Lost Paul
019a9eed9d Exclude folders default settings & fix exclude folder pattern bug 2024-01-03 15:39:44 +01:00
Lost Paul
e6f7c435fe Delete XjlSBfx6ouPrRpb2JmcL.png 2024-01-01 18:15:10 +01:00
Lost Paul
b9e1c3bad9 Update index.md 2024-01-01 18:06:00 +01:00
Lost Paul
5c5a9129d7 Update Getting started.md 2024-01-01 18:03:03 +01:00
Lost Paul
2fe7ef0b57 Getting started update 2023-12-31 19:44:28 +01:00
Lost Paul
69ac23abe7 Update manifest.json 2023-12-30 18:39:53 +01:00
Lost Paul
d34e181da8 Fix ITS theme folder overview style bug 2023-12-30 18:27:05 +01:00
Lost Paul
2917ff57e0 Open folder notes with ctrl/alt only 2023-12-30 18:03:36 +01:00
Lost Paul
1fb8ead51e Fix two bugs
When you opened a folder note you couldn't collapse it anymore even when you disabled "disable collapsing"

The bold and cursive styles for the names of folder notes went away after restarting Obsidian
2023-12-30 18:01:21 +01:00
Lost Paul
89cf7d694f Update manifest.json 2023-12-29 14:18:16 +01:00
Lost Paul
5f252cc4d6 Hover popup support for folder notes #61 2023-12-29 13:19:03 +01:00
Lost Paul
4ddd56cef8 Update manifest.json 2023-12-28 18:39:23 +01:00
Lost Paul
c4d9af53f1 Fix #68
Fix template not being applied bug
2023-12-28 18:33:58 +01:00
Lost Paul
e2ee295e6c Add fr #69 2023-12-28 18:33:12 +01:00
Lost Paul
015602e29c Added helpUrl 2023-12-01 20:52:32 +01:00
Lost Paul
e2efbb3110 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-12-01 16:02:40 +01:00
Lost Paul
dd5f829585 Add helpUrl to manifest 2023-12-01 16:02:33 +01:00
Lost Paul
d0e47e8d1c
Update manifest.json 2023-12-01 15:58:44 +01:00
Lost Paul
f7a28db487
Update README.md 2023-11-19 00:00:31 +01:00
Lost Paul
b80a7acfba Update Getting started.md 2023-11-18 22:30:15 +01:00
Lost Paul
130a4076c5 Update Getting started.md 2023-11-18 22:28:15 +01:00
Lost Paul
346b451f01 Update Getting started.md 2023-11-18 22:23:49 +01:00
Lost Paul
4151efe9f7 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-11-18 22:13:01 +01:00
Lost Paul
562e1fde5b Get started docs site 2023-11-18 22:12:57 +01:00
Lost Paul
3d7b9177c2
Update README.md 2023-11-17 21:03:56 +01:00
Lost Paul
ef4906427b Update manifest.json 2023-11-09 21:51:36 +01:00
Lost Paul
b337729c31 Use renameFile function instead to fix #63 2023-11-09 21:51:24 +01:00
Lost Paul
358c498f99 Fix #64 2023-11-09 21:45:12 +01:00
Lost Paul
fa380c0544 Fix #58 2023-11-06 16:19:08 +01:00
Lost Paul
270a19e462 Update manifest.json 2023-11-04 22:07:47 +01:00
Lost Paul
628e9392da Fix #35 & other issue with renaming all folder notes 2023-11-04 21:53:30 +01:00
Lost Paul
50e515e4dd Update FolderOverview.ts 2023-11-04 18:35:53 +01:00
Lost Paul
ebbc2e56dc Command to create a folder note for every supported file types 2023-11-04 18:28:52 +01:00
Lost Paul
666fb97133 Fix #57 2023-11-04 17:20:48 +01:00
Lost Paul
40d3a34e2b Fix #56 2023-11-02 17:56:57 +01:00
Lost Paul
6afadde899 Update manifest.json 2023-10-28 17:57:49 +02:00
Lost Paul
64763d0bdf Update manifest.json 2023-10-28 17:57:02 +02:00
Lost Paul
a329191a61 Bold, Cursive option for folder notes 2023-10-28 17:46:06 +02:00
Lost Paul
dabe261541 Fix error message for the default folder overview settings and split up settings code 2023-10-28 17:25:04 +02:00
Lost Paul
be4df08e7e Fix for 2023-10-28 16:41:47 +02:00
Lost Paul
19f8a91569 Fix #53 the second issue 2023-10-21 22:19:22 +02:00
Lost Paul
46ec0b14e2 Fix bug with auto create of folder notes and multiple folder note types 2023-10-21 21:41:09 +02:00
Lost Paul
d41710ac50 Fix excalidraw template bug 2023-10-07 23:28:21 +02:00
Lost Paul
947ddf635d Fix excalidraw bug 2023-10-07 22:36:03 +02:00
Lost Paul
974cedabd2 Update manifest.json 2023-10-07 14:06:01 +02:00
Lost Paul
77cece4427 Support for excalidraw and other extension
Added option to ask the extension when trying to create a new folder note
Unlimited support for file extensions
New canvas folder notes now work even when there is no template path set
2023-10-07 14:05:19 +02:00
Lost Paul
3463adcda5 Update index.md 2023-10-03 19:22:44 +02:00
Lost Paul
88b3125a54 Features list 2023-10-03 19:16:58 +02:00
Lost Paul
c89fa0ea6a Update index.md 2023-10-03 18:33:57 +02:00
Lost Paul
6f042a5f54 Update index.md 2023-10-03 18:23:23 +02:00
Lost Paul
c4617e8ea7 Update index.md 2023-10-03 18:18:06 +02:00
Lost Paul
7183145273 Updated docs, tested if images work 2023-10-03 18:10:04 +02:00
Lost Paul
f8be003704 Update manifest.json 2023-10-03 16:53:14 +02:00
Lost Paul
3f24150e1e Bug fix & 2 new commands
Fixed the template insertion bug of the core template plugin
Two new commands for inserting the folder overview & creating a folder note of a selected text
2023-10-03 16:52:57 +02:00
Lost Paul
ea7b72521e Fix #49 2023-10-03 15:15:34 +02:00
Lost Paul
e32f957a5d Update manifest.json 2023-09-19 21:32:21 +02:00
Lost Paul
01a75d8bbc Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-09-19 21:32:00 +02:00
Lost Paul
8e537c0f0a Setting tabs 2023-09-19 21:31:58 +02:00
Lost Paul
0c5715b4d7
Update deploy-mkdocs.yml 2023-09-09 18:01:09 +02:00
Lost Paul
47200625cc Update index.md 2023-09-09 17:57:58 +02:00
Lost Paul
5d94e27be6
Update deploy-mkdocs.yml 2023-09-09 17:49:03 +02:00
Lost Paul
6680918c50 Delete mkdocs.yml 2023-09-09 17:46:40 +02:00
Lost Paul
9259bb7f42 Update index.md 2023-09-09 17:28:47 +02:00
Lost Paul
c8c88dd1ce Moved mdkdoc.yml 2023-09-09 17:02:33 +02:00
Lost Paul
f174cf416e Create mkdocs.yml 2023-09-09 16:57:55 +02:00
Lost Paul
8bc0090d5d Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-09-09 16:51:01 +02:00
Lost Paul
d233e007c4 Moved mkdocs file 2023-09-09 16:50:50 +02:00
Lost Paul
8df5ed544d
Create deploy-mkdocs.yml 2023-09-09 16:48:00 +02:00
Lost Paul
2c5096f638
Delete .github/workflows/deploy-mkdocs.yml 2023-09-09 16:47:32 +02:00
Lost Paul
32b098771f Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-09-09 16:44:55 +02:00
Lost Paul
7bf6cd7b13 Docs for plugin 2023-09-09 16:43:46 +02:00
Lost Paul
f549de1ce2
Update deploy-mkdocs.yml 2023-09-09 16:42:59 +02:00
Lost Paul
17182dc316
Rename deploy-mkdocs to deploy-mkdocs.yml 2023-09-09 16:30:43 +02:00
Lost Paul
3d570f73ce
Create deploy-mkdocs 2023-09-09 16:30:31 +02:00
Lost Paul
ce21b2ebc0 Fix canvas turning into markdown file 2023-08-15 20:39:06 +02:00
Lost Paul
00046b6d9f Exclude source file from overview 2023-08-15 19:46:15 +02:00
Lost Paul
a11d301525 Update manifest.json 2023-08-15 19:01:15 +02:00
Lost Paul
bc6ab7bddf Fix sort after and missing edit button 2023-08-15 19:01:03 +02:00
Lost Paul
f235200551 Included file types bug & sort after bug 2023-08-14 22:19:38 +02:00
Lost Paul
23a7e39cb2 Fix empty overview without a button 2023-08-12 23:56:42 +02:00
Lost Paul
50ab633e6f Fix empty overview without a button 2023-08-12 23:56:31 +02:00
Lost Paul
c430d9ea46 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-08-12 23:42:13 +02:00
Lost Paul
baebfe9630 Fix bug if setting is missing 2023-08-12 23:41:46 +02:00
Lost Paul
3aeb8f6854 Fix bug if setting is missing 2023-08-12 23:38:02 +02:00
Lost Paul
d57d5e983d Fix vault overview 2023-08-12 23:25:40 +02:00
Lost Paul
0f42669043 Fixed some bugs 2023-08-12 23:10:24 +02:00
Lost Paul
d632b4e975 Fix show title bug 2023-08-12 22:51:03 +02:00
Lost Paul
7f64339ffc
Create release.yaml 2023-08-12 22:16:32 +02:00
Lost Paul
c994f5256a
Delete release.yml 2023-08-12 22:15:02 +02:00
Lost Paul
190cf013ce Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-08-12 21:51:16 +02:00
Lost Paul
5f6b20ca8b Update manifest.json 2023-08-12 21:50:34 +02:00
Lost Paul
f5b545cefd
Rename commands.ts to Commands.ts 2023-08-12 21:48:40 +02:00
Lost Paul
515d9e879c
Rename frontMatterTitle.ts to FrontMatterTitle.ts 2023-08-12 21:45:53 +02:00
Lost Paul
d4500f092b
Rename modalSettings.ts to ModalSettings.ts 2023-08-12 21:45:35 +02:00
Lost Paul
2fbe4384aa
Rename patternSettings.ts to PatternSettings.ts 2023-08-12 21:45:13 +02:00
Lost Paul
3691f26372
Rename folderName.ts to FolderName.ts 2023-08-12 21:45:03 +02:00
Lost Paul
de610bbbb0
Rename existingNote.ts to ExistingNote.ts 2023-08-12 21:44:51 +02:00
Lost Paul
1105f198d6
Rename excludeFolderSettings.ts to ExcludeFolderSettings.ts 2023-08-12 21:44:38 +02:00
Lost Paul
11cd03be69
Rename deleteConfirmation.ts to DeleteConfirmation.ts 2023-08-12 21:44:27 +02:00
Lost Paul
0b0544dbdb
Rename confirmCreation.ts to ConfirmCreation.ts 2023-08-12 21:44:17 +02:00
Lost Paul
14e10d8a42
Rename suggest.ts to Suggest.ts 2023-08-12 21:43:58 +02:00
Lost Paul
7dd715894c
Rename templateSuggester.ts to TemplateSuggester.ts 2023-08-12 21:43:45 +02:00
Lost Paul
8a5ea01d0d
Rename settings.ts to Settings.ts 2023-08-12 21:43:25 +02:00
Lost Paul
19d5339b02 Update package-lock.json 2023-08-12 21:42:20 +02:00
Lost Paul
f233e4e5dd Update manifest.json 2023-08-12 21:38:33 +02:00
Lost Paul
afda85027e Remove console.log 2023-08-12 21:34:09 +02:00
Lost Paul
4dbcc9fc7e Show folder notes in folder overview option and new commands 2023-08-12 21:32:29 +02:00
Lost Paul
e040d554ec Rename files and fix one overview bug 2023-08-12 18:01:16 +02:00
Lost Paul
0b33958f3c Fix store folder condition show as activated bug 2023-08-10 22:15:37 +02:00
Lost Paul
008c4715b4 Remove console.log 2023-08-10 22:05:55 +02:00
Lost Paul
8a685f9f16 Remove on layout ready code block handling 2023-08-10 22:04:57 +02:00
Lost Paul
2d34383cd0 File explorer clone progress 2023-08-10 21:54:22 +02:00
Lost Paul
5e066e8f17 File explorer folder overview style progress 2023-08-06 22:25:09 +02:00
Lost Paul
97409d1dfe Include excluding of subfolders in folder overview 2023-08-06 19:24:09 +02:00
Lost Paul
f301478535 Fixed * pattern bug for excluding folders from the overview 2023-08-06 17:20:03 +02:00
Lost Paul
5e3424ae3e Exclude folder from overview 2023-08-06 16:52:21 +02:00
Lost Paul
4b3a5a09a2 Update manifest.json 2023-08-05 22:15:38 +02:00
Lost Paul
507e4f77ac Remove console.log 2023-08-05 22:15:26 +02:00
Lost Paul
80193b9b58 Edit overview button & create folder note for every existing folder 2023-08-05 22:15:01 +02:00
Lost Paul
b796389d37 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-08-02 15:54:47 +02:00
Lost Paul
8f91a53fed Front matter title bug fix 2023-08-02 15:54:17 +02:00
Lost Paul
e04f41d25f
Delete main.js 2023-08-01 21:44:42 +02:00
Lost Paul
8c84faecd3
Create main.js 2023-08-01 21:42:33 +02:00
Lost Paul
b40b81bf98 Modified excluded folders list style 2023-07-26 22:16:54 +02:00
Lost Paul
dab7312ec8 Update manifest.json 2023-07-26 22:11:58 +02:00
Lost Paul
1d51e12de6 Fixed excluded folder list style issues 2023-07-26 22:10:19 +02:00
Lost Paul
9e8f1fe4e9 Update manifest.json 2023-07-26 21:37:37 +02:00
Lost Paul
d7423a148b Fixed front matter title plugin bugs 2023-07-26 21:35:48 +02:00
Lost Paul
b9f5c63011 Update manifest.json 2023-07-26 16:24:25 +02:00
Lost Paul
36a74e65a6 Update package-lock.json 2023-07-26 16:16:39 +02:00
Lost Paul
103145c45a Support front matter title plugin 2023-07-26 16:11:37 +02:00
Lost Paul
cd943b3df4 Add all file types dropdown option 2023-07-08 22:41:32 +02:00
Lost Paul
3eefeef7fd Update manifest.json 2023-07-08 22:08:51 +02:00
Lost Paul
20ff71ed11 Option to show empty folders 2023-07-08 22:07:46 +02:00
Lost Paul
6423d35b00 Support more files than .md and .canvas 2023-07-08 21:37:27 +02:00
Lost Paul
f2ac573821 Folder name var for folder overview title 2023-07-08 10:04:36 +02:00
Lost Paul
1369cda622 Update manifest.json 2023-07-07 22:35:24 +02:00
Lost Paul
4053b5cf0a Fix sort by created/modified 2023-07-06 15:49:14 +02:00
Lost Paul
4cddc2a04f Folder overview on root level fix 2023-07-06 13:52:07 +02:00
Lost Paul
5e582e66b6 Insert folder overview command 2023-07-06 13:50:43 +02:00
Lost Paul
d414b8bb41 Style fix for certain plugins 2023-07-06 13:50:28 +02:00
Lost Paul
b2976d922f Update manifest.json 2023-07-05 22:31:36 +02:00
Lost Paul
8e7ac4a589 Sort by option 2023-07-05 22:31:16 +02:00
Lost Paul
46c529610c Fix empty folder overview settings modal 2023-07-05 22:31:05 +02:00
Lost Paul
45c2a788e6 Update manifest.json 2023-07-05 18:57:22 +02:00
Lost Paul
747679eb52 Fix empty file through create folder note command 2023-07-05 18:49:06 +02:00
Lost Paul
b3d8f6e01e Fixed renaming & added sync move setting 2023-07-05 18:29:55 +02:00
Lost Paul
61db5be4b1 Update manifest.json 2023-07-04 22:12:57 +02:00
Lost Paul
7207ce6e66 Remove grid style option temporary 2023-07-04 22:03:39 +02:00
Lost Paul
e97d80a30f Option to disable submenus 2023-07-04 22:02:03 +02:00
Lost Paul
d70de78357
Merge pull request #22 from Lisandra-dev/main
feat: turn folderNote filemenu commands into submenu
2023-07-01 17:42:39 +02:00
Mara-Li
2791dd736d
Merge branch 'main' into main 2023-07-01 17:09:07 +02:00
Lost Paul
c8022055e6 Platform check & only use submenu when options are available 2023-07-01 15:10:57 +02:00
Lost Paul
203c067d8d Folder overview list 2023-06-28 23:01:13 +02:00
Mara
64571285bc feat: turn folderNote filemenu commands into submenu 2023-06-26 19:05:27 +02:00
Lost Paul
794cc1499b Folder overview list option 2023-06-26 13:14:31 +02:00
Lost Paul
fd245c128b Update manifest.json 2023-06-16 22:05:14 +02:00
Lost Paul
c59f59b793 Check if note is already a folder note 2023-06-16 22:05:07 +02:00
Lost Paul
d27712b4b4 Update manifest.json 2023-06-16 22:00:44 +02:00
Lost Paul
446f56904c Check if folder exists 2023-06-16 22:00:30 +02:00
Lost Paul
7edd9fbf6d Update manifest.json 2023-06-16 21:52:23 +02:00
Lost Paul
888122c9d8 Create folder with note inside a folder with a command 2023-06-16 21:52:08 +02:00
Lost Paul
7d803748fe Modal for existing folder note when using turn into folder note 2023-06-16 21:51:29 +02:00
Lost Paul
ed0421bcd5 File name cannot end with a dot error 2023-06-11 14:54:58 +02:00
Lost Paul
581b273407 Update manifest.json 2023-06-11 14:40:50 +02:00
Lost Paul
3e2a60a191 Support custom folder note names and fixed a bug 2023-06-11 14:40:22 +02:00
Lost Paul
d5c80d3a20 Apply css styles on create if name matches 2023-06-11 14:39:33 +02:00
Lost Paul
fe627f9944 Apply/remove css styles when turning note into folder note 2023-06-11 14:39:09 +02:00
Lost Paul
8f55b618f3 Random number for folder note if file exists 2023-06-10 12:05:48 +02:00
Lost Paul
493ce0de27 Update manifest.json 2023-06-10 11:43:05 +02:00
Lost Paul
7fc4ff1fad Fixed css class bug 2023-06-09 17:37:18 +02:00
Lost Paul
d2c1baf5a0 Update manifest.json 2023-06-09 17:26:22 +02:00
Lost Paul
0ef6ece62c Fixed folder note delete button 2023-06-09 17:25:57 +02:00
Lost Paul
6291cceb9d Update manifest.json 2023-06-09 17:12:06 +02:00
Lost Paul
069be643db Some small settings tab improvements 2023-06-09 17:10:16 +02:00
Lost Paul
990a3d5305 Turn existing files into folder notes, create folder notes from text, open folder note button 2023-06-09 13:34:28 +02:00
Lost Paul
6b13e52d72 Update manifest.json 2023-06-08 15:41:00 +02:00
Lost Paul
9f181ccafb Fixed moving of excluded folders 2023-06-08 15:36:49 +02:00
Lost Paul
72dc9dacab Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-06-06 22:11:25 +02:00
Lost Paul
da68103ac1 Exclude folder wildcard and regex support 2023-06-06 22:11:22 +02:00
Lost Paul
c2367ccbec
Update README.md 2023-06-06 16:37:30 +02:00
Lost Paul
24ee7f72a1 Update manifest.json 2023-06-04 16:22:57 +02:00
Lost Paul
fe2421f836 Switch storage location button and fixed some bugs
The observer for the path now also stops if you turn off the setting to open folder notes through the path.
You can also now only use one of the two storage location options at the same time.
The way I did it before caused some bugs and performance issues.
2023-06-04 15:31:08 +02:00
Lost Paul
fd091606f2
Update README.md 2023-06-02 11:42:44 +02:00
Lost Paul
0ba38cc092 Reload click handlers 2023-05-30 20:42:59 +02:00
Lost Paul
e1ebd6ac91 Fix folder highlighting and updating custom names 2023-05-30 20:30:25 +02:00
Lost Paul
229f3e44f2 Remove css class after deleting folder note 2023-05-30 12:36:41 +02:00
Lost Paul
39a9f6dffe Store folder note in parent folder option 2023-05-29 22:17:30 +02:00
Lost Paul
a05e7b03ae Fix outline 2023-05-22 10:14:37 +02:00
Lost Paul
91606f76c7 Funding url 2023-05-21 16:17:11 +02:00
Lost Paul
00cf009439 Updated folder note type description 2023-05-21 15:40:02 +02:00
Lost Paul
c0315208fd new custom folder note variable instead of old 2023-05-21 15:00:25 +02:00
Lost Paul
a2f3dfb373 Split functions into different files 2023-05-21 14:16:22 +02:00
Lost Paul
41b0c5a2fe
Update README.md 2023-05-19 21:39:52 +02:00
Lost Paul
9d23948900 Merge branch 'main' of https://github.com/LostPaul/obsidian-folder-notes 2023-05-19 16:54:01 +02:00
Lost Paul
9a2057f15e Small color style fix 2023-05-19 16:53:58 +02:00
Lost Paul
651d74ac9c
Create FUNDING.yml 2023-05-19 13:56:08 +02:00
116 changed files with 19665 additions and 3121 deletions

View file

@ -1,2 +0,0 @@
npm node_modules
build

140
.eslintrc
View file

@ -1,140 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": {
"node": true
},
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "none"
}
],
"@typescript-eslint/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
}
],
"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"
]
}
}

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
ko_fi: paul305844

36
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View file

@ -0,0 +1,36 @@
---
name: Bug report
about: Report issues with plugin
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**Steps to Reproduce**
(It would be great if you could do it in the sandbox vault (https://help.obsidian.md/sandbox) or at least disable all other plugins except folder notes)
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Obsidian version**
- Installer version: [e.g. v1.8.7]
- Current version: [e.g. v1.8.10]
**Plugin information**
- Version: [e.g. v1.7.30]
- [ ] Beta version
**Additional context**
Add any other context about the problem here.

7
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View file

@ -0,0 +1,7 @@
contact_links:
- name: Request a new feature
url: https://github.com/LostPaul/obsidian-folder-notes/discussions/
about: Instead of creating a new issue for feature requests, use the discussions feature instead
- name: Anything regarding the folder overview feature
url: https://github.com/LostPaul/obsidian-folder-overview
about: The folder overview feature is a separate plugin, but it is bundled with this plugin. If you have any issues or feature requests regarding the folder overview feature, please create an issue in the folder overview repository.

10
.github/ISSUE_TEMPLATE/docs-issue.md vendored Normal file
View file

@ -0,0 +1,10 @@
---
name: Docs issue
about: Report issues related to the docs of the plugin
title: ''
labels: ''
assignees: ''
---

81
.github/workflows/beta-release.yaml vendored Normal file
View file

@ -0,0 +1,81 @@
# https://github.com/SilentVoid13/Templater/blob/master/.github/workflows/release.yml
name: Plugin-beta release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+-[0-9]+-beta"
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout current branch
uses: actions/checkout@v3
with:
# This checks out the branch that triggered the workflow
submodules: recursive
fetch-depth: 0
ref: ${{ github.ref }}
- name: Initialize and update submodules
run: |
git submodule init
git submodule update --remote
- name: Fetch all branches
run: git fetch --all
- name: Extract version from tag
id: extract_version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
- name: Strip -beta from version
run: |
VERSION=${{ env.VERSION }}
RELEASE_VERSION=${VERSION%-beta}
echo "RELEASE_VERSION=$RELEASE_VERSION" >> $GITHUB_ENV
# Update manifest-beta.json in main branch
- name: Checkout main branch to update manifest-beta.json
run: |
git checkout main
git pull origin main
- name: Update manifest-beta.json in main branch
run: |
VERSION=${{ env.VERSION }}
jq --arg version "$VERSION" '.version = $version' manifest-beta.json > manifest-beta-temp.json
mv manifest-beta-temp.json manifest-beta.json
- name: Commit and push manifest-beta.json changes in main
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add manifest-beta.json
git commit -m "Update manifest-beta.json to version $VERSION in main branch"
git push origin main
# Switch back to the original branch that triggered the workflow
- name: Switch back to branch that triggered the workflow
run: |
git checkout ${{ github.ref_name }}
git pull origin ${{ github.ref_name }}
- name: npm build
run: |
npm install
npm run fn-build --if-present
- name: Set Release Name
run: echo "RELEASE_NAME=${{ env.RELEASE_VERSION }}" >> $GITHUB_ENV
- name: Create Plugin beta release
uses: ncipollo/release-action@v1.12.0
with:
name: Beta release ${{ env.RELEASE_NAME }}
prerelease: true
artifacts: "main.js,manifest.json,styles.css"
token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ env.VERSION }}

66
.github/workflows/build-and-push.yml vendored Normal file
View file

@ -0,0 +1,66 @@
name: Build and Push to Folder Overview
on:
workflow_dispatch:
inputs:
tag:
description: "Tag to build"
required: true
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout folder-notes
uses: actions/checkout@v3
with:
submodules: false
fetch-depth: 0
- name: Update submodules manually
run: |
git submodule sync
git submodule update --init --recursive --remote
- name: Install dependencies
run: npm install
- name: Build folder-overview
run: npm run fv-build
- name: Verify build output
run: |
ls -la src/obsidian-folder-overview/
- name: Checkout folder-overview
uses: actions/checkout@v3
with:
repository: LostPaul/obsidian-folder-overview
token: ${{ secrets.PAT }}
ref: main
path: folder-overview
- name: Copy built files to folder-overview
run: |
cp src/obsidian-folder-overview/main.js \
src/obsidian-folder-overview/styles.css folder-overview/
- name: Commit and push built files
run: |
cd folder-overview
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -f main.js styles.css
if git diff --cached --quiet; then
echo "No changes to commit."
else
git commit -m "Add built files for release"
git push origin main
fi
- name: Trigger release on folder-overview
run: |
curl -X POST -H "Authorization: token ${{ secrets.PAT }}" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/LostPaul/obsidian-folder-overview/actions/workflows/release.yml/dispatches \
-d '{"ref": "main", "inputs": {"tag": "${{ github.event.inputs.tag }}"}}'

29
.github/workflows/deploy-mkdocs.yml vendored Normal file
View file

@ -0,0 +1,29 @@
name: ci
on:
push:
branches:
- master
- main
permissions:
contents: write
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: '3.x'
- uses: actions/cache@v4
with:
path: .cache
key: mkdocs-${{ runner.os }}-${{ github.sha }}
restore-keys: |
mkdocs-${{ runner.os }}-
- run: pip install mkdocs-material mkdocs-video pillow cairosvg
- working-directory: ./docs
run: mkdocs gh-deploy --force

30
.github/workflows/label-beta.yml vendored Normal file
View file

@ -0,0 +1,30 @@
name: Label Beta Issues
on:
issues:
types: [opened, edited]
jobs:
label-beta:
runs-on: ubuntu-latest
steps:
- name: Check for Beta Version checkbox
uses: actions/github-script@v7
with:
script: |
const betaChecked = context.payload.issue.body.includes("- [x] Beta version");
if (betaChecked) {
const issue_number = context.payload.issue.number;
const labels = context.payload.issue.labels.map(label => label.name);
if (!labels.includes('beta')) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issue_number,
labels: ['beta']
});
}
}

80
.github/workflows/release.yaml vendored Normal file
View file

@ -0,0 +1,80 @@
# 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
# Checkout the main branch to update manifest.json
- name: Checkout main branch to update manifest
run: |
git checkout main
git pull origin main
- name: Update manifest.json in main branch
run: |
VERSION=${GITHUB_REF#refs/tags/}
jq --arg version "$VERSION" '.version = $version' manifest.json > manifest-temp.json
mv manifest-temp.json manifest.json
- name: Commit and push manifest.json changes in main
run: |
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 push origin main
- name: Ensure HEAD is at updated main commit
run: |
git fetch origin
git checkout main
git reset --hard origin/main
- 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:
artifacts: "main.js,manifest.json,styles.css"
token: ${{ secrets.GITHUB_TOKEN }}
tag: ${{ github.ref_name }}

View file

@ -1,21 +0,0 @@
# https://github.com/SilentVoid13/Templater/blob/master/.github/workflows/release.yml
name: Plugin release
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: npm build
run: |
npm install
npm run build --if-present
- name: Plugin release
uses: ncipollo/release-action@v1.12.0
with:
artifacts: "main.js,manifest.json,styles.css"
token: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View file

@ -17,6 +17,9 @@ main.js
# obsidian
data.json
.obsidian
# Exclude macOS Finder (System Explorer) View States
.DS_Store
venv

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "src/obsidian-folder-overview"]
path = src/obsidian-folder-overview
url = https://github.com/LostPaul/obsidian-folder-overview

View file

@ -1,51 +1,19 @@
# Obsidian folder notes
Create notes within folders that can be accessed without collapsing the folder, similar to the functionality offered in Notion.
## Table of contents
Folder notes is a plugin for the note taking app [Obsidian](https://obsidian.md/) that lets you attach notes to folders so that you can click on the name of a folder to open the note like in the app [Notion](https://www.notion.so/).
This plugin has some unique features that separate it from similar "Folder note" plugins like opening folder notes through the path, creating folder notes for every existing folder, templater/template support and more.
- [Features](#features)
- [Credits](#credits)
- [Showcase](#showcase)
- [How to install the plugin](#how-to-install-the-plugin)
Support the development of the plugin
## Features
<a href='https://ko-fi.com/D1D1GHGSI' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
## Documentation & download link
The plugin can be downloaded by clicking on https://obsidian.md/plugins?id=folder-notes and then on install. If you need help with the plugin or want to know what the features are that the plugin has then you can find the documentation at https://lostpaul.github.io/obsidian-folder-notes/.
- Notes in folders
- Click on folder name to open folder note
- Folders don't collapse (can be changed in the settings)
- Templater and templates support
- The name of folders that contain folder notes are underlined (can get toggled in the settings)
- Automatically create folder notes in folder on folder creation (can be turned on in the settings)
- Exclude folders
- Hide folder notes from file explorer
#### Soon
- Create folder note for every existing folder
## How to install the beta version
## Showcase
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.
#### Open folder notes
![wzk25GXUpc](https://user-images.githubusercontent.com/70213368/227169041-9bc7552e-c5f5-4001-94d9-b4c80ea88bd1.gif)
Join the Discord server to chat about the beta and to also get the beta user role.
#### Automatically create folder notes
![Obsidian_NLUDzVBWRe](https://user-images.githubusercontent.com/70213368/227169603-3a105622-e6cb-4889-a709-acf52ad06d6e.gif)
#### Exlude folders
![Obsidian_NLUDzVBWRe](https://user-images.githubusercontent.com/70213368/227179537-605c3337-6de6-4a6b-a0f6-296c983a7c7b.gif)
## How to install the plugin
1. Download main.js, manifest.json, styles.css from the latest release (see [releases](https://github.com/LostPaul/obsidian-folder-notes/releases/))
2. Create a new folder in `Vault-name/.obsidian/plugins` and put the downloaded files in there
3. Reload your plugins and enable the plugin
<img width="585" alt="Obsidian_QaY1e3nCzL" src="https://user-images.githubusercontent.com/70213368/227159411-5a2b0a99-f612-42f2-bc57-4f0a7f97d65e.png">
<img width="582" alt="Obsidian_w4hTprcOjW" src="https://user-images.githubusercontent.com/70213368/227159064-e116ea0c-c249-4750-9b0f-b010e4a6afc5.png">
## Credits
Thank you to everyone who has created a plugin that inspired me and I took code from.
- Template suggester, created by [SilentVoid13](https://github.com/SilentVoid13) and [Liamcain](https://github.com/liamcain) (their plugins: [Templater](https://github.com/SilentVoid13/Templater/), [Periodic notes](https://github.com/liamcain/obsidian-periodic-notes))
- Apply template to note, first seen in https://github.com/mgmeyers/obsidian-kanban/ from [Mgmeyers](https://github.com/mgmeyers)
- Folder underline, inspired from https://github.com/aidenlx/alx-folder-note
- Stop folders from collapsing, the basic idea from https://github.com/alangrainger/obsidian-folder-notes and some code lines
- The basic idea is from https://github.com/xpgo/obsidian-folder-note-plugin
## 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

@ -0,0 +1,18 @@
# Command palette commands
## Use this file as the folder note for its parent folder
The note that's currently in focus will be the new folder note for the parent folder of the file.
## Make a folder with this file as its folder note
This command creates a new folder and uses the current file that is in focus as the folder note. The new folder will be in the same folder as the folder the file was in.
## Create {type} folder note for this folder
Create a new folder note for the folder of the current file that is in focus. The plugin will not use the file in focus but instead a completely new file.
## Delete this folder's linked note
Delete the folder note (only the file itself) of the parent folder of the currently focused file.
## Open this folder's linked note
Open the folder note of the parent folder of the currently focused file.
## Insert folder overview
Insert a folder overview in a note. [Click here to know more about a folder overview](./Folder%20overview.md).
## Edit folder overview
Edit the default settings or an already created folder overview in the right view. [Click here to know more about a folder overview](./Folder%20overview.md).
## Create folder note from selection
Create a new file, a new folder and the file as the folder note. The text that has been selected will be the name of the folder note and the folder.

View file

@ -0,0 +1,38 @@
# Exclude folders
The exclude folder feature can be found in the plugin settings under the tab "Exclude folders". Depending on what you exclude a folder from you for example can't open a folder note, the file/folder names don't sync, it doesn't show up in the folder overview and more.
## Exclude only one folder
To only exclude the folder itself and not the sub folders also you have to click on the edit icon and disable include subfolders in the settings.
## How to exclude a folder?
### Directly in the settings
Click on the plus icon, then click on the text input, search a folder name and then select the folder you want to exclude from the list.
![Video type:video](../assets/xdS3VV7wjIRFNHADE0lx.mp4)
### Through the file explorer
Click on a folder and then use the context menu and click on "exclude folder from folder notes". The plugin will then apply the [default exclude folder settings](#Default settings) and add it to the excluded folders list.
![Screenshot](../assets/Ny4aCJtZStlFmlsN9zp2.png)
## Exclude a folder and their subfolders
To exclude a folder and its subfolders you have to do the same steps as in how to exclude only one folder but enable include subfolders in the settings of an excluded folder.
## Exclude multiple folders (with a pattern)
The pattern only looks at the names of the folders and nothing else. You have two options to exclude folders with a pattern and the first option is to use * before and after the string. The second option is to use a [regex](https://en.wikipedia.org/wiki/Regular_expression).
To use a regex you have to add "{regex}" at the beginning of the string and everything after it is going to be a regex that matches the folder names.
For example this regex matches every folder in the file explorer.
![Screenshot](../assets/xUo6aJuIiABXaPl7eosX.png)
When we use * to match a folder it only works when it's at the start or at the end of a string or both.
For example when we use the folder name "Test" and the following patterns we get this:
\*est => matches the name
Test* => matches the name
\*es\* => matches the name
\*Test\* => matches the name
\*Test 2\* => doesn't match the name
## How to change the settings
Open the exclude folders settings tab and click on a edit button if you've already excluded folders or added patterns to exclude folders.
![Screenshot](../assets/mbZW1SXv8o3fKU4g8j3V.png)
## Default settings
To edit the default settings for new excluded folders/patterns you just have to click on one of the "Manage" buttons in the settings that are next to "Exclude folder/pattern default settings".
## Whitelisted folders
The whitelisted folders overwrite the already excluded folders with options that are the opposite from the excluded folders. For example when you add a pattern to exclude all folders, select to disable opening of folders and then add one specific folder to the whitelist and select "open folder ..." you can only open the specific folder and not all the other folder notes.

View file

@ -0,0 +1,10 @@
# File context menu commands
## Create folder note
This command creates a new folder and uses the file you clicked on as the folder note. The new folder will be in the same folder as the folder the file was in.
![Example type:video](../assets/videos/Obsidian_Nc9OYAHHT1.mp4)
## Turn into folder note for "{folderName}"
This command will attach the file to the folder it's in. For example file "test" that is in the folder "test2" will be the new folder note for the folder "test2".
![Example type:video](../assets/videos/Obsidian_bUlTDSG9a9.mp4)

View file

@ -0,0 +1,14 @@
# Folder context menu commands
## Exclude folder from folder notes
This will add the folder to the excluded folder list. [Click here to find more about excluded folders out.](Exclude%20folders.md)
## Create {type} folder note
This command will create a folder note of the file type you selected and depending on your settings the folder note will be in the folder itself or in the parent folder of the folder.
## Delete folder note
The folder note from the folder you clicked on gets deleted. (Only the file and note the folder itself)
## Open folder note
Opens folder notes in the currently active tab or if you have the setting "open folder note in a new tab by default" enabled the note opens in a new tab.
## Copy Obsidian URL
Copies the Obsidian URL of the file which is linked to the folder and it can be used in other programs to directly open the file.

View file

@ -0,0 +1,32 @@
# File explorer settings
### Hide folder note
Hide files in the file explorer which are linked to folders
### Disable click-to-open folder note on mobile
Disable the option to open folder notes by tapping on the folder name of a folder with a folder note in the file explorer. With that option enabled it's only possible to open folder notes with the command palette oder the context menu.
### Open folder notes by only clicking directly on the folder name
With this option enabled you can only open a folder note by clicking when doing so exactly on the folder name and not on the whitespace around it. When you also have "Disable folder collapsing" enabled folders also won't collapse when you click on a folder name.
### Disable folder collapsing
Stop folders with folder notes from collapsing when click directly on the folder name or on the whitespace around it. Except when you click on the collapse icon you can still collapse folders.
### Use submenus
When this option is enabled and you use the context menu for folder/files at first you can only see "Folder Note Commands" in the menu and you have to hover over it to see all options.
## Style settings
### Highlight folder in the file explorer
Normally only the file gets highlighted in the explorer but when you enable this option the folder of a folder note gets also highlighted.
### Hide collapse icon
Hide the collapse icon next to a folder name for a folder with a folder note or also for every folder with no files.
### Underline the name of folder note
Underline the names of folders with folder notes in the file explorer.
### Bold the name of folder notes
Bold folders in the file explorer that have a folder note.
### Cursive the name of folder notes
Make folder with folder notes in the file explorer cursive.

View file

@ -0,0 +1,29 @@
# General settings
### Folder note name template
The entered text is going to be the name of all folder notes and when file name matches this text it becomes a folder note. {{folder_name}} is a placeholder for the name of the folder linked to the file. If you change the text every existing folder note which matched the old text won't be a folder note anymore and you have to use the button "Rename existing folder notes" to rename all folder notes.
### Supported file types
Only the file types you select will be selected will be recognized as folder notes. This means you that a file that matches the name of a folder and normally would be a folder note but has an extension that isn't included in the supported file types list won't be a folder note and you can't open it by click on the folder name.
### Template path
When you don't have templater installed & enabled you can choose any file in your vault as a template. But if you have templater enabled you first have to choose the folder where your templates are located, in the settings of the templater plugin. You can also use the core templates plugin for applying templates to new folder notes.
### Storage location
Choose if the folder notes should be stored in the folder they're linked to or in the parent folder of the folder they're linked to. If you switch the storage method all existing folder won't be recognized as folder notes anymore until you click on the switch button and the location of old folder note files gets changed.
## Folder note behavior
### Confirm folder note deletion
When enabled a modal pops up when you try to delete folder note using the context menu.
### Open folder note in a new tab by default
When enabled every folder note opens in a new tab unless it is already open in the currently focused file in the editor and if the subsetting "focus existing tab instead of creating a new one" is also enabled every folder note only has one tab open. If you have different tab open than of a folder note you want to open and the folder note already has a tab open it'll switch to it.
## Integration & Compatibility
### Enable [front matter title plugin](https://github.com/snezhig/obsidian-front-matter-title) integration
Applies the changes made to the files names in the frontmatter also to the folder names in the file explorer & the path for folders with folder notes. This is also requires to enable the auto update setting in the file explorer & path settings of the folder notes plugin.
## Session & Persistence
### Persist tab after restart
Open the same tab you had open in the plugin settings before restarting Obsidian.
### Persist tab during session only
Open the same tab after closing the plugin settings and opening it again but if "Persist tab after restart" is disabled it won't open the same tab again after restarting Obsidian.

View file

@ -0,0 +1,22 @@
# Path settings
The "path" is the file path above a note in the editor.
![Screenshot](../../assets/screenshots/Obsidian_LaoyRX8jr2.png)
### Open folder note through path
Open a folder note by click on a folder name in the path when it has a folder note linked to it.
### Open sidebar when opening a folder note through path
Disable/enable if the left sidebar should also open when you open a folder note through the path.
### Auto update folder name in the path ([front matter title plugin only](https://github.com/snezhig/obsidian-front-matter-title))
Automatically update the folder name in the path when the front matter title plugin is enabled and the title for a folder note is changed in the front matter. This will not change the file name, only the displayed name in the path.
## Style settings
### Underline folder in the path
Underline every folder with a folder note in the path.
### Bold folders in the path
Bold every folder with a folder note in the path.
### Cursive folders in the path
Make every folder with a folder note in the path cursive.

View file

@ -0,0 +1,80 @@
# 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.
Additionally, you can **customize** the overview by selecting **specific file types**, adjusting **file depth**, and changing the **file order** (e.g., alphabetical: A-Z).
## Create overview
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"
## 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.
![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?
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?
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.

View file

@ -0,0 +1,46 @@
# Getting started
## How to install the plugin
The plugin can be downloaded by clicking on [https://obsidian.md/plugins?id=folder-notes](https://obsidian.md/plugins?id=folder-notes) and then on install.
## Basic features
### Create folder note
There are three options: the context menu, ctrl/strg + click and commands
Right click or on mobile hold longer on a folder name to get to the context menu and then click on create folder note.
![Context menu](./assets/TOtiFIYzUI8rwxjCLhyN.png)
On PC either use ctrl or strg (depending on your settings) and then click on a folder name to create a folder note.
Open the [command palette](https://help.obsidian.md/Plugins/Command+palette) and then type "folder notes" and select the command you need.
![Command palette](./assets/screenshots/Obsidian_3VEQ3kOT4S.png)
### Open folder note
If you haven't changed any settings the simplest option is just to click on the name of a folder to open its folder note.
Use the context menu and instead of create click of open folder note. It only works with folders that also have a folder note and [if you don't know how to open the context menu click here.](#Create folder note)
![Context menu](./assets/XjlSBfx6ouPrRpb2JmcL.png)
The second option is to open one file of the files from the folder from which you want to open the folder note and then use the command "Open folder note of current folder of active note" from the [command palette](https://help.obsidian.md/Plugins/Command+palette)
The last option and the option that is only available to desktop users is to use alt or ctrl and click on the folder name. When you don't hold ctrl/alt and click on a folder name the folder gets collapsed as normal. To use this option you first have to enable it in the plugin settings under the tab general.
![Settings page](./assets/screenshots/Obsidian_7B8zdp5xDh.png)
### Create folder overview
Find more out about the folder overview feature on the [folder overview page](./Folder%20overview.md)
To create a folder overview in a note either use the command "Insert folder overview" or on pc right click and select "create folder overview"
![Context menu](./assets/VyBTGhA5eJAVVFusZXIz.png){: style="max-width: 40%;max-height: 40%"}
The folder overview with the default settings looks like this with one note
![Folder overview](./assets/u6ccTTzVbwzBivySFacZ.png)
To find out how to edit the folder overview have a look at [folder overview page](./Folder%20overview.md)

View file

@ -0,0 +1,7 @@
# Troubleshooting
## Folder with folder note still collapses
Issue: You've "Disable folder collapsing" enabled, click on a folder name that has folder note linked to it and it still collapses.
Possible cause & solution: You've "auto-reveal current file" enabled and need to disable it in order to make this feature work.
![Auto-reveal current file](../assets/screenshots/Obsidian_K3ph5zw0mq.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

34
docs/docs/index.md Normal file
View file

@ -0,0 +1,34 @@
# Overview
Folder Notes is a plugin for the note-taking app [Obsidian](https://obsidian.md/) that allows you to attach notes directly to folders. This lets you open a note by clicking on a folder name—similar to the functionality found in [Notion](https://www.notion.so/).
Unlike other “folder note” plugins, Folder Notes offers several unique features, including support for custom file types, templating, and advanced folder handling options.
## Get started
Read the [Getting started page](Getting%20started.md) for further details and the pages under the "Features" category explain every feature in detail.
## Features
- Open folder notes by clicking folder names in the file explorer or path
- Optional modifier key (Alt or Ctrl + click) to open folder notes
- Full support for any file type as folder notes (e.g., Excalidraw, Canvas, etc.)
- Automatically sync folder note names with their folder names
- Auto-create a folder note when a new folder is created
- Convert all existing folders into folder notes
- Use a folder-overview code block with configurable default settings
- Option to hide folder notes from the file explorer
- Style folder names (underline, bold, italic) in the file explorer or path
- Prevent automatic collapsing of folders that have folder notes
- Choose to only open the folder note when clicking the folder name; other clicks collapse the folder
- Front matter title plugin support
- Exclude specific folders using regex, path, or wildcard patterns (`*`)
## Credits
Thank you to everyone who has created a plugin that inspired me and I took code from.
- Template suggester, created by [SilentVoid13](https://github.com/SilentVoid13) and [Liamcain](https://github.com/liamcain) (their plugins: [Templater](https://github.com/SilentVoid13/Templater/), [Periodic notes](https://github.com/liamcain/obsidian-periodic-notes))
- Apply template to note, first seen in [https://github.com/mgmeyers/obsidian-kanban/](https://github.com/mgmeyers/obsidian-kanban/) from [Mgmeyers](https://github.com/mgmeyers)
- Folder underline, inspired from [https://github.com/aidenlx/alx-folder-note](https://github.com/aidenlx/alx-folder-note)
- Stop folders from collapsing, the basic idea from [https://github.com/alangrainger/obsidian-folder-notes](https://github.com/alangrainger/obsidian-folder-notes)
- The basic idea is from [https://github.com/xpgo/obsidian-folder-note-plugin](https://github.com/xpgo/obsidian-folder-note-plugin)
## Support the development of the plugin
<a href='https://ko-fi.com/D1D1GHGSI' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi2.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

25
docs/mkdocs.yml Normal file
View file

@ -0,0 +1,25 @@
site_name: Obsidian folder notes
theme:
name: material
logo: assets/2023_Obsidian_logo.svg.png
features:
- navigation.expand
palette:
- media: "(prefers-color-scheme: light)"
scheme: default
toggle:
icon: material/brightness-7
name: Switch to dark mode
- media: "(prefers-color-scheme: dark)"
scheme: slate
toggle:
icon: material/brightness-4
name: Switch to light mode
markdown_extensions:
- attr_list
- md_in_html
- def_list
plugins:
- mkdocs-video

View file

@ -3,7 +3,7 @@ import process from "process";
import builtins from 'builtin-modules'
const banner =
`/*
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
@ -39,4 +39,5 @@ esbuild.build({
sourcemap: prod ? false : 'inline',
treeShaking: true,
outfile: 'main.js',
conditions: ['types'],
}).catch(() => process.exit(1));

177
eslint.config.mjs Normal file
View file

@ -0,0 +1,177 @@
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,
}
],
}
}
];

12
manifest-beta.json Normal file
View file

@ -0,0 +1,12 @@
{
"id": "folder-notes",
"name": "Folder notes beta",
"version": "1.8.20-1-beta",
"minAppVersion": "0.15.0",
"description": "Create notes within folders that can be accessed without collapsing the folder, similar to the functionality offered in Notion.",
"author": "Lost Paul",
"authorUrl": "https://github.com/LostPaul",
"fundingUrl": "https://ko-fi.com/paul305844",
"helpUrl": "https://lostpaul.github.io/obsidian-folder-notes/",
"isDesktopOnly": false
}

View file

@ -1,10 +1,12 @@
{
"id": "folder-notes",
"name": "Folder notes",
"version": "1.2.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",
"isDesktopOnly": false
"id": "folder-notes",
"name": "Folder notes",
"version": "1.8.26",
"minAppVersion": "1.4.10",
"description": "Create notes within folders that can be accessed without collapsing the folder, similar to the functionality offered in Notion.",
"author": "Lost Paul",
"authorUrl": "https://github.com/LostPaul",
"fundingUrl": "https://ko-fi.com/paul305844",
"helpUrl": "https://lostpaul.github.io/obsidian-folder-notes/",
"isDesktopOnly": false
}

12550
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -4,22 +4,34 @@
"description": "Adds Folder Notes to the default file tree.",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
"fn-dev": "node esbuild.config.mjs",
"dev": "npm run fn-dev",
"fn-build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"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": "Alan Grainger",
"author": "Lost Paul",
"license": "GPL-3.0-or-later",
"devDependencies": {
"@eslint/js": "^8.57.1",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"@typescript-eslint/eslint-plugin": "^8.60.0",
"@typescript-eslint/parser": "^8.60.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": "4.7.4"
"typescript": "^6.0.3",
"typescript-eslint": "^8.38.0"
},
"dependencies": {
"@popperjs/core": "^2.11.6"

582
src/Commands.ts Normal file
View file

@ -0,0 +1,582 @@
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 { ExcludedFolder } from './ExcludeFolders/ExcludeFolder';
import { getFolderPathFromString, getFileExplorerActiveFolder } from './functions/utils';
import {
deleteExcludedFolder,
getDetachedFolder,
getExcludedFolder,
} from './ExcludeFolders/functions/folderFunctions';
import {
hideFolderNoteInFileExplorer,
showFolderNoteInFileExplorer,
} from './functions/styleFunctions';
type MarkdownEditorContext = MarkdownView | MarkdownFileInfo;
export class Commands {
plugin: FolderNotesPlugin;
app: App;
constructor(app: App, plugin: FolderNotesPlugin) {
this.plugin = plugin;
this.app = app;
}
registerCommands(): void {
this.editorCommands();
this.fileCommands();
this.regularCommands();
}
regularCommands(): void {
this.plugin.addCommand({
id: 'turn-into-folder-note',
name: 'Use this file as the folder note for its parent folder',
checkCallback: (checking: boolean) => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return false;
const folder = file.parent;
if (!folder || !(folder instanceof TFolder)) return false;
// Only show if file is NOT in the root folder
if (folder.path === '' || folder.path === '/') return false;
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);
},
});
this.plugin.addCommand({
id: 'create-folder-note',
name: 'Make a folder with this file as its folder note',
callback: async () => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return;
let newPath = file.parent?.path + '/' + file.basename;
if (file.parent?.path === '' || file.parent?.path === '/') {
newPath = file.basename;
}
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
void 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);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
void this.plugin.saveSettings();
},
});
this.plugin.addCommand({
id: 'create-folder-note-for-current-folder',
name: 'Create Markdown folder note for this folder',
checkCallback: (checking) => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return false;
const folder = file.parent;
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);
},
});
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
if (fileType === 'md') return;
this.plugin.addCommand({
id: `create-${fileType}-folder-note-for-current-folder`,
name: `Create ${fileType} folder note for this folder`,
checkCallback: (checking) => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return false;
const folder = file.parent;
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);
},
});
});
this.plugin.settings.supportedFileTypes.forEach((fileType) => {
const type = fileType === 'md' ? 'markdown' : fileType;
this.plugin.addCommand({
id: `create-${type}-folder-note-for-active-file-explorer-folder`,
name: `Create ${type} folder note for current active folder in file explorer`,
checkCallback: (checking: boolean) => {
const folder = getFileExplorerActiveFolder(this.plugin);
if (!folder) return false;
// Is there already a folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
if (folderNote instanceof TFile) return false;
if (checking) return true;
// 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);
},
});
});
this.plugin.addCommand({
id: 'delete-folder-note-for-current-folder',
name: 'Delete this folder\'s linked note',
checkCallback: (checking) => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return false;
const folder = file.parent;
if (!(folder instanceof TFolder)) return false;
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
void deleteFolderNote(this.plugin, folderNote, true);
},
});
this.plugin.addCommand({
id: 'delete-folder-note-of-active-file-explorer-folder',
name: 'Delete folder note of current active folder in file explorer',
checkCallback: (checking: boolean) => {
const folder = getFileExplorerActiveFolder(this.plugin);
if (!folder) return false;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
// Everything is fine and not checking, let's delete the folder note.
void deleteFolderNote(this.plugin, folderNote, true);
},
});
this.plugin.addCommand({
id: 'open-folder-note-for-current-folder',
name: 'Open this folder\'s linked note',
checkCallback: (checking) => {
const file = this.app.workspace.getActiveFile();
if (!(file instanceof TFile)) return false;
const folder = file.parent;
if (!(folder instanceof TFolder)) return false;
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
void openFolderNote(this.plugin, folderNote);
},
});
this.plugin.addCommand({
id: 'open-folder-note-of-active-file-explorer-folder',
name: 'Open folder note of current active folder in file explorer',
checkCallback: (checking: boolean) => {
const folder = getFileExplorerActiveFolder(this.plugin);
if (!folder) return false;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(this.plugin, folder.path);
if (!(folderNote instanceof TFile)) return false;
if (checking) return true;
// Everything is fine and not checking, let's open the folder note.
void openFolderNote(this.plugin, folderNote);
},
});
this.plugin.addCommand({
id: 'create-folder-note-from-selected-text',
name: 'Create folder note from selection',
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownEditorContext) => {
const text = editor.getSelection().trim();
const { file } = view;
if (!(file instanceof TFile)) return false;
if (text && text.trim() !== '') {
if (checking) { return true; }
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
for (const char of blacklist) {
if (text.includes(char)) {
// eslint-disable-next-line max-len
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
return false;
}
}
if (text.endsWith('.')) {
new Notice('File name cannot end with a dot');
return;
}
let folder: TAbstractFile | null;
const folderPath = getFolderPathFromString(file.path);
if (folderPath === '') {
folder = this.plugin.app.vault.getAbstractFileByPath(text);
if (folder instanceof TFolder) {
new Notice('Folder note already exists');
return 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);
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,
)
) {
new Notice('File already exists');
return false;
}
}
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
void createFolderNote(this.plugin, folderPath + '/' + text, false);
}
const { folderNoteName } = this.plugin.settings;
const fileName = folderNoteName.replace('{{folder_name}}', text);
if (fileName !== text) {
editor.replaceSelection(`[[${fileName}]]`);
} else {
editor.replaceSelection(`[[${fileName}|${text}]]`);
}
return true;
}
return false;
},
});
}
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,
);
}
}
}
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) return;
let newPath = folder.path + '/' + file.basename;
if (folder.path === '' || folder.path === '/') {
newPath = file.basename;
}
if (this.plugin.app.vault.getAbstractFileByPath(newPath)) {
return new Notice('Folder already exists');
}
const automaticallyCreateFolderNote =
this.plugin.settings.autoCreate;
this.plugin.settings.autoCreate = false;
void this.plugin.saveSettings();
await this.plugin.app.vault.createFolder(newPath);
const newFolder = this.plugin.app.vault
.getAbstractFileByPath(newPath);
if (!(newFolder instanceof TFolder)) return;
await createFolderNote(
this.plugin,
newFolder.path,
true,
'.' + file.extension,
false,
file,
);
this.plugin.settings.autoCreate = automaticallyCreateFolderNote;
void 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 (!folder || !(folder instanceof TFolder)) return;
const folderNote = getFolderNote(this.plugin, folder.path);
void 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);
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);
});
});
}
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);
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) {
folderMenu.addItem((item) => {
item.setTitle('Delete folder note');
item.setIcon('trash');
item.onClick(() => {
void deleteFolderNote(this.plugin, folderNote, true);
});
});
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(() => {
detachFolderNote(this.plugin, folderNote);
});
});
folderMenu.addItem((item) => {
item.setTitle('Copy Obsidian URL');
item.setIcon('link');
item.onClick(() => {
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(() => {
hideFolderNoteInFileExplorer(file.path, this.plugin);
});
});
} else {
folderMenu.addItem((item) => {
item.setTitle('Show folder note in explorer');
item.setIcon('eye');
item.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);
}
}));
}
editorCommands(): void {
// eslint-disable-next-line max-len
this.plugin.registerEvent(this.plugin.app.workspace.on('editor-menu', (menu: Menu, editor: Editor, view: MarkdownEditorContext) => {
const text = editor.getSelection().trim();
if (!text || text.trim() === '') return;
menu.addItem((item) => {
item.setTitle('Create folder note')
.setIcon('edit')
.onClick(() => {
const { file } = view;
if (!(file instanceof TFile)) return;
const blacklist = ['*', '\\', '"', '/', '<', '>', '?', '|', ':'];
for (const char of blacklist) {
if (text.includes(char)) {
// eslint-disable-next-line max-len
new Notice('File name cannot contain any of the following characters: * " \\ / < > : | ?');
return;
}
}
if (text.endsWith('.')) {
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);
if (folderPath === '') {
folder = this.plugin.app.vault.getAbstractFileByPath(text);
if (folder instanceof TFolder) {
return new Notice('Folder note already exists');
}
void this.plugin.app.vault.createFolder(text);
void createFolderNote(this.plugin, text, false);
} else {
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,
)
) {
return new Notice('File already exists');
}
}
void this.plugin.app.vault.createFolder(folderPath + '/' + text);
void createFolderNote(this.plugin, folderPath + '/' + text, false);
}
if (fileName !== text) {
editor.replaceSelection(`[[${fileName}]]`);
} else {
editor.replaceSelection(`[[${fileName}|${text}]]`);
}
});
});
}));
}
}

View file

@ -0,0 +1,34 @@
import type FolderNotesPlugin from '../main';
export class ExcludedFolder {
type: string;
id: string;
path: string;
string: string;
subFolders: boolean;
disableSync: boolean;
disableAutoCreate: boolean;
disableFolderNote: boolean;
enableCollapsing: boolean;
position: number;
excludeFromFolderOverview: boolean;
hideInSettings: boolean;
detached: boolean = false;
detachedFilePath?: string;
showFolderNote: boolean;
constructor(path: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {
this.type = 'folder';
this.id = id || crypto.randomUUID();
this.path = path;
this.subFolders = plugin.settings.excludeFolderDefaultSettings.subFolders;
this.disableSync = plugin.settings.excludeFolderDefaultSettings.disableSync;
this.disableAutoCreate = plugin.settings.excludeFolderDefaultSettings.disableAutoCreate;
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;
this.showFolderNote = plugin.settings.excludeFolderDefaultSettings.showFolderNote;
}
}

View file

@ -0,0 +1,39 @@
import type FolderNotesPlugin from '../main';
export class ExcludePattern {
type: string;
id: string;
string: string;
path: string;
position: number;
subFolders: boolean;
disableSync: boolean;
disableAutoCreate: boolean;
disableFolderNote: boolean;
enableCollapsing: boolean;
excludeFromFolderOverview: boolean;
hideInSettings: boolean;
detached: boolean = false;
detachedFilePath?: string;
showFolderNote: boolean;
constructor(
pattern: string,
position: number,
id: string | undefined,
plugin: FolderNotesPlugin,
) {
this.type = 'pattern';
this.id = id || crypto.randomUUID();
this.string = pattern;
this.position = position;
this.subFolders = plugin.settings.excludePatternDefaultSettings.subFolders;
this.disableSync = plugin.settings.excludePatternDefaultSettings.disableSync;
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;
this.showFolderNote = plugin.settings.excludePatternDefaultSettings.showFolderNote;
}
}

View file

@ -0,0 +1,24 @@
import type 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;
position: number;
hideInSettings: boolean = false;
constructor(path: string, position: number, id: string | undefined, plugin: FolderNotesPlugin) {
this.type = 'folder';
this.id = id || crypto.randomUUID();
this.path = path;
this.subFolders = plugin.settings.excludeFolderDefaultSettings.subFolders;
this.position = position;
this.string = '';
}
}

View file

@ -0,0 +1,29 @@
import type FolderNotesPlugin from '../main';
export class WhitelistedPattern {
type: string;
id: string;
string: string;
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,
) {
this.type = 'pattern';
this.id = id || crypto.randomUUID();
this.subFolders = plugin.settings.excludePatternDefaultSettings.subFolders;
this.position = position;
this.string = pattern;
this.path = '';
}
}

View file

@ -0,0 +1,328 @@
import type FolderNotesPlugin from '../../main';
import { getFolderNameFromPathString, getFolderPathFromString } from '../../functions/utils';
import type { ExcludedFolder } from '../ExcludeFolder';
import { ExcludePattern } from '../ExcludePattern';
import { Platform, Setting } from 'obsidian';
import { FolderSuggest } from '../../suggesters/FolderSuggester';
import type { SettingsTab } from '../../settings/SettingsTab';
import ExcludedFolderSettings from '../modals/ExcludeFolderSettings';
import {
updatePattern,
getExcludedFoldersByPattern,
addExcludePatternListItem,
} from './patternFunctions';
import { getWhitelistedFolder } from './whitelistFolderFunctions';
import type { WhitelistedFolder } from '../WhitelistFolder';
import type { WhitelistedPattern } from '../WhitelistPattern';
function combineExcluded(
plugin: FolderNotesPlugin,
path: string,
includeDetached: boolean,
pathOnly?: boolean,
): Array<ExcludedFolder | ExcludePattern> {
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;
}
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',
'disableSync',
'enableCollapsing',
'excludeFromFolderOverview',
'detached',
'hideInSettings',
'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;
}
}
}
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;
}
function defaultExcludedIfEmpty(
value: Partial<ExcludedFolder> | undefined,
): ExcludedFolder | undefined {
if (value && Object.keys(value).length === 0) {
return {
type: 'folder',
id: '',
path: '',
string: '',
subFolders: false,
disableSync: false,
disableAutoCreate: false,
disableFolderNote: false,
enableCollapsing: false,
position: 0,
excludeFromFolderOverview: false,
hideInSettings: false,
detached: false,
showFolderNote: false,
};
}
return value as ExcludedFolder | undefined;
}
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 {
return plugin.settings.excludeFolders.find((f) => f.path === path && f.detached);
}
export function getExcludedFolderByPath(
plugin: FolderNotesPlugin,
path: string,
): ExcludedFolder | undefined {
return plugin.settings.excludeFolders.find((excludedFolder) => {
if (path.trim() === '' || !excludedFolder.path) { return false; }
if (excludedFolder.path === path) { return true; }
if (!excludedFolder.subFolders) { return false; }
const excludedFolderPath = excludedFolder.path.includes('/')
? excludedFolder.path
: `${excludedFolder.path}/`;
let folderPath = getFolderPathFromString(path);
folderPath = folderPath.includes('/') ? folderPath : `${folderPath}/`;
if (folderPath.includes('/') || folderPath.includes('\\')) {
return folderPath.startsWith(excludedFolderPath) || folderPath === excludedFolderPath;
}
return folderPath === excludedFolderPath;
});
}
export function getExcludedFoldersByPath(
plugin: FolderNotesPlugin,
path: string,
): ExcludedFolder[] {
return plugin.settings.excludeFolders.filter((excludedFolder) => {
if (path.trim() === '' || !excludedFolder.path) { return false; }
if (excludedFolder.path === path) { return true; }
if (!excludedFolder.subFolders) { return false; }
const excludedFolderPath = excludedFolder.path.includes('/')
? excludedFolder.path
: `${excludedFolder.path}/`;
let folderPath = getFolderPathFromString(path);
folderPath = folderPath.includes('/') ? folderPath : `${folderPath}/`;
if (folderPath.includes('/') || folderPath.includes('\\')) {
return folderPath.startsWith(excludedFolderPath) || folderPath === excludedFolderPath;
}
return folderPath === excludedFolderPath;
});
}
export function addExcludedFolder(
plugin: FolderNotesPlugin,
excludedFolder: ExcludedFolder,
reloadStyles = true,
): void {
plugin.settings.excludeFolders.push(excludedFolder);
void 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);
resyncArray(plugin);
}
export function updateExcludedFolder(
plugin: FolderNotesPlugin,
excludedFolder: ExcludePattern,
newExcludeFolder: ExcludePattern,
): void {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(folder) => folder.id !== excludedFolder.id,
);
addExcludedFolder(plugin, newExcludeFolder);
}
export function resyncArray(plugin: FolderNotesPlugin): void {
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();
}
export function addExcludeFolderListItem(
settings: SettingsTab,
containerEl: HTMLElement,
excludedFolder: ExcludedFolder,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
new FolderSuggest(
cb.inputEl,
plugin,
false,
);
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,
);
addExcludedFolder(plugin, pattern);
addExcludePatternListItem(settings, containerEl, pattern);
setting.clear();
setting.settingEl.remove();
}
if (!plugin.app.vault.getAbstractFileByPath(value)) return;
excludedFolder.path = value;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
});
});
setting.addButton((cb) => {
cb.setIcon('edit');
cb.setTooltip('Edit folder note');
cb.onClick(() => {
new ExcludedFolderSettings(plugin.app, plugin, excludedFolder).open();
});
});
if (Platform.isDesktop || Platform.isTablet) {
setting.addButton((cb) => {
cb.setIcon('up-chevron-glyph');
cb.setTooltip('Move up');
cb.onClick(() => {
if (excludedFolder.position === 0) { return; }
excludedFolder.position -= 1;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
const oldExcludedFolder = plugin.settings.excludeFolders.find(
(folder) => folder.position === excludedFolder.position,
);
if (oldExcludedFolder) {
oldExcludedFolder.position += 1;
if (oldExcludedFolder.type === 'pattern') {
updatePattern(plugin, oldExcludedFolder, oldExcludedFolder);
} else {
updateExcludedFolder(plugin, oldExcludedFolder, oldExcludedFolder);
}
}
settings.display();
});
});
setting.addButton((cb) => {
cb.setIcon('down-chevron-glyph');
cb.setTooltip('Move down');
cb.onClick(() => {
if (excludedFolder.position === plugin.settings.excludeFolders.length - 1) {
return;
}
excludedFolder.position += 1;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
const oldExcludedFolder = plugin.settings.excludeFolders.find(
(folder) => folder.position === excludedFolder.position,
);
if (oldExcludedFolder) {
oldExcludedFolder.position -= 1;
if (oldExcludedFolder.type === 'pattern') {
updatePattern(plugin, oldExcludedFolder, oldExcludedFolder);
} else {
updateExcludedFolder(plugin, oldExcludedFolder, oldExcludedFolder);
}
}
settings.display();
});
});
}
setting.addButton((cb) => {
cb.setIcon('trash-2');
cb.setTooltip('Delete excluded folder');
cb.onClick(() => {
deleteExcludedFolder(plugin, excludedFolder);
setting.clear();
setting.settingEl.remove();
});
});
}

View file

@ -0,0 +1,182 @@
import type FolderNotesPlugin from '../../main';
import type { ExcludePattern } from '../ExcludePattern';
import { Setting, Platform } from 'obsidian';
import type { 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,
);
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);
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 getExcludedFolderByPattern(
plugin: FolderNotesPlugin,
folderName: string,
): ExcludePattern | undefined {
return (
plugin.settings.excludeFolders
.filter((s) => s.type === 'pattern')
.find((pattern) => matchesPatternSpec(pattern.string, folderName))
) as ExcludePattern | undefined;
}
export function addExcludePatternListItem(
settings: SettingsTab,
containerEl: HTMLElement,
pattern: ExcludePattern,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
cb.containerEl.addClass('fn-exclude-folder-path');
cb.setPlaceholder('Pattern');
cb.setValue(pattern.string);
cb.onChange((value) => {
pattern.string = value;
updatePattern(plugin, pattern, pattern);
});
});
setting.addButton((cb) => {
cb.setIcon('edit');
cb.setTooltip('Edit pattern');
cb.onClick(() => {
new PatternSettings(plugin.app, plugin, pattern).open();
});
});
if (Platform.isDesktop || Platform.isTablet) {
setting.addButton((cb) => {
cb.setIcon('up-chevron-glyph');
cb.setTooltip('Move up');
cb.onClick(() => {
if (pattern.position === 0) { return; }
pattern.position -= 1;
updatePattern(plugin, pattern, pattern);
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,
);
} else {
updateExcludedFolder(plugin, oldPattern, oldPattern);
}
}
settings.display();
});
});
setting.addButton((cb) => {
cb.setIcon('down-chevron-glyph');
cb.setTooltip('Move down');
cb.onClick(() => {
if (pattern.position === plugin.settings.excludeFolders.length - 1) {
return;
}
pattern.position += 1;
updatePattern(plugin, pattern, pattern);
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,
);
} else {
updateExcludedFolder(plugin, oldPattern, oldPattern);
}
}
settings.display();
});
});
}
setting.addButton((cb) => {
cb.setIcon('trash-2');
cb.setTooltip('Delete pattern');
cb.onClick(() => {
void deletePattern(plugin, pattern);
setting.clear();
setting.settingEl.remove();
});
});
}

View file

@ -0,0 +1,220 @@
import type FolderNotesPlugin from '../../main';
import { getFolderNameFromPathString, getFolderPathFromString } from '../../functions/utils';
import type { WhitelistedFolder } from '../WhitelistFolder';
import { WhitelistedPattern } from '../WhitelistPattern';
import { Setting, ButtonComponent } from 'obsidian';
import { FolderSuggest } from '../../suggesters/FolderSuggester';
import type { SettingsTab } from '../../settings/SettingsTab';
import WhitelistFolderSettings from '../modals/WhitelistFolderSettings';
import {
updateWhitelistedPattern,
getWhitelistedFoldersByPattern,
addWhitelistedPatternListItem,
} from './whitelistPatternFunctions';
export function getWhitelistedFolder(
plugin: FolderNotesPlugin,
path: string,
): WhitelistedFolder | WhitelistedPattern | undefined {
let whitelistedFolder: Partial<WhitelistedFolder> | undefined = {};
const folderName = getFolderNameFromPathString(path);
const matchedPatterns = getWhitelistedFoldersByPattern(plugin, folderName);
const whitelistedFolders = getWhitelistedFoldersByPath(plugin, path);
const combinedWhitelistedFolders = [...matchedPatterns, ...whitelistedFolders];
const propertiesToCopy: (keyof WhitelistedFolder)[] = [
'enableAutoCreate',
'enableFolderNote',
'enableSync',
'showInFolderOverview',
];
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 (
whitelistedFolder
&& Object.keys(whitelistedFolder).length === 0
) {
whitelistedFolder = undefined;
}
return whitelistedFolder as WhitelistedFolder | WhitelistedPattern | undefined;
}
export function getWhitelistedFolderByPath(
plugin: FolderNotesPlugin,
path: string,
): WhitelistedFolder | WhitelistedPattern | undefined {
return plugin.settings.whitelistFolders.find((whitelistedFolder) => {
if (whitelistedFolder.path === path) { return true; }
if (!whitelistedFolder.subFolders) { return false; }
return getFolderPathFromString(path).startsWith(whitelistedFolder.path);
});
}
export function getWhitelistedFoldersByPath(
plugin: FolderNotesPlugin,
path: string,
): Array<WhitelistedFolder | WhitelistedPattern> {
return plugin.settings.whitelistFolders.filter((whitelistedFolder) => {
if (whitelistedFolder.path === path) { return true; }
if (!whitelistedFolder.subFolders) { return false; }
return getFolderPathFromString(path).startsWith(whitelistedFolder.path);
});
}
export function addWhitelistedFolder(
plugin: FolderNotesPlugin,
whitelistedFolder: WhitelistedFolder | WhitelistedPattern,
): void {
plugin.settings.whitelistFolders.push(whitelistedFolder);
void plugin.saveSettings(true);
}
export async function deleteWhitelistedFolder(
plugin: FolderNotesPlugin,
whitelistedFolder: WhitelistedFolder | WhitelistedPattern,
): Promise<void> {
plugin.settings.whitelistFolders = plugin.settings.whitelistFolders.filter(
(folder) => folder.id !== whitelistedFolder.id || folder.type === 'pattern',
);
await plugin.saveSettings(true);
resyncArray(plugin);
}
export function updateWhitelistedFolder(
plugin: FolderNotesPlugin,
whitelistedFolder: WhitelistedFolder,
newWhitelistFolder: WhitelistedFolder,
): void {
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,
);
plugin.settings.whitelistFolders.forEach((folder, index) => {
folder.position = index;
});
void plugin.saveSettings();
}
export function addWhitelistFolderListItem(
settings: SettingsTab,
containerEl: HTMLElement,
whitelistedFolder: WhitelistedFolder,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
const inputContainer = setting.settingEl.createDiv({
cls: 'fn-whitelist-folder-input-container',
});
const SearchComponent = new Setting(inputContainer);
SearchComponent.addSearch((cb) => {
new FolderSuggest(
cb.inputEl,
plugin,
true,
);
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,
);
addWhitelistedFolder(plugin, pattern);
addWhitelistedPatternListItem(settings, containerEl, pattern);
setting.clear();
setting.settingEl.remove();
}
if (!plugin.app.vault.getAbstractFileByPath(value)) return;
whitelistedFolder.path = value;
updateWhitelistedFolder(plugin, whitelistedFolder, whitelistedFolder);
});
});
const buttonContainer = setting.settingEl.createDiv({ cls: 'fn-whitelist-folder-buttons' });
new ButtonComponent(buttonContainer)
.setIcon('edit')
.setTooltip('Edit folder note')
.onClick(() => {
new WhitelistFolderSettings(plugin.app, plugin, whitelistedFolder).open();
});
new ButtonComponent(buttonContainer)
.setIcon('up-chevron-glyph')
.setTooltip('Move up')
.onClick(() => {
if (whitelistedFolder.position === 0) { return; }
whitelistedFolder.position -= 1;
updateWhitelistedFolder(plugin, whitelistedFolder, whitelistedFolder);
const oldWhitelistedFolder = plugin.settings.whitelistFolders.find(
(folder) => folder.position === whitelistedFolder.position,
);
if (oldWhitelistedFolder) {
oldWhitelistedFolder.position += 1;
if (oldWhitelistedFolder.type === 'pattern') {
updateWhitelistedPattern(plugin, oldWhitelistedFolder, oldWhitelistedFolder);
} else {
updateWhitelistedFolder(plugin, oldWhitelistedFolder, oldWhitelistedFolder);
}
}
settings.display();
});
new ButtonComponent(buttonContainer)
.setIcon('down-chevron-glyph')
.setTooltip('Move down')
.onClick(() => {
if (whitelistedFolder.position === plugin.settings.whitelistFolders.length - 1) {
return;
}
whitelistedFolder.position += 1;
updateWhitelistedFolder(plugin, whitelistedFolder, whitelistedFolder);
const oldWhitelistedFolder = plugin.settings.whitelistFolders.find(
(folder) => folder.position === whitelistedFolder.position,
);
if (oldWhitelistedFolder) {
oldWhitelistedFolder.position -= 1;
if (oldWhitelistedFolder.type === 'pattern') {
updateWhitelistedPattern(plugin, oldWhitelistedFolder, oldWhitelistedFolder);
} else {
updateWhitelistedFolder(plugin, oldWhitelistedFolder, oldWhitelistedFolder);
}
}
settings.display();
});
new ButtonComponent(buttonContainer)
.setIcon('trash-2')
.setTooltip('Delete excluded folder')
.onClick(() => {
void deleteWhitelistedFolder(plugin, whitelistedFolder);
setting.clear();
setting.settingEl.remove();
});
}

View file

@ -0,0 +1,184 @@
import type FolderNotesPlugin from '../../main';
import { Setting } from 'obsidian';
import type { SettingsTab } from '../../settings/SettingsTab';
import { resyncArray } from './folderFunctions';
import WhitelistPatternSettings from '../modals/WhitelistPatternSettings';
import type { 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,
);
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);
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 getWhitelistedFoldersByPattern(
plugin: FolderNotesPlugin,
folderName: string,
): WhitelistedPattern[] {
return (
plugin.settings.whitelistFolders
.filter((s) => s.type === 'pattern')
.filter((pattern) => matchesPatternSpec(pattern.string, folderName))
) as WhitelistedPattern[];
}
export function addWhitelistedPatternListItem(
settings: SettingsTab,
containerEl: HTMLElement,
pattern: WhitelistedPattern,
): void {
const { plugin } = settings;
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list');
setting.addSearch((cb) => {
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; }
pattern.string = value;
updateWhitelistedPattern(plugin, pattern, pattern);
});
});
setting.addButton((cb) => {
cb.setIcon('edit');
cb.setTooltip('Edit pattern');
cb.onClick(() => {
new WhitelistPatternSettings(plugin.app, plugin, pattern).open();
});
});
setting.addButton((cb) => {
cb.setIcon('up-chevron-glyph');
cb.setTooltip('Move up');
cb.onClick(() => {
if (pattern.position === 0) { return; }
pattern.position -= 1;
updateWhitelistedPattern(plugin, pattern, pattern);
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,
);
} else {
updateWhitelistedFolder(plugin, oldPattern, oldPattern);
}
}
settings.display();
});
});
setting.addButton((cb) => {
cb.setIcon('down-chevron-glyph');
cb.setTooltip('Move down');
cb.onClick(() => {
if (pattern.position === plugin.settings.whitelistFolders.length - 1) {
return;
}
pattern.position += 1;
updateWhitelistedPattern(plugin, pattern, pattern);
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,
);
} else {
updateWhitelistedFolder(plugin, oldPattern, oldPattern);
}
}
settings.display();
});
});
setting.addButton((cb) => {
cb.setIcon('trash-2');
cb.setTooltip('Delete pattern');
cb.onClick(() => {
void deletePattern(plugin, pattern);
setting.clear();
setting.settingEl.remove();
});
});
}

View file

@ -1,6 +1,7 @@
import { App, Modal, Setting } from 'obsidian';
import FolderNotesPlugin from '../main';
import { ExcludedFolder } from '../settings';
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import { updateCSSClassesForFolder } from 'src/functions/styleFunctions';
export default class ExcludedFolderSettings extends Modal {
plugin: FolderNotesPlugin;
app: App;
@ -11,13 +12,13 @@ export default class ExcludedFolderSettings extends Modal {
this.app = app;
this.excludedFolder = excludedFolder;
}
onOpen() {
onOpen(): void {
this.display();
}
display() {
display(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl('h2', { text: 'Exluded folder settings' });
contentEl.createEl('h2', { text: 'Excluded folder settings' });
new Setting(contentEl)
.setName('Include subfolders')
.setDesc('Choose if the subfolders of the folder should also be excluded')
@ -26,8 +27,8 @@ export default class ExcludedFolderSettings extends Modal {
.setValue(this.excludedFolder.subFolders)
.onChange(async (value) => {
this.excludedFolder.subFolders = value;
await this.plugin.saveSettings();
})
await this.plugin.saveSettings(true);
}),
);
new Setting(contentEl)
@ -39,7 +40,33 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.disableSync = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
.setName('Don\'t show folder in folder overview')
.setDesc('Choose if the folder should be shown in the folder overview')
.addToggle((toggle) =>
toggle
.setValue(this.excludedFolder.excludeFromFolderOverview)
.onChange(async (value) => {
this.excludedFolder.excludeFromFolderOverview = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Show folder note in the file explorer')
.setDesc('Choose if the folder note should be shown in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(this.excludedFolder.showFolderNote)
.onChange(async (value) => {
this.excludedFolder.showFolderNote = value;
updateCSSClassesForFolder(this.excludedFolder.path, this.plugin);
await this.plugin.saveSettings();
this.display();
}),
);
new Setting(contentEl)
@ -51,10 +78,9 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.disableAutoCreate = value;
await this.plugin.saveSettings();
})
}),
);
new Setting(contentEl)
.setName('Disable open folder note')
.setDesc('Choose if the folder note should be opened when the folder is opened')
@ -63,9 +89,9 @@ export default class ExcludedFolderSettings extends Modal {
.setValue(this.excludedFolder.disableFolderNote)
.onChange(async (value) => {
this.excludedFolder.disableFolderNote = value;
await this.plugin.saveSettings();
await this.plugin.saveSettings(true);
this.display();
})
}),
);
if (!this.excludedFolder.disableFolderNote) {
@ -78,12 +104,12 @@ export default class ExcludedFolderSettings extends Modal {
.onChange(async (value) => {
this.excludedFolder.enableCollapsing = value;
await this.plugin.saveSettings();
})
}),
);
}
}
onClose() {
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}

View file

@ -0,0 +1,110 @@
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { ExcludePattern } from 'src/ExcludeFolders/ExcludePattern';
import { refreshAllFolderStyles } from 'src/functions/styleFunctions';
export default class PatternSettings extends Modal {
plugin: FolderNotesPlugin;
app: App;
pattern: ExcludePattern;
constructor(app: App, plugin: FolderNotesPlugin, pattern: ExcludePattern) {
super(app);
this.plugin = plugin;
this.app = app;
this.pattern = pattern;
}
onOpen(): void {
this.display();
}
display(): void {
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
.setValue(this.pattern.disableSync)
.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
.setValue(this.pattern.disableAutoCreate)
.onChange(async (value) => {
this.pattern.disableAutoCreate = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Don\'t show folder in folder overview')
.setDesc('Choose if the folder should be shown in the folder overview')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.excludeFromFolderOverview)
.onChange(async (value) => {
this.pattern.excludeFromFolderOverview = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Show folder note in the file explorer')
.setDesc('Choose if the folder note should be shown in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.showFolderNote)
.onChange(async (value) => {
this.pattern.showFolderNote = value;
await this.plugin.saveSettings();
refreshAllFolderStyles(true, this.plugin);
this.display();
}),
);
new Setting(contentEl)
.setName('Disable open folder note')
.setDesc('Choose if the folder note should be opened when the folder is opened')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.disableFolderNote)
.onChange(async (value) => {
this.pattern.disableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
}),
);
if (!this.pattern.disableFolderNote) {
new Setting(contentEl)
.setName('Collapse folder when opening folder note')
.setDesc('Choose if the folder should be collapsed when the folder note is opened')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.enableCollapsing)
.onChange(async (value) => {
this.pattern.enableCollapsing = value;
await this.plugin.saveSettings();
}),
);
}
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -0,0 +1,116 @@
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { WhitelistedFolder } from '../WhitelistFolder';
export default class WhitelistFolderSettings extends Modal {
plugin: FolderNotesPlugin;
app: App;
whitelistedFolder: WhitelistedFolder;
constructor(app: App, plugin: FolderNotesPlugin, whitelistedFolder: WhitelistedFolder) {
super(app);
this.plugin = plugin;
this.app = app;
this.whitelistedFolder = whitelistedFolder;
}
onOpen(): void {
this.display();
}
display(): void {
const { contentEl } = this;
contentEl.empty();
contentEl.createEl('h2', { text: 'Whitelisted folder settings' });
new Setting(contentEl)
.setName('Include subfolders')
.setDesc('Choose if the subfolders of the folder should also be whitelisted')
.addToggle((toggle) =>
toggle
.setValue(this.whitelistedFolder.subFolders)
.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
.setValue(this.whitelistedFolder.enableSync)
.onChange(async (value) => {
this.whitelistedFolder.enableSync = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Show folder in folder overview')
.setDesc('Choose if the folder should be shown in the folder overview')
.addToggle((toggle) =>
toggle
.setValue(this.whitelistedFolder.showInFolderOverview)
.onChange(async (value) => {
this.whitelistedFolder.showInFolderOverview = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Hide folder note in file explorer')
.setDesc('Choose if the folder note should be hidden in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(this.whitelistedFolder.hideInFileExplorer)
.onChange(async (value) => {
this.whitelistedFolder.hideInFileExplorer = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Allow auto creation of folder notes in this folder')
.addToggle((toggle) =>
toggle
.setValue(this.whitelistedFolder.enableAutoCreate)
.onChange(async (value) => {
this.whitelistedFolder.enableAutoCreate = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Open folder note when clicking on the folder')
.setDesc('Choose if the folder note should be opened when the folder is opened')
.addToggle((toggle) =>
toggle
.setValue(this.whitelistedFolder.enableFolderNote)
.onChange(async (value) => {
this.whitelistedFolder.enableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
}),
);
if (this.whitelistedFolder.enableFolderNote) {
new Setting(contentEl)
.setName('Don\'t collapse folder when opening folder note')
.setDesc('Choose if the folder should be collapsed when the folder note is opened')
.addToggle((toggle) =>
toggle
.setValue(this.whitelistedFolder.disableCollapsing)
.onChange(async (value) => {
this.whitelistedFolder.disableCollapsing = value;
await this.plugin.saveSettings();
}),
);
}
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -0,0 +1,93 @@
import { Modal, Setting, type App } from 'obsidian';
import type FolderNotesPlugin from '../../main';
import type { WhitelistedPattern } from '../WhitelistPattern';
export default class WhitelistPatternSettings extends Modal {
plugin: FolderNotesPlugin;
app: App;
pattern: WhitelistedPattern;
constructor(app: App, plugin: FolderNotesPlugin, pattern: WhitelistedPattern) {
super(app);
this.plugin = plugin;
this.app = app;
this.pattern = pattern;
}
onOpen(): void {
this.display();
}
display(): void {
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
.setValue(this.pattern.enableSync)
.onChange(async (value) => {
this.pattern.enableSync = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Allow auto creation of folder notes in this folder')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.enableAutoCreate)
.onChange(async (value) => {
this.pattern.enableAutoCreate = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Show folder in folder overview')
.setDesc('Choose if the folder should be shown in the folder overview')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.showInFolderOverview)
.onChange(async (value) => {
this.pattern.showInFolderOverview = value;
await this.plugin.saveSettings();
}),
);
new Setting(contentEl)
.setName('Open folder note when clicking on the folder')
.setDesc('Choose if the folder note should be opened when you click on the folder')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.enableFolderNote)
.onChange(async (value) => {
this.pattern.enableFolderNote = value;
await this.plugin.saveSettings(true);
this.display();
}),
);
if (this.pattern.enableFolderNote) {
new Setting(contentEl)
.setName('Don\'t collapse folder when opening folder note')
.setDesc('Choose if the folder should be collapsed when the folder note is opened')
.addToggle((toggle) =>
toggle
.setValue(this.pattern.disableCollapsing)
.onChange(async (value) => {
this.pattern.disableCollapsing = value;
await this.plugin.saveSettings();
}),
);
}
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -0,0 +1,63 @@
import { Modal, Setting, type App } from 'obsidian';
import type { SettingsTab } from 'src/settings/SettingsTab';
import type FolderNotesPlugin from '../../main';
import { WhitelistedFolder } from '../WhitelistFolder';
import {
addWhitelistFolderListItem,
addWhitelistedFolder,
} from '../functions/whitelistFolderFunctions';
import { addWhitelistedPatternListItem } from '../functions/whitelistPatternFunctions';
export default class WhitelistedFoldersSettings extends Modal {
plugin: FolderNotesPlugin;
app: App;
settingsTab: SettingsTab;
constructor(settingsTab: SettingsTab) {
super(settingsTab.app);
this.plugin = settingsTab.plugin;
this.settingsTab = settingsTab;
this.app = settingsTab.app;
}
onOpen(): void {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'Manage whitelisted folders' });
new Setting(contentEl)
.setName('Add whitelisted folder')
.setClass('add-exclude-folder-item')
.addButton((cb) => {
cb.setIcon('plus');
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,
);
addWhitelistedFolder(this.plugin, whitelistedFolder);
this.settingsTab.renderSettingsPage(this.settingsTab.plugin.settings.settingsTab);
});
});
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 {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -1,63 +0,0 @@
import { App, TFolder, Menu, TAbstractFile, Notice, TFile } from 'obsidian';
import FolderNotesPlugin from './main';
import { ExcludedFolder } from './settings';
export class Commands {
plugin: FolderNotesPlugin;
app: App;
constructor(app: App, plugin: FolderNotesPlugin) {
this.plugin = plugin;
this.app = app;
}
registerCommands() {
this.plugin.registerEvent(this.app.workspace.on('file-menu', (menu: Menu, file: TAbstractFile) => {
if (!(file instanceof TFolder)) return;
if (this.plugin.settings.excludeFolders.find((folder) => folder.path === file.path)) {
menu.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);
this.plugin.saveSettings();
new Notice('Successfully removed folder from excluded folders');
});
});
return;
}
menu.addItem((item) => {
item.setTitle('Exclude folder from folder notes')
.setIcon('x-circle')
.onClick(() => {
const excludedFolder = new ExcludedFolder(file.path, this.plugin.settings.excludeFolders.length);
this.plugin.settings.excludeFolders.push(excludedFolder);
this.plugin.saveSettings();
new Notice('Successfully excluded folder from folder notes');
});
});
if (!(file instanceof TFolder)) return;
const path = file.path + '/' + this.plugin.settings.folderNoteName.replace('{{folder_name}}', file.name) + this.plugin.settings.folderNoteType;
let folderNote = this.plugin.app.vault.getAbstractFileByPath(path.slice(0, -this.plugin.settings.folderNoteType.length) + '.md') || this.plugin.app.vault.getAbstractFileByPath(path);
if (!folderNote) {
folderNote = this.plugin.app.vault.getAbstractFileByPath(`${file.path}/${file.name}.md`) || this.plugin.app.vault.getAbstractFileByPath(`${file.path}/${file.name}.md`);
}
if (folderNote instanceof TFile) {
menu.addItem((item) => {
item.setTitle('Delete folder note')
.setIcon('trash')
.onClick(() => {
// @ts-ignore
this.plugin.deleteFolderNote(folderNote);
});
});
} else {
menu.addItem((item) => {
item.setTitle('Create folder note')
.setIcon('edit')
.onClick(() => {
this.plugin.createFolderNote(path, true);
});
});
}
}));
}
}

View file

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

View file

@ -0,0 +1,163 @@
import type FolderNotesPlugin from 'src/main';
import {
type Listener,
type Events,
type ApiInterface,
type DeferInterface,
type ListenerRef,
type EventDispatcherInterface,
getDefer,
} from 'front-matter-plugin-api-provider';
import { type App, TFile, TFolder } from 'obsidian';
import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions';
interface UpdateData {
id: string;
result: boolean;
path: string;
pathOnly: boolean;
breadcrumb?: HTMLElement;
}
interface WrappedUpdateData {
data: UpdateData;
}
export class FrontMatterTitlePluginHandler {
plugin: FolderNotesPlugin;
app!: App;
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;
constructor(plugin: FolderNotesPlugin) {
this.plugin = plugin;
this.app = plugin.app;
(async (): Promise<void> => {
this.deffer = getDefer(this.app);
if (this.deffer.isPluginReady()) {
this.api = this.deffer.getApi();
} else {
await this.deffer.awaitPlugin();
this.api = this.deffer.getApi();
// if you want to wait features you can use the following chain
if (!this.deffer.isFeaturesReady()) {
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();
}
})();
}
deleteEvent(): void {
if (this.eventRef && this.dispatcher) {
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);
if (!(file instanceof TFile)) { return; }
const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#');
const newName = resolver?.resolve(file?.path ?? '');
const folder = getFolder(this.plugin, file);
if (!(folder instanceof TFolder)) { return; }
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) { return; }
if (folderNote !== file) { return; }
if (!actualData.pathOnly) {
this.plugin.changeFolderNameInExplorer(folder, newName);
}
const { breadcrumb } = actualData;
if (breadcrumb) {
this.plugin.changeFolderNameInPath(folder, newName, breadcrumb);
}
if (isEvent) {
this.plugin.updateAllBreadcrumbs();
}
if (newName) {
folder.newName = newName;
this.modifiedFolders.set(folder.path, folder);
} else {
folder.newName = null;
this.modifiedFolders.delete(folder.path);
}
}
async fmptUpdateFolderName(data: UpdateData, _replacePath: boolean): Promise<void> {
const hasNestedData = 'data' in (data as unknown as Record<string, unknown>);
const actualData: UpdateData = hasNestedData
? (data as unknown as WrappedUpdateData).data
: data;
const folder = this.app.vault.getAbstractFileByPath(actualData.path);
if (!(folder instanceof TFolder)) { return; }
const folderNote = getFolderNote(this.plugin, folder.path);
if (!folderNote) { return; }
const resolver = this.api?.getResolverFactory()?.createResolver('#feature-id#');
const newName = resolver?.resolve(folderNote?.path ?? '');
if (!newName) return;
if (!actualData.pathOnly) {
this.plugin.changeFolderNameInExplorer(folder, newName);
}
const { breadcrumb } = actualData;
if (breadcrumb) {
this.plugin.changeFolderNameInPath(folder, newName, breadcrumb);
}
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

@ -0,0 +1,240 @@
import { Keymap, Platform } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { getFolderNote, getFolderNoteFolder } 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 {
// Run once on initial layout
plugin.app.workspace.onLayoutReady(() => {
initializeFolderNoteFeatures(plugin);
initializeBreadcrumbs(plugin);
});
// Re-run when layout changes (e.g. File Explorer is reopened)
plugin.registerEvent(
plugin.app.workspace.on('layout-change', () => {
initializeFolderNoteFeatures(plugin);
const activeLeaf = plugin.app.workspace.getActiveFileView()?.containerEl;
if (!activeLeaf) return;
const titleContainer = activeLeaf.querySelector('.view-header-title-container');
if (!(titleContainer instanceof HTMLElement)) return;
updateFolderNamesInPath(plugin, titleContainer);
}),
);
}
export function unregisterFileExplorerObserver(): void {
if (fileExplorerMutationObserver) {
fileExplorerMutationObserver.disconnect();
fileExplorerMutationObserver = null;
}
}
function initializeFolderNoteFeatures(plugin: FolderNotesPlugin): void {
initializeAllFolderTitles(plugin);
observeFolderTitleMutations(plugin);
}
function initializeBreadcrumbs(plugin: FolderNotesPlugin): void {
const titleContainers = activeDocument.querySelectorAll('.view-header-title-container');
if (!titleContainers.length) return;
titleContainers.forEach((container) => {
if (!(container.instanceOf(HTMLElement))) return;
scheduleIdle(() => updateFolderNamesInPath(plugin, container), { timeout: 1000 });
});
}
/**
* 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 {
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;
processAddedFolders(node, plugin);
}
}
});
fileExplorerMutationObserver.observe(document, { childList: true, subtree: true });
}
function initializeAllFolderTitles(plugin: FolderNotesPlugin): void {
const allTitles = activeDocument.querySelectorAll('.nav-folder-title-content');
for (const title of Array.from(allTitles)) {
const folderTitle = title as HTMLElement;
const folderEl = folderTitle.closest('.nav-folder-title');
if (!folderEl) continue;
const folderPath = folderEl.getAttribute('data-path') || '';
setupFolderTitle(folderTitle, plugin, folderPath);
}
}
function processAddedFolders(node: HTMLElement, plugin: FolderNotesPlugin): void {
const titles: HTMLElement[] = [];
if (node.matches('.nav-folder-title-content')) {
titles.push(node);
}
node.querySelectorAll('.nav-folder-title-content').forEach((el) => {
titles.push(el as HTMLElement);
});
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(() => {
const retryFolderEl = folderTitle.closest('.nav-folder-title');
const retryFolderPath = retryFolderEl?.getAttribute('data-path') || '';
if (retryFolderEl && retryFolderPath) {
setupFolderTitle(folderTitle, plugin, retryFolderPath);
}
}, RETRY_TIMEOUT);
return;
}
setupFolderTitle(folderTitle, plugin, folderPath);
});
}
async function setupFolderTitle(
folderTitle: HTMLElement,
plugin: FolderNotesPlugin,
folderPath: string,
): Promise<void> {
if (folderTitle.dataset.initialized === 'true') return;
if (!folderPath) return;
folderTitle.dataset.initialized = 'true';
await updateCSSClassesForFolder(folderPath, plugin);
if (plugin.settings.frontMatterTitle.enabled) {
plugin.fmtpHandler?.fmptUpdateFolderName(
{ id: '', result: false, path: folderPath, pathOnly: false },
false,
);
}
if (Platform.isMobile && plugin.settings.disableOpenFolderNoteOnClick) return;
plugin.registerDomEvent(folderTitle, 'pointerover', (event: MouseEvent) => {
plugin.hoveredElement = folderTitle;
plugin.mouseEvent = event;
if (!Keymap.isModEvent(event)) return;
if (!(event.target instanceof HTMLElement)) return;
const folderNote = getFolderNote(plugin, folderPath);
if (!folderNote) return;
plugin.app.workspace.trigger('hover-link', {
event,
source: 'preview',
hoverParent: { file: folderNote },
targetEl: event.target,
linktext: folderNote.basename,
sourcePath: folderNote.path,
});
plugin.hoverLinkTriggered = true;
});
plugin.registerDomEvent(folderTitle, 'pointerout', () => {
plugin.hoveredElement = null;
plugin.mouseEvent = null;
plugin.hoverLinkTriggered = false;
});
}
// eslint-disable-next-line complexity
async function updateFolderNamesInPath(
plugin: FolderNotesPlugin,
titleContainer: HTMLElement,
): Promise<void> {
const titleParent = titleContainer.querySelector('.view-header-title-parent');
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();
path += '/';
const folderPath = path.slice(0, -TRAILING_SLASH_LENGTH);
const excludedFolder = getExcludedFolder(plugin, folderPath, true);
if (excludedFolder?.disableFolderNote) return;
const folderNote = getFolderNote(plugin, folderPath);
const viewHeaderTitle = titleContainer.querySelector('.view-header-title');
if (viewHeaderTitle && folderNote) {
const filePath = path + (viewHeaderTitle as HTMLElement).innerText.trim() + '.md';
const file = plugin.app.vault.getAbstractFileByPath(
filePath);
const folder = getFolderNoteFolder(plugin, folderNote, file?.name ?? '');
if (folder && file && file.path === folderNote?.path && file.parent?.path !== '/') {
viewHeaderTitle.parentElement?.classList.add('hide-folder-note-title-in-path');
viewHeaderTitle.classList.add('path-is-folder-note');
} else {
viewHeaderTitle.parentElement?.classList.remove('hide-folder-note-title-in-path');
viewHeaderTitle.classList.remove('path-is-folder-note');
}
}
if (!folderNote) {
breadcrumb.classList.remove('has-folder-note');
breadcrumb.removeAttribute('data-path');
continue;
}
breadcrumb.classList.add('has-folder-note');
breadcrumb?.setAttribute('data-path', path.slice(0, -TRAILING_SLASH_LENGTH));
if (!breadcrumb.onclick) {
breadcrumb.addEventListener('click', (e) => {
handleViewHeaderClick(e, plugin);
}, { capture: true });
}
if (plugin.settings.frontMatterTitle.enabled) {
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;
if ('requestIdleCallback' in window) {
const windowWithIdle = window as Window & {
requestIdleCallback: (callback: () => void, options?: { timeout: number }) => void
};
windowWithIdle.requestIdleCallback(callback, options);
} else {
globalThis.setTimeout(callback, options?.timeout || DEFAULT_IDLE_TIMEOUT);
}
}

58
src/events/TabManager.ts Normal file
View file

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

81
src/events/handleClick.ts Normal file
View file

@ -0,0 +1,81 @@
import { Keymap, Platform, type TFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { openFolderNote, createFolderNote, getFolderNote } from 'src/functions/folderNoteFunctions';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import {
addCSSClassToFileExplorerEl,
removeCSSClassFromFileExplorerEL,
} from 'src/functions/styleFunctions';
export async function handleViewHeaderClick(
event: MouseEvent,
plugin: FolderNotesPlugin,
): Promise<void> {
if (!plugin.settings.openFolderNoteOnClickInPath) return;
event.stopImmediatePropagation();
event.preventDefault();
event.stopPropagation();
if (!(event.target instanceof HTMLElement)) return;
const folderPath = event.target.getAttribute('data-path');
if (!folderPath) { return; }
if (await isExcludedFolder(event, plugin, folderPath)) return;
const folderNote = getFolderNote(plugin, folderPath);
if (folderNote) {
await openFolderNote(plugin, folderNote, event).then(() =>
handleFolderNoteReveal(plugin, folderNote),
);
return;
} else if (event.altKey || Keymap.isModEvent(event) === 'tab') {
if (await handleFolderNoteCreation(event, plugin, folderPath)) 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;
}

View file

@ -0,0 +1,85 @@
import { TFolder, TFile, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import {
createFolderNote,
getFolder,
getFolderNote,
turnIntoFolderNote,
} from 'src/functions/folderNoteFunctions';
import { getExcludedFolder } from 'src/ExcludeFolders/functions/folderFunctions';
import {
removeCSSClassFromFileExplorerEL,
addCSSClassToFileExplorerEl,
} from 'src/functions/styleFunctions';
import { isFileInAttachmentFolder } from '@app/functions/utils';
export async function handleCreate(file: TAbstractFile, plugin: FolderNotesPlugin): Promise<void> {
if (!plugin.app.workspace.layoutReady) return;
const folder = file.parent;
if (folder instanceof TFolder) {
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
addCSSClassToFileExplorerEl(folder.path, 'only-has-folder-note', true, plugin);
} else {
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
}
if (file instanceof TFile) {
handleFileCreation(file, plugin);
} else if (file instanceof TFolder && plugin.settings.autoCreate) {
handleFolderCreation(file, plugin);
}
}
async function handleFileCreation(file: TFile, plugin: FolderNotesPlugin): Promise<void> {
const folder = getFolder(plugin, file);
if (!(folder instanceof TFolder) && plugin.settings.autoCreateForFiles) {
if (!file.parent) { return; }
const newFolder = await plugin.app.fileManager.createNewFolder(file.parent);
turnIntoFolderNote(plugin, file, newFolder);
} else if (folder instanceof TFolder) {
if (folder.children.length >= 1) {
removeCSSClassFromFileExplorerEL(folder.path, 'fn-empty-folder', false, plugin);
}
const detachedFolder = getExcludedFolder(plugin, folder.path, true);
if (detachedFolder) { return; }
const folderNote = getFolderNote(plugin, folder.path);
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; }
if (!file.parent) { return; }
const newFolder = await plugin.app.fileManager.createNewFolder(file.parent);
turnIntoFolderNote(plugin, file, newFolder);
}
}
}
async function handleFolderCreation(folder: TFolder, plugin: FolderNotesPlugin): Promise<void> {
let openFile = plugin.settings.autoCreateFocusFiles;
const attachmentFolderPath = plugin.app.vault.getConfig('attachmentFolderPath') as string;
const cleanAttachmentFolderPath = attachmentFolderPath?.replace('./', '') || '';
const attachmentsAreInRootFolder = attachmentFolderPath === './' || attachmentFolderPath === '';
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, plugin);
if (!plugin.settings.autoCreateForAttachmentFolder) {
if (!attachmentsAreInRootFolder && cleanAttachmentFolderPath === folder.name) return;
} else if (!attachmentsAreInRootFolder && cleanAttachmentFolderPath === folder.name) {
openFile = false;
}
const excludedFolder = getExcludedFolder(plugin, folder.path, true);
if (excludedFolder?.disableAutoCreate) return;
const folderNote = getFolderNote(plugin, folder.path);
if (folderNote) return;
void createFolderNote(plugin, folder.path, openFile, undefined, true);
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
}

View file

@ -0,0 +1,39 @@
import { TFolder, TFile, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { getFolderNote, getFolder, deleteFolderNote } from 'src/functions/folderNoteFunctions';
import {
removeCSSClassFromFileExplorerEL,
addCSSClassToFileExplorerEl,
hideFolderNoteInFileExplorer,
} from 'src/functions/styleFunctions';
import { getFolderPathFromString } from 'src/functions/utils';
export function handleDelete(file: TAbstractFile, plugin: FolderNotesPlugin): void {
const folder = plugin.app.vault.getAbstractFileByPath(getFolderPathFromString(file.path));
if (folder instanceof TFolder) {
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
addCSSClassToFileExplorerEl(folder.path, 'only-has-folder-note', true, plugin);
} else {
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
}
if (file instanceof TFile) {
const folderNoteFolder = getFolder(plugin, file);
if (!folderNoteFolder) { return; }
const folderNote = getFolderNote(plugin, folderNoteFolder.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);
}
if (!(file instanceof TFolder)) { return; }
const folderNote = getFolderNote(plugin, file.path);
if (!folderNote) { return; }
removeCSSClassFromFileExplorerEL(folderNote.path, 'is-folder-note', false, plugin);
if (!plugin.settings.syncDelete) { return; }
deleteFolderNote(plugin, folderNote, false);
}

433
src/events/handleRename.ts Normal file
View file

@ -0,0 +1,433 @@
import { TFile, TFolder, Notice, type TAbstractFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import {
extractFolderName, getFolderNote, getFolderNoteFolder,
} from '../functions/folderNoteFunctions';
import {
getExcludedFolder, addExcludedFolder,
updateExcludedFolder, deleteExcludedFolder, getDetachedFolder,
} from '../ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import {
removeCSSClassFromFileExplorerEL, addCSSClassToFileExplorerEl,
markFileAsFolderNote, unmarkFileAsFolderNote,
unmarkFolderAsFolderNote, markFolderWithFolderNoteClasses,
hideFolderNoteInFileExplorer, removeActiveFolder, setActiveFolder,
} from 'src/functions/styleFunctions';
import {
getFolderPathFromString, removeExtension, getFileNameFromPathString,
} from 'src/functions/utils';
export function handleRename(
file: TAbstractFile,
oldPath: string,
plugin: FolderNotesPlugin,
): void {
let folder = file.parent;
const oldFolder = plugin.app.vault.getAbstractFileByPath(getFolderPathFromString(oldPath));
if (folder instanceof TFolder) {
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
addCSSClassToFileExplorerEl(folder.path, 'only-has-folder-note', true, plugin);
} else {
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
}
if (oldFolder instanceof TFolder) {
if (plugin.isEmptyFolderNoteFolder(oldFolder) && getFolderNote(plugin, oldFolder.path)) {
addCSSClassToFileExplorerEl(oldFolder.path, 'only-has-folder-note', true, plugin);
} else {
removeCSSClassFromFileExplorerEL(oldFolder.path, 'only-has-folder-note', true, plugin);
}
}
if (file instanceof TFolder) {
folder = file;
plugin.tabManager.updateTab(folder.path);
updateExcludedFolderPath(folder, oldPath, plugin);
if (isFolderRename(folder, oldPath)) {
handleFolderRename(folder, oldPath, plugin);
return;
}
return handleFolderMove(folder, oldPath, plugin);
} else if (file instanceof TFile) {
if (isFileRename(file, oldPath)) {
handleFileRename(file, oldPath, plugin);
return;
}
handleFileMove(file, oldPath, plugin);
return;
}
}
function isFileRename(file: TFile, oldPath: string): boolean {
const oldFolderPath = getFolderPathFromString(oldPath);
const newFolderPath = file.parent?.path;
const oldName = getFileNameFromPathString(oldPath);
const newName = file.name;
return oldFolderPath === newFolderPath && oldName !== newName;
}
function isFolderRename(folder: TFolder, oldPath: string): boolean {
const oldName = getFileNameFromPathString(oldPath);
const newName = folder.name;
const oldParent = getFolderPathFromString(oldPath);
const newParent = folder.parent?.path;
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);
}
// eslint-disable-next-line complexity
export async function handleFileMove(
file: TFile,
oldPath: string,
plugin: FolderNotesPlugin,
): Promise<void> {
const { folderName, oldFileName, newFolder, excludedFolder, oldFolder, folderNote } = getArgs(
plugin, file, oldPath,
);
const isFolderNoteInNewFolder = folderName === newFolder?.name;
const fileMovedFromOldFolderNote = oldFolder && oldFolder.name === oldFileName
&& newFolder?.path !== oldFolder.path;
// this is for turning files into folder notes for folders that already have a folder note
// e.g. Turn into folder note for "Folder name"
const isFileWithExistingNote = 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 (oldFolder instanceof TFolder) {
hideFolderNoteInFileExplorer(oldFolder.path, plugin);
unmarkFolderAsFolderNote(oldFolder, plugin);
}
} else if (fileMovedFromOldFolderNote) {
unmarkFileAsFolderNote(file, plugin);
if (oldFolder instanceof TFolder) {
removeActiveFolder(plugin);
hideFolderNoteInFileExplorer(oldFolder.path, plugin);
unmarkFolderAsFolderNote(oldFolder, plugin);
}
}
}
function getArgs(plugin: FolderNotesPlugin, file: TFile, oldPath: string): {
folderName: string;
oldFileName: string;
newFolder: TAbstractFile | null;
excludedFolder: ExcludedFolder | undefined;
oldFolder: TAbstractFile | null;
folderNote: TFile | null | undefined;
} {
const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename)
|| file.basename;
const oldFileName = removeExtension(getFileNameFromPathString(oldPath));
const newFolder = getFolderNoteFolder(plugin, file, file.basename);
let excludedFolder = getExcludedFolder(plugin, newFolder?.path || '', true);
const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName);
const folderNote = getFolderNote(plugin, oldPath, plugin.settings.storageLocation, file);
return {
folderName,
oldFileName,
newFolder,
excludedFolder,
oldFolder,
folderNote,
};
}
function renameExistingFolderNote(
file: TFile, oldPath: string, plugin: FolderNotesPlugin,
excludedFolder: ExcludedFolder | undefined,
oldFolder: TAbstractFile | null,
): void {
let excludedFolderExisted = true;
let disabledSync = false;
if (!excludedFolder) {
excludedFolderExisted = false;
excludedFolder = new ExcludedFolder(
oldFolder?.path || '',
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
addExcludedFolder(plugin, excludedFolder);
} else if (!excludedFolder.disableSync) {
disabledSync = false;
excludedFolder.disableSync = true;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
plugin.app.fileManager.renameFile(file, oldPath).then(() => {
if (!excludedFolder) { return; }
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
}
export async function handleFolderRename(
file: TFolder, oldPath: string, plugin: FolderNotesPlugin,
): Promise<void> {
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', file.name);
const oldFileName = plugin.settings.folderNoteName
.replace('{{folder_name}}', getFileNameFromPathString(oldPath));
if (fileName === oldFileName) { return; }
const folderNote = getFolderNote(plugin, oldPath);
if (!(folderNote instanceof TFile)) return;
const excludedFolder = getExcludedFolder(plugin, file.path, true);
if (excludedFolder?.disableSync && !folderNote) {
return removeCSSClassFromFileExplorerEL(file.path, 'has-folder-note', false, plugin);
}
if (!plugin.settings.syncFolderName) { return; }
let newPath = '';
if (plugin.settings.storageLocation === 'parentFolder') {
const parentFolderPath = getFolderPathFromString(file.path);
const oldParentFolderPath = getFolderPathFromString(oldPath);
if (parentFolderPath !== oldParentFolderPath) {
if (!plugin.settings.syncMove) { return; }
newPath = `${parentFolderPath}/${fileName}.${folderNote.extension}`;
} else if (parentFolderPath.trim() === '') {
folderNote.path = `${folderNote.name}`;
newPath = `${fileName}.${folderNote.extension}`;
} else {
folderNote.path = `${parentFolderPath}/${folderNote.name}`;
newPath = `${parentFolderPath}/${fileName}.${folderNote.extension}`;
}
} else {
folderNote.path = `${file.path}/${folderNote.name}`;
newPath = `${file.path}/${fileName}.${folderNote.extension}`;
}
plugin.app.fileManager.renameFile(folderNote, newPath);
}
// eslint-disable-next-line complexity
export async function handleFileRename(
file: TFile,
oldPath: string,
plugin: FolderNotesPlugin,
): Promise<void> {
const oldFileName = removeExtension(getFileNameFromPathString(oldPath));
const newFileName = file.basename;
if (oldFileName === newFileName) { return; }
const oldFolder = getFolderNoteFolder(plugin, oldPath, oldFileName);
const folderName = extractFolderName(plugin.settings.folderNoteName, file.basename)
|| file.basename;
const oldFolderName = extractFolderName(plugin.settings.folderNoteName, oldFileName)
|| oldFileName;
const 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);
}
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);
}
}
async function renameFolderOnFileRename(
file: TFile,
oldPath: string,
oldFolder: TAbstractFile,
plugin: FolderNotesPlugin,
): Promise<void> {
const newFolderName = extractFolderName(plugin.settings.folderNoteName, file.basename);
if (!newFolderName) {
removeCSSClassFromFileExplorerEL(oldFolder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin);
return;
} else if (newFolderName === oldFolder.name) {
addCSSClassToFileExplorerEl(oldFolder.path, 'has-folder-note', false, plugin);
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
return;
}
let newFolderPath = '';
if (plugin.settings.storageLocation === 'insideFolder') {
if (oldFolder.parent?.path === '/') {
newFolderPath = `${newFolderName}`;
} else {
newFolderPath = oldFolder.parent?.path + '/' + newFolderName;
}
} else {
const parentFolderPath = getFolderPathFromString(file.path);
if (parentFolderPath.trim() === '' || parentFolderPath.trim() === '/') {
newFolderPath = `${newFolderName}`;
} else {
newFolderPath = `${parentFolderPath}/${newFolderName}`;
}
}
if (plugin.app.vault.getAbstractFileByPath(newFolderPath)) {
await plugin.app.fileManager.renameFile(file, oldPath);
new Notice('A folder with the same name already exists');
return;
}
plugin.app.fileManager.renameFile(oldFolder, newFolderPath);
}
function updateExcludedFolderPath(
folder: TFolder,
oldPath: string,
plugin: FolderNotesPlugin,
): void {
const excludedFolders = plugin.settings.excludeFolders.filter(
(excludedFolder) => excludedFolder.path?.includes(oldPath),
);
excludedFolders.forEach((excludedFolder) => {
if (excludedFolder.path === oldPath) {
excludedFolder.path = folder.path;
return;
}
if (!excludedFolder.path) return;
const folders = excludedFolder.path.split('/');
if (folders.length < 1) {
folders.push(excludedFolder.path);
}
folders[folders.indexOf(folder.name)] = folder.name;
excludedFolder.path = folders.join('/');
});
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

@ -0,0 +1,106 @@
import { CustomEventEmitter } from 'src/events/EventEmitter';
export class ListComponent {
emitter: CustomEventEmitter;
containerEl: HTMLElement;
controlEl: HTMLElement;
emptyStateEl!: HTMLElement;
listEl: HTMLElement;
values!: string[];
defaultValues: string[];
constructor(containerEl: HTMLElement, values: string[] = [], defaultValues: string[] = []) {
this.emitter = new CustomEventEmitter();
this.containerEl = containerEl;
this.controlEl = containerEl.querySelector('.setting-item-control') || containerEl;
this.listEl = this.controlEl.createDiv('setting-command-hotkeys');
this.addResetButton();
this.setValues(values);
this.defaultValues = defaultValues;
}
on(event: string, listener: (data?: unknown) => void): void {
this.emitter.on(event, listener);
}
off(event: string, listener: (data?: unknown) => void): void {
this.emitter.off(event, listener);
}
private emit(event: string, data?: unknown): void {
this.emitter.emit(event, data);
}
setValues(values: string[]): void {
this.removeElements();
this.values = values;
if (values.length !== 0) {
values.forEach((value) => {
this.addElement(value);
});
}
this.emit('update', this.values);
}
removeElements(): void {
this.listEl.empty();
}
addElement(value: string): void {
this.listEl.createSpan('setting-hotkey', (span) => {
if (value.toLocaleLowerCase() === 'md') {
const markdown = '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(() => {
this.removeValue(value);
span.remove();
});
});
}
async addValue(value: string): Promise<void> {
this.values.push(value);
this.addElement(value);
this.emit('add', value);
this.emit('update', this.values);
}
addResetButton(): this {
// eslint-disable-next-line max-len
const resetButton = this.controlEl.createEl('span', { cls: 'clickable-icon setting-restore-hotkey-button' });
// eslint-disable-next-line max-len
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="svg-icon lucide-rotate-ccw"><path d="M3 2v6h6"></path><path d="M3 13a9 9 0 1 0 3-7.7L3 8"></path></svg>';
const svgElement = resetButton.createEl('span', { cls: 'ofn-list-item-remove-icon' });
const parser = new DOMParser();
const svgDoc = parser.parseFromString(svg, 'image/svg+xml');
const svgNode = svgDoc.documentElement;
if (svgNode) {
svgElement.appendChild(activeDocument.importNode(svgNode, true));
}
resetButton.onClickEvent(() => {
this.setValues(this.defaultValues);
});
return this;
}
removeValue(value: string): void {
this.values = this.values.filter((v) => v !== value);
this.listEl.find(`[extension='${value}']`).remove();
this.emit('remove', value);
this.emit('update', this.values);
}
}

View file

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

View file

@ -0,0 +1,623 @@
/* eslint-disable complexity */
/* eslint-disable max-len */
import type 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 DeleteConfirmationModal from '../modals/DeleteConfirmation';
import {
addExcludedFolder,
deleteExcludedFolder,
getDetachedFolder,
getExcludedFolder,
updateExcludedFolder,
} from '../ExcludeFolders/functions/folderFunctions';
import { ExcludedFolder } from '../ExcludeFolders/ExcludeFolder';
import { getDefaultTemplate, 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';
export async function createFolderNote(
plugin: FolderNotesPlugin,
folderPath: string,
openFile: boolean,
extension?: string,
displayModal?: boolean,
preexistingNote?: TFile,
): Promise<void> {
let {
leaf,
fileName,
folderNote,
folderNoteType,
detachedFolder,
path,
} = getArgs(plugin, folderPath, extension, preexistingNote);
if (folderNoteType === '.excalidraw') {
folderNoteType = '.md';
extension = '.excalidraw';
} else if (folderNoteType === '.ask') {
if (plugin.askModalCurrentlyOpen) return;
return new AskForExtensionModal(
plugin,
folderPath,
openFile,
folderNoteType,
displayModal,
preexistingNote,
).open();
}
if (plugin.settings.storageLocation === 'parentFolder') {
const parentFolderPath = getFolderPathFromString(folderPath);
if (parentFolderPath.trim() === '') {
path = `${fileName}${folderNoteType}`;
} else {
path = `${parentFolderPath}/${fileName}${folderNoteType}`;
}
} else if (plugin.settings.storageLocation === 'vaultFolder') {
path = `${fileName}${folderNoteType}`;
} else {
path = `${folderPath}/${fileName}${folderNoteType}`;
}
if (detachedFolder && folderNote?.extension !== extension && folderNote) {
await handleTurnNoteIntoFolderNote(plugin, folderNote, detachedFolder, folderPath, fileName);
}
if (!extension) {
extension = folderNoteType;
}
if (!folderNote) {
folderNote = await handleCreateFolderNote(plugin, folderNoteType, openFile, leaf, folderNote, path, extension);
} else {
await plugin.app.fileManager.renameFile(folderNote, path);
}
if (openFile) {
if (plugin.app.workspace.getActiveFile()?.path === path) {
removeActiveFolder(plugin);
const folder = getFolder(plugin, folderNote);
if (!folder) { return; }
setActiveFolder(folder.path, plugin);
}
await leaf.openFile(folderNote);
if (plugin.settings.folderNoteType === '.excalidraw' || extension === '.excalidraw') {
openExcalidrawView(plugin.app, folderNote);
}
}
const matchingExtension =
extension?.split('.').pop() ===
plugin.settings.templatePath.split('.').pop();
if (folderNote && matchingExtension && plugin.settings.folderNoteType !== '.excalidraw') {
applyTemplate(plugin, folderNote, leaf, plugin.settings.templatePath);
}
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
if (!(folder instanceof TFolder)) return;
addCSSClassToFileExplorerEl(path, 'is-folder-note', false, plugin, true);
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;
const detachedExcludedFolder = getDetachedFolder(plugin, folder.path);
if (folderNote) {
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}`;
plugin.app.fileManager.renameFile(folderNote, newPath).then(() => {
if (!excludedFolder) return;
if (!excludedFolderExisted) {
deleteExcludedFolder(plugin, excludedFolder);
} else if (!disabledSync) {
excludedFolder.disableSync = false;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
});
}
const folderName = folder.name;
const fileName = plugin.settings.folderNoteName.replace('{{folder_name}}', folderName);
let path = `${folder.path}/${fileName}.${extension}`;
if (plugin.settings.storageLocation === 'parentFolder') {
const parentFolderPath = folder.parent?.path;
if (!parentFolderPath) return;
if (parentFolderPath.trim() === '') {
path = `${fileName}.${extension}`;
} else {
path = `${parentFolderPath}/${fileName}.${extension}`;
}
}
if (detachedExcludedFolder) {
void 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);
removeActiveFolder(plugin);
setActiveFolder(folder.path, plugin);
}
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.disableSync = true;
addExcludedFolder(plugin, excludedFolder);
} else if (!excludedFolder.disableSync) {
disabledSync = false;
excludedFolder.disableSync = true;
updateExcludedFolder(plugin, excludedFolder, excludedFolder);
}
return [excludedFolder, excludedFolderExisted, disabledSync];
}
export async function openFolderNote(
plugin: FolderNotesPlugin,
file: TAbstractFile,
evt?: MouseEvent,
): Promise<void> {
const { path } = file;
const focusExistingTab = plugin.settings.focusExistingTab && plugin.settings.openInNewTab;
const activeFilePath = plugin.app.workspace.getActiveFile()?.path;
// If already active and not opening in new tab, do nothing
if (activeFilePath === path && !(Keymap.isModEvent(evt) === 'tab')) {
return;
}
// Try to find an existing tab with this file open
let foundLeaf = null;
if (focusExistingTab && file instanceof TFile) {
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (
leaf.getViewState().type === 'markdown' &&
(leaf.view as MarkdownView).file?.path === path
) {
foundLeaf = leaf;
}
});
}
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);
if (file instanceof TFile) {
await leaf.openFile(file);
}
}
}
export async function deleteFolderNote(
plugin: FolderNotesPlugin,
file: TFile,
displayModal: boolean,
): Promise<void> {
if (plugin.settings.showDeleteConfirmation && displayModal) {
return new DeleteConfirmationModal(plugin.app, plugin, file).open();
}
const folder = getFolder(plugin, file);
if (!folder) return;
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(excludedFolder) => (excludedFolder.path !== folder.path) || !excludedFolder.showFolderNote);
plugin.saveSettings(false);
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
switch (plugin.settings.deleteFilesAction) {
case 'trash':
await plugin.app.vault.trash(file, true);
break;
case 'obsidianTrash':
await plugin.app.vault.trash(file, false);
break;
case 'delete':
await plugin.app.vault.delete(file);
break;
}
}
export function extractFolderName(template: string, changedFileName: string): string | null {
const [prefix, suffix] = template.split('{{folder_name}}');
if (prefix.trim() === '' && suffix.trim() === '') {
return changedFileName;
}
if (!changedFileName.startsWith(prefix) || !changedFileName.endsWith(suffix)) {
return null;
}
if (changedFileName.startsWith(prefix) && prefix.trim() !== '') {
return changedFileName.slice(prefix.length).replace(suffix, '');
} else if (changedFileName.endsWith(suffix) && suffix.trim() !== '') {
return changedFileName.slice(0, -suffix.length);
}
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;
}
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);
const path = buildFullPath(folder, fileName);
const primaryType = normalizeFolderNoteType(plugin.settings.folderNoteType);
return findFolderNoteFile(plugin, path, primaryType);
}
export function detachFolderNote(plugin: FolderNotesPlugin, file: TFile): void {
const folder = getFolder(plugin, file);
if (!folder) return;
const excludedFolder = new ExcludedFolder(
folder.path,
plugin.settings.excludeFolders.length,
undefined,
plugin,
);
excludedFolder.showFolderNote = true;
excludedFolder.hideInSettings = true;
excludedFolder.disableFolderNote = true;
excludedFolder.disableSync = true;
excludedFolder.subFolders = false;
excludedFolder.excludeFromFolderOverview = false;
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 {
if (!file) return null;
let folderName = extractFolderName(plugin.settings.folderNoteName, file.basename);
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 (folderPath.trim() === '' || folderPath === '/') {
folderPath = folderName;
} else {
folderPath = `${folderPath}/${folderName}`;
}
folder = plugin.app.vault.getAbstractFileByPath(folderPath);
} 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 {
if (!folderNote) return null;
let filePath = '';
if (typeof folderNote === 'string') {
filePath = folderNote;
} else {
fileName = folderNote.basename;
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') {
if (folderPath.trim() === '') {
folderPath = folderName;
} else {
folderPath = `${folderPath}/${folderName}`;
}
} else {
folderPath = getFolderPathFromString(filePath);
}
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
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

@ -0,0 +1,253 @@
import { TFile, TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
import {
getDetachedFolder,
getExcludedFolder,
addExcludedFolder,
} from 'src/ExcludeFolders/functions/folderFunctions';
import { getFolder, getFolderNote } from 'src/functions/folderNoteFunctions';
import { getFileExplorer } from './utils';
import { ExcludedFolder } from 'src/ExcludeFolders/ExcludeFolder';
import type FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
/**
* @description Refreshes the CSS classes for all folder notes in the file explorer.
*/
export function refreshAllFolderStyles(forceReload = false, plugin: FolderNotesPlugin): void {
const fileExplorer = getFileExplorer(plugin);
if (!fileExplorer) { return; }
if (plugin.activeFileExplorer === fileExplorer && !forceReload) { return; }
plugin.activeFileExplorer = fileExplorer;
plugin.app.vault.getAllLoadedFiles().forEach(async (file) => {
if (file instanceof TFolder) {
await updateCSSClassesForFolder(file.path, plugin);
}
});
}
/**
* @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> {
const folder = plugin.app.vault.getAbstractFileByPath(folderPath);
if (!folder || !(folder instanceof TFolder)) { return; }
const folderNote = getFolderNote(plugin, folder.path);
const detachedFolderNote = getDetachedFolder(plugin, folder.path);
if (folder.children.length === 0) {
addCSSClassToFileExplorerEl(folder.path, 'fn-empty-folder', false, plugin);
}
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;
}
const excludedFolder = getExcludedFolder(plugin, folder.path, true);
if (excludedFolder?.disableFolderNote) {
removeCSSClassFromFileExplorerEL(folderNote.path, 'is-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder?.path, 'only-has-folder-note', true, plugin);
} else {
markFolderWithFolderNoteClasses(folder, plugin);
if (excludedFolder?.showFolderNote) {
addCSSClassToFileExplorerEl(folder.path, 'show-folder-note-in-explorer', true, plugin);
unmarkFileAsFolderNote(folderNote, plugin);
return;
}
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
addCSSClassToFileExplorerEl(folder.path, 'only-has-folder-note', true, plugin);
} else {
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
}
markFolderAndNoteWithClasses(folderNote, folder, plugin);
}
/**
* @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> {
const file = plugin.app.vault.getAbstractFileByPath(filePath);
if (!file || !(file instanceof TFile)) { return; }
const folder = getFolder(plugin, file);
if (!folder || !(folder instanceof TFolder)) { return; }
updateCSSClassesForFolder(folder.path, plugin);
}
export function markFolderAndNoteWithClasses(
file: TFile,
folder: TFolder,
plugin: FolderNotesPlugin,
): void {
markFileAsFolderNote(file, plugin);
markFolderWithFolderNoteClasses(folder, plugin);
}
export function clearFolderAndNoteClasses(
folder: TFolder,
file: TFile,
plugin: FolderNotesPlugin,
): void {
unmarkFileAsFolderNote(file, plugin);
clearFolderNoteClassesFromFolder(folder, plugin);
}
export function markFolderWithFolderNoteClasses(folder: TFolder, plugin: FolderNotesPlugin): void {
addCSSClassToFileExplorerEl(folder.path, 'has-folder-note', false, plugin);
if (plugin.isEmptyFolderNoteFolder(folder) && getFolderNote(plugin, folder.path)) {
addCSSClassToFileExplorerEl(folder.path, 'only-has-folder-note', true, plugin);
} else {
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
}
export function markFileAsFolderNote(file: TFile, plugin: FolderNotesPlugin): void {
addCSSClassToFileExplorerEl(file.path, 'is-folder-note', false, plugin);
}
export function unmarkFileAsFolderNote(file: TFile, plugin: FolderNotesPlugin): void {
removeCSSClassFromFileExplorerEL(file.path, 'is-folder-note', false, plugin);
}
export function unmarkFolderAsFolderNote(folder: TFolder, plugin: FolderNotesPlugin): void {
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
export function clearFolderNoteClassesFromFolder(folder: TFolder, plugin: FolderNotesPlugin): void {
removeCSSClassFromFileExplorerEL(folder.path, 'has-folder-note', false, plugin);
removeCSSClassFromFileExplorerEL(folder.path, 'only-has-folder-note', true, plugin);
}
/**
* @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> {
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));
addCSSClassToFileExplorerEl(path, cssClass, parent, plugin, waitForCreate, count + 1);
return;
}
return;
}
if (parent) {
const parentElement = fileExplorerItem?.parentElement;
if (parentElement) {
parentElement.addClass(cssClass);
}
} else {
fileExplorerItem.addClass(cssClass);
activeDocument.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
item.addClass(cssClass);
});
}
}
/**
* @param path Can be a folder or file path
* @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 {
if (!path) return;
const fileExplorerItem = getFileExplorerElement(path, plugin);
activeDocument.querySelectorAll(`[data-path='${CSS.escape(path)}']`).forEach((item) => {
item.removeClass(cssClass);
});
if (!fileExplorerItem) { return; }
if (parent) {
const parentElement = fileExplorerItem?.parentElement;
if (parentElement) {
parentElement.removeClass(cssClass);
}
return;
}
fileExplorerItem.removeClass(cssClass);
}
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,
);
excludedFolder.subFolders = false;
excludedFolder.disableSync = false;
excludedFolder.disableAutoCreate = false;
excludedFolder.disableFolderNote = false;
excludedFolder.enableCollapsing = false;
excludedFolder.excludeFromFolderOverview = false;
excludedFolder.hideInSettings = true;
excludedFolder.showFolderNote = true;
addExcludedFolder(plugin, excludedFolder, false);
addCSSClassToFileExplorerEl(path, 'show-folder-note-in-explorer', true, plugin);
updateCSSClassesForFolder(path, plugin);
}
export function hideFolderNoteInFileExplorer(folderPath: string, plugin: FolderNotesPlugin): void {
plugin.settings.excludeFolders = plugin.settings.excludeFolders.filter(
(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 {
const fileExplorerItem = getFileExplorerElement(folderPath, plugin);
if (fileExplorerItem) {
fileExplorerItem.addClass('fn-is-active');
plugin.activeFolderDom = fileExplorerItem;
}
}
export function removeActiveFolder(plugin: FolderNotesPlugin): void {
if (plugin.activeFolderDom) {
plugin.activeFolderDom.removeClass('fn-is-active');
plugin.activeFolderDom?.removeClass('has-focus');
plugin.activeFolderDom = null;
}
}

111
src/functions/utils.ts Normal file
View file

@ -0,0 +1,111 @@
import { TFolder, TFile, View, type TAbstractFile } from 'obsidian';
import type { FileExplorerWorkspaceLeaf, FileExplorerView } from 'src/globals';
import { getFolderNote } from './folderNoteFunctions';
import type FolderNotesPlugin from 'src/main';
import type { FileTreeItem } from 'obsidian-typings';
import type FolderOverviewPlugin from 'src/obsidian-folder-overview/src/main';
function isFileExplorerWorkspaceLeaf(leaf: unknown): leaf is FileExplorerWorkspaceLeaf {
const tabHeaderEl = (leaf as { tabHeaderEl?: HTMLElement | null } | null)?.tabHeaderEl;
return tabHeaderEl?.dataset?.type === 'file-explorer';
}
export function getFileNameFromPathString(path: string): string {
return path.substring(path.lastIndexOf('/') >= 0 ? path.lastIndexOf('/') + 1 : 0);
}
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(LAST_FOLDER_INDEX)[0];
}
export function removeExtension(name: string): string {
return name.replace(/\.[^/.]+$/, '');
}
export function getExtensionFromPathString(path: string): string {
return path.slice(path.lastIndexOf('.') >= 0 ? path.lastIndexOf('.') : 0);
}
export function getFolderPathFromString(path: string): string {
const subString = path.lastIndexOf('/') >= 0 ? path.lastIndexOf('/') : 0;
const folderPath = path.substring(0, subString);
if (folderPath === '') {
return '/';
}
return folderPath;
}
export function getParentFolderPath(path: string): string {
return getFolderPathFromString(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 getFileExplorerView(plugin: FolderNotesPlugin): FileExplorerView | undefined {
return getFileExplorer(plugin)?.view;
}
export function getFocusedItem(plugin: FolderNotesPlugin): FileTreeItem | null {
const fileExplorer = getFileExplorer(plugin);
if (!fileExplorer) { return null; }
const { focusedItem } = fileExplorer.view.tree;
return focusedItem;
}
export function getFileExplorerActiveFolder(plugin: FolderNotesPlugin): TFolder | null {
// Check if the active view is a file explorer.
const view = plugin.app.workspace.getActiveViewOfType(View);
if (view?.getViewType() !== 'file-explorer') return null;
// Check if there is a focused or active item in the file explorer.
const fe = view as FileExplorerView;
const activeFileOrFolder =
fe.tree.focusedItem?.file ?? fe.activeDom?.file;
if (!(activeFileOrFolder instanceof TFolder)) return null;
return activeFileOrFolder;
}
export function getFileExplorerActiveFolderNote(plugin: FolderNotesPlugin): TFile | null {
const folder = getFileExplorerActiveFolder(plugin);
if (!folder) return null;
// Is there any folder note for the active folder?
const folderNote = getFolderNote(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 + '/');
}

80
src/globals.d.ts vendored
View file

@ -1,25 +1,32 @@
import { Plugin, TAbstractFile, View, WorkspaceLeaf } from 'obsidian';
import type { TAbstractFile, TFile, TFolder, View, WorkspaceLeaf } from 'obsidian';
declare module 'obsidian' {
interface App {
internalPlugins: {
plugins: {
[pluginId: string]: Plugin & {
[pluginImplementations: string]: unknown;
};
};
enablePlugin: (id: string) => Promise<void>;
disablePlugin: (id: string) => Promise<void>;
};
interface Setting {
createList: (list: ListComponent | ((list: ListComponent) => void)) => ListComponent;
}
interface Vault {
getConfig: (config: string) => unknown;
interface TFolder {
newName: string | null;
collapsed: boolean;
}
interface MenuItem {
dom: HTMLElement;
}
interface FileManager {
promptForFolderDeletion: (folder: TFolder) => void;
}
class ListComponent {
containerEl: HTMLElement;
emptyStateEl: HTMLElement;
listEl: HTMLElement;
values: string[];
constructor(containerEl: HTMLElement);
}
}
interface FileExplorerWorkspaceLeaf extends WorkspaceLeaf {
containerEl: HTMLElement;
view: FileExplorerView;
containerEl: HTMLElement;
view: FileExplorerView;
}
interface FileExplorerViewFileItem extends TAbstractFile {
@ -27,6 +34,45 @@ interface FileExplorerViewFileItem extends TAbstractFile {
selfEl: HTMLElement
}
interface FileExplorerView extends View {
fileItems: { [path: string]: FileExplorerViewFileItem };
type FileOrFolderItem = FolderItem | FileItem;
interface FileItem {
el: HTMLDivElement;
file: TFile;
fileExplorer: FileExplorerView;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
}
interface FolderItem {
el: HTMLDivElement;
fileExplorer: FileExplorerView;
selfEl: HTMLDivElement;
innerEl: HTMLDivElement;
file: TFolder;
children: FileOrFolderItem[];
childrenEl: HTMLDivElement;
collapseIndicatorEl: HTMLDivElement;
collapsed: boolean;
setCollapsed: (collapsed: boolean) => void;
pusherEl: HTMLDivElement;
}
interface TreeItem {
focusedItem: FileOrFolderItem;
setFocusedItem: (item: FileOrFolderItem, moveViewport: boolean) => void;
selectedDoms: Set<FileOrFolderItem>;
}
interface FileExplorerView extends View {
fileItems: { [path: string]: FileExplorerViewFileItem };
activeDom: FileOrFolderItem;
tree: TreeItem;
}
declare global {
interface Window {
i18next: {
t: (key: string, options?: { [key: string]: string }) => string;
};
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,62 @@
import { Modal, Setting, Notice, type App, type SettingTab } from 'obsidian';
import type FolderNotesPlugin from '../main';
import type { ListComponent } from 'src/functions/ListComponent';
export default class AddSupportedFileModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
name: string;
list: ListComponent;
settingsTab: SettingTab;
constructor(app: App, plugin: FolderNotesPlugin, settingsTab: SettingTab, list: ListComponent) {
super(app);
this.plugin = plugin;
this.app = app;
this.name = '';
this.list = list;
this.settingsTab = settingsTab;
}
onOpen(): void {
const { contentEl } = this;
// close when user presses enter
contentEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.close();
}
});
contentEl.createEl('h2', { text: 'Extension name' });
new Setting(contentEl)
.setName('Enter the name of the extension (only the short form, e.g. "md")')
.addText((text) =>
text
.setValue('')
.onChange(async (value) => {
if (value.trim() !== '') {
this.name = value.trim();
}
}),
);
}
onClose(): void {
if (this.name.toLocaleLowerCase() === 'markdown') {
this.name = 'md';
}
const { contentEl } = this;
if (this.name === '') {
contentEl.empty();
this.settingsTab.display();
} else if (this.plugin.settings.supportedFileTypes.includes(this.name.toLowerCase())) {
new Notice('This extension is already supported');
return;
} 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();
})();
}
}
}

View file

@ -0,0 +1,52 @@
import { FuzzySuggestModal, type TFile } from 'obsidian';
import type FolderNotesPlugin from 'src/main';
import { createFolderNote } from 'src/functions/folderNoteFunctions';
export class AskForExtensionModal extends FuzzySuggestModal<string> {
plugin: FolderNotesPlugin;
extension: string;
folderPath: string;
openFile: boolean;
useModal: boolean | undefined;
existingNote: TFile | undefined;
constructor(
plugin: FolderNotesPlugin,
folderPath: string,
openFile: boolean,
extension: string,
useModal?: boolean,
existingNote?: TFile,
) {
super(plugin.app);
this.plugin = plugin;
this.folderPath = folderPath;
this.extension = extension;
this.openFile = openFile;
this.useModal = useModal;
this.existingNote = existingNote;
plugin.askModalCurrentlyOpen = true;
}
getItems(): string[] {
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;
this.extension = '.' + item;
void createFolderNote(
this.plugin,
this.folderPath,
this.openFile,
this.extension,
this.useModal,
this.existingNote,
);
this.close();
}
}

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,87 @@
import { Modal, Platform, type App, type TFile } from 'obsidian';
import type FolderNotesPlugin from '../main';
import { deleteFolderNote } from 'src/functions/folderNoteFunctions';
export default class DeleteConfirmationModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
file: TFile;
constructor(app: App, plugin: FolderNotesPlugin, file: TFile) {
super(app);
this.plugin = plugin;
this.app = app;
this.file = file;
}
onOpen(): void {
const { contentEl, plugin } = this;
const modalTitle = contentEl.createDiv({ cls: 'fn-modal-title' });
const modalContent = contentEl.createDiv({ cls: 'fn-modal-content' });
modalTitle.createEl('h2', { text: 'Delete folder note' });
// eslint-disable-next-line max-len
modalContent.createEl('p', { text: `Are you sure you want to delete the folder note '${this.file.name}' ?` });
switch (plugin.settings.deleteFilesAction) {
case 'trash':
modalContent.createEl('p', { text: 'It will be moved to your system trash.' });
break;
case 'obsidianTrash':
// eslint-disable-next-line max-len
modalContent.createEl('p', { text: 'It will be moved to your Obsidian trash, which is located in the ".trash" hidden folder in your vault.' });
break;
case 'delete':
modalContent
.createEl('p', { text: 'It will be permanently deleted.' })
.setCssStyles({ color: 'red' });
break;
}
const buttonContainer = contentEl.createEl('div', { cls: 'modal-button-container' });
if (!Platform.isMobile) {
const checkbox = buttonContainer.createEl('label', { cls: 'mod-checkbox' });
checkbox.tabIndex = -1;
const input = checkbox.createEl('input', { type: 'checkbox' });
checkbox.appendText('Don\'t ask again');
input.addEventListener('change', (e) => {
const target = e.target as HTMLInputElement;
if (target.checked) {
plugin.settings.showDeleteConfirmation = false;
} else {
plugin.settings.showDeleteConfirmation = true;
}
plugin.saveSettings();
});
} else {
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();
this.close();
deleteFolderNote(plugin, this.file, false);
});
}
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',
});
cancelButton.addEventListener('click', async () => {
this.close();
});
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}

View file

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

View file

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

View file

@ -0,0 +1,83 @@
import { Modal, type App, type TFolder } from 'obsidian';
import type FolderNotesPlugin from '../main';
export default class NewFolderNameModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
folder: TFolder;
constructor(app: App, plugin: FolderNotesPlugin, folder: TFolder) {
super(app);
this.plugin = plugin;
this.app = app;
this.folder = folder;
}
onOpen(): void {
const { contentEl } = this;
contentEl.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
this.saveFolderName();
this.close();
}
});
this.modalEl.classList.add('mod-file-rename');
const modalTitle = this.modalEl.querySelector('div.modal-title');
if (modalTitle) {
modalTitle.textContent = 'Folder title';
}
const textarea = contentEl.createEl('textarea', {
text: this.folder.name.replace(this.plugin.settings.folderNoteType, ''),
attr: {
placeholder: 'Enter the name of the folder',
rows: '1',
spellcheck: 'false',
class: 'rename-textarea',
},
});
textarea.addEventListener('focus', function () {
this.select();
});
textarea.focus();
const buttonContainer = this.modalEl.createDiv({ cls: 'modal-button-container' });
const saveButton = buttonContainer.createEl('button', { text: 'Save', cls: 'mod-cta' });
saveButton.addEventListener('click', async () => {
this.saveFolderName();
this.close();
});
const cancelButton = buttonContainer.createEl('button', {
text: 'Cancel',
cls: 'mod-cancel',
});
cancelButton.addEventListener('click', () => {
this.close();
});
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
saveFolderName(): void {
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);
}
}
}
}
}

View file

@ -1,48 +0,0 @@
import { App, Modal, Setting, TFolder } from 'obsidian';
import FolderNotesPlugin from '../main';
export default class ConfirmationModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
folder: TFolder;
constructor(app: App, plugin: FolderNotesPlugin) {
super(app);
this.plugin = plugin;
this.app = app;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'Create folder note for every folder' });
const setting = new Setting(contentEl);
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.' });
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.' });
setting.infoEl.parentElement?.classList.add('fn-confirmation-modal');
const button = setting.infoEl.createEl('button', { text: 'Create' });
button.classList.add('mod-warning', 'fn-confirmation-modal-button');
button.addEventListener('click', async () => {
this.close();
this.app.vault.getAllLoadedFiles().forEach(async (folder) => {
if (folder instanceof TFolder) {
const excludedFolder = this.plugin.settings.excludeFolders.find(
(excludedFolder) => (excludedFolder.path === folder.path) ||
(excludedFolder.path === folder.path?.slice(0, folder?.path.lastIndexOf('/') >= 0 ? folder.path?.lastIndexOf('/') : folder.path.length)
&& excludedFolder.subFolders));
if (excludedFolder) return;
const path = folder.path + '/' + this.plugin.settings.folderNoteName.replace('{{folder_name}}', folder.name) + '.md';
if (this.app.vault.getAbstractFileByPath(path)) return;
await this.plugin.createFolderNote(folder.path + '/' + folder.name + '.md', true);
}
});
});
button.focus();
const cancelButton = setting.infoEl.createEl('button', { text: 'Cancel' });
cancelButton.addEventListener('click', async () => {
this.close();
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -1,68 +0,0 @@
import { App, Modal, Setting, TFile, Platform } from 'obsidian';
import FolderNotesPlugin from '../main';
export default class DeleteConfirmationModal extends Modal {
plugin: FolderNotesPlugin;
app: App;
file: TFile;
constructor(app: App, plugin: FolderNotesPlugin, file: TFile) {
super(app);
this.plugin = plugin;
this.app = app;
this.file = file;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl('h2', { text: 'Delete folder note' });
const setting = new Setting(contentEl);
setting.infoEl.createEl('p', { text: `Are you sure you want to delete the folder note "${this.file.name}" ?` });
setting.infoEl.createEl('p', { text: 'It will be moved to your system trash.' });
setting.infoEl.parentElement?.classList.add('fn-delete-confirmation-modal');
// Create a container for the buttons and the checkbox
const buttonContainer = setting.infoEl.createEl('div', { cls: 'fn-delete-confirmation-modal-buttons' });
if (Platform.isMobileApp) {
const confirmButton = buttonContainer.createEl('button', { text: 'Delete and don\'t ask again' });
confirmButton.classList.add('mod-warning', 'fn-confirmation-modal-button');
confirmButton.addEventListener('click', async () => {
this.plugin.settings.showDeleteConfirmation = false;
this.plugin.saveSettings();
this.close();
this.plugin.removeCSSClassFromEL(this.file.parent.path, 'has-folder-note');
this.app.vault.delete(this.file);
});
} else {
const checkbox = buttonContainer.createEl('input', { type: 'checkbox' });
checkbox.addEventListener('change', (e) => {
const target = e.target as HTMLInputElement;
if (target.checked) {
this.plugin.settings.showDeleteConfirmation = false;
} else {
this.plugin.settings.showDeleteConfirmation = true;
}
this.plugin.saveSettings();
});
const checkBoxText = buttonContainer.createEl('span', { text: 'Don\'t ask again' });
checkBoxText.addEventListener('click', () => {
checkbox.click();
});
}
const button = buttonContainer.createEl('button', { text: 'Delete' });
button.classList.add('mod-warning', 'fn-confirmation-modal-button');
button.addEventListener('click', async () => {
this.close();
this.plugin.removeCSSClassFromEL(this.file.parent.path, 'has-folder-note');
this.app.vault.delete(this.file);
});
button.focus();
const cancelButton = buttonContainer.createEl('button', { text: 'Cancel' });
cancelButton.addEventListener('click', async () => {
this.close();
});
}
onClose() {
const { contentEl } = this;
contentEl.empty();
}
}

@ -0,0 +1 @@
Subproject commit f047859cc9a7f1c04ad35ecdc440a31a300bc6c2

View file

@ -1,450 +0,0 @@
import { App, Notice, Platform, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian';
import FolderNotesPlugin from './main';
import { FolderSuggest } from './suggesters/FolderSuggester';
import ExcludedFolderSettings from './modals/exludeFolderSettings';
import { TemplateSuggest } from './suggesters/templateSuggester';
// import ConfirmationModal from "./modals/confirmCreation";
export interface FolderNotesSettings {
syncFolderName: boolean;
ctrlKey: boolean;
altKey: boolean;
hideFolderNote: boolean;
templatePath: string;
autoCreate: boolean;
enableCollapsing: boolean;
excludeFolders: ExcludedFolder[];
showDeleteConfirmation: boolean;
underlineFolder: boolean;
allowWhitespaceCollapsing: boolean;
underlineFolderInPath: boolean;
openFolderNoteOnClickInPath: boolean;
openInNewTab: boolean;
folderNoteName: string;
oldFolderNoteName: string;
folderNoteType: '.md' | '.canvas';
disableFolderHighlighting: boolean;
}
export const DEFAULT_SETTINGS: FolderNotesSettings = {
syncFolderName: true,
ctrlKey: true,
altKey: false,
hideFolderNote: true,
templatePath: '',
autoCreate: false,
enableCollapsing: false,
excludeFolders: [],
showDeleteConfirmation: true,
underlineFolder: true,
allowWhitespaceCollapsing: false,
underlineFolderInPath: true,
openFolderNoteOnClickInPath: true,
openInNewTab: false,
folderNoteName: '{{folder_name}}',
folderNoteType: '.md',
disableFolderHighlighting: false,
oldFolderNoteName: '{{folder_name}}',
};
export class SettingsTab extends PluginSettingTab {
plugin: FolderNotesPlugin;
app: App;
excludeFolders: ExcludedFolder[];
constructor(app: App, plugin: FolderNotesPlugin) {
super(app, plugin);
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Folder notes settings' });
new Setting(containerEl)
.setName('Folder note name')
.setDesc('{{folder_name}} will be replaced with the name of the folder')
.addText((text) =>
text
.setValue(this.plugin.settings.folderNoteName)
.onChange(async (value) => {
if (value.trim() === '') { return; }
this.plugin.settings.folderNoteName = value;
await this.plugin.saveSettings();
})
)
.addButton((button) =>
button
.setButtonText('Rename existing folder notes')
.setCta()
.onClick(async () => {
this.updateFolderNotes(this.plugin.settings.oldFolderNoteName, this.plugin.settings.folderNoteName);
})
);
new Setting(containerEl)
.setName('Folder note type')
.setDesc('Choose the file type of the folder note (markdown or canvas)')
.addDropdown((dropdown) =>
dropdown
.addOption('.md', 'markdown')
.addOption('.canvas', 'canvas')
.setValue(this.plugin.settings.folderNoteType)
.onChange(async (value: '.md' | '.canvas') => {
this.plugin.settings.folderNoteType = value;
await this.plugin.saveSettings();
})
);
const disableSetting = new Setting(containerEl);
disableSetting.setName('Disable folder collapsing');
disableSetting.setDesc('Disable the ability to collapse folders by clicking exactly on the folder name');
disableSetting.addToggle((toggle) =>
toggle
.setValue(!this.plugin.settings.enableCollapsing)
.onChange(async (value) => {
this.plugin.settings.enableCollapsing = !value;
await this.plugin.saveSettings();
})
);
disableSetting.infoEl.appendText('Requires a restart to take effect');
disableSetting.infoEl.style.color = this.app.vault.getConfig('accentColor') as string || '#7d5bed';
new Setting(containerEl)
.setName('Only open folder notes through the name')
.setDesc('Only open folder notes in the file explorer by clicking on the folder name')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.allowWhitespaceCollapsing)
.onChange(async (value) => {
if (!value) {
document.body.classList.add('fn-whitespace-stop-collapsing');
} else {
document.body.classList.remove('fn-whitespace-stop-collapsing');
}
this.plugin.settings.allowWhitespaceCollapsing = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Hide folder note')
.setDesc('Hide the folder note in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.hideFolderNote)
.onChange(async (value) => {
this.plugin.settings.hideFolderNote = value;
await this.plugin.saveSettings();
if (value) {
document.body.classList.add('hide-folder-note');
} else {
document.body.classList.remove('hide-folder-note');
}
this.display();
})
);
new Setting(containerEl)
.setName('Sync folder name')
.setDesc('Automatically rename the folder note when the folder name is changed')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.syncFolderName)
.onChange(async (value) => {
this.plugin.settings.syncFolderName = value;
await this.plugin.saveSettings();
this.display();
})
);
if (Platform.isDesktop) {
const setting3 = new Setting(containerEl);
setting3.setName('Open folder note in a new tab by default');
setting3.setDesc('Always open folder notes in a new tab (except when you try to open the same note) instead of having to use ctrl/cmd + click to open in a new tab');
setting3.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.openInNewTab)
.onChange(async (value) => {
this.plugin.settings.openInNewTab = value;
await this.plugin.saveSettings();
this.display();
})
);
setting3.infoEl.appendText('Requires a restart to take effect');
setting3.infoEl.style.color = '#7d5bed';
}
new Setting(containerEl)
.setName('Automatically create folder notes')
.setDesc('Automatically create a folder note when a new folder is created')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.autoCreate)
.onChange(async (value) => {
this.plugin.settings.autoCreate = value;
await this.plugin.saveSettings();
this.display();
})
);
if (Platform.isDesktopApp) {
new Setting(containerEl)
.setName('Key for creating folder note')
.setDesc('The key combination to create a folder note')
.addDropdown((dropdown) => {
if (!Platform.isMacOS) {
dropdown.addOption('ctrl', 'Ctrl + Click');
} else {
dropdown.addOption('ctrl', 'Cmd + Click');
}
dropdown.addOption('alt', 'Alt + Click');
dropdown.setValue(this.plugin.settings.ctrlKey ? 'ctrl' : 'alt');
dropdown.onChange(async (value) => {
this.plugin.settings.ctrlKey = value === 'ctrl';
this.plugin.settings.altKey = value === 'alt';
await this.plugin.saveSettings();
this.display();
});
});
}
new Setting(containerEl)
.setName('Add underline to folders with folder notes')
.setDesc('Add an underline to folders that have a folder note in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.underlineFolder)
.onChange(async (value) => {
this.plugin.settings.underlineFolder = value;
if (value) {
document.body.classList.add('folder-note-underline');
} else {
document.body.classList.remove('folder-note-underline');
}
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Underline folders in the path')
.setDesc('Add an underline to folders that have a folder note in the path above a note')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.underlineFolderInPath)
.onChange(async (value) => {
this.plugin.settings.underlineFolderInPath = value;
if (value) {
document.body.classList.add('folder-note-underline-path');
} else {
document.body.classList.remove('folder-note-underline-path');
}
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName('Open folder note through path')
.setDesc('Open a folder note when clicking on a folder name in the path if it is a folder note')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.openFolderNoteOnClickInPath)
.onChange(async (value) => {
this.plugin.settings.openFolderNoteOnClickInPath = value;
await this.plugin.saveSettings();
})
);
/*
new Setting(containerEl)
.setName('Disable folder highlighting through path')
.setDesc('Disable the highlighting of folders in the path that have a folder note when you click on a folder name in the path')
.addToggle((toggle) =>
toggle
.setValue(this.plugin.settings.disableFolderHighlighting)
.onChange(async (value) => {
this.plugin.settings.disableFolderHighlighting = value;
await this.plugin.saveSettings();
})
);
*/
const setting = new Setting(containerEl);
const desc = document.createDocumentFragment();
desc.append(
'After setting the template path, restart Obsidian if the template folder path (from templater/templates) had been changed beforehand.',
desc.createEl('br'),
'Obsidian should also be restarted if the template path was removed.'
);
setting.setName('Template path');
setting.setDesc(desc).descEl.style.color = this.app.vault.getConfig('accentColor') as string || '#7d5bed';
setting.addSearch((cb) => {
new TemplateSuggest(cb.inputEl, this.plugin);
cb.setPlaceholder('Template path');
cb.setValue(this.plugin.app.vault.getAbstractFileByPath(this.plugin.settings.templatePath)?.name.replace('.md', '') || '');
cb.onChange(async (value) => {
if (value.trim() === '') {
this.plugin.settings.templatePath = '';
await this.plugin.saveSettings();
this.display();
return;
}
});
});
// Due to issue with templater it'll be disabled for now
// If you want to try it yourself make a pr
// The issue was that it only used the first folder for all of the other folder notes
/*
new Setting(containerEl)
.setName('Create folder note for every folder')
.setDesc('Create a folder note for every folder in the vault')
.addButton((cb) => {
cb.setIcon('plus');
cb.setTooltip('Create folder notes');
cb.onClick(async () => {
new ConfirmationModal(this.app, this.plugin).open();
});
});
*/
new Setting(containerEl)
.setHeading()
.setName('Manage excluded folders');
new Setting(containerEl)
.setName('Add excluded folder')
.addButton((cb) => {
cb.setIcon('plus');
cb.setClass('add-exclude-folder');
cb.setTooltip('Add excluded folder');
cb.onClick(() => {
const excludedFolder = new ExcludedFolder('', this.plugin.settings.excludeFolders.length);
this.addExcludeFolderListItem(containerEl, excludedFolder);
this.addExcludedFolder(excludedFolder);
this.display();
});
});
this.plugin.settings.excludeFolders.sort((a, b) => a.position - b.position).forEach((excludedFolder) => {
this.addExcludeFolderListItem(containerEl, excludedFolder);
});
}
addExcludeFolderListItem(containerEl: HTMLElement, excludedFolder: ExcludedFolder) {
const setting = new Setting(containerEl);
setting.setClass('fn-exclude-folder-list-item');
setting.addSearch((cb) => {
new FolderSuggest(
cb.inputEl,
this.plugin
);
// @ts-ignore
cb.containerEl.addClass('fn-exclude-folder-path');
cb.setPlaceholder('Folder path');
cb.setValue(excludedFolder.path);
cb.onChange((value) => {
if (!this.app.vault.getAbstractFileByPath(value)) return;
excludedFolder.path = value;
this.updateExcludedFolder(excludedFolder, excludedFolder);
});
});
setting.addButton((cb) => {
cb.setIcon('edit');
cb.setTooltip('Edit folder note');
cb.onClick(() => {
new ExcludedFolderSettings(this.app, this.plugin, excludedFolder).open();
});
});
setting.addButton((cb) => {
cb.setIcon('up-chevron-glyph');
cb.setTooltip('Move up');
cb.onClick(() => {
if (excludedFolder.position === 0) return;
excludedFolder.position -= 1;
this.updateExcludedFolder(excludedFolder, excludedFolder);
const oldExcludedFolder = this.plugin.settings.excludeFolders.find((folder) => folder.position === excludedFolder.position);
if (oldExcludedFolder) {
oldExcludedFolder.position += 1;
this.updateExcludedFolder(oldExcludedFolder, oldExcludedFolder);
}
this.display();
});
});
setting.addButton((cb) => {
cb.setIcon('down-chevron-glyph');
cb.setTooltip('Move down');
cb.onClick(() => {
if (excludedFolder.position === this.plugin.settings.excludeFolders.length - 1) return;
excludedFolder.position += 1;
this.updateExcludedFolder(excludedFolder, excludedFolder);
const oldExcludedFolder = this.plugin.settings.excludeFolders.find((folder) => folder.position === excludedFolder.position);
if (oldExcludedFolder) {
oldExcludedFolder.position -= 1;
this.updateExcludedFolder(oldExcludedFolder, oldExcludedFolder);
}
this.display();
});
});
setting.addButton((cb) => {
cb.setIcon('trash-2');
cb.setTooltip('Delete excluded folder');
cb.onClick(() => {
this.deleteExcludedFolder(excludedFolder);
setting.clear();
setting.settingEl.remove();
});
});
}
addExcludedFolder(excludedFolder: ExcludedFolder) {
this.plugin.settings.excludeFolders.push(excludedFolder);
this.plugin.saveSettings();
}
deleteExcludedFolder(excludedFolder: ExcludedFolder) {
this.plugin.settings.excludeFolders = this.plugin.settings.excludeFolders.filter((folder) => folder.path !== excludedFolder.path);
this.plugin.saveSettings();
}
updateExcludedFolder(excludedFolder: ExcludedFolder, newExcludeFolder: ExcludedFolder) {
this.plugin.settings.excludeFolders = this.plugin.settings.excludeFolders.filter((folder) => folder.path !== excludedFolder.path);
this.addExcludedFolder(newExcludeFolder);
}
updateFolderNotes(oldTemplate: string, newTemplate: string) {
this.plugin.settings.oldFolderNoteName = newTemplate;
this.plugin.saveSettings();
new Notice('Starting to update folder notes...');
this.app.vault.getFiles().forEach((file) => {
if (file instanceof TFile) {
const folder = this.app.vault.getAbstractFileByPath(this.plugin.getFolderPathFromString(file.path));
if (!(folder instanceof TFolder)) return;
let fileName = file.name.slice(0, -file.extension.length - 1);
fileName = this.plugin.extractFolderName(oldTemplate, fileName) || '';
if (fileName === folder?.name) {
const newPath = `${folder?.path}/${this.plugin.settings.folderNoteName.replace('{{folder_name}}', fileName)}.${file.extension}`;
this.app.vault.rename(file, newPath);
} else if (folder?.name === file.name.slice(0, -file.extension.length - 1) || '') {
const newPath = `${folder?.path}/${this.plugin.settings.folderNoteName.replace('{{folder_name}}', file.name.slice(0, -file.extension.length - 1) || '')}.${file.extension}`;
this.app.vault.rename(file, newPath);
}
}
});
new Notice('Finished updating folder notes');
}
}
export class ExcludedFolder {
path: string;
subFolders: boolean;
disableSync: boolean;
disableAutoCreate: boolean;
disableFolderNote: boolean;
enableCollapsing: boolean;
position: number;
constructor(path: string, position: number) {
this.path = path;
this.subFolders = true;
this.disableSync = true;
this.disableAutoCreate = true;
this.disableFolderNote = false;
this.enableCollapsing = false;
this.position = position;
}
}

View file

@ -0,0 +1,113 @@
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 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> {
const containerEl = settingsTab.settingsPage;
const manageExcluded = new Setting(containerEl)
.setHeading()
.setClass('fn-excluded-folder-heading')
.setName('Manage excluded folders');
const desc3 = document.createDocumentFragment();
desc3.append(
'Add {regex} at the beginning of the folder name to use a regex pattern.',
desc3.createEl('br'),
'Use * before and after to exclude folders that include the name between the *s.',
desc3.createEl('br'),
'Use * before the folder name to exclude folders that end with the folder name.',
desc3.createEl('br'),
'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';
new Setting(containerEl)
.setName('Whitelisted folders')
.setDesc('Folders that override the excluded folders/patterns')
.addButton((cb) => {
cb.setButtonText('Manage');
cb.setCta();
cb.onClick(async () => {
new WhitelistedFoldersSettings(settingsTab).open();
});
});
new Setting(containerEl)
.setName('Exclude folder default settings')
.addButton((cb) => {
cb.setButtonText('Manage');
cb.setCta();
cb.onClick(async () => {
new ExcludedFolderSettings(
settingsTab.app,
settingsTab.plugin,
settingsTab.plugin.settings.excludeFolderDefaultSettings,
).open();
});
});
new Setting(containerEl)
.setName('Exclude pattern default settings')
.addButton((cb) => {
cb.setButtonText('Manage');
cb.setCta();
cb.onClick(async () => {
new PatternSettings(
settingsTab.app,
settingsTab.plugin,
settingsTab.plugin.settings.excludePatternDefaultSettings,
).open();
});
});
new Setting(containerEl)
.setName('Add excluded folder')
.setClass('add-exclude-folder-item')
.addButton((cb) => {
cb.setIcon('plus');
cb.setClass('add-exclude-folder');
cb.setTooltip('Add excluded folder');
cb.onClick(() => {
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.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

@ -0,0 +1,235 @@
/* eslint-disable max-len */
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
export async function renderFileExplorer(settingsTab: SettingsTab): Promise<void> {
const containerEl = settingsTab.settingsPage;
new Setting(containerEl)
.setName('Hide folder note')
.setDesc('Hide the folder note file from appearing in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.hideFolderNote)
.onChange(async (value) => {
settingsTab.plugin.settings.hideFolderNote = value;
await settingsTab.plugin.saveSettings();
if (value) {
activeDocument.body.classList.add('hide-folder-note');
} else {
activeDocument.body.classList.remove('hide-folder-note');
}
settingsTab.display();
}),
);
const setting2 = new Setting(containerEl)
.setName('Disable click-to-open folder note on mobile')
.setDesc('Prevents folder notes from opening when tapping the folder name or surrounding area on mobile devices. They can now only be opened via the context menu or a command.')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.disableOpenFolderNoteOnClick)
.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;
new Setting(containerEl)
.setName('Open folder notes by only clicking directly on the folder name')
.setDesc('Only allow folder notes to open when clicking directly on the folder name in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(!settingsTab.plugin.settings.stopWhitespaceCollapsing)
.onChange(async (value) => {
if (!value) {
activeDocument.body.classList.add('fn-whitespace-stop-collapsing');
} else {
activeDocument.body.classList.remove('fn-whitespace-stop-collapsing');
}
settingsTab.plugin.settings.stopWhitespaceCollapsing = !value;
await settingsTab.plugin.saveSettings();
}),
);
const disableSetting = new Setting(containerEl);
disableSetting.setName('Disable folder collapsing');
disableSetting.setDesc('When enabled, folders in the file explorer will only collapse when clicking the collapse icon next to the folder name, not when clicking near a folder name when it has a folder note.');
disableSetting.addToggle((toggle) =>
toggle
.setValue(!settingsTab.plugin.settings.enableCollapsing)
.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;
new Setting(containerEl)
.setName('Use submenus')
.setDesc('Use submenus for file/folder commands')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.useSubmenus)
.onChange(async (value) => {
settingsTab.plugin.settings.useSubmenus = value;
await settingsTab.plugin.saveSettings();
settingsTab.display();
}),
);
if (settingsTab.plugin.settings.frontMatterTitle.enabled) {
new Setting(containerEl)
.setName('Auto update folder name in the file explorer (front matter title plugin only)')
.setDesc('Automatically update the folder name in the file explorer when the front matter title plugin is enabled and the title for a folder note is changed in the front matter. This will not change the file name, only the displayed name in the file explorer.')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.frontMatterTitle.explorer)
.onChange(async (value) => {
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.settingsPage.createEl('h3', { text: 'Style settings' });
new Setting(containerEl)
.setName('Highlight folder in the file explorer')
.setDesc('Highlight the folder in the file explorer when it has a folder note and the folder note is open in the editor')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.highlightFolder)
.onChange(async (value) => {
settingsTab.plugin.settings.highlightFolder = value;
if (!value) {
activeDocument.body.classList.add('disable-folder-highlight');
} else {
activeDocument.body.classList.remove('disable-folder-highlight');
}
await settingsTab.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName('Hide collapse icon')
.setDesc('Hide the collapse icon in the file explorer next to the name of a folder when a folder only contains a folder note')
.addToggle((toggle) =>
toggle
.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();
settingsTab.display();
}),
);
new Setting(containerEl)
.setName('Hide collapse icon for every empty folder')
.setDesc('Hide the collapse icon in the file explorer next to the name of a folder when a folder is empty')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.hideCollapsingIconForEmptyFolders)
.onChange(async (value) => {
settingsTab.plugin.settings.hideCollapsingIconForEmptyFolders = value;
await settingsTab.plugin.saveSettings();
if (value) {
activeDocument.body.classList.add('fn-hide-empty-collapse-icon');
} else {
activeDocument.body.classList.remove('fn-hide-empty-collapse-icon');
}
settingsTab.display();
},
));
if (settingsTab.plugin.settings.hideCollapsingIcon) {
new Setting(containerEl)
.setName('Hide collapse icon also when only the attachment folder is in the same folder')
.addToggle((toggle) =>
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();
}),
);
}
new Setting(containerEl)
.setName('Underline the name of folder notes')
.setDesc('Add an underline to folders that have a folder note in the file explorer')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.underlineFolder)
.onChange(async (value) => {
settingsTab.plugin.settings.underlineFolder = value;
if (value) {
activeDocument.body.classList.add('folder-note-underline');
} else {
activeDocument.body.classList.remove('folder-note-underline');
}
await settingsTab.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName('Bold the name of folder notes')
.setDesc('Make the folder name bold in the file explorer when it has a folder note')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.boldName)
.onChange(async (value) => {
settingsTab.plugin.settings.boldName = value;
if (value) {
activeDocument.body.classList.add('folder-note-bold');
} else {
activeDocument.body.classList.remove('folder-note-bold');
}
await settingsTab.plugin.saveSettings();
}),
);
new Setting(containerEl)
.setName('Cursive the name of folder notes')
.setDesc('Make the folder name cursive in the file explorer when it has a folder note')
.addToggle((toggle) =>
toggle
.setValue(settingsTab.plugin.settings.cursiveName)
.onChange(async (value) => {
settingsTab.plugin.settings.cursiveName = value;
if (value) {
activeDocument.body.classList.add('folder-note-cursive');
} else {
activeDocument.body.classList.remove('folder-note-cursive');
}
await settingsTab.plugin.saveSettings();
}),
);
}

View file

@ -0,0 +1,51 @@
import { Setting } from 'obsidian';
import type { SettingsTab } from './SettingsTab';
import { createOverviewSettings } from 'src/obsidian-folder-overview/src/settings';
export async function renderFolderOverview(settingsTab: SettingsTab): Promise<void> {
const { plugin } = settingsTab;
const defaultOverviewSettings = plugin.settings.defaultOverview;
const containerEl = settingsTab.settingsPage;
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,
);
}

Some files were not shown because too many files have changed in this diff Show more