├── .eslintrc.js
├── .eslintrc.typescript.js
├── .gitignore
├── .prettierrc.js
├── .release-it.cjs
├── .vscode
└── settings.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── esbuild.config.mjs
├── manifest-beta.json
├── manifest.json
├── package.json
├── pnpm-lock.yaml
├── scripts
└── ob-bumper.mjs
├── src
├── click-handler.ts
├── drag-patch.ts
├── fe-handler
│ ├── active-folder.ts
│ ├── base.ts
│ ├── file-explorer.less
│ ├── focus.less
│ ├── folder-focus.ts
│ ├── folder-icon.less
│ ├── folder-mark.ts
│ └── index.ts
├── fe-patch.ts
├── fn-main.ts
├── main.less
├── misc.ts
├── modules
│ ├── long-press.ts
│ └── set-folder-icon.ts
├── settings.less
├── settings.ts
└── typings
│ ├── obsidian-ex.d.ts
│ └── svg.d.ts
├── tsconfig.json
└── versions.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | const typescriptOptions = {
2 | tsconfigRootDir: __dirname,
3 | project: "tsconfig.json",
4 | };
5 |
6 | /**
7 | * @type {import('eslint').Linter.Config}
8 | */
9 | module.exports = {
10 | extends: ["prettier", "./.eslintrc.typescript.js"],
11 | plugins: ["prettier"],
12 | root: true,
13 | parserOptions: {
14 | ...typescriptOptions,
15 | ecmaVersion: "latest",
16 | sourceType: "module",
17 | },
18 | rules: {
19 | "prettier/prettier": "error",
20 | "arrow-body-style": "off",
21 | "prefer-arrow-callback": "off",
22 | "import/no-unresolved": [2, { ignore: [".less$"] }],
23 | },
24 | settings: {
25 | "import/resolver": {
26 | typescript: typescriptOptions,
27 | },
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/.eslintrc.typescript.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Custom config base for projects using typescript / javascript.
3 | * @see https://github.com/belgattitude/nextjs-monorepo-example/tree/main/packages/eslint-config-bases
4 | */
5 |
6 | module.exports = {
7 | env: {
8 | es6: true,
9 | node: true,
10 | },
11 | parser: "@typescript-eslint/parser",
12 | parserOptions: {
13 | ecmaFeatures: {
14 | jsx: true,
15 | globalReturn: false,
16 | },
17 | ecmaVersion: "2022",
18 | project: ["tsconfig.json"],
19 | sourceType: "module",
20 | },
21 | settings: {
22 | "import/resolver": {
23 | typescript: {},
24 | },
25 | },
26 | extends: [
27 | "eslint:recommended",
28 | "plugin:@typescript-eslint/recommended",
29 | "plugin:import/recommended",
30 | "plugin:import/typescript",
31 | ],
32 | plugins: ["prefer-arrow", "simple-import-sort"],
33 | rules: {
34 | "spaced-comment": [
35 | "error",
36 | "always",
37 | {
38 | line: {
39 | markers: ["/"],
40 | exceptions: ["-", "+"],
41 | },
42 | block: {
43 | markers: ["!"],
44 | exceptions: ["*"],
45 | balanced: true,
46 | },
47 | },
48 | ],
49 | "linebreak-style": ["error", "unix"],
50 | "no-empty-function": "off",
51 | "import/default": "off",
52 | "import/no-duplicates": ["error", { considerQueryString: true }],
53 | "import/no-unresolved": "off",
54 | "import/no-named-as-default-member": "off",
55 | "import/no-named-as-default": "off",
56 | "import/order": [
57 | "error",
58 | {
59 | groups: [
60 | "builtin",
61 | "external",
62 | "internal",
63 | "parent",
64 | "sibling",
65 | "index",
66 | "object",
67 | ],
68 | alphabetize: { order: "asc", caseInsensitive: true },
69 | },
70 | ],
71 | "@typescript-eslint/ban-tslint-comment": ["error"],
72 | "@typescript-eslint/ban-ts-comment": [
73 | "error",
74 | {
75 | "ts-expect-error": "allow-with-description",
76 | minimumDescriptionLength: 10,
77 | "ts-ignore": false,
78 | "ts-nocheck": true,
79 | "ts-check": false,
80 | },
81 | ],
82 | "@typescript-eslint/no-explicit-any": "off",
83 | "@typescript-eslint/no-non-null-assertion": "off",
84 | "@typescript-eslint/no-empty-function": [
85 | "error",
86 | { allow: ["private-constructors"] },
87 | ],
88 | "@typescript-eslint/no-unused-vars": [
89 | "warn",
90 | {
91 | argsIgnorePattern: "^_",
92 | ignoreRestSiblings: true,
93 | destructuredArrayIgnorePattern: "^_",
94 | },
95 | ],
96 | "@typescript-eslint/consistent-type-exports": "error",
97 | "@typescript-eslint/consistent-type-imports": [
98 | "error",
99 | { prefer: "type-imports" },
100 | ],
101 | "@typescript-eslint/naming-convention": [
102 | "error",
103 | {
104 | selector: "default",
105 | format: ["camelCase"],
106 | leadingUnderscore: "forbid",
107 | trailingUnderscore: "forbid",
108 | },
109 | {
110 | selector: "variable",
111 | format: ["camelCase", "UPPER_CASE", "snake_case"],
112 | leadingUnderscore: "allow",
113 | },
114 | {
115 | selector: ["function"],
116 | format: ["camelCase"],
117 | },
118 | {
119 | selector: "parameter",
120 | format: ["camelCase", "snake_case"],
121 | leadingUnderscore: "allow",
122 | },
123 | {
124 | selector: "class",
125 | format: ["PascalCase"],
126 | },
127 | {
128 | selector: "classProperty",
129 | format: ["camelCase"],
130 | leadingUnderscore: "allow",
131 | },
132 | {
133 | selector: "objectLiteralProperty",
134 | format: [
135 | "camelCase",
136 | // Some external libraries use snake_case for params
137 | "snake_case",
138 | // Env variables are generally uppercase
139 | "UPPER_CASE",
140 | // DB / Graphql might use PascalCase for relationships
141 | "PascalCase",
142 | ],
143 | leadingUnderscore: "allowSingleOrDouble",
144 | trailingUnderscore: "allowSingleOrDouble",
145 | },
146 | {
147 | selector: ["typeAlias", "interface"],
148 | format: ["PascalCase"],
149 | },
150 | {
151 | selector: ["typeProperty"],
152 | format: ["camelCase", "UPPER_CASE"],
153 | // For graphql __typename
154 | leadingUnderscore: "allowDouble",
155 | },
156 | {
157 | selector: ["typeParameter"],
158 | format: ["PascalCase"],
159 | },
160 | {
161 | selector: ["enum"],
162 | format: ["PascalCase"],
163 | },
164 | ],
165 | },
166 | overrides: [
167 | {
168 | // commonjs or assumed
169 | files: ["*.js", "*.cjs", "*.mjs"],
170 | parser: "espree",
171 | parserOptions: {
172 | ecmaVersion: 2020,
173 | },
174 | rules: {
175 | "@typescript-eslint/naming-convention": "off",
176 | "@typescript-eslint/ban-ts-comment": "off",
177 | "@typescript-eslint/no-explicit-any": "off",
178 | "@typescript-eslint/no-var-requires": "off",
179 | "@typescript-eslint/explicit-module-boundary-types": "off",
180 | "@typescript-eslint/consistent-type-exports": "off",
181 | "@typescript-eslint/consistent-type-imports": "off",
182 | "import/order": "off",
183 | },
184 | },
185 | ],
186 | };
187 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # npm
2 | node_modules
3 | package-lock.json
4 | # yarn.lock
5 |
6 | # build
7 | build
8 |
9 | # https://yarnpkg.com/getting-started/qa#which-files-should-be-gitignored
10 | .pnp.*
11 | .yarn/*
12 | !.yarn/patches
13 | !.yarn/plugins
14 | !.yarn/releases
15 | !.yarn/sdks
16 | !.yarn/versions
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @type {import('prettier').Config}
3 | */
4 | module.exports = {
5 | singleQuote: false,
6 | semi: true,
7 | tabWidth: 2,
8 | bracketSpacing: true,
9 | trailingComma: "all",
10 | bracketSameLine: false,
11 | useTabs: false,
12 | endOfLine: "lf",
13 | overrides: [],
14 | };
--------------------------------------------------------------------------------
/.release-it.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | hooks: {
3 | // "before:init": ["pnpm run eslint"],
4 | "after:release":
5 | "echo Successfully released obsidian plugin ${name} v${version} to ${repo.repository}.",
6 | },
7 | git: {
8 | commitMessage: "chore: release obsidian plugin v${version}",
9 | tagName: "${version}",
10 | tagAnnotation: "Release Obsidian Plugin v${version}",
11 | addUntrackedFiles: true,
12 | },
13 | plugins: {
14 | "@release-it/conventional-changelog": {
15 | preset: "angular",
16 | infile: "CHANGELOG.md",
17 | },
18 | "./scripts/ob-bumper.mjs": {
19 | indent: 2,
20 | },
21 | },
22 | npm: {
23 | publish: false,
24 | },
25 | github: {
26 | release: true,
27 | assets: [
28 | "build/main.js",
29 | "build/manifest.json",
30 | "build/styles.css",
31 | ],
32 | proxy: process.env.HTTPS_PROXY,
33 | releaseName: "${version}",
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "conventionalCommits.scopes": ["click-handler", "drag-patch"],
3 | "editor.formatOnSave": false,
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "eslint.format.enable": true,
6 | "editor.codeActionsOnSave": {
7 | "source.fixAll.eslint": true
8 | },
9 | // Disable vscode formatting for js,jsx,ts,tsx files
10 | // to allow dbaeumer.vscode-eslint to format them
11 | "[javascript]": {
12 | "editor.formatOnSave": false,
13 | "editor.defaultFormatter": "dbaeumer.vscode-eslint"
14 | },
15 | "[typescript]": {
16 | "editor.formatOnSave": false,
17 | "editor.defaultFormatter": "dbaeumer.vscode-eslint"
18 | },
19 | "[typescriptreact]": {
20 | "editor.formatOnSave": false,
21 | "editor.defaultFormatter": "dbaeumer.vscode-eslint"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## [0.16.5](https://github.com/aidenlx/alx-folder-note/compare/0.16.4...0.16.5) (2023-05-05)
4 |
5 |
6 | ### Bug Fixes
7 |
8 | * click event ([a27e40a](https://github.com/aidenlx/alx-folder-note/commit/a27e40a370e38f55dc9c3b14513fab3cc62f5f12))
9 | * compatible with Obsidian 1.2.0 version ([1b13e92](https://github.com/aidenlx/alx-folder-note/commit/1b13e926d7f21f85afc8959de0d8ee77f89b4a37))
10 |
11 |
12 | ### Performance Improvements
13 |
14 | * **drag-patch:** patch drag manager with existing MarkdownView instead of init a new one ([07f64d6](https://github.com/aidenlx/alx-folder-note/commit/07f64d6112a93fd46b97c051520fd16dcc2f79ba))
15 | * use native frontmatter edit method instead of graymatter ([3948b68](https://github.com/aidenlx/alx-folder-note/commit/3948b68b21d437d9b5a90ae0da762ad52187f23b))
16 |
17 | ## [0.16.4](https://github.com/aidenlx/alx-folder-note/compare/0.16.3...0.16.4) (2022-05-03)
18 |
19 |
20 | ### Bug Fixes
21 |
22 | * **drag-patch:** fix no cursor shown on drag folder over editor in v0.14.8+ ([beb8c5e](https://github.com/aidenlx/alx-folder-note/commit/beb8c5e3294b24453b98febc579a1c3860f2bb37))## [0.16.3](https://github.com/aidenlx/alx-folder-note/compare/0.16.2...0.16.3) (2022-04-19)
23 |
24 |
25 | ### Bug Fixes
26 |
27 | * **drag-patch:** fix compatibility issue with legacy editor ([91ec54a](https://github.com/aidenlx/alx-folder-note/commit/91ec54a95fec14397c6ba9b18f346c45022bf581)), closes [#73](https://github.com/aidenlx/alx-folder-note/issues/73)## [0.16.2](https://github.com/aidenlx/alx-folder-note/compare/0.16.1...0.16.2) (2022-04-15)
28 |
29 |
30 | ### Bug Fixes
31 |
32 | * **drag-patch:** fix broken drag folder with folder note into another folder ([d8c5325](https://github.com/aidenlx/alx-folder-note/commit/d8c53254d878091e79f84a3d3fcfc6453afe30dc)), closes [#71](https://github.com/aidenlx/alx-folder-note/issues/71)## [0.16.1](https://github.com/aidenlx/alx-folder-note/compare/0.16.0...0.16.1) (2022-04-04)
33 |
34 |
35 | ### Bug Fixes
36 |
37 | * improve handling of loading folder-note-core API ([8d72225](https://github.com/aidenlx/alx-folder-note/commit/8d722255af83ef7e4a2da75ff94ba6fc94751412)), closes [#67](https://github.com/aidenlx/alx-folder-note/issues/67)# [0.16.0](https://github.com/aidenlx/alx-folder-note/compare/0.15.0...0.16.0) (2022-03-25)
38 |
39 |
40 | ### Bug Fixes
41 |
42 | * **click-handler:** fix shift selection not working on folder with note ([a4742e1](https://github.com/aidenlx/alx-folder-note/commit/a4742e1d90d6e277b48e6c6ea2e33d7e3c44f21b))
43 |
44 |
45 | ### Features
46 |
47 | * **click handler:** add option to expand collasped folder with note while opening it ([3f98380](https://github.com/aidenlx/alx-folder-note/commit/3f9838017f5ea9a8ee5449c5b85e52f572db6522)), closes [#32](https://github.com/aidenlx/alx-folder-note/issues/32)
48 | * **click-handler:** folder note is triggered only when click on folder title again ([f145d5a](https://github.com/aidenlx/alx-folder-note/commit/f145d5a3552a23fdf10e409ed511f4c0aee89e9e)), closes [#64](https://github.com/aidenlx/alx-folder-note/issues/64)
49 | * support drag folder to editor to insert link ([26e6cf9](https://github.com/aidenlx/alx-folder-note/commit/26e6cf96bd51355a3c71cc7bec892d989df0c62e)), closes [#45](https://github.com/aidenlx/alx-folder-note/issues/45)# [0.15.0](https://github.com/aidenlx/alx-folder-note/compare/0.14.0...0.15.0) (2022-03-06)
50 |
51 |
52 | ### Bug Fixes
53 |
54 | * **settings:** fix input[number] stylings ([eaeb33a](https://github.com/aidenlx/alx-folder-note/commit/eaeb33ab7648e71c05b8f79c221d5a26d9d16245))
55 |
56 |
57 | ### Features
58 |
59 | * **fe-handler:** adjust folder title and icon style ([03a8340](https://github.com/aidenlx/alx-folder-note/commit/03a83407afdf310eeccc50a629725299f299efdd))
60 | * **fe-patch:** support hover on folder to preview its folder note ([aa918ff](https://github.com/aidenlx/alx-folder-note/commit/aa918ff80887f6b1cae2237f5ad86fc43ba000db)), closes [#62](https://github.com/aidenlx/alx-folder-note/issues/62)
61 | * **folder-icon:** add command and file menu option to set folder icon ([03d4ec1](https://github.com/aidenlx/alx-folder-note/commit/03d4ec1c239476d6047d093afdf237ca29c01aab))
62 | * increase folder's collapse-indicator size on mobile ([b619b0d](https://github.com/aidenlx/alx-folder-note/commit/b619b0dbb512692383592f93562a75df152725b8))
63 |
64 | # [0.14.0](https://github.com/aidenlx/alx-folder-note/compare/0.13.1...0.14.0) (2022-02-25)
65 |
66 |
67 | ### Bug Fixes
68 |
69 | * **folder-icon:** fix basic styles broken in some themes; svg and emoji icons are now aligned properly ([12e28b4](https://github.com/aidenlx/alx-folder-note/commit/12e28b438dcb71304d19d3bf25d0ff5eca8a490a)), closes [#61](https://github.com/aidenlx/alx-folder-note/issues/61)
70 |
71 |
72 | ### Features
73 |
74 | * add `is-active` class on active folder's titleEl ([eccb473](https://github.com/aidenlx/alx-folder-note/commit/eccb4737b0557a807763da711e5cd3edd5591984)), closes [#53](https://github.com/aidenlx/alx-folder-note/issues/53)
75 | * **click-handler:** add option to disable click folder title to open folder note on mobile ([2d2bad6](https://github.com/aidenlx/alx-folder-note/commit/2d2bad6f4a84baa6ff935edf8e77d12f628da6f3)), closes [#56](https://github.com/aidenlx/alx-folder-note/issues/56) [#52](https://github.com/aidenlx/alx-folder-note/issues/52)
76 | * **settings:** add option to configure long press delay ([745680a](https://github.com/aidenlx/alx-folder-note/commit/745680a2f64539b7678713f2f64376373b62381a)), closes [#42](https://github.com/aidenlx/alx-folder-note/issues/42)
77 |
78 | ## [0.13.1](https://github.com/aidenlx/alx-folder-note/compare/0.13.0...0.13.1) (2022-02-19)
79 |
80 |
81 | ### Bug Fixes
82 |
83 | * fix folderv change notice keep popup after ignore it ([ca000ed](https://github.com/aidenlx/alx-folder-note/commit/ca000ed89e54e33e4a285f18dd5c427749d6b129))
84 |
85 | # [0.13.0](https://github.com/aidenlx/alx-folder-note/compare/0.12.3...0.13.0) (2022-02-19)
86 |
87 |
88 | ### Features
89 |
90 | * add notice about folderv changes ([22ede5e](https://github.com/aidenlx/alx-folder-note/commit/22ede5efa74b581236c56886ad179042f589d3f2))
91 | * **settings:** add clickable notice that jump to setting tab when folder-note-core not enabled ([156d1b3](https://github.com/aidenlx/alx-folder-note/commit/156d1b3c1a607677560805d1708f0ffc02a298f0))
92 | * **settings:** add guide to install dependencies ([8ea9769](https://github.com/aidenlx/alx-folder-note/commit/8ea9769fff29fb622f44f9ae0133bdc3a55c2f1a))
93 |
94 |
95 | ### Performance Improvements
96 |
97 | * separate folderv code from main repo to boost loading ([73e2149](https://github.com/aidenlx/alx-folder-note/commit/73e214987de2ef767877d06127fd364fa4893f06))
98 |
99 |
100 | ### BREAKING CHANGES
101 |
102 | * folder overview (folderv) is now an optional component that requires a dedicated plugin, go to setting tab to install it
103 |
104 | ## [0.12.3](https://github.com/aidenlx/alx-folder-note/compare/0.12.2...0.12.3) (2021-12-04)
105 |
106 |
107 | ### Bug Fixes
108 |
109 | * **fe-handler:** remove unintended console output ([25e79db](https://github.com/aidenlx/alx-folder-note/commit/25e79db1f41748e2e01b91dfea02b14e925f9eb1))
110 | * **folder-icon:** fix subfolder icon var polluted by parent folder's icon ([5ff6cc9](https://github.com/aidenlx/alx-folder-note/commit/5ff6cc95e6c5f6b8e18839151613b92b8e8cefe5))
111 |
112 | ## [0.12.2](https://github.com/aidenlx/alx-folder-note/compare/0.12.1...0.12.2) (2021-11-29)
113 |
114 |
115 | ### Features
116 |
117 | * **fe-handler:** add focus on folder for file explorer ([1759c82](https://github.com/aidenlx/alx-folder-note/commit/1759c827a80b88894ca653b7478f4bfb372ebbe2))
118 | * **fe-handler:** focused folder now auto unfold and scroll into view ([a872028](https://github.com/aidenlx/alx-folder-note/commit/a872028d05423fc18051972dfb7382c2478897c0))
119 | * **focus:** long press (0.8s) on folder to toggle focus ([e5d96b9](https://github.com/aidenlx/alx-folder-note/commit/e5d96b9e231a0ae0a0817fc698e0a84640012c3f))
120 | * **focus:** non-focused folders are now dimmed instead of being hidden ([529f685](https://github.com/aidenlx/alx-folder-note/commit/529f685792e524ad0828cc481924e5b68d616e18))
121 | * **settings:** add option to disable long press to toggle focus ([a948d56](https://github.com/aidenlx/alx-folder-note/commit/a948d56ae1536844b29876ba550cac091772b208))
122 |
123 | ## [0.12.1](https://github.com/aidenlx/alx-folder-note/compare/0.12.0...0.12.1) (2021-11-27)
124 |
125 |
126 | ### Features
127 |
128 | * **fe-handler:** add option to hide collapse indicator when folder contains only folder note ([7fa8ee8](https://github.com/aidenlx/alx-folder-note/commit/7fa8ee833acf898f781dc37aa1d7857e576897d5)), closes [#40](https://github.com/aidenlx/alx-folder-note/issues/40)
129 |
130 | * refactor: add body.alx-folder-icons when option is enabled (ff1fa46)
131 | * fix(initialize): fix feHanlder failed to update when workspace changes (9fe694a)
132 | * feat(fe-handler): set folder icon in folder note (7ba11ad)
133 | * docs(readme): update introduction (517c55e)
134 |
135 | ## [0.11.2](https://github.com/aidenlx/alx-folder-note/compare/0.11.1...0.11.2) (2021-09-15)
136 |
137 |
138 | ### Bug Fixes
139 |
140 | * **file-card:** fix mid-click and mod-click on file card ([fe89454](https://github.com/aidenlx/alx-folder-note/commit/fe8945443ad172b7d3562a27e09ca9c2543d3e8b)), closes [#20](https://github.com/aidenlx/alx-folder-note/issues/20)
141 | * **folderv:** folder notes are no longer included in folderv ([4b9fdf3](https://github.com/aidenlx/alx-folder-note/commit/4b9fdf368b2e6f61d72719283de124cac302bf3b))
142 | * **initialize:** add more retrys to get FileExplorer view ([38625dc](https://github.com/aidenlx/alx-folder-note/commit/38625dc26859253996e4b190567660c85c40f721)), closes [#21](https://github.com/aidenlx/alx-folder-note/issues/21) [#12](https://github.com/aidenlx/alx-folder-note/issues/12)
143 |
144 | ## [0.11.1](https://github.com/aidenlx/alx-folder-note/compare/0.11.0...0.11.1) (2021-09-12)
145 |
146 |
147 | ### Features
148 |
149 | * **settings:** expose log level settings of folder-note-core ([b7aecbf](https://github.com/aidenlx/alx-folder-note/commit/b7aecbfc9013f679dae9b9e17a25086bd83c68f0))
150 |
151 | # [0.11.0](https://github.com/aidenlx/alx-folder-note/compare/0.10.0...0.11.0) (2021-09-12)
152 |
153 |
154 | ### Bug Fixes
155 |
156 | * **fe-handler:** fix click not set on newly created folder items ([38ea6cf](https://github.com/aidenlx/alx-folder-note/commit/38ea6cfb8dd291713f5e65e68e963aec3a47012d))
157 | * **fe-handler:** fix click not set on newly created folder items (again) ([b50b6a6](https://github.com/aidenlx/alx-folder-note/commit/b50b6a61e9b7673731950f1c291aa425566a045d))
158 | * fix compatibility with folder-note-core v1.0.0 ([96c3105](https://github.com/aidenlx/alx-folder-note/commit/96c31053b72b45b839dcb02118dc07e22379127d))
159 |
160 |
161 | ### Features
162 |
163 | * migrate code to folder-note-core ([29c20eb](https://github.com/aidenlx/alx-folder-note/commit/29c20ebbbfff55315c0a2fc2b551394b7a623ba5))
164 |
165 | # [0.10.0](https://github.com/aidenlx/alx-folder-note/compare/0.9.2...0.10.0) (2021-08-12)
166 |
167 |
168 | ### Bug Fixes
169 |
170 | * **folderv:** tippy now append to parent ([a4d57c1](https://github.com/aidenlx/alx-folder-note/commit/a4d57c1aae848fd84676755375d33da589d07a3d))
171 |
172 |
173 | ### Features
174 |
175 | * add api support ([27e3489](https://github.com/aidenlx/alx-folder-note/commit/27e34894b2fa04c1ec7b7c50082f30dbbf7a0872))
176 | * **folderv:** add soft link support ([4c5d33d](https://github.com/aidenlx/alx-folder-note/commit/4c5d33d82756020bcccb2bf91dda7a28e4bdccda))
177 |
178 | ## [0.9.2](https://github.com/aidenlx/alx-folder-note/compare/0.9.1...0.9.2) (2021-08-09)
179 |
180 |
181 | ### Bug Fixes
182 |
183 | * **command:** add missing command for link-to-parent-folder; fix commands not available in preview mode ([4acd8d7](https://github.com/aidenlx/alx-folder-note/commit/4acd8d734a32ad6f17a85dd69f3fdfa6b41b502a))
184 | * fix glob filter matched as regex; add notice for invaild options; rename sort field ([6747616](https://github.com/aidenlx/alx-folder-note/commit/67476164ec46c73c2fedd3720e9977620a72d6e9))
185 |
186 | ## [0.9.1](https://github.com/aidenlx/alx-folder-note/compare/0.9.0...0.9.1) (2021-08-09)
187 |
188 |
189 | ### Bug Fixes
190 |
191 | * **folderv:** fix tooltip ([adc983b](https://github.com/aidenlx/alx-folder-note/commit/adc983b7bc59ea6a2ca4289504164cb58029dd2e))
192 |
193 | # [0.9.0](https://github.com/aidenlx/alx-folder-note/compare/0.8.0...0.9.0) (2021-08-09)
194 |
195 |
196 | ### Bug Fixes
197 |
198 | * fix css global pollution ([220f069](https://github.com/aidenlx/alx-folder-note/commit/220f06955d4053c3e1b34a0f7f87e815e5ee890d)), closes [#14](https://github.com/aidenlx/alx-folder-note/issues/14)
199 |
200 |
201 | ### Features
202 |
203 | * add support for dark mode ([e590015](https://github.com/aidenlx/alx-folder-note/commit/e5900159f1deff7ce41c49bedfc971266db5d8e3)), closes [#15](https://github.com/aidenlx/alx-folder-note/issues/15)
204 |
205 | # [0.8.0](https://github.com/aidenlx/alx-folder-note/compare/0.7.0...0.8.0) (2021-08-08)
206 |
207 |
208 | ### Bug Fixes
209 |
210 | * **sort:** fix regex not parsed correctly; fix test when regex flag set to g ([d0bdf4a](https://github.com/aidenlx/alx-folder-note/commit/d0bdf4a4d61e22383277178aa9c96b95ae1c8d1c))
211 |
212 |
213 | ### Features
214 |
215 | * add custom title and description field name; update default settting ([33ad9f8](https://github.com/aidenlx/alx-folder-note/commit/33ad9f880d47d0cca8c52f87e0c9d9b805c4d687))
216 | * add mid-click on folder to create new folder note ([ebefb6a](https://github.com/aidenlx/alx-folder-note/commit/ebefb6a7572ffd16afd5fe63ddbece22e4b9b3a8))
217 |
218 | # [0.7.0](https://github.com/alx-plugins/alx-folder-note/compare/0.6.0...0.7.0) (2021-08-08)
219 |
220 | ### Bug Fixes
221 |
222 | * **fe-handler.ts:** slience the error of no afitem found for path in waitingList ([1146529](https://github.com/alx-plugins/alx-folder-note/commit/11465298acc3ea39dfeceb8c1a816e8eb41a0bfd))
223 | * remove monkey-around when unload ([9df247f](https://github.com/alx-plugins/alx-folder-note/commit/9df247f0448aab4394d94f6e834a65f259f8fd83))
224 | * **vault-handler.ts:** fix folder note not update internal links when auto rename ([90922bf](https://github.com/alx-plugins/alx-folder-note/commit/90922bf115ddd89850f4d8de2df8bb060a726eb6))
225 |
226 | ### Features
227 |
228 | * folder overview support ([44303ee](https://github.com/alx-plugins/alx-folder-note/commit/44303eeade5209649144019ced2ed51cb6fd90ac), [7da2a69](https://github.com/alx-plugins/alx-folder-note/commit/7da2a69a6c47deb09b830844cbad5ebd4e920499), [a4a128e](https://github.com/alx-plugins/alx-folder-note/commit/a4a128e78a824c03717e74ea5dc81ef7d37133f0), [9ddd1e1](https://github.com/alx-plugins/alx-folder-note/commit/9ddd1e1bd1daec0ee91c0b4b574a250e630c33d8), [d00bb3e](https://github.com/alx-plugins/alx-folder-note/commit/d00bb3e2f32f3b5346ea554ed38bed4bc2c52e1c)), closes [#6](https://github.com/alx-plugins/alx-folder-note/issues/6)
229 |
230 | ### Performance Improvements
231 |
232 | * move debounce from vaultHandler to feHandler ([c83e125](https://github.com/alx-plugins/alx-folder-note/commit/c83e125fea78aea31c64b59c92d2a237b7bc82dd))
233 |
234 | # [0.6.0](https://github.com/alx-plugins/alx-folder-note/compare/0.5.0...0.6.0) (2021-06-11)
235 |
236 |
237 | ### Features
238 |
239 | * add underline to folder with note ([faeba8f](https://github.com/alx-plugins/alx-folder-note/commit/faeba8fab9d4ac78d09e83844a19ed8726266418))
240 | * **commands.ts:** add command to link current note to parent folder ([0fe327d](https://github.com/alx-plugins/alx-folder-note/commit/0fe327dd0421c6cf30676bc072f96b016c0ca1eb))
241 |
242 | # [0.5.0](https://github.com/alx-plugins/alx-folder-note/compare/0.4.0...0.5.0) (2021-06-10)
243 |
244 |
245 | ### Bug Fixes
246 |
247 | * **settings.ts:** fix folder note hide fails when toggle deleteOutsideNoteWithFolder ([4eabf9b](https://github.com/alx-plugins/alx-folder-note/commit/4eabf9bb7dadd0137cbeffa22db47a4bca277837))
248 |
249 |
250 | ### Features
251 |
252 | * add commands for folder note/folder operation ([a936d25](https://github.com/alx-plugins/alx-folder-note/commit/a936d2537c37f9a4588ad4cff1e6ca87234ee1ed))
253 | * add debounce to vault events ([9db2a46](https://github.com/alx-plugins/alx-folder-note/commit/9db2a46fbad2b54762f33c09ae3b740fffd17af8))
254 | * revealInFolder now jump to folder when folder note is hidden ([069b109](https://github.com/alx-plugins/alx-folder-note/commit/069b109b3596b6de4f31c6759dd0819b876d2272))
255 |
256 |
257 | ### Reverts
258 |
259 | * remove fileCountInExplorer ([df9b68a](https://github.com/alx-plugins/alx-folder-note/commit/df9b68a6776c3755905d5f640c97ed9b31cf02f1))
260 |
261 | # [0.4.0](https://github.com/alx-plugins/alx-folder-note/compare/0.3.0...0.4.0) (2021-05-29)
262 |
263 |
264 | ### Features
265 |
266 | * add option to toggle fileCountInExplorer ([46688ef](https://github.com/alx-plugins/alx-folder-note/commit/46688eff1aa43bec9cb1978f0a84ad8ba9e24ae1))
267 | * **folder-count.ts:** auto update count when file changes ([5458bdf](https://github.com/alx-plugins/alx-folder-note/commit/5458bdf9f58159944aa523600cc231675b04100c))
268 | * add initial support for file count in explorer ([f520f66](https://github.com/alx-plugins/alx-folder-note/commit/f520f66ce7acbfcf73878e83d9c4ea9d371bd450))
269 |
270 | # [0.3.0](https://github.com/alx-plugins/alx-folder-note/compare/0.2.0...0.3.0) (2021-05-28)
271 |
272 |
273 | ### Bug Fixes
274 |
275 | * **valut-handler.ts:** autorename no longer rename index files ([b057bd3](https://github.com/alx-plugins/alx-folder-note/commit/b057bd3bb500327906b7bb4082e146bde923a53a))
276 |
277 |
278 | ### Features
279 |
280 | * **vault-handler.ts:** add prompt to delete folder when folder note is removed ([fdc6c28](https://github.com/alx-plugins/alx-folder-note/commit/fdc6c28a7a2af6eace89d1d2f278d7e6d4aa93a8))
281 | * add create-folder-for-note command ([47470ad](https://github.com/alx-plugins/alx-folder-note/commit/47470ad626a2156cf05b72686d89f9a57430deef))
282 |
283 | # [0.2.0](https://github.com/alx-plugins/alx-folder-note/compare/0.1.1...0.2.0) (2021-05-28)
284 |
285 |
286 | ### Bug Fixes
287 |
288 | * **find.ts:** getFolderNote() throw error when move folder content ([d60e7ad](https://github.com/alx-plugins/alx-folder-note/commit/d60e7ad6c83782251f0ebf15f519dce6af815ca0))
289 |
290 |
291 | ### Features
292 |
293 | * **vault-handler.ts:** add prompt to comfirm deletion of folder note outside ([01b5032](https://github.com/alx-plugins/alx-folder-note/commit/01b5032be0e6b3aa4f500dcca5da76dd89bc9d0b))
294 |
295 | ## [0.1.1](https://github.com/alx-plugins/alx-folder-note/compare/0.1.0...0.1.1) (2021-05-27)
296 |
297 |
298 | ### Bug Fixes
299 |
300 | * **vault-handler.ts:** autoRename folder note not working when hideNoteInExplorer is disabled ([6c5ca94](https://github.com/alx-plugins/alx-folder-note/commit/6c5ca94f85dd812e0954c0f5c5fe868b54bfd4b1))
301 |
302 | # 0.1.0 (2021-05-27)
303 |
304 |
305 | ### Bug Fixes
306 |
307 | * **click-handler.ts:** mid click not opening new leaf ([da71c1a](https://github.com/alx-plugins/alx-folder-note/commit/da71c1a1a31a4007f23413ec0d58038592221ce8))
308 | * **note-handler.ts:** hideAll() fail to revert all hidden folder notes ([389300f](https://github.com/alx-plugins/alx-folder-note/commit/389300f1caa9c537a62f809f07f1a46ca6a61fec))
309 | * **settings.ts:** expose hideNoteInExplorer in setting tab ([f77202c](https://github.com/alx-plugins/alx-folder-note/commit/f77202c5feba83af76a557509f4e03407e3eff81))
310 |
311 |
312 | ### Features
313 |
314 | * **find.ts:** findFolderFromNote() now accepts path strings ([f490125](https://github.com/alx-plugins/alx-folder-note/commit/f4901255dcc9622e8f1c15511e6ef7e8159491c5))
315 | * **settings.ts:** add live update to folderNotePref and indexName ([3e42e67](https://github.com/alx-plugins/alx-folder-note/commit/3e42e670969215020719594659f034c1154a4ca5))
316 | * **vault-handler.ts:** folder note rename/mv now update hide state and folder ([8fec0ee](https://github.com/alx-plugins/alx-folder-note/commit/8fec0eeaff887b7f1a0e9072261d7a5e46e00d6f))
317 | * add types for undocumented FileExplorerView ([d1d673d](https://github.com/alx-plugins/alx-folder-note/commit/d1d673dacf65458efd3b39d7992476868911e1d8))
318 | * port core function from fork.dev ([6b24c46](https://github.com/alx-plugins/alx-folder-note/commit/6b24c4606d336fcc41ffd72fce153d6379d611ca))
319 | * support create new folder note ([2a1026f](https://github.com/alx-plugins/alx-folder-note/commit/2a1026f36670df846394963b02ab82fc59bc2bde))
320 | * update method to unload and load when layout is already ready ([521294b](https://github.com/alx-plugins/alx-folder-note/commit/521294bfa878697d25bc784fcc59d800038f4ff1))
321 | * **settings.ts:** port settings from xpgo's ([f8c2e2f](https://github.com/alx-plugins/alx-folder-note/commit/f8c2e2fa9435e49cc7b333b3e61bc8f0920a7cc2))
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (C) 2021-present AidenLx
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # AidenLx's Folder Note
2 |
3 | Add description, summary and more info to folders with folder notes.
4 |
5 | alx-folder-note is a maintained, completely rewritten, and improved folder note plugin that covers all core features of the original [folder note plugin](https://github.com/xpgo/obsidian-folder-note-plugin)) (except folder overview), with enhanced file explorer patches and lots of QoL improvements, aiming to make the folder with note work as seamless as native files.)
6 |
7 | Special thanks to [xpgo](https://github.com/xpgo), the author of original [folder note plugin](https://github.com/xpgo/obsidian-folder-note-plugin)!
8 |
9 | 
10 |
11 | Note:
12 |
13 | 1. Starting from v0.13.0, Folder Overview (folderv) has become an optional component, you can go to the Folder Overview section of the setting tab to install it
14 | 2. Starting from v0.11.0, this plugin require [folder-note-core](https://github.com/aidenlx/folder-note-core) to work properly. For old user, check [here](https://github.com/aidenlx/alx-folder-note/wiki/migrate-from-v0.10.0-and-lower) for migration guide.
15 |
16 | ## Intro
17 |
18 | - [create folder note](https://github.com/aidenlx/alx-folder-note/wiki/create-folder-note) easily, with [mulitple preferences](https://github.com/aidenlx/alx-folder-note/wiki/folder-note-pref) and [template support](https://github.com/aidenlx/alx-folder-note/wiki/core-settings#template)
19 | - folder and folder note working as one
20 | - [click on folder in file explorer to open folder note](https://github.com/aidenlx/alx-folder-note/wiki/open-folder-note-from-folder)
21 | - [folder and linked note synced as one](https://github.com/aidenlx/alx-folder-note/wiki/core-settings#auto-rename): change folder name from folder note; folder note moves with your folder
22 | - [folder note hidden from file explorer](https://github.com/aidenlx/alx-folder-note/wiki/core-settings#hide-note-in-explorer)
23 | - [reveal linked folder in file explorer](https://github.com/aidenlx/alx-folder-note/wiki/core-settings#hide-note-in-explorer)
24 | - [delete folder within folder note](https://github.com/aidenlx/alx-folder-note/wiki/delete-folder-from-folder-note)
25 | - [create folder overview](https://github.com/aidenlx/alx-folder-note/wiki/folder-overview) with codeblock `folderv` (Optional)
26 | - view all files within folder with brief cards
27 | - specify title and description in frontmatter (with [customizable field name](https://github.com/aidenlx/alx-folder-note/wiki/folderv-settings#field-names))
28 | - [fetch title from h1](https://github.com/aidenlx/alx-folder-note/wiki/folderv-settings#h1-as-title-source) if title not specified
29 | - [filter files](https://github.com/aidenlx/alx-folder-note/wiki/folderv-options#filter) with regex/glob
30 | - [sort files](https://github.com/aidenlx/alx-folder-note/wiki/folderv-options#sort) by name/create time/last modified time
31 | - folder focus mode: right-click on folder in file explorer and select Toggle Focus can dim other folders and files outside of selected folder, select option again to revert 
32 | - you can also long press with mouse on the folder name in file explorer to toggle folder focus (disabled by default, desktop only)
33 | - [folder icon in file explorer](https://github.com/aidenlx/alx-folder-note/issues/11)
34 | More to come:
35 |
36 | - [ ] `folderv`: show [children specified in dataview/frontmatter fields](https://github.com/SkepticMystic/breadcrumbs/wiki/Relationships---Basics) (works with [relation-resolver plugin](https://github.com/aidenlx/relation-resolver))
37 | - [ ] `folderv`: list view
38 |
39 | ## How to use
40 |
41 | Check [wiki](https://github.com/aidenlx/alx-folder-note/wiki) for more details
42 |
43 | ## Compatibility
44 |
45 | The required API feature is only available for Obsidian v0.14.8+.
46 |
47 | ## Installation
48 |
49 | Note: Starting from v0.11.0, this plugin require [folder-note-core](https://github.com/aidenlx/folder-note-core) to work properly, which is also available on community plugins list.
50 |
51 | ### From Obsidian
52 |
53 | 1. Open `Settings` > `Third-party plugin`
54 | 2. Make sure Safe mode is **off**
55 | 3. Click `Browse community plugins`
56 | 4. Search for this plugin
57 | 5. Click `Install`
58 | 6. Once installed, close the community plugins window and the plugin is ready to use.
59 |
60 | ### From GitHub
61 |
62 | 1. Download the Latest Release from the Releases section of the GitHub Repository
63 | 2. Put files to your vault's plugins folder: `/.obsidian/plugins/alx-folder-note`
64 | 3. Reload Obsidian
65 | 4. If prompted about Safe Mode, you can disable safe mode and enable the plugin.
66 | Otherwise, head to Settings, third-party plugins, make sure safe mode is off and
67 | enable the plugin from there.
68 |
69 | > Note: The `.obsidian` folder may be hidden. On macOS, you should be able to press `Command+Shift+Dot` to show the folder in Finder.
70 |
--------------------------------------------------------------------------------
/esbuild.config.mjs:
--------------------------------------------------------------------------------
1 | import obPlugin from "@aidenlx/esbuild-plugin-obsidian";
2 | import { build, context } from "esbuild";
3 | import { lessLoader } from "esbuild-plugin-less";
4 |
5 | const banner = `/*
6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
7 | if you want to view the source visit the plugins github repository
8 | */
9 | `;
10 |
11 | const isProd = process.env.BUILD === "production";
12 |
13 | const opts = {
14 | entryPoints: ["src/fn-main.ts"],
15 | bundle: true,
16 | platform: "browser",
17 | external: ["obsidian"],
18 | format: "cjs",
19 | mainFields: ["browser", "module", "main"],
20 | banner: { js: banner },
21 | sourcemap: isProd ? false : "inline",
22 | minify: isProd,
23 | define: {
24 | "process.env.NODE_ENV": JSON.stringify(process.env.BUILD),
25 | },
26 | outfile: "build/main.js",
27 | plugins: [
28 | lessLoader({
29 | javascriptEnabled: true,
30 | }),
31 | obPlugin(),
32 | ],
33 | };
34 |
35 | if (!isProd) {
36 | const ctx = await context({ ...opts, logLevel: "error" });
37 | try {
38 | await ctx.watch();
39 | } catch (err) {
40 | console.error(err);
41 | await cleanup();
42 | }
43 | process.on("SIGINT", cleanup);
44 | // eslint-disable-next-line no-inner-declarations
45 | async function cleanup() {
46 | await ctx.dispose();
47 | // scheduleOnDisposeCallbacks defer function calls using setTimeout(...,0)
48 | // so we need to wait a bit before exiting
49 | // setTimeout(() => process.exit(), 100);
50 | }
51 | } else {
52 | await build(opts);
53 | }
54 |
--------------------------------------------------------------------------------
/manifest-beta.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "alx-folder-note",
3 | "name": "AidenLx's Folder Note",
4 | "version": "0.16.5",
5 | "minAppVersion": "1.1.0",
6 | "description": "Add description, summary and more info to folders with folder notes.",
7 | "author": "AidenLx",
8 | "authorUrl": "https://github.com/aidenlx",
9 | "isDesktopOnly": false
10 | }
--------------------------------------------------------------------------------
/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "alx-folder-note",
3 | "name": "AidenLx's Folder Note",
4 | "version": "0.16.5",
5 | "minAppVersion": "1.1.0",
6 | "description": "Add description, summary and more info to folders with folder notes.",
7 | "author": "AidenLx",
8 | "authorUrl": "https://github.com/aidenlx",
9 | "isDesktopOnly": false
10 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@aidenlx/folder-note",
3 | "version": "0.16.5",
4 | "description": "Add description, summary and more info to folders with folder notes.",
5 | "scripts": {
6 | "dev": "cross-env BUILD=dev node esbuild.config.mjs",
7 | "check": "tsc --noEmit",
8 | "build": "cross-env BUILD=production node esbuild.config.mjs",
9 | "prettier": "prettier --write 'src/**/*.+(ts|tsx|json|html|css)'",
10 | "eslint": "eslint . --ext .ts,.tsx --fix",
11 | "release": "release-it"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "MIT",
16 | "devDependencies": {
17 | "@aidenlx/esbuild-plugin-obsidian": "^0.1.8",
18 | "@aidenlx/folder-note-core": "^1.3.3",
19 | "@aidenlx/obsidian-icon-shortcodes": "^0.9.0",
20 | "@aidenlx/obsidian-plugin-bumper": "^0.1.12",
21 | "@release-it/bumper": "^4.0.0",
22 | "@release-it/conventional-changelog": "^5.1.0",
23 | "@types/array.prototype.flat": "^1.2.1",
24 | "@types/node": "^17.0.23",
25 | "@typescript-eslint/eslint-plugin": "^5.55.0",
26 | "@typescript-eslint/parser": "^5.55.0",
27 | "array.prototype.flat": "^1.2.5",
28 | "assert-never": "^1.2.1",
29 | "conventional-changelog-angular": "^5.0.13",
30 | "cross-env": "^7.0.3",
31 | "cz-conventional-changelog": "^3.3.0",
32 | "esbuild": "^0.17.4",
33 | "esbuild-plugin-less": "^1.1.6",
34 | "eslint": "~8.36.0",
35 | "eslint-config-prettier": "^8.5.0",
36 | "eslint-import-resolver-typescript": "^2.7.0",
37 | "eslint-plugin-import": "^2.26.0",
38 | "eslint-plugin-jsdoc": "^38.0.6",
39 | "eslint-plugin-prefer-arrow": "~1.2.3",
40 | "eslint-plugin-prettier": "^4.2.1",
41 | "eslint-plugin-simple-import-sort": "~7.0.0",
42 | "fast-deep-equal": "^3.1.3",
43 | "monkey-around": "^2.3.0",
44 | "obsidian": "~1.2.8",
45 | "path-browserify": "^1.0.1",
46 | "prettier": "^2.6.1",
47 | "release-it": "^15.4.2",
48 | "semver": "^7.3.5",
49 | "typescript": "~5.0.4"
50 | },
51 | "browser": {
52 | "path": "path-browserify"
53 | },
54 | "config": {
55 | "commitizen": {
56 | "path": "./node_modules/cz-conventional-changelog"
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/scripts/ob-bumper.mjs:
--------------------------------------------------------------------------------
1 | import { promises } from "fs";
2 | import { copyFile } from "fs/promises";
3 | import { join } from "path";
4 | const { readFile, writeFile } = promises;
5 | import { Plugin } from "release-it";
6 | import semverPrerelease from "semver/functions/prerelease.js";
7 |
8 | const mainManifest = "manifest.json",
9 | betaManifest = "manifest-beta.json",
10 | versionsList = "versions.json";
11 | const targets = [mainManifest, betaManifest, versionsList];
12 |
13 | const isPreRelease = (version) => semverPrerelease(version) !== null;
14 |
15 | class ObsidianVersionBump extends Plugin {
16 | async readJson(path) {
17 | // const { isDryRun } = this.config;
18 | try {
19 | const result = JSON.parse(await readFile(path, "utf8"));
20 | return result;
21 | } catch (error) {
22 | if (error.code === "ENOENT") {
23 | return null;
24 | }
25 | throw error;
26 | }
27 | }
28 | async writeJson(file, data) {
29 | // const { isDryRun } = this.config;
30 | const { indent = 4 } = this.getContext();
31 | await writeFile(file, JSON.stringify(data, null, indent));
32 | // this.log.exec(`Write to ${file}`, isDryRun);
33 | }
34 |
35 | /**
36 | * always read from previous version of manifest
37 | */
38 | async readManifest() {
39 | const { isDryRun } = this.config;
40 | const latest = isPreRelease(this.config.contextOptions.latestVersion);
41 | let manifestToRead = this.getManifest(latest);
42 | this.log.exec(`Reading manifest from ${manifestToRead}`, isDryRun);
43 | let manifest;
44 | if (!(manifest = await this.readJson(manifestToRead))) {
45 | manifestToRead = this.getManifest(!latest);
46 | this.log.exec(`retry reading manifest from ${manifestToRead}`, isDryRun);
47 | manifest = await this.readJson(manifestToRead);
48 | }
49 | if (!manifest) throw new Error("Missing manifest data");
50 | return manifest;
51 | }
52 |
53 | async writeManifest(targetVersion, manifest) {
54 | const { isDryRun } = this.config;
55 | const manifestToWrite = this.getManifest(isPreRelease(targetVersion));
56 | const updatedMainfest = { ...manifest, version: targetVersion };
57 | !isDryRun && (await this.writeJson(manifestToWrite, updatedMainfest));
58 | this.log.exec(
59 | `Wrote version ${targetVersion} to ${manifestToWrite}`,
60 | isDryRun,
61 | );
62 | await this.syncManifest(targetVersion);
63 | }
64 |
65 | /**
66 | * if bump to official release
67 | * sync manifest-beta.json with manifest.json
68 | */
69 | async syncManifest(targetVersion) {
70 | const target = isPreRelease(targetVersion);
71 | const { isDryRun } = this.config;
72 | if (!target) {
73 | !isDryRun && (await copyFile(mainManifest, betaManifest));
74 | this.log.exec(`Syncing ${betaManifest} with ${mainManifest}`, isDryRun);
75 | }
76 | }
77 |
78 | async copyToRoot() {
79 | const { copyTo } = this.getContext();
80 | if (!copyTo) return;
81 | const { isDryRun } = this.config;
82 | if (!isDryRun) {
83 | await Promise.all(
84 | targets.map((file) =>
85 | copyFile(file, join(copyTo, file)).catch((err) => {
86 | if (err.code !== "ENOENT") throw err;
87 | }),
88 | ),
89 | );
90 | }
91 | this.log.exec(`Copied ${targets.join(", ")} to ${copyTo}`, isDryRun);
92 | }
93 |
94 | /**
95 | * update versions.json with target version and minAppVersion from manifest.json
96 | */
97 | async writeVersion(targetVersion, { minAppVersion }) {
98 | const { isDryRun } = this.config;
99 | const versions = await this.readJson(versionsList);
100 | versions[targetVersion] = minAppVersion;
101 | !isDryRun && (await this.writeJson(versionsList, versions));
102 | this.log.exec(
103 | `Wrote version ${targetVersion} to ${versionsList}`,
104 | isDryRun,
105 | );
106 | }
107 |
108 | getManifest(isPreRelease) {
109 | return isPreRelease ? betaManifest : mainManifest;
110 | }
111 |
112 | async bump(targetVersion) {
113 | // read minAppVersion from manifest and bump version to target version
114 | const manifest = await this.readManifest(targetVersion);
115 | this.log.info(`min obsidian app version: ${manifest.minAppVersion}`);
116 | await Promise.all([
117 | this.writeManifest(targetVersion, manifest),
118 | this.writeVersion(targetVersion, manifest),
119 | ]);
120 | await this.copyToRoot();
121 | }
122 | }
123 | export default ObsidianVersionBump;
124 |
--------------------------------------------------------------------------------
/src/click-handler.ts:
--------------------------------------------------------------------------------
1 | import type { FolderItem } from "obsidian";
2 | import { Platform } from "obsidian";
3 |
4 | import type ALxFolderNote from "./fn-main";
5 | import { isModifier, getFileItemInnerTitleEl } from "./misc";
6 | import type { LongPressEvent } from "./modules/long-press";
7 |
8 | export const getClickHandler = (plugin: ALxFolderNote) => {
9 | const { getFolderNote, getFolderNotePath, getNewFolderNote } = plugin.CoreApi;
10 | return async (item: FolderItem, evt: MouseEvent): Promise => {
11 | if (
12 | !item ||
13 | (Platform.isMobile && !plugin.settings.mobileClickToOpen) ||
14 | // allow folder shift selection to work
15 | evt.shiftKey ||
16 | // triggered only when click on title
17 | !(
18 | getFileItemInnerTitleEl(item) === evt.target ||
19 | getFileItemInnerTitleEl(item).contains(evt.target as Node)
20 | ) ||
21 | // ignore file being renamed
22 | item.fileExplorer.fileBeingRenamed === item.file
23 | )
24 | return false;
25 |
26 | if (evt.type === "auxclick" && evt.button !== 1) return false;
27 |
28 | // get the folder path
29 | const folder = item.file;
30 | const createNew =
31 | (evt.type === "click" &&
32 | isModifier(evt, plugin.settings.modifierForNewNote)) ||
33 | (evt.type === "auxclick" && evt.button === 1);
34 | try {
35 | // check if folder note exists
36 | let folderNote = getFolderNote(folder),
37 | fnPath;
38 | if (createNew && !folderNote && (fnPath = getFolderNotePath(folder))) {
39 | folderNote = await plugin.app.vault.create(
40 | fnPath.path,
41 | getNewFolderNote(folder),
42 | );
43 | }
44 |
45 | if (!folderNote) return false;
46 |
47 | // show the note
48 | await plugin.app.workspace.openLinkText(
49 | folderNote.path,
50 | "",
51 | createNew || evt.type === "auxclick",
52 | { active: true },
53 | );
54 | if (plugin.settings.expandFolderOnClick && item.collapsed)
55 | await item.setCollapsed(false);
56 | return true;
57 | } catch (error) {
58 | console.error(error);
59 | return false;
60 | }
61 | };
62 | };
63 | export const pressHandler = (
64 | item: FolderItem,
65 | _evt: LongPressEvent,
66 | ): boolean => {
67 | if (!item || item.fileExplorer.fileBeingRenamed === item.file) return false;
68 | const folder = item.file;
69 | item.fileExplorer.folderNoteUtils?.folderFocus.toggleFocusFolder(folder);
70 | return true;
71 | };
72 |
--------------------------------------------------------------------------------
/src/drag-patch.ts:
--------------------------------------------------------------------------------
1 | import { around } from "monkey-around";
2 | import type { ClipboardManager, DragManager, MarkdownView } from "obsidian";
3 | import { Platform, TFolder, WorkspaceLeaf } from "obsidian";
4 |
5 | import type ALxFolderNote from "./fn-main";
6 |
7 | declare global {
8 | var i18next: any;
9 | }
10 | declare module "obsidian" {
11 | interface App {
12 | dragManager: DragManager;
13 | getObsidianUrl(file: TFile): string;
14 | }
15 | interface DragInfo {
16 | source?: string;
17 | type: string;
18 | icon: string;
19 | title: string;
20 | file?: TFolder | TFile;
21 | }
22 | interface DragFolderInfo extends DragInfo {
23 | type: "folder";
24 | file: TFolder;
25 | }
26 | interface DragFileInfo extends DragInfo {
27 | type: "file";
28 | file: TFile;
29 | }
30 | interface DragFilesInfo extends DragInfo {
31 | type: "files";
32 | files: TFile[];
33 | }
34 | class DragManager {
35 | draggable: DragInfo | null;
36 | setAction: (action: string) => any;
37 | dragFile(evt: DragEvent, file: TFile, source?: string): DragFolderInfo;
38 | dragFiles(evt: DragEvent, files: TFile[], source?: string): DragFilesInfo;
39 | dragFolder(
40 | evt: DragEvent,
41 | folder: TFolder,
42 | source?: string,
43 | ): DragFolderInfo;
44 | // handleDrop: (
45 | // el: HTMLElement,
46 | // handler: (
47 | // evt: DragEvent,
48 | // dragable: DragInfo,
49 | // draging: boolean,
50 | // ) => {
51 | // action: string;
52 | // dropEffect: string;
53 | // hoverEl?: HTMLElement;
54 | // hoverClass?: string;
55 | // },
56 | // arg0: boolean,
57 | // ) => void;
58 | }
59 | interface MarkdownView {
60 | editMode?: MarkdownEditView;
61 | sourceMode?: MarkdownEditView;
62 | }
63 | interface MarkdownEditView {
64 | clipboardManager: ClipboardManager;
65 | }
66 | class ClipboardManager {
67 | app: App;
68 | handleDrop: (evt: DragEvent) => boolean;
69 | handleDragOver: (evt: DragEvent) => void;
70 | }
71 | }
72 |
73 | const HD = {
74 | none: [],
75 | copy: ["copy"],
76 | copyLink: ["copy", "link"],
77 | copyMove: ["copy", "move"],
78 | link: ["link"],
79 | linkMove: ["link", "move"],
80 | move: ["move"],
81 | all: ["copy", "link", "move"],
82 | uninitialized: [],
83 | } as const;
84 | function VD(e: DragEvent, t: DataTransfer["dropEffect"]) {
85 | t &&
86 | (function (e, t) {
87 | if ("none" === t) return !0;
88 | const n = HD[e.dataTransfer!.effectAllowed];
89 | return !!n && (n as any).contains(t);
90 | })(e, t) &&
91 | (e.dataTransfer!.dropEffect = t);
92 | }
93 |
94 | const getMarkdownView = () => {
95 | const leaves = app.workspace.getLeavesOfType("markdown");
96 | if (leaves.length > 0) {
97 | return leaves[0].view as MarkdownView;
98 | } else return null;
99 | };
100 |
101 | const PatchDragManager = (plugin: ALxFolderNote) => {
102 | const { getFolderNote } = plugin.CoreApi;
103 |
104 | const patchClipboardManager = (): boolean => {
105 | const view = getMarkdownView();
106 | if (!view) return false;
107 | const editMode = view.editMode ?? view.sourceMode;
108 |
109 | if (!editMode)
110 | throw new Error("Failed to patch clipboard manager: no edit view found");
111 |
112 | plugin.register(
113 | around(
114 | editMode.clipboardManager.constructor.prototype as ClipboardManager,
115 | {
116 | handleDragOver: (next) =>
117 | function (this: ClipboardManager, evt, ...args) {
118 | const { draggable } = this.app.dragManager;
119 | if (
120 | draggable &&
121 | !(Platform.isMacOS ? evt.shiftKey : evt.altKey) &&
122 | draggable.file instanceof TFolder &&
123 | getFolderNote(draggable.file)
124 | ) {
125 | // evt.preventDefault();
126 | VD(evt, "link");
127 | this.app.dragManager.setAction(
128 | i18next.t("interface.drag-and-drop.insert-link-here"),
129 | );
130 | } else {
131 | next.call(this, evt, ...args);
132 | }
133 | },
134 | handleDrop: (next) =>
135 | function (this: ClipboardManager, evt, ...args) {
136 | const fallback = () => next.call(this, evt, ...args);
137 | const { draggable } = plugin.app.dragManager;
138 | let note;
139 | if (
140 | draggable?.type === "folder" &&
141 | draggable.file instanceof TFolder &&
142 | (note = getFolderNote(draggable.file))
143 | ) {
144 | draggable.file = note;
145 | draggable.type = "file";
146 | }
147 | return fallback();
148 | },
149 | },
150 | ),
151 | );
152 | console.log("alx-folder-note: clipboard manager patched");
153 | return true;
154 | };
155 |
156 | plugin.app.workspace.onLayoutReady(() => {
157 | if (!patchClipboardManager()) {
158 | const evt = app.workspace.on("layout-change", () => {
159 | patchClipboardManager() && app.workspace.offref(evt);
160 | });
161 | plugin.registerEvent(evt);
162 | }
163 | });
164 |
165 | plugin.register(
166 | around(app.dragManager.constructor.prototype as DragManager, {
167 | dragFolder: (next) =>
168 | function (this: DragManager, evt, folder, source, ...args) {
169 | let note;
170 | if ((note = getFolderNote(folder))) {
171 | const url = app.getObsidianUrl(note);
172 | evt.dataTransfer!.setData("text/plain", url);
173 | evt.dataTransfer!.setData("text/uri-list", url);
174 | }
175 | return next.call(this, evt, folder, source, ...args);
176 | },
177 | }),
178 | );
179 | };
180 | export default PatchDragManager;
181 |
--------------------------------------------------------------------------------
/src/fe-handler/active-folder.ts:
--------------------------------------------------------------------------------
1 | import type { FileExplorerView, TFolder, WorkspaceLeaf } from "obsidian";
2 | import { MarkdownView } from "obsidian";
3 |
4 | import type ALxFolderNote from "../fn-main";
5 | import { getFileItemTitleEl } from "../misc";
6 | import FEHandler_Base from "./base";
7 |
8 | const isActiveClass = "is-active";
9 |
10 | export default class ActiveFolder extends FEHandler_Base {
11 | queues = {};
12 | constructor(plugin: ALxFolderNote, fileExplorer: FileExplorerView) {
13 | super(plugin, fileExplorer);
14 | const { workspace } = plugin.app;
15 | this.handleActiveLeafChange(workspace.activeLeaf);
16 | plugin.registerEvent(
17 | workspace.on(
18 | "active-leaf-change",
19 | this.handleActiveLeafChange.bind(this),
20 | ),
21 | );
22 | this.plugin.register(() => (this.activeFolder = null));
23 | }
24 | private _activeFolder: TFolder | null = null;
25 | public set activeFolder(folder: TFolder | null) {
26 | const getTitleEl = (folder: TFolder | null) =>
27 | folder && this.fileExplorer.fileItems[folder.path]
28 | ? getFileItemTitleEl(this.fileExplorer.fileItems[folder.path])
29 | : undefined;
30 | if (!folder) {
31 | getTitleEl(this._activeFolder)?.removeClass(isActiveClass);
32 | } else if (folder !== this._activeFolder) {
33 | getTitleEl(this._activeFolder)?.removeClass(isActiveClass);
34 | getTitleEl(folder)?.addClass(isActiveClass);
35 | }
36 | this._activeFolder = folder;
37 | }
38 | public get activeFolder(): TFolder | null {
39 | return this._activeFolder;
40 | }
41 | private handleActiveLeafChange(leaf: WorkspaceLeaf | null) {
42 | let folder;
43 | if (
44 | leaf &&
45 | leaf.view instanceof MarkdownView &&
46 | (folder = this.fncApi.getFolderFromNote(leaf.view.file))
47 | ) {
48 | this.activeFolder = folder;
49 | } else {
50 | this.activeFolder = null;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/fe-handler/base.ts:
--------------------------------------------------------------------------------
1 | import "./file-explorer.less";
2 |
3 | import type { AFItem, FileExplorerView } from "obsidian";
4 | import { debounce } from "obsidian";
5 |
6 | import type ALxFolderNote from "../fn-main";
7 | import type { afItemMark } from "../misc";
8 |
9 | export default abstract class FEHandler_Base {
10 | longPressRegistered = new WeakSet();
11 | constructor(
12 | public plugin: ALxFolderNote,
13 | public fileExplorer: FileExplorerView,
14 | ) {}
15 | get fncApi() {
16 | return this.plugin.CoreApi;
17 | }
18 | get app() {
19 | return this.plugin.app;
20 | }
21 | get files() {
22 | return this.fileExplorer.files;
23 | }
24 | getAfItem = (path: string): afItemMark | null =>
25 | this.fileExplorer.fileItems[path] ?? null;
26 | iterateItems = (callback: (item: AFItem) => any): void =>
27 | Object.values(this.fileExplorer.fileItems).forEach(callback);
28 |
29 | abstract queues: Record<
30 | string,
31 | {
32 | action: (id: string, ...args: any[]) => any;
33 | queue: Set | Map;
34 | }
35 | >;
36 | private debouncers = {} as Record any>;
37 | private _execQueue(queueName: string) {
38 | const { action, queue } = this.queues[queueName];
39 | if (queue.size <= 0) return;
40 | if (queue instanceof Set) {
41 | queue.forEach((id) => action(id));
42 | } else {
43 | queue.forEach((args, id) => action(id, ...args));
44 | }
45 | queue.clear();
46 | }
47 | protected execQueue(queueName: string) {
48 | if (!Object.keys(this.queues).includes(queueName)) return;
49 | const debouncer =
50 | this.debouncers[queueName] ??
51 | (this.debouncers[queueName] = debounce(
52 | () => this._execQueue(queueName),
53 | 200,
54 | true,
55 | ));
56 | debouncer();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/fe-handler/file-explorer.less:
--------------------------------------------------------------------------------
1 | body:not(.alx-no-hide-note) .nav-file.alx-folder-note > .nav-file-title {
2 | display: none;
3 | }
4 |
5 | .nav-folder.alx-folder-with-note {
6 | & > .nav-folder-title > .nav-folder-title-content {
7 | /* color: var(--text-accent); */
8 | text-decoration-line: underline;
9 | text-decoration-color: var(--text-faint);
10 | text-decoration-thickness: 2px;
11 | text-underline-offset: 1px;
12 | }
13 |
14 | &.alx-empty-folder > .nav-folder-title > .nav-folder-collapse-indicator {
15 | visibility: hidden;
16 | }
17 | }
18 |
19 | body.is-mobile:not(.alx-no-click-on-mobile)
20 | .nav-folder
21 | > .nav-folder-title
22 | > .nav-folder-collapse-indicator {
23 | width: 28px;
24 | // justify-content: center;
25 | }
26 |
--------------------------------------------------------------------------------
/src/fe-handler/focus.less:
--------------------------------------------------------------------------------
1 | .nav-files-container.alx-folder-focus .nav-folder:not(.mod-root) {
2 | .nav-file-title,
3 | .nav-folder-title {
4 | opacity: 0.2;
5 | }
6 | &.alx-focused-folder {
7 | .nav-file-title,
8 | .nav-folder-title {
9 | opacity: 1;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/fe-handler/folder-focus.ts:
--------------------------------------------------------------------------------
1 | import "./focus.less";
2 |
3 | import type { FileExplorerView, FolderItem } from "obsidian";
4 | import { TFolder } from "obsidian";
5 |
6 | import type ALxFolderNote from "../fn-main";
7 | import FEHandler_Base from "./base";
8 |
9 | const focusedFolderCls = "alx-focused-folder";
10 | const focusModeCls = "alx-folder-focus";
11 |
12 | export default class FolderFocus extends FEHandler_Base {
13 | queues = {};
14 | constructor(plugin: ALxFolderNote, fileExplorer: FileExplorerView) {
15 | super(plugin, fileExplorer);
16 | const { workspace } = plugin.app;
17 | this.plugin.register(
18 | () => this.focusedFolder && this.toggleFocusFolder(null),
19 | );
20 |
21 | [
22 | workspace.on("file-menu", (menu, af) => {
23 | if (!(af instanceof TFolder) || af.isRoot()) return;
24 | menu.addItem((item) =>
25 | item
26 | .setTitle("Toggle Focus")
27 | .setIcon("crossed-star")
28 | .onClick(() => this.toggleFocusFolder(af)),
29 | );
30 | }),
31 | ].forEach(this.plugin.registerEvent.bind(this.plugin));
32 | }
33 |
34 | private _focusedFolder: {
35 | folder: FolderItem;
36 | collapsedCache: boolean;
37 | } | null = null;
38 | get focusedFolder() {
39 | return this._focusedFolder?.folder ?? null;
40 | }
41 | set focusedFolder(item: FolderItem | null) {
42 | // restore previous folder collapse state
43 | if (this._focusedFolder) {
44 | const { folder, collapsedCache } = this._focusedFolder;
45 | if (folder.collapsed !== collapsedCache)
46 | folder.setCollapsed(collapsedCache);
47 | }
48 | this._focusedFolder = item
49 | ? { folder: item, collapsedCache: item.collapsed }
50 | : null;
51 | // unfold folder if it's collapsed
52 | if (item && item.collapsed) {
53 | item.setCollapsed(false);
54 | // @ts-ignore
55 | this.plugin.app.nextFrame(() => {
56 | // @ts-ignore
57 | this.fileExplorer.dom.infinityScroll.computeSync();
58 | // @ts-ignore
59 | this.fileExplorer.dom.infinityScroll.scrollIntoView(item);
60 | });
61 | }
62 | this.fileExplorer.dom.navFileContainerEl.toggleClass(focusModeCls, !!item);
63 | }
64 | toggleFocusFolder(folder: TFolder | null) {
65 | const folderItem = folder
66 | ? (this.getAfItem(folder.path) as FolderItem | null)
67 | : null;
68 | if (this.focusedFolder) {
69 | this._focusFolder(this.focusedFolder, true);
70 | }
71 | // if given same folder as current cached, toggle it off
72 | if (folderItem && folderItem.file.path === this.focusedFolder?.file.path) {
73 | this.focusedFolder = null;
74 | } else {
75 | folderItem && this._focusFolder(folderItem, false);
76 | this.focusedFolder = folderItem;
77 | }
78 | }
79 | private _focusFolder(folder: FolderItem, revert = false) {
80 | if (folder.file.isRoot()) throw new Error("Cannot focus on root dir");
81 | folder.el.toggleClass(focusedFolderCls, !revert);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/fe-handler/folder-icon.less:
--------------------------------------------------------------------------------
1 | @folder-title-selector: .nav-folder-title > .nav-folder-title-content;
2 |
3 | .alx-folder-icons .nav-folder.alx-folder-with-note[data-icon] {
4 | &[data-icon-type="emoji"] > @{folder-title-selector}::before {
5 | content: var(--alx-folder-icon-txt);
6 | background-image: var(--alx-folder-icon-url);
7 | height: unset !important;
8 | vertical-align: text-top;
9 | }
10 | &[data-icon-type="svg"] > @{folder-title-selector}::before {
11 | content: " ";
12 | background-image: var(--alx-folder-icon-url);
13 | vertical-align: text-bottom;
14 | }
15 | /** basic style setup */
16 | & > @{folder-title-selector}::before {
17 | width: 1em;
18 | height: 1em;
19 | margin-right: 3px;
20 | display: inline-block;
21 | background-size: contain;
22 | background-repeat: no-repeat;
23 | background-position: center center;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/fe-handler/folder-mark.ts:
--------------------------------------------------------------------------------
1 | import "./folder-icon.less";
2 |
3 | import { dirname } from "path";
4 | import type {
5 | AFItem,
6 | CachedMetadata,
7 | FileExplorerView,
8 | FolderItem,
9 | } from "obsidian";
10 | import { TAbstractFile, TFile, TFolder } from "obsidian";
11 |
12 | import type ALxFolderNote from "../fn-main";
13 | import type { afItemMark } from "../misc";
14 | import { isFolder } from "../misc";
15 | import FEHandler_Base from "./base";
16 |
17 | export const folderIconMark = "alx-folder-icons";
18 |
19 | const folderNoteClass = "alx-folder-note";
20 | const folderClass = "alx-folder-with-note";
21 | const emptyFolderClass = "alx-empty-folder";
22 |
23 | export default class FolderMark extends FEHandler_Base {
24 | constructor(plugin: ALxFolderNote, fileExplorer: FileExplorerView) {
25 | super(plugin, fileExplorer);
26 | this.initFolderMark();
27 | if (this.plugin.settings.folderIcon) {
28 | this.initFolderIcon();
29 | }
30 | if (this.plugin.settings.hideCollapseIndicator) {
31 | this.initHideCollapseIndicator();
32 | }
33 | }
34 | queues = {
35 | mark: {
36 | queue: new Map(),
37 | action: (path: string, revert: boolean) => {
38 | const item = this.getAfItem(path);
39 | if (!item) {
40 | console.warn("no afitem found for path %s, escaping...", path);
41 | return;
42 | }
43 | if (isFolder(item)) {
44 | if (revert === !!item.isFolderWithNote) {
45 | item.el.toggleClass(folderClass, !revert);
46 | item.isFolderWithNote = revert ? undefined : true;
47 | if (this.plugin.settings.hideCollapseIndicator)
48 | item.el.toggleClass(
49 | emptyFolderClass,
50 | revert ? false : item.file.children.length === 1,
51 | );
52 | }
53 | this._updateIcon(path, revert, item);
54 | } else if (revert === !!item.isFolderNote) {
55 | item.el.toggleClass(folderNoteClass, !revert);
56 | item.isFolderNote = revert ? undefined : true;
57 | }
58 | },
59 | },
60 | changedFolder: {
61 | queue: new Set(),
62 | action: (path: string) => {
63 | const note = this.fncApi.getFolderNote(path);
64 | if (note) {
65 | (this.getAfItem(path) as FolderItem)?.el.toggleClass(
66 | emptyFolderClass,
67 | note.parent.children.length === 1,
68 | );
69 | }
70 | },
71 | },
72 | };
73 | private initFolderMark() {
74 | const { vault, metadataCache } = this.app;
75 | this.markAll();
76 | // #region folder note events setup
77 | [
78 | vault.on("folder-note:create", (note: TFile, folder: TFolder) => {
79 | this.setMark(note);
80 | this.setMark(folder);
81 | }),
82 | vault.on("folder-note:delete", (note: TFile, folder: TFolder) => {
83 | this.setMark(note, true);
84 | this.setMark(folder, true);
85 | }),
86 | vault.on("folder-note:rename", () => {
87 | // fe-item in dom will be reused, do nothing for now
88 | }),
89 | vault.on("folder-note:cfg-changed", () => {
90 | this.markAll(true);
91 | window.setTimeout(this.markAll, 200);
92 | }),
93 | metadataCache.on("changed", (file) => {
94 | let folder;
95 | if ((folder = this.fncApi.getFolderFromNote(file))) {
96 | this.setMark(folder);
97 | }
98 | }),
99 | ].forEach(this.plugin.registerEvent.bind(this.plugin));
100 | }
101 | // #region set class mark for folder notes and folders
102 | public setMark = (
103 | target: AFItem | TAbstractFile | string,
104 | revert = false,
105 | ) => {
106 | if (!target) return;
107 | const { queue } = this.queues.mark;
108 | let path: string;
109 | if (target instanceof TAbstractFile) {
110 | path = target.path;
111 | } else if (typeof target === "string") {
112 | path = target;
113 | } else {
114 | path = target.file.path;
115 | }
116 | queue.set(path, [revert]);
117 | this.execQueue("mark");
118 | };
119 | public markAll = (revert = false) => {
120 | this.iterateItems((item: AFItem) => {
121 | if (isFolder(item) && !revert) {
122 | this.markFolderNote(item.file);
123 | } else if (revert) {
124 | this.setMark(item, true);
125 | }
126 | });
127 | };
128 | markFolderNote = (af: TAbstractFile): boolean => {
129 | if (af instanceof TFolder && af.isRoot()) return false;
130 | const { getFolderNote, getFolderFromNote } = this.fncApi;
131 |
132 | let found: TAbstractFile | null = null;
133 | if (af instanceof TFile) found = getFolderFromNote(af);
134 | else if (af instanceof TFolder) found = getFolderNote(af);
135 |
136 | if (found) {
137 | this.setMark(found);
138 | this.setMark(af);
139 | } else {
140 | this.setMark(af, true);
141 | }
142 | return !!found;
143 | };
144 | // #endregion
145 | // #region folder icon setup
146 | private initFolderIcon() {
147 | document.body.toggleClass(folderIconMark, this.plugin.settings.folderIcon);
148 | const { vault } = this.app;
149 | const updateIcon = () => {
150 | for (const path of this.foldersWithIcon) {
151 | this.setMark(path);
152 | }
153 | };
154 | [
155 | vault.on("iconsc:initialized", updateIcon),
156 | vault.on("iconsc:changed", updateIcon),
157 | ].forEach(this.plugin.registerEvent.bind(this.plugin));
158 | }
159 | foldersWithIcon = new Set();
160 | private _updateIcon(path: string, revert: boolean, item: afItemMark) {
161 | const api = this.plugin.IconSCAPI;
162 | if (!api) return;
163 |
164 | let folderNotePath: string | undefined,
165 | metadata: CachedMetadata | undefined;
166 | const revertIcon = () => {
167 | delete item.el.dataset.icon;
168 | delete item.el.dataset["icon-type"];
169 | this.foldersWithIcon.delete(path);
170 | item.el.style.removeProperty("--alx-folder-icon-txt");
171 | item.el.style.removeProperty("--alx-folder-icon-url");
172 | };
173 | if (revert) {
174 | revertIcon();
175 | } else if (
176 | (folderNotePath = this.fncApi.getFolderNotePath(path)?.path) &&
177 | (metadata = this.plugin.app.metadataCache.getCache(folderNotePath))
178 | ) {
179 | let iconId = metadata.frontmatter?.icon,
180 | icon;
181 | if (
182 | iconId &&
183 | typeof iconId === "string" &&
184 | (icon = api.getIcon(iconId, true))
185 | ) {
186 | this.foldersWithIcon.add(path);
187 | item.el.dataset.icon = iconId.replace(/^:|:$/g, "");
188 | if (!api.isEmoji(iconId)) {
189 | item.el.dataset.iconType = "svg";
190 | item.el.style.setProperty("--alx-folder-icon-url", `url("${icon}")`);
191 | item.el.style.setProperty("--alx-folder-icon-txt", '" "');
192 | } else {
193 | item.el.dataset.iconType = "emoji";
194 | item.el.style.setProperty("--alx-folder-icon-url", '""');
195 | item.el.style.setProperty("--alx-folder-icon-txt", `"${icon}"`);
196 | }
197 | } else if (item.el.dataset.icon) {
198 | revertIcon();
199 | }
200 | }
201 | }
202 | // #endregion
203 | // #region set hide collapse indicator
204 | private initHideCollapseIndicator() {
205 | if (!this.plugin.settings.hideCollapseIndicator) return;
206 | const { vault } = this.app;
207 | [
208 | vault.on("create", (file) => this.setChangedFolder(file.parent.path)),
209 | vault.on("delete", (file) => {
210 | const parent = dirname(file.path);
211 | this.setChangedFolder(parent === "." ? "/" : parent);
212 | }),
213 | vault.on("rename", (file, oldPath) => {
214 | this.setChangedFolder(file.parent.path);
215 | const parent = dirname(oldPath);
216 | this.setChangedFolder(parent === "." ? "/" : parent);
217 | }),
218 | ].forEach(this.plugin.registerEvent.bind(this.plugin));
219 | }
220 |
221 | setChangedFolder = (folderPath: string) => {
222 | if (!folderPath || folderPath === "/") return;
223 | this.queues.changedFolder.queue.add(folderPath);
224 | this.execQueue("changedFolder");
225 | };
226 | // #endregion
227 | }
228 |
--------------------------------------------------------------------------------
/src/fe-handler/index.ts:
--------------------------------------------------------------------------------
1 | import type { FileExplorerView } from "obsidian";
2 |
3 | import type ALxFolderNote from "../fn-main";
4 | import ActiveFolder from "./active-folder";
5 | import FolderFocus from "./folder-focus";
6 | import FolderMark from "./folder-mark";
7 |
8 | const getFileExplorerHandlers = (
9 | plugin: ALxFolderNote,
10 | fileExplorer: FileExplorerView,
11 | ) => ({
12 | // initialized (mark folders, hook evt handlers...) when constructed
13 | plugin,
14 | folderFocus: new FolderFocus(plugin, fileExplorer),
15 | folderMark: new FolderMark(plugin, fileExplorer),
16 | activeFolder: new ActiveFolder(plugin, fileExplorer),
17 | });
18 |
19 | export default getFileExplorerHandlers;
20 |
--------------------------------------------------------------------------------
/src/fe-patch.ts:
--------------------------------------------------------------------------------
1 | import { around } from "monkey-around";
2 | import type {
3 | FileExplorerPlugin as FEPluginCls,
4 | FileExplorerView as FEViewCls,
5 | FileExplorerView,
6 | FolderItem as FolderItemCls,
7 | TAbstractFile,
8 | } from "obsidian";
9 | import { TFile, TFolder } from "obsidian";
10 |
11 | import { getClickHandler, pressHandler } from "./click-handler";
12 | import getFileExplorerHandlers from "./fe-handler";
13 | import type ALxFolderNote from "./fn-main";
14 | import { getViewOfType } from "./misc";
15 | import type { LongPressEvent } from "./modules/long-press";
16 | import AddLongPressEvt from "./modules/long-press";
17 |
18 | const getFolderItemFromEl = (navEl: HTMLElement, view: FEViewCls) => {
19 | const folder = view.files.get(navEl);
20 | return folder instanceof TFolder
21 | ? (view.fileItems[folder.path] as FolderItemCls)
22 | : null;
23 | };
24 | const Rt = (evt: MouseEvent, target: HTMLElement) => {
25 | const n = evt.relatedTarget;
26 | return !(n instanceof Node && target.contains(n));
27 | };
28 | /**
29 | * reset existing file explorer views
30 | */
31 | const resetFileExplorer = async (plugin: ALxFolderNote) => {
32 | for (const leaf of plugin.app.workspace.getLeavesOfType("file-explorer")) {
33 | const state = leaf.getViewState();
34 | await leaf.setViewState({ type: "empty" });
35 | leaf.setViewState(state);
36 | }
37 | };
38 |
39 | const PatchFileExplorer = (plugin: ALxFolderNote) => {
40 | const { getFolderFromNote } = plugin.CoreApi,
41 | clickHandler = getClickHandler(plugin);
42 |
43 | let FileExplorerViewInst: FEViewCls | null = getViewOfType(
44 | "file-explorer",
45 | plugin.app,
46 | ),
47 | FileExplorerPluginInst =
48 | plugin.app.internalPlugins.plugins["file-explorer"]?.instance;
49 | if (!FileExplorerViewInst || !FileExplorerPluginInst) return;
50 |
51 | // get constructors
52 | const FileExplorerView = FileExplorerViewInst.constructor as typeof FEViewCls,
53 | FileExplorerPlugin =
54 | FileExplorerPluginInst.constructor as typeof FEPluginCls,
55 | FolderItem = FileExplorerViewInst.createFolderDom(
56 | plugin.app.vault.getRoot(),
57 | ).constructor as typeof FolderItemCls;
58 |
59 | FileExplorerViewInst = null;
60 |
61 | const uninstallers: ReturnType[] = [
62 | around(FileExplorerView.prototype, {
63 | load: (next) =>
64 | function (this: FEViewCls) {
65 | const self = this;
66 | next.call(self);
67 | self.folderNoteUtils = getFileExplorerHandlers(plugin, self);
68 | AddLongPressEvt(plugin, self.dom.navFileContainerEl);
69 | self.containerEl.on(
70 | "auxclick",
71 | ".nav-folder",
72 | (evt: MouseEvent, navEl: HTMLElement) => {
73 | const item = getFolderItemFromEl(navEl, self);
74 | item && clickHandler(item, evt);
75 | },
76 | );
77 | self.containerEl.on(
78 | "long-press" as any,
79 | ".nav-folder",
80 | (evt: LongPressEvent, navEl: HTMLElement) => {
81 | const item = getFolderItemFromEl(navEl, self);
82 | item && pressHandler(item, evt);
83 | },
84 | );
85 | },
86 | onFileMouseover: (next) =>
87 | function (this: FileExplorerView, evt, navTitleEl) {
88 | next.call(this, evt, navTitleEl);
89 | if (!Rt(evt, navTitleEl)) return;
90 | const af = this.currentHoverFile;
91 | if (
92 | !af ||
93 | // if event is triggered on same file, do nothing
94 | (this._AFN_HOVER && this._AFN_HOVER === af) ||
95 | !(af instanceof TFolder)
96 | )
97 | return;
98 | const note = plugin.CoreApi.getFolderNote(af);
99 | if (note) {
100 | this.app.workspace.trigger("hover-link", {
101 | event: evt,
102 | source: "file-explorer",
103 | hoverParent: this,
104 | targetEl: navTitleEl,
105 | linktext: note.path,
106 | });
107 | }
108 | // indicate that this file is handled by monkey patch
109 | this._AFN_HOVER = af;
110 | },
111 | onFileMouseout: (next) =>
112 | function (this: FileExplorerView, evt, navTitleEl) {
113 | next.call(this, evt, navTitleEl);
114 | if (!Rt(evt, navTitleEl)) return;
115 | delete this._AFN_HOVER;
116 | },
117 | }),
118 | // patch reveal in folder to alter folder note target to linked folder
119 | around(FileExplorerPlugin.prototype, {
120 | revealInFolder: (next) =>
121 | function (this: FEPluginCls, file: TAbstractFile) {
122 | if (file instanceof TFile && plugin.settings.hideNoteInExplorer) {
123 | const findResult = getFolderFromNote(file);
124 | if (findResult) file = findResult;
125 | }
126 | return next.call(this, file);
127 | },
128 | }),
129 | around(FolderItem.prototype, {
130 | onTitleElClick: (next) =>
131 | async function (this: FolderItemCls, evt) {
132 | // if folder note click not success,
133 | // fallback to default
134 | if (!(await clickHandler(this, evt))) next.call(this, evt);
135 | },
136 | onSelfClick: (next) =>
137 | async function (this: FolderItemCls, evt) {
138 | // if folder note click not success,
139 | // fallback to default
140 | if (!(await clickHandler(this, evt))) next.call(this, evt);
141 | },
142 | }),
143 | ];
144 | resetFileExplorer(plugin);
145 | plugin.register(() => {
146 | // uninstall monkey patches
147 | uninstallers.forEach((revert) => revert());
148 | resetFileExplorer(plugin);
149 | });
150 | };
151 | export default PatchFileExplorer;
152 |
--------------------------------------------------------------------------------
/src/fn-main.ts:
--------------------------------------------------------------------------------
1 | import "./main.less";
2 |
3 | import type { FolderNoteAPI } from "@aidenlx/folder-note-core";
4 | import { getApi as getFNCApi } from "@aidenlx/folder-note-core";
5 | import { getApi as getISCApi } from "@aidenlx/obsidian-icon-shortcodes";
6 | import { Notice, Plugin } from "obsidian";
7 |
8 | import PatchDragManager from "./drag-patch";
9 | import PatchFileExplorer from "./fe-patch";
10 | import { ClickNotice } from "./misc";
11 | import registerSetFolderIconCmd from "./modules/set-folder-icon";
12 | import type { ALxFolderNoteSettings } from "./settings";
13 | import {
14 | ALxFolderNoteSettingTab,
15 | DEFAULT_SETTINGS,
16 | MobileNoClickMark,
17 | noHideNoteMark,
18 | } from "./settings";
19 |
20 | const foldervNotifiedKey = "foldervNotified";
21 |
22 | export default class ALxFolderNote extends Plugin {
23 | settings: ALxFolderNoteSettings = DEFAULT_SETTINGS;
24 |
25 | get CoreApi(): FolderNoteAPI {
26 | let message;
27 | const api = getFNCApi(this) || getFNCApi();
28 | if (api) {
29 | return api;
30 | } else {
31 | message = "Failed to initialize alx-folder-note";
32 | new ClickNotice(message + ": Click here for more details", () =>
33 | this.app.setting.openTabById(this.manifest.id),
34 | );
35 | throw new Error(message + ": folder-note-core not available");
36 | }
37 | }
38 | get IconSCAPI() {
39 | if (this.settings.folderIcon) {
40 | return getISCApi(this);
41 | }
42 | return null;
43 | }
44 |
45 | noticeFoldervChange() {
46 | if (
47 | !this.app.plugins.plugins["alx-folder-note-folderv"] && // not installed
48 | !Number(localStorage.getItem(foldervNotifiedKey)) // not notified
49 | ) {
50 | new ClickNotice(
51 | (frag) => {
52 | frag.appendText(
53 | "Since v0.13.0, folder overview (folderv) has become an optional component " +
54 | "that requires a dedicated plugin, ",
55 | );
56 | frag
57 | .createEl("button", {
58 | text: "Go to Folder Overview Section of the Setting Tab to Install",
59 | })
60 | .addEventListener("click", () =>
61 | this.app.setting.openTabById(this.manifest.id),
62 | );
63 | frag.createEl("button", {
64 | text: "Don't show this again",
65 | });
66 | },
67 | () => localStorage.setItem(foldervNotifiedKey, "1"),
68 | 5e3,
69 | );
70 | }
71 | }
72 |
73 | initialized = false;
74 | initialize() {
75 | if (this.initialized) return;
76 | PatchFileExplorer(this);
77 | document.body.toggleClass(
78 | MobileNoClickMark,
79 | !this.settings.mobileClickToOpen,
80 | );
81 | document.body.toggleClass(
82 | noHideNoteMark,
83 | !this.settings.hideNoteInExplorer,
84 | );
85 | this.initialized = true;
86 | }
87 |
88 | async onload() {
89 | console.log("loading alx-folder-note");
90 |
91 | await this.loadSettings();
92 |
93 | const tab = new ALxFolderNoteSettingTab(this.app, this);
94 | if (!tab.checkMigrated())
95 | new Notice(
96 | "Old config not yet migrated, \n" +
97 | "Open Settings Tab of ALx Folder Note for details",
98 | );
99 | this.addSettingTab(tab);
100 |
101 | let initCalled = false;
102 | const init = () => {
103 | initCalled = true;
104 | registerSetFolderIconCmd(this);
105 | this.app.workspace.onLayoutReady(this.initialize.bind(this));
106 | PatchDragManager(this);
107 | this.noticeFoldervChange();
108 | };
109 |
110 | if (getFNCApi(this)) {
111 | init();
112 | } else {
113 | if (this.app.plugins.enabledPlugins.has("folder-note-core")) {
114 | const timeoutId = window.setTimeout(() => {
115 | if (!initCalled) {
116 | this.app.vault.offref(evtRef);
117 | throw new Error(
118 | "folder-note-core enabled but fail to load within 5s",
119 | );
120 | }
121 | }, 5e3);
122 | const evtRef = this.app.vault.on("folder-note:api-ready", () => {
123 | init();
124 | if (timeoutId) window.clearTimeout(timeoutId);
125 | this.app.vault.offref(evtRef); // register event only once
126 | });
127 | } else {
128 | this.CoreApi; // prompt to enable folder-note-core
129 | }
130 | }
131 | }
132 |
133 | async loadSettings() {
134 | this.settings = { ...this.settings, ...(await this.loadData()) };
135 | this.setupLongPressDelay();
136 | }
137 |
138 | async saveSettings() {
139 | await this.saveData(this.settings);
140 | }
141 |
142 | get longPressDelay(): number {
143 | return this.settings.longPressDelay;
144 | }
145 | set longPressDelay(delay: number) {
146 | this.settings.longPressDelay = delay;
147 | document.body.dataset[longPressDelayDataKey] = `${delay}`;
148 | }
149 | setupLongPressDelay() {
150 | // set long press delay to the body
151 | this.longPressDelay = this.longPressDelay;
152 | this.register(() => delete document.body.dataset[longPressDelayDataKey]);
153 | }
154 | }
155 |
156 | const longPressDelayDataKey = "longPressDelay";
157 |
--------------------------------------------------------------------------------
/src/main.less:
--------------------------------------------------------------------------------
1 | .notice button {
2 | margin: 5px 0;
3 | }
4 |
--------------------------------------------------------------------------------
/src/misc.ts:
--------------------------------------------------------------------------------
1 | import { dirname, extname, join } from "path";
2 | import assertNever from "assert-never";
3 | import type {
4 | AFItem,
5 | App,
6 | FolderItem,
7 | Modifier,
8 | TAbstractFile,
9 | TFile,
10 | View,
11 | FileItem,
12 | } from "obsidian";
13 | import { WorkspaceLeaf, Notice, Platform, TFolder } from "obsidian";
14 |
15 | export type afItemMark = AFItem & {
16 | evtDone?: true;
17 | isFolderNote?: true;
18 | isFolderWithNote?: true;
19 | };
20 |
21 | export const getViewOfType = (
22 | type: string,
23 | app: App,
24 | ): V | null => {
25 | const vc = app.viewRegistry.getViewCreatorByType(type);
26 | return vc ? (vc(new (WorkspaceLeaf as any)(app)) as V) : null;
27 | };
28 |
29 | export const isFolder = (item: AFItem): item is FolderItem =>
30 | (item as FolderItem).file instanceof TFolder;
31 |
32 | export const isMd = (file: TFile | string) =>
33 | typeof file === "string" ? extname(file) === ".md" : file.extension === "md";
34 |
35 | export enum NoteLoc {
36 | Index,
37 | Inside,
38 | Outside,
39 | }
40 |
41 | export const isModifier = (evt: MouseEvent, pref: Modifier): boolean => {
42 | const { altKey, metaKey, ctrlKey, shiftKey } = evt;
43 | switch (pref) {
44 | case "Mod":
45 | return Platform.isMacOS ? metaKey : ctrlKey;
46 | case "Ctrl":
47 | return ctrlKey;
48 | case "Meta":
49 | return metaKey;
50 | case "Shift":
51 | return shiftKey;
52 | case "Alt":
53 | return altKey;
54 | default:
55 | assertNever(pref);
56 | }
57 | };
58 |
59 | /**
60 | * @param newName include extension
61 | */
62 | export const getRenamedPath = (af: TAbstractFile, newName: string) =>
63 | join(getParentPath(af.path), newName);
64 |
65 | export const getParentPath = (src: string) => {
66 | const path = dirname(src);
67 | if (path === ".") return "/";
68 | else return path;
69 | };
70 |
71 | export class ClickNotice extends Notice {
72 | constructor(
73 | message: string | ((desc: DocumentFragment) => void),
74 | action: (evt: MouseEvent) => any,
75 | timeout?: number,
76 | ) {
77 | super(typeof message === "string" ? message : "", timeout);
78 | this.noticeEl.addEventListener("click", action);
79 | if (typeof message === "function") {
80 | this.noticeEl.empty();
81 | const frag = new DocumentFragment();
82 | message(frag);
83 | this.noticeEl.append(frag);
84 | }
85 | }
86 | }
87 |
88 | export function getFileItemTitleEl(fileItem: FileItem): HTMLElement {
89 | return fileItem.titleEl ?? fileItem.selfEl;
90 | }
91 |
92 | export function getFileItemInnerTitleEl(fileItem: FileItem): HTMLElement {
93 | return fileItem.titleInnerEl ?? fileItem.innerEl;
94 | }
95 |
--------------------------------------------------------------------------------
/src/modules/long-press.ts:
--------------------------------------------------------------------------------
1 | /*!
2 | * long-press-event
3 | * Pure JavaScript long-press-event
4 | * https://github.com/john-doherty/long-press-event
5 | * @author John Doherty
6 | * @license MIT
7 | */
8 |
9 | import { Platform } from "obsidian";
10 |
11 | import type ALxFolderNote from "../fn-main";
12 |
13 | // local timer object based on rAF
14 | let timer: {
15 | value: number;
16 | } | null = null;
17 |
18 | // track number of pixels the mouse moves during long press
19 | let startX = 0; // mouse x position when timer started
20 | let startY = 0; // mouse y position when timer started
21 | const maxDiffX = 10; // max number of X pixels the mouse can move during long press before it is canceled
22 | const maxDiffY = 10; // max number of Y pixels the mouse can move during long press before it is canceled
23 |
24 | /**
25 | * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
26 | * @param {function} fn The callback function
27 | * @param {int} delay The delay in milliseconds
28 | * @returns {object} handle to the timeout object
29 | */
30 | const requestTimeout = (fn: Function, delay: number): { value: number } => {
31 | const start = new Date().getTime();
32 | const handle: { value?: number } = {};
33 |
34 | const loop = () => {
35 | const current = new Date().getTime();
36 | const delta = current - start;
37 |
38 | if (delta >= delay) {
39 | fn();
40 | } else {
41 | handle.value = requestAnimationFrame(loop);
42 | }
43 | };
44 |
45 | handle.value = requestAnimationFrame(loop);
46 |
47 | return handle as Required;
48 | };
49 |
50 | /**
51 | * Behaves the same as clearTimeout except uses cancelRequestAnimationFrame() where possible for better performance
52 | * @param {object} handle The callback function
53 | * @returns {void}
54 | */
55 | const clearRequestTimeout = (handle: typeof timer) => {
56 | handle && cancelAnimationFrame(handle.value);
57 | };
58 |
59 | /**
60 | * method responsible for clearing a pending long press timer
61 | * @returns {void}
62 | */
63 | const clearLongPressTimer = () => {
64 | clearRequestTimeout(timer);
65 | timer = null;
66 | };
67 |
68 | export type LongPressEvent = CustomEvent<{ clientX: number; clientY: number }>;
69 |
70 | /**
71 | * Fires the 'long-press' event on element
72 | * @param originalEvent The original event being fired
73 | * @returns {void}
74 | */
75 | const fireLongPressEvent = (originalEvent: PointerEvent): void => {
76 | clearLongPressTimer();
77 |
78 | // fire the long-press event
79 | const allowClickEvent = originalEvent.target?.dispatchEvent(
80 | new CustomEvent("long-press", {
81 | bubbles: true,
82 | cancelable: true,
83 |
84 | // custom event data (legacy)
85 | detail: {
86 | clientX: originalEvent.clientX,
87 | clientY: originalEvent.clientY,
88 | },
89 |
90 | // add coordinate data that would typically acompany a touch/click event
91 | // @ts-ignore
92 | clientX: originalEvent.clientX,
93 | clientY: originalEvent.clientY,
94 | offsetX: originalEvent.offsetX,
95 | offsetY: originalEvent.offsetY,
96 | pageX: originalEvent.pageX,
97 | pageY: originalEvent.pageY,
98 | screenX: originalEvent.screenX,
99 | screenY: originalEvent.screenY,
100 | }) as LongPressEvent,
101 | );
102 |
103 | if (!allowClickEvent) {
104 | const suppressEvent = (e: Event) => {
105 | moniterIn?.removeEventListener("click", suppressEvent, true);
106 | cancelEvent(e);
107 | };
108 |
109 | // suppress the next click event if e.preventDefault() was called in long-press handler
110 | moniterIn?.addEventListener("click", suppressEvent, true);
111 | }
112 | };
113 |
114 | /**
115 | * method responsible for starting the long press timer
116 | * @param {event} e - event object
117 | * @returns {void}
118 | */
119 | const startLongPressTimer = (e: PointerEvent) => {
120 | clearLongPressTimer();
121 |
122 | const el = e.target as EventTarget;
123 |
124 | // get delay from html attribute if it exists, otherwise default to 800
125 | const longPressDelayInMs = parseInt(
126 | getNearestAttribute(el, "data-long-press-delay", "800"),
127 | 10,
128 | ); // default 800
129 |
130 | // start the timer
131 | timer = requestTimeout(fireLongPressEvent.bind(el, e), longPressDelayInMs);
132 | };
133 |
134 | /**
135 | * Cancels the current event
136 | * @param {object} e - browser event object
137 | * @returns {void}
138 | */
139 | const cancelEvent = (e: Event) => {
140 | e.stopImmediatePropagation();
141 | e.preventDefault();
142 | e.stopPropagation();
143 | };
144 |
145 | /**
146 | * Starts the timer on mouse down and logs current position
147 | * @param {object} e - browser event object
148 | * @returns {void}
149 | */
150 | const mouseDownHandler = (e: PointerEvent) => {
151 | startX = e.clientX;
152 | startY = e.clientY;
153 | startLongPressTimer(e);
154 | };
155 |
156 | /**
157 | * If the mouse moves n pixels during long-press, cancel the timer
158 | * @param {object} e - browser event object
159 | * @returns {void}
160 | */
161 | const mouseMoveHandler = (e: DragEvent) => {
162 | clearLongPressTimer();
163 | // // calculate total number of pixels the pointer has moved
164 | // let diffX = Math.abs(startX - e.clientX);
165 | // let diffY = Math.abs(startY - e.clientY);
166 |
167 | // console.log(diffX >= maxDiffX || diffY >= maxDiffY, diffX, diffY);
168 | // // if pointer has moved more than allowed, cancel the long-press timer and therefore the event
169 | // if (diffX >= maxDiffX || diffY >= maxDiffY) {
170 | // clearLongPressTimer();
171 | // }
172 | };
173 |
174 | /**
175 | * Gets attribute off HTML element or nearest parent
176 | * @param {object} el - HTML element to retrieve attribute from
177 | * @param {string} attributeName - name of the attribute
178 | * @param {any} defaultValue - default value to return if no match found
179 | * @returns {any} attribute value or defaultValue
180 | */
181 | const getNearestAttribute = (
182 | el: EventTarget | null,
183 | attributeName: string,
184 | defaultValue: string,
185 | ) => {
186 | // walk up the dom tree looking for data-action and data-trigger
187 | while (el instanceof Element && el !== document.documentElement) {
188 | const attributeValue = el.getAttribute(attributeName);
189 |
190 | if (attributeValue) {
191 | return attributeValue;
192 | }
193 |
194 | el = el.parentNode;
195 | }
196 |
197 | return defaultValue;
198 | };
199 |
200 | let moniterIn: HTMLElement | null;
201 |
202 | const removeEvtListener = (el: HTMLElement) => {
203 | moniterIn = null;
204 | el.removeEventListener("pointerup", clearLongPressTimer, true);
205 | el.removeEventListener("drag", mouseMoveHandler, true);
206 | el.removeEventListener("wheel", clearLongPressTimer, true);
207 | el.removeEventListener("scroll", clearLongPressTimer, true);
208 |
209 | el.removeEventListener("pointerdown", mouseDownHandler, true); // <- start
210 | };
211 |
212 | const AddLongPressEvt = (plugin: ALxFolderNote, el: HTMLElement) => {
213 | // disable on mobile (conflict with file-menu)
214 | if (!plugin.settings.longPressFocus || Platform.isMobile) return;
215 | if (moniterIn) {
216 | removeEvtListener(moniterIn);
217 | }
218 | moniterIn = el;
219 | // hook events that clear a pending long press event
220 | el.addEventListener("pointerup", clearLongPressTimer, true);
221 | el.addEventListener("drag", mouseMoveHandler, true);
222 | el.addEventListener("wheel", clearLongPressTimer, true);
223 | el.addEventListener("scroll", clearLongPressTimer, true);
224 |
225 | // hook events that can trigger a long press event
226 | el.addEventListener("pointerdown", mouseDownHandler, true); // <- start
227 |
228 | plugin.register(() => removeEvtListener(el));
229 | };
230 |
231 | export default AddLongPressEvt;
232 |
--------------------------------------------------------------------------------
/src/modules/set-folder-icon.ts:
--------------------------------------------------------------------------------
1 | import type { IconInfo } from "@aidenlx/obsidian-icon-shortcodes/lib/icon-packs/types";
2 | import { MarkdownView, TFile, TFolder } from "obsidian";
3 |
4 | import type ALxFolderNote from "../fn-main";
5 |
6 | const registerSetFolderIconCmd = (plugin: ALxFolderNote) => {
7 | const { workspace, vault, fileManager } = plugin.app;
8 | const setIconField = async (icon: IconInfo | null, file: TFile) => {
9 | if (!icon) return;
10 | await fileManager.processFrontMatter(file, (fm) => {
11 | fm.icon = icon.id;
12 | });
13 | };
14 | plugin.addCommand({
15 | id: "set-folder-icon",
16 | name: "Set Folder Icon",
17 | checkCallback: (checking) => {
18 | const iscAPI = plugin.IconSCAPI;
19 | if (!iscAPI) return false;
20 | const mdView = workspace.getActiveViewOfType(MarkdownView);
21 | if (!mdView) return false;
22 | const folder = plugin.CoreApi.getFolderFromNote(mdView.file);
23 | if (!folder) return false;
24 | if (checking) return true;
25 | iscAPI.getIconFromUser().then((icon) => setIconField(icon, mdView.file));
26 | },
27 | });
28 | plugin.registerEvent(
29 | workspace.on("file-menu", (menu, af, src) => {
30 | const iscAPI = plugin.IconSCAPI;
31 | if (!iscAPI) return;
32 | let note;
33 | if (
34 | (af instanceof TFolder && (note = plugin.CoreApi.getFolderNote(af))) ||
35 | (af instanceof TFile &&
36 | ((note = af), plugin.CoreApi.getFolderFromNote(af)))
37 | ) {
38 | const folderNote = note;
39 | menu.addItem((item) =>
40 | item
41 | .setIcon("image-glyph")
42 | .setTitle("Set Folder Icon")
43 | .onClick(async () =>
44 | setIconField(await iscAPI.getIconFromUser(), folderNote),
45 | ),
46 | );
47 | }
48 | }),
49 | );
50 | };
51 | export default registerSetFolderIconCmd;
52 |
--------------------------------------------------------------------------------
/src/settings.less:
--------------------------------------------------------------------------------
1 | .setting-item-control > input[type="number"] {
2 | &:invalid {
3 | color: var(--text-error);
4 | background-color: var(--background-modifier-error);
5 | & + span.validity::after {
6 | content: "✖";
7 | padding-left: 5px;
8 | }
9 | }
10 | &.input-short {
11 | width: 5em;
12 | }
13 | & + span.unit {
14 | margin-left: 5px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/settings.ts:
--------------------------------------------------------------------------------
1 | import "./settings.less";
2 |
3 | import { getApi } from "@aidenlx/obsidian-icon-shortcodes";
4 | import type { App, Modifier } from "obsidian";
5 | import { Platform, PluginSettingTab, Setting } from "obsidian";
6 |
7 | import { folderIconMark } from "./fe-handler/folder-mark";
8 | import type ALxFolderNote from "./fn-main";
9 | import type { NoteLoc } from "./misc";
10 |
11 | export const noHideNoteMark = "alx-no-hide-note";
12 | export const MobileNoClickMark = "alx-no-click-on-mobile";
13 |
14 | export interface ALxFolderNoteSettings {
15 | modifierForNewNote: Modifier;
16 | hideNoteInExplorer: boolean;
17 | hideCollapseIndicator: boolean;
18 | longPressFocus: boolean;
19 | folderIcon: boolean;
20 | folderNotePref: NoteLoc | null;
21 | deleteOutsideNoteWithFolder: boolean | null;
22 | indexName: string | null;
23 | autoRename: boolean | null;
24 | folderNoteTemplate: string | null;
25 | mobileClickToOpen: boolean;
26 | longPressDelay: number;
27 | expandFolderOnClick: boolean;
28 | }
29 |
30 | export const DEFAULT_SETTINGS: ALxFolderNoteSettings = {
31 | modifierForNewNote: "Mod",
32 | hideNoteInExplorer: true,
33 | hideCollapseIndicator: false,
34 | longPressFocus: false,
35 | folderIcon: true,
36 | folderNotePref: null,
37 | deleteOutsideNoteWithFolder: null,
38 | indexName: null,
39 | autoRename: null,
40 | folderNoteTemplate: null,
41 | mobileClickToOpen: true,
42 | longPressDelay: 800,
43 | expandFolderOnClick: false,
44 | };
45 |
46 | type SettingKeyWithType = {
47 | [K in keyof ALxFolderNoteSettings]: ALxFolderNoteSettings[K] extends T
48 | ? K
49 | : never;
50 | }[keyof ALxFolderNoteSettings];
51 |
52 | const old = [
53 | "folderNotePref",
54 | "deleteOutsideNoteWithFolder",
55 | "indexName",
56 | "autoRename",
57 | "folderNoteTemplate",
58 | ] as const;
59 |
60 | export class ALxFolderNoteSettingTab extends PluginSettingTab {
61 | plugin: ALxFolderNote;
62 |
63 | constructor(app: App, plugin: ALxFolderNote) {
64 | super(app, plugin);
65 | this.plugin = plugin;
66 | }
67 |
68 | checkMigrated(): boolean {
69 | return old.every((key) => this.plugin.settings[key] === null);
70 | }
71 |
72 | getInitGuide(
73 | desc: string,
74 | targetPluginID: string,
75 | container: HTMLElement,
76 | ): Setting {
77 | return new Setting(container)
78 | .setDesc(
79 | desc +
80 | "use the buttons to install & enable it then reload alx-folder-note to take effects",
81 | )
82 | .addButton((btn) =>
83 | btn
84 | .setIcon("down-arrow-with-tail")
85 | .setTooltip("Go to Plugin Page")
86 | .onClick(() =>
87 | window.open(`obsidian://show-plugin?id=${targetPluginID}`),
88 | ),
89 | )
90 | .addButton((btn) =>
91 | btn
92 | .setIcon("reset")
93 | .setTooltip("Reload alx-folder-note")
94 | .onClick(async () => {
95 | await this.app.plugins.disablePlugin(this.plugin.manifest.id);
96 | await this.app.plugins.enablePlugin(this.plugin.manifest.id);
97 | this.app.setting.openTabById(this.plugin.manifest.id);
98 | }),
99 | );
100 | }
101 |
102 | display(): void {
103 | const { containerEl } = this;
104 | containerEl.empty();
105 |
106 | new Setting(containerEl).setHeading().setName("Core");
107 | try {
108 | this.plugin.CoreApi; // throw error when not available
109 | if (this.checkMigrated()) {
110 | this.plugin.CoreApi.renderCoreSettings(containerEl);
111 | } else this.setMigrate();
112 | } catch (error) {
113 | this.getInitGuide(
114 | "Seems like Folder Note Core is not enabled, ",
115 | "folder-note-core",
116 | containerEl,
117 | );
118 | return;
119 | }
120 |
121 | this.setFolderIcon();
122 | this.setModifier();
123 | this.setHide();
124 | this.addToggle(this.containerEl, "expandFolderOnClick")
125 | .setName("Expand Folder on Click")
126 | .setDesc(
127 | "Expand collapsed folders with note while opening them by clicking on folder title",
128 | );
129 | this.setMobile();
130 | this.setFocus();
131 |
132 | new Setting(containerEl).setHeading().setName("Folder Overview");
133 | const folderv = this.app.plugins.plugins["alx-folder-note-folderv"];
134 | if (folderv?.renderFoldervSettings) {
135 | folderv.renderFoldervSettings(containerEl);
136 | } else {
137 | this.getInitGuide(
138 | "Folder Overview (folderv) is now an optional component, ",
139 | "alx-folder-note-folderv",
140 | containerEl,
141 | );
142 | }
143 |
144 | new Setting(containerEl).setHeading().setName("Debug");
145 | this.plugin.CoreApi.renderLogLevel(containerEl);
146 | }
147 |
148 | setMigrate() {
149 | new Setting(this.containerEl)
150 | .setName("Migrate settings to Folder Note Core")
151 | .setDesc(
152 | "Some settings has not been migrated to Folder Note Core, " +
153 | "click Migrate to migrate old config " +
154 | "or Cancel to use config in Folder Note Core in favor of old config",
155 | )
156 | .addButton((cb) =>
157 | cb.setButtonText("Migrate").onClick(async () => {
158 | const toImport = old.reduce(
159 | (obj, k) => ((obj[k] = this.plugin.settings[k] ?? undefined), obj),
160 | {} as any,
161 | );
162 | this.plugin.CoreApi.importSettings(toImport);
163 | old.forEach((k) => ((this.plugin.settings as any)[k] = null));
164 | await this.plugin.saveSettings();
165 | this.display();
166 | }),
167 | )
168 | .addButton((cb) =>
169 | cb.setButtonText("Cancel").onClick(async () => {
170 | old.forEach((k) => ((this.plugin.settings as any)[k] = null));
171 | await this.plugin.saveSettings();
172 | this.display();
173 | }),
174 | );
175 | }
176 |
177 | setMobile() {
178 | if (!Platform.isMobile) return;
179 | this.addToggle(this.containerEl, "mobileClickToOpen", (value) =>
180 | document.body.toggleClass(MobileNoClickMark, !value),
181 | )
182 | .setName("Click folder title to open folder note on mobile")
183 | .setDesc(
184 | "Disable this if you want to the default action. You can still use context menu to open folder note",
185 | );
186 | }
187 |
188 | setModifier = () => {
189 | new Setting(this.containerEl)
190 | .setName("Modifier for New Note")
191 | .setDesc("Choose a modifier to click folders with to create folder notes")
192 | .addDropdown((dropDown) => {
193 | type NoShift = Exclude;
194 | const windowsOpts: Record = {
195 | Mod: "Ctrl (Cmd in macOS)",
196 | Ctrl: "Ctrl (Ctrl in macOS)",
197 | Meta: "⊞ Win",
198 | // Shift: "Shift",
199 | Alt: "Alt",
200 | };
201 | const macOSOpts: Record = {
202 | Mod: "⌘ Cmd (Ctrl in Windows)",
203 | Ctrl: "⌃ Control",
204 | Meta: "⌘ Cmd (Win in Windows)",
205 | // Shift: "⇧ Shift",
206 | Alt: "⌥ Option",
207 | };
208 |
209 | const options = Platform.isMacOS ? macOSOpts : windowsOpts;
210 |
211 | dropDown
212 | .addOptions(options)
213 | .setValue(this.plugin.settings.modifierForNewNote)
214 | .onChange(async (value: string) => {
215 | this.plugin.settings.modifierForNewNote = value as NoShift;
216 | await this.plugin.saveSettings();
217 | });
218 | });
219 | };
220 |
221 | setHide() {
222 | this.addToggle(this.containerEl, "hideNoteInExplorer", (value) =>
223 | document.body.toggleClass(noHideNoteMark, !value),
224 | )
225 | .setName("Hide Folder Note")
226 | .setDesc("Hide folder note files from file explorer");
227 | this.addToggle(this.containerEl, "hideCollapseIndicator")
228 | .setName("Hide Collapse Indicator")
229 | .setDesc(
230 | "Hide collapse indicator when folder contains only folder note, reload obsidian to take effects",
231 | );
232 | }
233 | setFolderIcon() {
234 | this.addToggle(this.containerEl, "folderIcon", (value) =>
235 | document.body.toggleClass(folderIconMark, value),
236 | )
237 | .setName("Set Folder Icon in Folder Notes")
238 | .setDesc(
239 | createFragment((el) => {
240 | el.appendText(
241 | "Set `icon` field with icon shortcode in frontmatter of foler note to specify linked folder's icon",
242 | );
243 | el.createEl("br");
244 |
245 | el.createEl("a", {
246 | href: "https://github.com/aidenlx/obsidian-icon-shortcodes",
247 | text: "Icon Shortcodes v0.5.1+",
248 | });
249 | el.appendText(" Required. ");
250 | if (!getApi(this.plugin)) el.appendText("(Currently not enabled)");
251 | el.createEl("br");
252 |
253 | el.appendText("Restart obsidian to take effects");
254 | }),
255 | );
256 | }
257 | setFocus() {
258 | new Setting(this.containerEl)
259 | .setHeading()
260 | .setName("Focus")
261 | .setDesc(
262 | `You can use "Toggle Focus" option in folder context menu${
263 | Platform.isMobile ? "" : " or long press on folder title"
264 | } to focus on a specific folder`,
265 | );
266 | if (!Platform.isMobile)
267 | this.addToggle(this.containerEl, "longPressFocus")
268 | .setName("Long Press on Folder to Focus")
269 | .setDesc(
270 | "Long press with mouse on folder name inside file explorer to focus the folder. " +
271 | "Only work on Desktop, reload obsidian to take effects",
272 | );
273 | new Setting(this.containerEl)
274 | .addText((text) => {
275 | Object.assign(text.inputEl, {
276 | type: "number",
277 | min: "0.2",
278 | step: "0.1",
279 | required: true,
280 | });
281 | text.inputEl.addClass("input-short");
282 | text.inputEl.insertAdjacentElement(
283 | "afterend",
284 | createSpan({ cls: ["validity", "unit"], text: "second(s)" }),
285 | );
286 | text
287 | .setValue(`${this.plugin.longPressDelay / 1e3}`)
288 | .onChange(async (val) => {
289 | const delay = +val * 1e3;
290 | this.plugin.longPressDelay = delay;
291 | await this.plugin.saveSettings();
292 | });
293 | })
294 | .setName("Long Press Delay");
295 | }
296 |
297 | addToggle(
298 | addTo: HTMLElement,
299 | key: SettingKeyWithType,
300 | onSet?: (value: boolean) => any,
301 | ): Setting {
302 | return new Setting(addTo).addToggle((toggle) => {
303 | toggle
304 | .setValue(this.plugin.settings[key])
305 | .onChange(
306 | (value) => (
307 | (this.plugin.settings[key] = value),
308 | onSet && onSet(value),
309 | this.plugin.saveSettings()
310 | ),
311 | );
312 | });
313 | }
314 | }
315 |
--------------------------------------------------------------------------------
/src/typings/obsidian-ex.d.ts:
--------------------------------------------------------------------------------
1 | import "obsidian";
2 |
3 | import type getFileExplorerHandlers from "../fe-handler";
4 |
5 | declare module "obsidian" {
6 | class FileExplorerView extends ItemView {
7 | prototype: FileExplorerView;
8 | fileBeingRenamed: TAbstractFile | null;
9 | fileItems: {
10 | [key: string]: AFItem;
11 | "/": FolderItem;
12 | };
13 | files: WeakMap;
14 | getViewType(): string;
15 | getDisplayText(): string;
16 | onClose(): Promise;
17 | dom: {
18 | infinityScroll: HTMLDivElement;
19 | navFileContainerEl: HTMLDivElement;
20 | };
21 | onOpen(): Promise;
22 | onFileClick(evt: MouseEvent, navEl: HTMLDivElement): void;
23 | handleFileClick(evt: MouseEvent, item: AFItem): boolean;
24 | createFolderDom(folder: TFolder): FolderItem;
25 | folderNoteUtils?: ReturnType;
26 | currentHoverFile: TAbstractFile | null;
27 | onFileMouseover(evt: MouseEvent, navTitleEl: HTMLElement): void;
28 | onFileMouseout(evt: MouseEvent, navTitleEl: HTMLElement): void;
29 | _AFN_HOVER?: TFolder;
30 | }
31 |
32 | interface ViewRegistry {
33 | typeByExtension: Record;
34 | viewByType: Record;
35 | getViewCreatorByType(type: string): ViewCreator | undefined;
36 | isExtensionRegistered(ext: string): boolean;
37 | registerExtensions(exts: string[], type: string): void;
38 | registerViewWithExtensions(
39 | exts: string[],
40 | type: string,
41 | viewCreator: ViewCreator,
42 | ): void;
43 | unregisterExtensions(exts: string[]): void;
44 | }
45 |
46 | type AFItem = FileItem | FolderItem;
47 |
48 | class FileItem {
49 | el: HTMLDivElement;
50 | file: TFile;
51 | fileExplorer: FileExplorerView;
52 | info: any;
53 | /**
54 | * @deprecated After Obsidian 1.2.0, use `selfEl` instead.
55 | */
56 | titleEl?: HTMLDivElement;
57 | /**
58 | * @deprecated After Obsidian 1.2.0, use `innerEl` instead.
59 | */
60 | titleInnerEl?: HTMLDivElement;
61 | selfEl: HTMLDivElement;
62 | innerEl: HTMLDivElement;
63 | }
64 |
65 | class FolderItem {
66 | el: HTMLDivElement;
67 | fileExplorer: FileExplorerView;
68 | info: any;
69 | /**
70 | * @deprecated After Obsidian 1.2.0, use `selfEl` instead.
71 | */
72 | titleEl?: HTMLDivElement;
73 | /**
74 | * @deprecated After Obsidian 1.2.0, use `innerEl` instead.
75 | */
76 | titleInnerEl?: HTMLDivElement;
77 | selfEl: HTMLDivElement;
78 | innerEl: HTMLDivElement;
79 | file: TFolder;
80 | children: AFItem[];
81 | childrenEl: HTMLDivElement;
82 | collapseIndicatorEl: HTMLDivElement;
83 | collapsed: boolean;
84 | pusherEl: HTMLDivElement;
85 | setCollapsed(collapsed: boolean): Promise;
86 | /**
87 | * @deprecated After Obsidian 1.2.0, use `onSelfClick` instead.
88 | */
89 | onTitleElClick?(evt: MouseEvent): any;
90 | onSelfClick(evt: MouseEvent): any;
91 | }
92 |
93 | interface Vault {
94 | exists(normalizedPath: string, sensitive?: boolean): Promise;
95 | }
96 |
97 | class FileExplorerPlugin extends Plugin_2 {
98 | revealInFolder(this: any, ...args: any[]): any;
99 | }
100 |
101 | interface App {
102 | viewRegistry: ViewRegistry;
103 | plugins: {
104 | enabledPlugins: Set;
105 | plugins: {
106 | [id: string]: any;
107 | "alx-folder-note-folderv"?: {
108 | renderFoldervSettings(containerEl: HTMLElement): void;
109 | };
110 | };
111 | enablePlugin(id: string): Promise;
112 | disablePlugin(id: string): Promise;
113 | };
114 | internalPlugins: {
115 | plugins: {
116 | [id: string]: any;
117 | ["file-explorer"]?: {
118 | instance: FileExplorerPlugin;
119 | };
120 | };
121 | };
122 | setting: {
123 | openTabById(id: string): any;
124 | };
125 | }
126 | interface Notice {
127 | noticeEl: HTMLElement;
128 | }
129 | }
130 |
--------------------------------------------------------------------------------
/src/typings/svg.d.ts:
--------------------------------------------------------------------------------
1 | declare module "*.svg" {
2 | const src: string;
3 | export default src;
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "compilerOptions": {
4 | "baseUrl": "src",
5 | "outDir": "dist",
6 | "target": "ES2022",
7 | "lib": ["dom", "ES2018"],
8 | "moduleResolution": "bundler",
9 | "module": "ESNext",
10 | "strict": true,
11 | "useUnknownInCatchVariables": true,
12 | "allowJs": true,
13 | "skipLibCheck": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noEmit": true,
16 | "esModuleInterop": false,
17 | "allowSyntheticDefaultImports": true,
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "incremental": true,
21 | "newLine": "lf"
22 | },
23 | "include": ["src/**/*.ts", "src/**/*.tsx", "src/worker/*/api.ts"],
24 | "exclude": ["**/node_modules", "**/.*/", "dist", "build"]
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/versions.json:
--------------------------------------------------------------------------------
1 | {
2 | "0.1.0": "0.9.12",
3 | "0.1.1": "0.9.12",
4 | "0.2.0": "0.9.12",
5 | "0.3.0": "0.9.12",
6 | "0.4.0": "0.9.12",
7 | "0.5.0": "0.12.5",
8 | "0.6.0": "0.12.5",
9 | "0.7.0": "0.12.5",
10 | "0.8.0": "0.12.5",
11 | "0.9.0": "0.12.5",
12 | "0.9.1": "0.12.5",
13 | "0.9.2": "0.12.5",
14 | "0.10.0": "0.12.5",
15 | "0.11.0": "0.12.5",
16 | "0.11.1": "0.12.5",
17 | "0.11.2": "0.12.5",
18 | "0.12.0": "0.12.5",
19 | "0.12.1": "0.12.5",
20 | "0.12.2": "0.12.5",
21 | "0.12.3": "0.12.5",
22 | "0.13.0": "0.12.5",
23 | "0.13.1": "0.12.5",
24 | "0.14.0": "0.12.5",
25 | "0.15.0": "0.12.5",
26 | "0.16.0": "0.13.24",
27 | "0.16.1": "0.13.24",
28 | "0.16.2": "0.13.24",
29 | "0.16.3": "0.13.24",
30 | "0.16.4": "0.14.8",
31 | "0.16.5": "1.1.0"
32 | }
--------------------------------------------------------------------------------