Compare commits

..

No commits in common. "master" and "1.0.0" have entirely different histories.

48 changed files with 357 additions and 1926 deletions

View file

@ -9,6 +9,7 @@
"allow": [
"warn",
"error",
"debug"
]
}
],

View file

@ -26,8 +26,6 @@ If not related issue exists, open a new one using the appropriate <a href="../..
#### 1. Fork the repository.
#### 2. Create a new vault
Just for testing the plugin so you **can't** exedentially destroy your real vault
> [!NOTE]
> When you working with ``npm run dev`` the plugin will use another key to identify the favorite lists. This **should** ensure you are not overwriting your real favorite lists. When you are working with ``npm run build`` the plugin will use the real key.
#### 3. Clone repository to your local machine.
```shell
cd path/to/vault

1
.gitignore vendored
View file

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

View file

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2025 4Source
Copyright (c) 2024 4Source
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

114
README.md
View file

@ -3,16 +3,15 @@
Stop re-searching the same plugins or themes in every vault. With one central list, your essential tools and styles are always easy to find.
# Features
- **Global Favorites list for Plugins & Themes** - Mark your go-to plugins and themes once and access them across all vaults.
- **Global In Use list for Plugins & Themes** - Automatically maintained list of plugins and themes that are currently enabled in at least one vault.
- **Global Known list for Plugins & Themes** - Automatically maintained history of plugins and themes that have been installed at least once in any vault.
- **Ease of Use** - Favorite/unfavorite directly inside the Obsidian's
- Plugin browser
- Community plugins tab
- Theme browser
- via [Commands](#commands)
- **Global Favorites for Plugins & Themes** - Mark your go to plugins and themes once and use them in all vaults
- **Ease of Use** - Favorite/unfavorite directly from the Obsidian's plugin and theme details browser
- **Vault Independence** - Favorites are stored globally on your device, not tied to a single vault
# Screenshots
<img width="1104" height="1004" alt="image" src="https://github.com/user-attachments/assets/61b41e7b-4569-4509-95f3-d5e8a667d653" />
<img width="1104" height="1004" alt="image" src="https://github.com/user-attachments/assets/960eac0d-fea2-48e1-8faf-e54a7504149e" />
# How It Works
1. Install and enable ***Favorites***.
2. Open Obsidian's plugin or theme browser.
@ -20,103 +19,6 @@ Stop re-searching the same plugins or themes in every vault. With one central li
4. Use the Favorite button.
5. Your favorites are saved globally and appear in every vault.
For plugins you can also favorite already installed plugins directly from the Community plugins tab.
# Screenshots
![FavoritesPluginBrowserDemo_1 4 0](https://github.com/user-attachments/assets/6b93f818-207a-4381-ab4d-f5aa57cad021)
![FavoritesThemeBrowserDemo_1 4 0](https://github.com/user-attachments/assets/3f3b802e-3a82-45fd-a174-b51d5dd4c0f0)
<img width="1100" height="1000" alt="image" src="https://github.com/user-attachments/assets/5c8537dd-f5dc-4393-8cf7-6b506806631c" />
<img width="1100" height="1000" alt="image" src="https://github.com/user-attachments/assets/a6aebde5-a93c-4a93-ba0e-ccb9ad1c6560" />
<img width="1100" height="1000" alt="image" src="https://github.com/user-attachments/assets/e34ec7f4-57f2-4dc1-a150-45e9ab152295" />
<img width="1100" height="1000" alt="image" src="https://github.com/user-attachments/assets/ec9dfdf9-d607-4ff5-8679-fc286cb5e43c" />
<img width="1100" height="1000" alt="image" src="https://github.com/user-attachments/assets/a0b35f98-b194-433f-bd9e-66eecb5dd111" />
# Commands
If you prefer managing your favorites list with commands here are the available commands
- [Search and add plugin to favorite list](#search-and-add-plugin-to-favorite-list)
- [Search and add theme to favorite list](#search-and-add-theme-to-favorite-list)
- [Search and remove plugin from favorite list](#search-and-remove-plugin-from-favorite-list)
- [Search and remove theme from favorite list](#search-and-remove-theme-from-favorite-list)
- [Clear the plugin favorites list](#clear-the-plugin-favorites-list)
- [Clear the plugin in use list](#clear-the-plugin-in-use-list)
- [Clear the plugin known list](#clear-the-plugin-known-list)
- [Clear the theme favorites list](#clear-the-theme-favorites-list)
- [Clear the theme in use list](#clear-the-theme-in-use-list)
- [Clear the theme known list](#clear-the-theme-known-list)
- [Manually load the lists](#manually-load-the-lists)
- [Manually save the lists](#manually-save-the-lists)
- [Export lists to file](#export-lists-to-file)
- [Import lists from file](#import-lists-from-file)
## Search and add plugin to favorite list
Search and select a community plugin to add it to your favorites list. The list is fetched from the community plugin registry.
## Search and add theme to favorite list
Search and select a community theme to add it to your favorites list. The list is fetched from the community theme registry.
## Search and remove plugin from favorite list
Search and select from your current favorite plugins to remove it from your favorites list.
## Search and remove theme from favorite list
Search and select from your current favorite themes to remove it from your favorites list.
## Clear the plugin favorites list
Permanently removes ***all*** plugins from your favorites list after confirmation.
> [!WARNING]
> This action cannot be undone.
## Clear the plugin in use list
Permanently removes ***all*** plugins from your in use list after confirmation.
> [!WARNING]
> This action cannot be undone.
## Clear the plugin known list
Permanently removes ***all*** plugins from your known list after confirmation.
> [!WARNING]
> This action cannot be undone.
## Clear the theme favorites list
Permanently removes ***all*** themes from your favorites list after confirmation.
> [!WARNING]
> This action cannot be undone.
## Clear the theme in use list
Permanently removes ***all*** themes from your in use list after confirmation.
> [!WARNING]
> This action cannot be undone.
## Clear the theme known list
Permanently removes ***all*** themes from your known list after confirmation.
> [!WARNING]
> This action cannot be undone.
## Manually load the lists
Reloads the saved lists for plugins and themes. Use this to restore your lists if they were changed externally or need to be refreshed.
> [!NOTE]
> This is normally not necessary because the plugin handles this internally.
## Manually save the lists
Forces the plugin to immediately save your current lists for plugins and themes. Use this if you want to ensure your lists are persistent.
> [!WARNING]
> This could overwrite changes from another running Obsidian instance if the changes from there are not loaded beforehand.
> [!NOTE]
> This is normally not necessary because the plugin handles this internally.
## Export lists to file
Creates a backup file containing your current lists for plugins and themes. The backup is saved as a timestamped JSON file inside the plugins backup folder within your vault configuration directory.
## Import lists from file
Restores lists for plugins and themes from a previously created backup file. You will be prompted to select a backup file from the plugins backup folder. After confirmation imported lists will replaces your current lists with the content of the selected backup.
> [!WARNING]
> This will overwrite current lists and can't be undone.
# Contribution
Feel free to contribute.
@ -124,7 +26,7 @@ You can create an [issue](https://github.com/4Source/favorites-obsidian-plugin/i
Or instead create a [pull request](https://github.com/4Source/favorites-obsidian-plugin/pulls) to contribute a bug fix or improvement for this plugin.
*Not a developer* but still want to help? You can help with testing. Either you test the current version for possible problems or, if there are currently any, you can also test [pre-releases (alpha/beta)](https://github.com/4Source/favorites-obsidian-plugin/releases). This can be easily accomplished if you install [VARE](https://obsidian.md/plugins?id=vare). Any version of this plugin must be installed. Then simply go to VARE's Settings tab and select the version you want to test from the drop-down menu. You could also add this plugin manually just ![plus](https://github.com/4Source/settings-profiles-obsidian-plugin/assets/38220764/663a0bd6-53f9-4da3-b0ab-33e30eae3029)``Add unlisted plugin`` and enter ``4Source`` and ``favorites-obsidian-plugin``. If you found a bug or unexpected behavior create an [issue](https://github.com/4Source/favorites-obsidian-plugin/issues) with the version you have tested. Before creating an issue, make sure you know how to reproduce the bug. If there is already an issue for the bug you found that is still open but you have additional information, add it to the issue.
*Not a developer* but still want to help? You can help with testing. Either you test the current version for possible problems or, if there are currently any, you can also test [pre-releases (alpha/beta)](https://github.com/4Source/favorites-obsidian-plugin/releases). This can be easily accomplished if you install [VARE](https://obsidian.md/plugins?id=vare). Any version of this plugin must be installed. Then simply go to VARE's Settings tab and select the version you want to test from the drop-down menu. You could also add this plugin manually just ![plus](https://github.com/4Source/favorites-obsidian-plugin/assets/38220764/663a0bd6-53f9-4da3-b0ab-33e30eae3029)``Add unlisted plugin`` and enter ``4Source`` and ``favorites-obsidian-plugin``. If you found a bug or unexpected behavior create an [issue](https://github.com/4Source/favorites-obsidian-plugin/issues) with the version you have tested. Before creating an issue, make sure you know how to reproduce the bug. If there is already an issue for the bug you found that is still open but you have additional information, add it to the issue.
For more info see [CONTRIBUTING](https://github.com/4Source/favorites-obsidian-plugin/blob/master/.github/CONTRIBUTING.md)

View file

@ -38,14 +38,6 @@ const context = await esbuild.context({
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
define: {
'process.env.FAVORITE_PLUGINS_KEY': JSON.stringify(prod ? 'favorite-plugins' : 'dev-favorite-plugins'),
'process.env.FAVORITE_THEMES_KEY': JSON.stringify(prod ? 'favorite-themes' : 'dev-favorite-themes'),
'process.env.INUSE_PLUGINS_KEY': JSON.stringify(prod ? 'in-use-plugins' : 'dev-in-use-plugins'),
'process.env.INUSE_THEMES_KEY': JSON.stringify(prod ? 'in-use-themes' : 'dev-in-use-themes'),
'process.env.KNOWN_PLUGINS_KEY': JSON.stringify(prod ? 'known-plugins' : 'dev-known-plugins'),
'process.env.KNOWN_THEMES_KEY': JSON.stringify(prod ? 'known-themes' : 'dev-known-themes'),
}
});
if (prod) {

View file

@ -7,5 +7,5 @@
"authorUrl": "https://github.com/4Source",
"isDesktopOnly": false,
"fundingUrl": "https://www.buymeacoffee.com/4Source",
"version": "1.4.0"
"version": "1.0.0"
}

99
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "favorites",
"version": "1.4.0",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "favorites",
"version": "1.4.0",
"version": "0.0.1",
"license": "MIT",
"devDependencies": {
"@stylistic/eslint-plugin": "^3.1.0",
@ -19,7 +19,6 @@
"eslint": "^8.57.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"monkey-around": "^3.0.0",
"obsidian": "latest",
"semver": "^7.6.0",
"ts-jest": "^29.1.2",
@ -1059,11 +1058,10 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -1106,11 +1104,10 @@
}
},
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -1177,11 +1174,10 @@
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@ -2210,11 +2206,10 @@
}
},
"node_modules/ajv": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@ -3230,11 +3225,10 @@
}
},
"node_modules/eslint/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -3448,11 +3442,10 @@
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
},
@ -3503,11 +3496,10 @@
}
},
"node_modules/flatted": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true
},
"node_modules/form-data": {
"version": "4.0.4",
@ -3674,11 +3666,10 @@
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -4126,11 +4117,10 @@
}
},
"node_modules/jake/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@ -4739,11 +4729,10 @@
"dev": true
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
@ -5036,13 +5025,12 @@
}
},
"node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -5060,12 +5048,6 @@
"node": "*"
}
},
"node_modules/monkey-around": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/monkey-around/-/monkey-around-3.0.0.tgz",
"integrity": "sha512-jL6uY2lEAoaHxZep1cNRkCZjoIWY4g5VYCjriEWmcyHU7w8NU1+JH57xE251UVTohK0lCxMjv0ZV4ByDLIXEpw==",
"dev": true
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -5875,11 +5857,10 @@
}
},
"node_modules/test-exclude/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},

View file

@ -26,12 +26,11 @@
"eslint": "^8.57.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"monkey-around": "^3.0.0",
"obsidian": "latest",
"semver": "^7.6.0",
"ts-jest": "^29.1.2",
"tslib": "2.4.0",
"typescript": "4.7.4"
},
"version": "1.4.0"
"version": "1.0.0"
}

View file

@ -1,21 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'clear-plugin-favorites-list',
name: 'Clear the plugin favorites list',
callback: () => {
plugin.loadFavoritePlugins();
const numberClear = plugin.favoritePlugins.length;
new DialogModal(plugin.app, `Clear ${numberClear} plugin(s) permanently from favorite list?`, '', () => {
plugin.favoritePlugins = [];
plugin.saveFavoritePlugins();
new Notice(`Cleared ${numberClear} plugins from favorite list`);
}, () => {
new Notice('Canceled clear plugins from favorite list');
}, 'Clear', true, 'Cancel', false).open();
},
};
}

View file

@ -1,21 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'clear-plugin-in-use-list',
name: 'Clear the plugin in use list',
callback: () => {
plugin.loadInUsePlugins();
const numberClear = Object.keys(plugin.inUsePlugins).length;
new DialogModal(plugin.app, `Clear ${numberClear} plugin(s) permanently from in use list?`, '', () => {
plugin.inUsePlugins = {};
plugin.saveInUsePlugins();
new Notice(`Cleared ${numberClear} plugins from in use list`);
}, () => {
new Notice('Canceled clear plugins from in use list');
}, 'Clear', true, 'Cancel', false).open();
},
};
}

View file

@ -1,21 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'clear-plugin-known-list',
name: 'Clear the plugin known list',
callback: () => {
plugin.loadKnownPlugins();
const numberClear = plugin.knownPlugins.length;
new DialogModal(plugin.app, `Clear ${numberClear} plugin(s) permanently from known list?`, '', () => {
plugin.knownPlugins = [];
plugin.saveKnownPlugins();
new Notice(`Cleared ${numberClear} plugins from known list`);
}, () => {
new Notice('Canceled clear plugins from known list');
}, 'Clear', true, 'Cancel', false).open();
},
};
}

View file

@ -1,21 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'clear-theme-favorites-list',
name: 'Clear the theme favorites list',
callback: () => {
plugin.loadFavoriteThemes();
const numberClear = plugin.favoriteThemes.length;
new DialogModal(plugin.app, `Clear ${numberClear} theme(s) permanently from favorite list?`, '', () => {
plugin.favoriteThemes = [];
plugin.saveFavoriteThemes();
new Notice(`Cleared ${numberClear} themes from favorite list`);
}, () => {
new Notice('Canceled clear themes from favorite list');
}, 'Clear', true, 'Cancel', false).open();
},
};
}

View file

@ -1,21 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'clear-theme-in-use-list',
name: 'Clear the theme in use list',
callback: () => {
plugin.loadInUseThemes();
const numberClear = Object.keys(plugin.inUseThemes).length;
new DialogModal(plugin.app, `Clear ${numberClear} theme(s) permanently from in use list?`, '', () => {
plugin.inUseThemes = {};
plugin.saveInUseThemes();
new Notice(`Cleared ${numberClear} themes from in use list`);
}, () => {
new Notice('Canceled clear themes from in use list');
}, 'Clear', true, 'Cancel', false).open();
},
};
}

View file

@ -1,21 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'clear-theme-known-list',
name: 'Clear the theme known list',
callback: () => {
plugin.loadKnownThemes();
const numberClear = plugin.knownThemes.length;
new DialogModal(plugin.app, `Clear ${numberClear} theme(s) permanently from known list?`, '', () => {
plugin.knownThemes = [];
plugin.saveKnownThemes();
new Notice(`Cleared ${numberClear} themes from known list`);
}, () => {
new Notice('Canceled clear themes from known list');
}, 'Clear', true, 'Cancel', false).open();
},
};
}

View file

@ -1,37 +0,0 @@
import { Command, normalizePath, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { PLUGIN_BACKUP_BASE_PATH } from 'src/constants';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'export-lists',
name: 'Export lists to file',
callback: async () => {
if (plugin.favoritePlugins.length <= 0 && plugin.favoriteThemes.length <= 0 && Object.keys(plugin.inUsePlugins).length <= 0 && Object.keys(plugin.inUseThemes).length <= 0 && plugin.knownPlugins.length <= 0 && plugin.knownThemes.length <= 0) {
new Notice('No lists to backup');
return;
}
// Ensure backup path does exist
const backupPath = normalizePath(`${plugin.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`);
await plugin.app.vault.adapter.mkdir(backupPath);
// Create the file name
const pad = (n: number) => n.toString().padStart(2, '0');
const date = new Date();
const dateString = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}-${pad(date.getHours())}-${pad(date.getMinutes())}`;
const backupFilePath = normalizePath(`${backupPath}/favorite-${dateString}.json`);
// Write backup file for lists
await plugin.app.vault.adapter.write(backupFilePath, JSON.stringify({
favoritePlugins: plugin.favoritePlugins,
favoriteThemes: plugin.favoriteThemes,
inUsePlugins: plugin.inUsePlugins,
inUseThemes: plugin.inUseThemes,
knownPlugins: plugin.knownPlugins,
knownThemes: plugin.knownThemes,
}));
new Notice(`Lists backup successful written to: ${backupFilePath}`);
},
};
}

View file

@ -1,51 +0,0 @@
import { Command, normalizePath, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { PLUGIN_BACKUP_BASE_PATH } from 'src/constants';
import { StringSuggestModal } from 'src/modals/StringSuggestModal';
import { DialogModal } from 'src/modals/DialogModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'import-lists',
name: 'Import lists from file',
callback: async () => {
const backupPath = normalizePath(`${plugin.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`);
if (!(await plugin.app.vault.adapter.exists(backupPath))) {
// Backup path does not exist
new Notice(`Backup path does not exist: ${backupPath}`);
return;
}
const backupFiles = (await plugin.app.vault.adapter.list(backupPath)).files;
if (backupFiles.length <= 0) {
// Backup path is empty
new Notice('Backup path is empty');
return;
}
new StringSuggestModal(plugin.app, 'Select backup file to load from', backupFiles, async (result) => {
const content = await plugin.app.vault.adapter.read(result);
const json = JSON.parse(content);
const loadedFavoritePlugins = json.favoritePlugins || [];
const loadedFavoriteThemes = json.favoriteThemes || [];
const loadedInUsePlugins = json.inUsePlugins || [];
const loadedInUseThemes = json.inUseThemes || [];
const loadedKnownPlugins = json.knownPlugins || [];
const loadedKnownThemes = json.knownThemes || [];
new DialogModal(plugin.app, 'Are you sure you want to overwrite current lists?', `The following lists will be overwritten: favorite plugins will change from ${plugin.favoritePlugins.length} items to ${loadedFavoritePlugins.length} items, favorite themes will change from ${plugin.favoriteThemes.length} items to ${loadedFavoriteThemes.length} items, in use plugins will change from ${plugin.inUsePlugins.length} items to ${loadedInUsePlugins.length} items, in use themes will change from ${plugin.inUseThemes.length} items to ${loadedInUseThemes.length} items, known plugins will change from ${plugin.knownPlugins.length} items to ${loadedKnownPlugins.length} items, and known themes will change from ${plugin.knownThemes.length} items to ${loadedKnownThemes.length} items.`, () => {
plugin.favoritePlugins = loadedFavoritePlugins;
plugin.favoriteThemes = loadedFavoriteThemes;
plugin.inUsePlugins = loadedInUsePlugins;
plugin.inUseThemes = loadedInUseThemes;
plugin.knownPlugins = loadedKnownPlugins;
plugin.knownThemes = loadedKnownThemes;
plugin.saveLists();
new Notice('Loaded backup successfully');
}, () => {
new Notice('Canceled loading backup by user');
}, 'Overwrite', true, 'Cancel', false).open();
}).open();
},
};
}

View file

@ -1,13 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'load-lists',
name: 'Manually load the lists',
callback: () => {
plugin.loadLists();
new Notice('Loaded lists');
},
};
}

View file

@ -1,13 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'save-lists',
name: 'Manually save the lists',
callback: () => {
plugin.saveLists();
new Notice('Saved lists');
},
};
}

View file

@ -1,33 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
import { CommunityPlugin, fetchCommunityPluginList } from 'src/util/GitHub';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-add-plugin-to-favorite-list',
name: 'Search and add plugin to favorite list',
callback: async () => {
let items = await fetchCommunityPluginList();
if (!items) {
new Notice('Failed to fetch community plugins. See console for more information.');
return;
}
// Filter out favorites
plugin.loadFavoritePlugins();
items = items.filter(value => !plugin.favoritePlugins.contains(value.id));
if (items.length <= 0) {
new Notice('No plugin left which is could be added to favorite list');
return;
}
new CommunitySuggestModal<CommunityPlugin>(plugin.app, 'Select plugin which should be added to favorites list...', items, (result) => {
plugin.favoritePlugins.push(result.id);
plugin.saveFavoritePlugins();
new Notice(`Added ${result.name} to favorite list`);
}).open();
},
};
}

View file

@ -1,33 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
import { CommunityTheme, fetchCommunityThemeList } from 'src/util/GitHub';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-add-theme-to-favorite-list',
name: 'Search and add theme to favorite list',
callback: async () => {
let items = await fetchCommunityThemeList();
if (!items) {
new Notice('Failed to fetch community themes. See console for more information.');
return;
}
// Filter out favorites
plugin.loadFavoriteThemes();
items = items.filter(value => !plugin.favoriteThemes.contains(value.name));
if (items.length <= 0) {
new Notice('No theme left which is could be added to favorite list');
return;
}
new CommunitySuggestModal<CommunityTheme>(plugin.app, 'Select theme which should be added to favorites list...', items, (result) => {
plugin.favoriteThemes.push(result.name);
plugin.saveFavoriteThemes();
new Notice(`Added ${result.name} to favorite list`);
}).open();
},
};
}

View file

@ -1,29 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { CommunityPlugin, fetchCommunityPluginList } from 'src/util/GitHub';
import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-remove-plugin-from-favorite-list',
name: 'Search and remove plugin from favorite list',
callback: async () => {
plugin.loadFavoritePlugins();
if (plugin.favoritePlugins.length <= 0) {
new Notice('Plugin favorite list is empty');
return;
}
const communityItems = await fetchCommunityPluginList();
const installedItems = Object.values(plugin.app.plugins.manifests);
const items = plugin.favoritePlugins.map((value) => {
return communityItems?.find((communityValue) => value === communityValue.id) || installedItems.find(installedValue => value === installedValue.id) as unknown as CommunityPlugin || { id: value, name: value, author: 'unknown' } as CommunityPlugin;
});
new CommunitySuggestModal<CommunityPlugin>(plugin.app, 'Select plugin which should be removed from the favorites list...', items, (result) => {
plugin.favoritePlugins.remove(result.id);
plugin.saveFavoritePlugins();
new Notice(`Removed ${result.name} from favorite list`);
}).open();
},
};
}

View file

@ -1,29 +0,0 @@
import { Command, Notice } from 'obsidian';
import CurrentPlugin from '../main';
import { CommunityTheme, fetchCommunityThemeList } from 'src/util/GitHub';
import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-remove-theme-from-favorite-list',
name: 'Search and remove theme from favorite list',
callback: async () => {
plugin.loadFavoriteThemes();
if (plugin.favoriteThemes.length <= 0) {
new Notice('Theme favorite list is empty');
return;
}
const communityItems = await fetchCommunityThemeList();
const installedItems = Object.values(plugin.app.customCss.themes);
const items = plugin.favoriteThemes.map((value) => {
return communityItems?.find((communityValue) => value === communityValue.name) || installedItems.find(installedValue => value === installedValue.name) as unknown as CommunityTheme || { name: value, author: 'unknown' } as CommunityTheme;
});
new CommunitySuggestModal<CommunityTheme>(plugin.app, 'Select theme which should be removed from the favorites list...', items, (result) => {
plugin.favoriteThemes.remove(result.name);
plugin.saveFavoriteThemes();
new Notice(`Removed ${result.name} from favorite list`);
}).open();
},
};
}

View file

@ -1,34 +0,0 @@
import CurrentPlugin from '../main';
import ClearPluginFavoritesList from './ClearPluginFavoritesList';
import ClearPluginInUseList from './ClearPluginInUseList';
import ClearPluginKnownList from './ClearPluginKnownList';
import ClearThemeFavoritesList from './ClearThemeFavoritesList';
import ClearThemeInUseList from './ClearThemeInUseList';
import ClearThemeKnownList from './ClearThemeKnownList';
import ExportLists from './ExportLists';
import ImportLists from './ImportLists';
import LoadLists from './LoadLists';
import SaveLists from './SaveLists';
import SearchAndAddPluginToFavoriteList from './SearchAndAddPluginToFavoriteList';
import SearchAndAddThemeToFavoriteList from './SearchAndAddThemeToFavoriteList';
import SearchAndRemovePluginFromFavoriteList from './SearchAndRemovePluginFromFavoriteList';
import SearchAndRemoveThemeFromFavoriteList from './SearchAndRemoveThemeFromFavoriteList';
export async function addAllCommands(plugin: CurrentPlugin) {
// TODO: Dynamically import all files in the commands folder during bundling time
plugin.addCommand(ClearPluginFavoritesList(plugin));
plugin.addCommand(ClearPluginInUseList(plugin));
plugin.addCommand(ClearPluginKnownList(plugin));
plugin.addCommand(ClearThemeFavoritesList(plugin));
plugin.addCommand(ClearThemeInUseList(plugin));
plugin.addCommand(ClearThemeKnownList(plugin));
plugin.addCommand(ExportLists(plugin));
plugin.addCommand(ImportLists(plugin));
plugin.addCommand(LoadLists(plugin));
plugin.addCommand(SaveLists(plugin));
plugin.addCommand(SearchAndAddPluginToFavoriteList(plugin));
plugin.addCommand(SearchAndAddThemeToFavoriteList(plugin));
plugin.addCommand(SearchAndRemovePluginFromFavoriteList(plugin));
plugin.addCommand(SearchAndRemoveThemeFromFavoriteList(plugin));
}

View file

@ -1,2 +0,0 @@
export const PLUGIN_ID = 'favorites';
export const PLUGIN_BACKUP_BASE_PATH = `/plugins/${PLUGIN_ID}/backup`;

View file

@ -1,429 +1,297 @@
import { Plugin } from 'obsidian';
import { addAllCommands } from './commands';
import patchSettingsModal from './patches/patch.SettingsModal';
import patchModal from './patches/patch.Modal';
import patchPluginManager from './patches/patch.PluginManager';
import patchStyleManager from './patches/patch.StyleManager';
import { CommunityItem, CommunityModal, Modal, Plugin } from 'obsidian';
export default class FavoritesPlugin extends Plugin {
pluginFavoritesKey: string;
favoritePlugins: string[];
themeFavoritesKey: string;
favoriteThemes: string[];
pluginInUseKey: string;
inUsePlugins: Record<string, string[]>;
themeInUseKey: string;
inUseThemes: Record<string, string[]>;
pluginKnownKey: string;
knownPlugins: string[];
themeKnownKey: string;
knownThemes: string[];
uninstallCommunityPluginModalPatch?: () => void;
uninstallCommunityPluginsSettingTabPatch?: () => void;
uninstallCommunityThemeModalPatch?: () => void;
uninstallModalPatch?: () => void;
uninstallPluginManagerPatch?: () => void;
uninstallSettingsModalPatch?: () => void;
uninstallStyleManagerPatch?: () => void;
favoritePlugins?: string[];
favoriteThemes?: string[];
communityPluginModalPrototype?: CommunityModal;
communityThemesModalPrototype?: CommunityModal;
async onload() {
this.loadStorageKeys();
this.loadLists();
await this.loadSettings();
// Patch SettingsModal
if (!this.uninstallSettingsModalPatch) {
// console.debug('Patch SettingsModal');
this.uninstallSettingsModalPatch = patchSettingsModal(this);
if (!Modal.prototype.originalOpen) {
Modal.prototype.originalOpen = Modal.prototype.open;
const originalModalOpen = Modal.prototype.open;
Modal.prototype.open = (function (originalModalOpen, plugin) {
return function () {
originalModalOpen?.apply(this);
// Check the opened Modal is the Community plugin browse modal
if (this.containerEl?.querySelector('.mod-community-plugin')) {
// Store the Community Plugin Browse Modal prototype to be able to reset to the original
plugin.communityPluginModalPrototype = this.constructor.prototype;
// Check the plugin updateItems is already overwritten
if (!this.constructor.prototype.originalUpdateItems) {
// Save original method
this.constructor.prototype.originalUpdateItems = this.constructor.prototype.updateItems;
const originalUpdateItems = this.constructor.prototype.updateItems;
if (!this.constructor.prototype.originalUpdateItems) {
console.error('Favorites plugin: Failed to access original updateItems method!');
return;
}
// Overwrite method with additional functionality
this.constructor.prototype.updateItems = (function (originalUpdateItems, plugin) {
return function () {
// Load the favorite plugins
plugin.favoritePlugins = JSON.parse(localStorage.getItem('favorite-plugins') || '[]');
const result = originalUpdateItems.apply(this);
// Add to the favorite plugins a tag to visualize it for the user
if (plugin.favoritePlugins) {
plugin.favoritePlugins.forEach(id => {
if (this.items && this.items[id]?.nameEl) {
this.items[id].nameEl.createSpan({
cls: 'flair',
text: 'favorite',
});
}
});
}
return result;
};
})(originalUpdateItems, plugin);
}
// Check the plugin showItem is already overwritten
if (!this.constructor.prototype.originalShowItem) {
// Save original method
this.constructor.prototype.originalShowItem = this.constructor.prototype.showItem;
const originalShowItem = this.constructor.prototype.showItem;
if (!this.constructor.prototype.originalShowItem) {
console.error('Favorites: Failed to access original showItem method!');
return;
}
// Overwrite method with additional functionality
this.constructor.prototype.showItem = (function (originalShowItem, plugin) {
return function (...args: CommunityItem[]) {
// Load the favorite plugins
plugin.favoritePlugins = JSON.parse(localStorage.getItem('favorite-plugins') || '[]');
return originalShowItem.apply(this, args).then(async (r: void) => {
const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0];
const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0];
const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0];
const isFavorite = plugin.favoritePlugins?.contains(this.selectedItemId);
// Add to the favorite plugins a tag to visualize it for the user
if (isFavorite) {
if (infoNameEl) {
infoNameEl.createSpan({
cls: 'flair',
text: 'favorite',
});
}
}
plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => {
if (isFavorite) {
plugin.favoritePlugins?.remove(this.selectedItemId);
}
else {
plugin.favoritePlugins?.push(this.selectedItemId);
}
plugin.saveSettings();
// Redraw
infoEl.detach();
this.showItem(this.items[this.selectedItemId]);
this.update();
}));
return r;
});
};
})(originalShowItem, plugin);
}
}
// Check the opened Modal is the Community themes browse modal
if (this.containerEl?.querySelector('.mod-community-theme')) {
// Store the Community Theme Browse Modal prototype to be able to reset to the original
plugin.communityThemesModalPrototype = this.constructor.prototype;
// Check the theme updateItems is already overwritten
if (!this.constructor.prototype.originalUpdateItems) {
// Save original method
this.constructor.prototype.originalUpdateItems = this.constructor.prototype.updateItems;
const originalUpdateItems = this.constructor.prototype.updateItems;
if (!this.constructor.prototype.originalUpdateItems) {
console.error('Favorites: Failed to access original updateItems method!');
return;
}
// Overwrite method with additional functionality
this.constructor.prototype.updateItems = (function (originalUpdateItems, plugin) {
return function () {
// Load the favorite themes
plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]');
const result = originalUpdateItems.apply(this);
// Ignore the default theme
if (this.selectedItemId === '') {
return;
}
// Add to the favorite themes a tag to visualize it for the user
if (plugin.favoriteThemes) {
plugin.favoriteThemes.forEach(id => {
if (this.items && this.items[id]?.nameEl) {
this.items[id].nameEl.createSpan({
cls: 'flair',
text: 'favorite',
});
}
});
}
return result;
};
})(originalUpdateItems, plugin);
}
// Check the theme showItem is already overwritten
if (!this.constructor.prototype.originalShowItem) {
// Save original method
this.constructor.prototype.originalShowItem = this.constructor.prototype.showItem;
const originalShowItem = this.constructor.prototype.showItem;
if (!this.constructor.prototype.originalShowItem) {
console.error('Favorites: Failed to access original showItem method!');
return;
}
// Overwrite method with additional functionality
this.constructor.prototype.showItem = (function (originalShowItem, plugin) {
// Load the favorite themes
plugin.favoriteThemes = JSON.parse(localStorage.getItem('favorite-themes') || '[]');
return function (...args: CommunityItem[]) {
return originalShowItem.apply(this, args).then(async (r: void) => {
// Ignore the default theme
if (this.selectedItemId === '') {
return;
}
const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0];
const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0];
const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0];
const isFavorite = plugin.favoriteThemes?.contains(this.selectedItemId);
// Add to the favorite themes a tag to visualize it for the user
if (isFavorite) {
if (infoNameEl) {
infoNameEl.createSpan({
cls: 'flair',
text: 'favorite',
});
}
}
plugin.registerEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }).addEventListener('click', () => {
if (isFavorite) {
plugin.favoriteThemes?.remove(this.selectedItemId);
}
else {
plugin.favoriteThemes?.push(this.selectedItemId);
}
plugin.saveSettings();
// Redraw
infoEl.detach();
this.showItem(this.items[this.selectedItemId]);
this.update();
}));
return r;
});
};
})(originalShowItem, plugin);
}
}
// Restore the original Modal open function as soon as the CommunityPluginModal and CommunityThemesModal is opened once
if (Modal.prototype.originalOpen && plugin.communityPluginModalPrototype && plugin.communityThemesModalPrototype) {
Modal.prototype.open = Modal.prototype.originalOpen;
delete Modal.prototype.originalOpen;
}
};
})(originalModalOpen, this);
}
else {
console.warn('Favorites plugin: Modal prototype already has originalOpen, this is not intended!');
}
// Patch Modal
if (!this.uninstallModalPatch) {
// console.debug('Patch Modal');
this.uninstallModalPatch = patchModal(this);
}
// Patch PluginManager
if (!this.uninstallPluginManagerPatch) {
// console.debug('Patch PluginManager');
this.uninstallPluginManagerPatch = patchPluginManager(this);
}
// Patch StyleManager
if (!this.uninstallStyleManagerPatch) {
// console.debug('Patch StyleManager');
this.uninstallStyleManagerPatch = patchStyleManager(this);
}
// Check the state of the vault and update lists
this.updateListsForVault();
// Add all commands registered
addAllCommands(this);
/*
* TODO: Add command to save and reload the favorites list
* TODO: Add command to remove all favorites from list
* TODO: Add command to search for plugin to add to favorites list
*/
}
async onunload() {
try {
// Restore the original functions
if (this.uninstallCommunityPluginModalPatch) {
// console.debug('Uninstall CommunityPluginModal patch');
this.uninstallCommunityPluginModalPatch();
this.uninstallCommunityPluginModalPatch = undefined;
// Restore the original Modal open function
if (Modal.prototype.originalOpen) {
Modal.prototype.open = Modal.prototype.originalOpen;
delete Modal.prototype.originalOpen;
}
// Restore the original plugin updateItems function
if (this.communityPluginModalPrototype && this.communityPluginModalPrototype.originalUpdateItems) {
this.communityPluginModalPrototype.updateItems = this.communityPluginModalPrototype.originalUpdateItems;
delete this.communityPluginModalPrototype.originalUpdateItems;
}
// Restore the original plugin showItem function
if (this.communityPluginModalPrototype && this.communityPluginModalPrototype.originalShowItem) {
this.communityPluginModalPrototype.showItem = this.communityPluginModalPrototype.originalShowItem;
delete this.communityPluginModalPrototype.originalShowItem;
}
// Restore the original theme updateItems function
if (this.communityThemesModalPrototype && this.communityThemesModalPrototype.originalUpdateItems) {
this.communityThemesModalPrototype.updateItems = this.communityThemesModalPrototype.originalUpdateItems;
delete this.communityThemesModalPrototype.originalUpdateItems;
}
// Restore the original theme showItem function
if (this.communityThemesModalPrototype && this.communityThemesModalPrototype.originalShowItem) {
this.communityThemesModalPrototype.showItem = this.communityThemesModalPrototype.originalShowItem;
delete this.communityThemesModalPrototype.originalShowItem;
}
}
async loadSettings() {
}
async saveSettings() {
if (this.favoritePlugins) {
if (this.favoritePlugins.length > 0) {
localStorage.setItem('favorite-plugins', JSON.stringify(this.favoritePlugins));
}
if (this.uninstallCommunityPluginsSettingTabPatch) {
// console.debug('Uninstall CommunityPluginsSettingTab patch');
this.uninstallCommunityPluginsSettingTabPatch();
this.uninstallCommunityPluginsSettingTabPatch = undefined;
}
if (this.uninstallCommunityThemeModalPatch) {
// console.debug('Uninstall CommunityThemeModal patch');
this.uninstallCommunityThemeModalPatch();
this.uninstallCommunityThemeModalPatch = undefined;
}
if (this.uninstallModalPatch) {
// console.debug('Uninstall Modal patch');
this.uninstallModalPatch();
this.uninstallModalPatch = undefined;
}
if (this.uninstallPluginManagerPatch) {
// console.debug('Uninstall PluginManager patch');
this.uninstallPluginManagerPatch();
this.uninstallPluginManagerPatch = undefined;
}
if (this.uninstallSettingsModalPatch) {
// console.debug('Uninstall SettingsModal patch');
this.uninstallSettingsModalPatch();
this.uninstallSettingsModalPatch = undefined;
}
if (this.uninstallStyleManagerPatch) {
// console.debug('Uninstall StyleManager patch');
this.uninstallStyleManagerPatch();
this.uninstallStyleManagerPatch = undefined;
}
}
catch (e) {
console.error('Failed uninstalling patch functions!');
console.error(e);
}
}
loadStorageKeys() {
// favorite keys
if (process.env.FAVORITE_PLUGINS_KEY) {
this.pluginFavoritesKey = process.env.FAVORITE_PLUGINS_KEY;
}
else {
throw Error('Missing environment variable \'FAVORITE_PLUGINS_KEY\'');
}
if (process.env.FAVORITE_THEMES_KEY) {
this.themeFavoritesKey = process.env.FAVORITE_THEMES_KEY;
}
else {
throw Error('Missing environment variable \'FAVORITE_THEMES_KEY\'');
}
// console.debug(`Plugins favorite key: ${this.pluginFavoritesKey} Themes favorite key: ${this.themeFavoritesKey}`);
// In use keys
if (process.env.INUSE_PLUGINS_KEY) {
this.pluginInUseKey = process.env.INUSE_PLUGINS_KEY;
}
else {
throw Error('Missing environment variable \'INUSE_PLUGINS_KEY\'');
}
if (process.env.INUSE_THEMES_KEY) {
this.themeInUseKey = process.env.INUSE_THEMES_KEY;
}
else {
throw Error('Missing environment variable \'INUSE_THEMES_KEY\'');
}
// console.debug(`Plugins in use key: ${this.pluginInUseKey} Themes in use key: ${this.themeInUseKey}`);
// Known keys
if (process.env.KNOWN_PLUGINS_KEY) {
this.pluginKnownKey = process.env.KNOWN_PLUGINS_KEY;
}
else {
throw Error('Missing environment variable \'KNOWN_PLUGINS_KEY\'');
}
if (process.env.KNOWN_THEMES_KEY) {
this.themeKnownKey = process.env.KNOWN_THEMES_KEY;
}
else {
throw Error('Missing environment variable \'KNOWN_THEMES_KEY\'');
}
// console.debug(`Plugins known key: ${this.pluginKnownKey} Themes known key: ${this.themeKnownKey}`);
// Register for storage key changes
this.registerDomEvent(window, 'storage', (event) => {
if (event.key === this.pluginFavoritesKey) {
// console.debug('Plugins favorites key content changed', event);
this.onFavoritePluginsChanged(event.newValue);
}
if (event.key === this.themeFavoritesKey) {
// console.debug('Themes favorites key content changed', event);
this.onFavoriteThemesChanged(event.newValue);
}
if (event.key === this.pluginInUseKey) {
// console.debug('Plugins in use key content changed', event);
this.onInUsePluginsChanged(event.newValue);
}
if (event.key === this.themeInUseKey) {
// console.debug('Themes in use key content changed', event);
this.onInUseThemesChanged(event.newValue);
}
if (event.key === this.pluginKnownKey) {
// console.debug('Plugins known key content changed', event);
this.onKnownPluginsChanged(event.newValue);
}
if (event.key === this.themeKnownKey) {
// console.debug('Themes known key content changed', event);
this.onKnownThemesChanged(event.newValue);
}
});
}
onFavoritePluginsChanged(newValue: string | null) {
// console.debug(`onFavoritePluginsChanged: ${newValue}`);
if (newValue) {
this.favoritePlugins = JSON.parse(newValue);
}
else {
this.favoritePlugins = [];
}
}
onFavoriteThemesChanged(newValue: string | null) {
// console.debug(`onFavoriteThemesChanged: ${newValue}`);
if (newValue) {
this.favoriteThemes = JSON.parse(newValue);
}
else {
this.favoriteThemes = [];
}
}
onInUsePluginsChanged(newValue: string | null) {
// console.debug(`onInUsePluginsChanged: ${newValue}`);
if (newValue) {
this.inUsePlugins = JSON.parse(newValue);
}
else {
this.inUsePlugins = {};
}
}
onInUseThemesChanged(newValue: string | null) {
// console.debug(`onInUseThemesChanged: ${newValue}`);
if (newValue) {
this.inUseThemes = JSON.parse(newValue);
}
else {
this.inUseThemes = {};
}
}
onKnownPluginsChanged(newValue: string | null) {
// console.debug(`onKnownPluginsChanged: ${newValue}`);
if (newValue) {
this.knownPlugins = JSON.parse(newValue);
}
else {
this.knownPlugins = [];
}
}
onKnownThemesChanged(newValue: string | null) {
// console.debug(`onKnownThemesChanged: ${newValue}`);
if (newValue) {
this.knownThemes = JSON.parse(newValue);
}
else {
this.knownThemes = [];
}
}
loadFavoritePlugins() {
// Load the favorite plugins
this.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginFavoritesKey) || '[]');
}
loadFavoriteThemes() {
// Load the favorite plugins
this.favoriteThemes = JSON.parse(localStorage.getItem(this.themeFavoritesKey) || '[]');
}
loadInUsePlugins() {
// Load the in use plugins
this.inUsePlugins = JSON.parse(localStorage.getItem(this.pluginInUseKey) || '{}');
}
loadInUseThemes() {
// Load the in use plugins
this.inUseThemes = JSON.parse(localStorage.getItem(this.themeInUseKey) || '{}');
}
loadKnownPlugins() {
// Load the known plugins
this.knownPlugins = JSON.parse(localStorage.getItem(this.pluginKnownKey) || '[]');
}
loadKnownThemes() {
// Load the known plugins
this.knownThemes = JSON.parse(localStorage.getItem(this.themeKnownKey) || '[]');
}
loadLists() {
this.loadFavoritePlugins();
this.loadFavoriteThemes();
this.loadInUsePlugins();
this.loadInUseThemes();
this.loadKnownPlugins();
this.loadKnownThemes();
}
saveFavoritePlugins() {
if (this.favoritePlugins.length > 0) {
localStorage.setItem(this.pluginFavoritesKey, JSON.stringify(this.favoritePlugins));
}
else {
localStorage.removeItem(this.pluginFavoritesKey);
}
}
saveFavoriteThemes() {
if (this.favoriteThemes.length > 0) {
localStorage.setItem(this.themeFavoritesKey, JSON.stringify(this.favoriteThemes));
}
else {
localStorage.removeItem(this.themeFavoritesKey);
}
}
saveInUsePlugins() {
if (Object.keys(this.inUsePlugins).length > 0) {
localStorage.setItem(this.pluginInUseKey, JSON.stringify(this.inUsePlugins));
}
else {
localStorage.removeItem(this.pluginInUseKey);
}
}
saveInUseThemes() {
if (Object.keys(this.inUseThemes).length > 0) {
localStorage.setItem(this.themeInUseKey, JSON.stringify(this.inUseThemes));
}
else {
localStorage.removeItem(this.themeInUseKey);
}
}
saveKnownPlugins() {
if (this.knownPlugins.length > 0) {
localStorage.setItem(this.pluginKnownKey, JSON.stringify(this.knownPlugins));
}
else {
localStorage.removeItem(this.pluginKnownKey);
}
}
saveKnownThemes() {
if (this.knownThemes.length > 0) {
localStorage.setItem(this.themeKnownKey, JSON.stringify(this.knownThemes));
}
else {
localStorage.removeItem(this.themeKnownKey);
}
}
saveLists() {
this.saveFavoritePlugins();
this.saveFavoriteThemes();
this.saveInUsePlugins();
this.saveInUseThemes();
this.saveKnownPlugins();
this.saveKnownThemes();
}
updateListsForVault() {
const currentVault = this.app.appId;
// Check the installed plugins and update known plugins list
for (const id of Object.keys(this.app.plugins.manifests)) {
// Add Plugin to known list if not already
if (!this.knownPlugins.contains(id)) {
this.knownPlugins.push(id);
else {
localStorage.removeItem('favorite-plugins');
}
}
// Check the enabled plugins and update in use plugins list
for (const id of this.app.plugins.enabledPlugins) {
// Add the plugin to the in use list with this vault as user
if (!this.inUsePlugins[id] || !Array.isArray(this.inUsePlugins[id])) {
this.inUsePlugins[id] = [];
if (this.favoriteThemes) {
if (this.favoriteThemes.length > 0) {
localStorage.setItem('favorite-themes', JSON.stringify(this.favoriteThemes));
}
if (!this.inUsePlugins[id].contains(currentVault)) {
this.inUsePlugins[id].push(currentVault);
else {
localStorage.removeItem('favorite-themes');
}
}
// Check the in use plugins list does not contain old entries of this vault
for (const id of Object.keys(this.inUsePlugins)) {
if (Array.isArray(this.inUsePlugins[id]) && this.inUsePlugins[id].contains(currentVault)) {
if (!this.app.plugins.enabledPlugins.has(id)) {
this.inUsePlugins[id].remove(currentVault);
// Cleanup in use if last vault stops using it
if (this.inUsePlugins[id].length <= 0) {
delete this.inUsePlugins[id];
}
}
}
}
// Check the installed themes and update known themes list
for (const name of Object.keys(this.app.customCss.themes)) {
// Add theme to known list if not already
if (!this.knownThemes.contains(name)) {
this.knownThemes.push(name);
}
}
// Check the enabled theme and update in use themes list
const name = this.app.customCss.theme;
// Ignore the default theme
if (name !== '') {
// Add the theme to the in use list with this vault as user
if (!this.inUseThemes[name] || !Array.isArray(this.inUseThemes[name])) {
this.inUseThemes[name] = [];
}
if (!this.inUseThemes[name].contains(currentVault)) {
this.inUseThemes[name].push(currentVault);
}
}
// Check the in use themes list does not contain old entries of this vault
for (const name of Object.keys(this.inUseThemes)) {
if (Array.isArray(this.inUseThemes[name]) && this.inUseThemes[name].contains(currentVault)) {
if (this.app.customCss.theme !== name) {
this.inUseThemes[name].remove(currentVault);
// Cleanup in use if last vault stops using it
if (this.inUseThemes[name].length <= 0) {
delete this.inUseThemes[name];
}
}
}
}
this.saveLists();
}
}

View file

@ -1,35 +0,0 @@
import { App, FuzzyMatch, FuzzySuggestModal, renderResults } from 'obsidian';
export class CommunitySuggestModal<T extends { name: string, author: string }> extends FuzzySuggestModal<T> {
private items: T[] = [];
private onSubmit: (result: T) => void = () => { };
constructor(app: App, title: string, items: T[], onSubmit: (result: T) => void) {
super(app);
this.setPlaceholder(title);
this.items = items;
this.onSubmit = onSubmit;
}
getItemText(item: T): string {
return `${item.name} ${item.author}`;
}
getItems(): T[] {
return this.items;
}
onChooseItem(item: T): void {
this.onSubmit(item);
}
renderSuggestion(match: FuzzyMatch<T>, el: HTMLElement) {
const titleEl = el.createDiv();
renderResults(titleEl, match.item.name, match.match);
// Only render the matches in the author name.
const authorEl = el.createEl('small');
const offset = -(match.item.name.length + 1);
renderResults(authorEl, match.item.author, match.match, offset);
}
}

View file

@ -1,77 +0,0 @@
import { App, Modal, Setting } from 'obsidian';
export class DialogModal extends Modal {
message: string;
submit: string;
submitWarning: boolean;
deny: string;
denyWarning: boolean;
onSubmit: () => void;
onDeny: () => void;
constructor(app: App, title: string, message: string, onSubmit: () => void, onDeny: () => void, submit = 'Agree', submitWarning = false, deny = 'Cancel', denyWarning = true) {
super(app);
this.titleEl.setText(title);
this.message = message;
this.onSubmit = onSubmit;
this.onDeny = onDeny;
this.submit = submit;
this.submitWarning = submitWarning;
this.deny = deny;
this.denyWarning = denyWarning;
}
onOpen(): void {
const { contentEl } = this;
if (this.message && this.message !== '') {
contentEl.createEl('span', { text: this.message });
}
const setting = new Setting(contentEl);
if (this.submitWarning) {
setting.addButton(button => button
.setButtonText(this.submit)
.setWarning()
.onClick(() => {
this.close();
this.onSubmit();
}));
}
else {
setting.addButton(button => button
.setButtonText(this.submit)
.onClick(() => {
this.close();
this.onSubmit();
}));
}
if (this.denyWarning) {
setting.addButton(button => button
.setButtonText(this.deny)
.setWarning()
.onClick(() => {
this.close();
this.onDeny();
}));
}
else {
setting.addButton(button => button
.setButtonText(this.deny)
.onClick(() => {
this.close();
this.onDeny();
}));
}
setting.setClass('modal-buttons');
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}

View file

@ -1,23 +0,0 @@
import { App, FuzzySuggestModal } from 'obsidian';
export class StringSuggestModal extends FuzzySuggestModal<string> {
private items: string[] = [];
onSubmit: (result: string) => void = () => { };
constructor(app: App, title: string, items: string[], onSubmit: (result: string) => void) {
super(app);
this.setPlaceholder(title);
this.items = items;
this.onSubmit = onSubmit;
}
getItems(): string[] {
return this.items;
}
getItemText(item: string): string {
return item;
}
onChooseItem(item: string): void {
this.onSubmit(item);
}
}

View file

@ -1,100 +0,0 @@
import { around, dedupe } from 'monkey-around';
import MyPlugin from '../main';
import { PLUGIN_ID } from 'src/constants';
import { CommunityItem, CommunityPluginModal } from 'obsidian';
import FavoriteFlair from 'src/ui-components/FavoriteFlair';
import InUseFlair from 'src/ui-components/InUseFlair';
import KnownFlair from 'src/ui-components/KnownFlair';
import InstalledFlair from 'src/ui-components/InstalledFlair';
const MONKEY_KEY = `${PLUGIN_ID}-CommunityPluginModal-`;
export default (context: CommunityPluginModal, plugin: MyPlugin) => around(context.constructor.prototype, {
showItem(oldMethod) {
return dedupe(`${MONKEY_KEY}showItem`, oldMethod, async function (item: CommunityItem) {
// console.debug('Call CommunityPluginModal.showItem');
const result = oldMethod && await oldMethod.apply(this, [item]);
const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0];
const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0];
const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0];
// Load the plugin lists
plugin.loadFavoritePlugins();
plugin.loadInUsePlugins();
plugin.loadKnownPlugins();
// Add aria label to installed flair
new InstalledFlair(infoNameEl);
// Add to the favorite plugins a flair to visualize it for the user
const isFavorite = plugin.favoritePlugins.contains(this.selectedItemId);
if (isFavorite) {
new FavoriteFlair(infoNameEl);
}
// Add to the in use plugins a flair to visualize it for the user
if (Object.keys(plugin.inUsePlugins).contains(this.selectedItemId)) {
new InUseFlair(infoNameEl);
}
// Add to the known plugins a flair to visualize it for the user
if (plugin.knownPlugins.contains(this.selectedItemId)) {
new KnownFlair(infoNameEl);
}
plugin.registerDomEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }), 'click', async () => {
if (isFavorite) {
plugin.favoritePlugins.remove(this.selectedItemId);
}
else {
plugin.favoritePlugins.push(this.selectedItemId);
}
plugin.saveFavoritePlugins();
// Redraw
infoEl.detach();
await this.showItem(this.items[this.selectedItemId]);
this.update();
});
return result;
});
},
updateItems(oldMethod) {
return dedupe(`${MONKEY_KEY}updateItems`, oldMethod, function () {
// console.debug('Call PluginBrowserModal.updateItems');
// Load the plugin lists
plugin.loadFavoritePlugins();
plugin.loadInUsePlugins();
plugin.loadKnownPlugins();
const result = oldMethod && oldMethod.apply(this);
// Add aria label to installed flair
Object.keys(plugin.app.plugins.manifests).forEach(id => {
new InstalledFlair(this.items[id]?.nameEl);
});
// Add to the favorite plugins a flair to visualize it for the user
plugin.favoritePlugins.forEach(id => {
new FavoriteFlair(this.items[id]?.nameEl);
});
// Add to the in use plugins a flair to visualize it for the user
Object.keys(plugin.inUsePlugins).forEach(id => {
new InUseFlair(this.items[id]?.nameEl);
});
// Add to the known plugins a flair to visualize it for the user
plugin.knownPlugins.forEach(id => {
new KnownFlair(this.items[id]?.nameEl);
});
return result;
});
},
});

View file

@ -1,50 +0,0 @@
import { CommunityItem, CommunityPluginsSettingTab, setIcon, setTooltip } from 'obsidian';
import MyPlugin from '../main';
import { around, dedupe } from 'monkey-around';
import { PLUGIN_ID } from 'src/constants';
const MONKEY_KEY = `${PLUGIN_ID}-CommunityPluginsSettingTab-`;
export default (context: CommunityPluginsSettingTab, plugin: MyPlugin) => around(context, {
renderInstalledPlugin(oldMethod) {
return dedupe(`${MONKEY_KEY}renderInstalledPlugin`, oldMethod, function (item: CommunityItem, containerEl: HTMLElement, n: unknown, i: unknown, r: unknown) {
// console.debug('Call CommunityPluginsSettingTab.renderInstalledPlugin');
oldMethod && oldMethod.apply(this, [item, containerEl, n, i, r]);
// Load the favorite plugins
plugin.loadFavoritePlugins();
const selectedPluginID = item.id;
const isFavorite = plugin.favoritePlugins.contains(selectedPluginID);
const favEl = createDiv('clickable-icon extra-setting-button');
plugin.registerDomEvent(favEl, 'click', () => {
if (isFavorite) {
plugin.favoritePlugins.remove(selectedPluginID);
}
else {
plugin.favoritePlugins.push(selectedPluginID);
}
plugin.saveFavoritePlugins();
// Redraw
this.render(false);
});
if (isFavorite) {
setIcon(favEl, 'star-off');
setTooltip(favEl, 'Unfavorite');
}
else {
setIcon(favEl, 'star');
setTooltip(favEl, 'Favorite');
}
// Add Favorite Icon to plugin control
const controlEl: HTMLElement = this.installedPlugins.listEl.lastChild?.getElementsByClassName('setting-item-control')[0];
controlEl.insertBefore(favEl, controlEl.children[0]);
});
},
});

View file

@ -1,114 +0,0 @@
import { CommunityItem, CommunityThemeModal } from 'obsidian';
import MyPlugin from '../main';
import { around, dedupe } from 'monkey-around';
import { PLUGIN_ID } from 'src/constants';
import InstalledFlair from 'src/ui-components/InstalledFlair';
import FavoriteFlair from 'src/ui-components/FavoriteFlair';
import InUseFlair from 'src/ui-components/InUseFlair';
import KnownFlair from 'src/ui-components/KnownFlair';
import ActiveFlair from 'src/ui-components/ActiveFlair';
const MONKEY_KEY = `${PLUGIN_ID}-ThemePluginModal-`;
export default (context: CommunityThemeModal, plugin: MyPlugin) => around(context.constructor.prototype, {
showItem(oldMethod) {
return dedupe(`${MONKEY_KEY}showItem`, oldMethod, async function (item: CommunityItem) {
// console.debug('Call CommunityThemeModal.showItem');
const result = oldMethod && await oldMethod.apply(this, [item]);
const infoEl = await this.detailsEl.getElementsByClassName('community-modal-info')[0];
const infoNameEl = await infoEl?.getElementsByClassName('community-modal-info-name')[0];
// Add aria label to installed flair
new InstalledFlair(infoNameEl);
// Ignore the default theme
if (this.selectedItemId === '') {
return result;
}
const buttonContainerEl = await infoEl?.getElementsByClassName('community-modal-button-container')[0];
// Load the theme lists
plugin.loadFavoriteThemes();
plugin.loadInUseThemes();
plugin.loadKnownThemes();
// Add to the favorite themes a flair to visualize it for the user
const isFavorite = plugin.favoriteThemes.contains(this.selectedItemId);
if (isFavorite) {
new FavoriteFlair(infoNameEl);
}
// Add to the in use themes a flair to visualize it for the user
if (Object.keys(plugin.inUseThemes).contains(this.selectedItemId)) {
new InUseFlair(infoNameEl);
}
// Add to the known themes a flair to visualize it for the user
if (plugin.knownThemes.contains(this.selectedItemId)) {
new KnownFlair(infoNameEl);
}
plugin.registerDomEvent(buttonContainerEl?.createEl('button', { text: isFavorite ? 'Unfavorite' : 'Favorite' }), 'click', async () => {
if (isFavorite) {
plugin.favoriteThemes.remove(this.selectedItemId);
}
else {
plugin.favoriteThemes.push(this.selectedItemId);
}
plugin.saveFavoriteThemes();
// Redraw
infoEl.detach();
await this.showItem(this.items[this.selectedItemId]);
this.update();
});
return result;
});
},
updateItems(oldMethod) {
return dedupe(`${MONKEY_KEY}updateItems`, oldMethod, function () {
// console.debug('Call CommunityThemeModal.updateItems');
// Load the theme lists
plugin.loadFavoriteThemes();
plugin.loadInUseThemes();
plugin.loadKnownThemes();
const result = oldMethod && oldMethod.apply(this);
// Add aria label to installed flair
Object.keys(plugin.app.customCss.themes).forEach(id => {
new InstalledFlair(this.items[id]?.nameEl);
});
// Add aria label to active flair
new ActiveFlair(this.items[plugin.app.customCss.theme]?.nameEl);
// Ignore the default theme
if (this.selectedItemId === '') {
return result;
}
// Add to the favorite themes a flair to visualize it for the user
plugin.favoriteThemes.forEach(id => {
new FavoriteFlair(this.items[id]?.nameEl);
});
// Add to the in use themes a flair to visualize it for the user
Object.keys(plugin.inUseThemes).forEach(id => {
new InUseFlair(this.items[id]?.nameEl);
});
// Add to the known themes a flair to visualize it for the user
plugin.knownThemes.forEach(id => {
new KnownFlair(this.items[id]?.nameEl);
});
return result;
});
},
});

View file

@ -1,43 +0,0 @@
import { Modal } from 'obsidian';
import MyPlugin from '../main';
import { around, dedupe } from 'monkey-around';
import { PLUGIN_ID } from 'src/constants';
import patchCommunityPluginModal from './patch.CommunityPluginModal';
import patchCommunityThemeModal from './patch.CommunityThemeModal';
const MONKEY_KEY = `${PLUGIN_ID}-Modal-`;
export default (plugin: MyPlugin) => around(Modal.prototype, {
open(oldMethod) {
return dedupe(`${MONKEY_KEY}open`, oldMethod, function () {
// console.debug('Call Modal.open');
const result = oldMethod && oldMethod.apply(this);
// Check the opened Modal is the Community plugin browse modal
if (this.containerEl?.querySelector('.mod-community-plugin')) {
// Patch CommunityPluginModal
if (!plugin.uninstallCommunityPluginModalPatch) {
// console.debug('Patch CommunityPluginModal');
plugin.uninstallCommunityPluginModalPatch = patchCommunityPluginModal(this, plugin);
}
}
// Check the opened Modal is the Community themes browse modal
if (this.containerEl?.querySelector('.mod-community-theme')) {
// Patch CommunityThemeModal
if (!plugin.uninstallCommunityThemeModalPatch) {
// console.debug('Patch CommunityThemeModal');
plugin.uninstallCommunityThemeModalPatch = patchCommunityThemeModal(this, plugin);
}
}
if (plugin.uninstallModalPatch && plugin.uninstallCommunityPluginModalPatch && plugin.uninstallCommunityThemeModalPatch) {
// console.debug('Uninstall Modal patch');
plugin.uninstallModalPatch();
plugin.uninstallModalPatch = undefined;
}
return result;
});
},
});

View file

@ -1,74 +0,0 @@
import MyPlugin from '../main';
import { around, dedupe } from 'monkey-around';
import { PluginManifest } from 'obsidian';
import { PLUGIN_ID } from 'src/constants';
const MONKEY_KEY = `${PLUGIN_ID}-PluginManager-`;
export default (plugin: MyPlugin) => around(plugin.app.plugins, {
installPlugin(oldMethod) {
return dedupe(`${MONKEY_KEY}installPlugin`, oldMethod, async function (repo: string, version: string, manifest: PluginManifest) {
// console.debug('Call PluginManager.installPlugin');
const result = oldMethod && await oldMethod.apply(this, [repo, version, manifest]);
// Load the plugin lists
plugin.loadKnownPlugins();
// Add Plugin to known list if not already
if (!plugin.knownPlugins.contains(manifest.id)) {
plugin.knownPlugins.push(manifest.id);
}
plugin.saveKnownPlugins();
return result;
});
},
loadPlugin(oldMethod) {
return dedupe(`${MONKEY_KEY}loadPlugin`, oldMethod, async function (id: string) {
// console.debug('Call PluginManager.loadPlugin');
const currentVault = plugin.app.appId;
// Load the plugin lists
plugin.loadInUsePlugins();
// Add the plugin to the in use list with this vault as user
if (!plugin.inUsePlugins[id] || !Array.isArray(plugin.inUsePlugins[id])) {
plugin.inUsePlugins[id] = [];
}
if (!plugin.inUsePlugins[id].contains(currentVault)) {
plugin.inUsePlugins[id].push(currentVault);
}
plugin.saveInUsePlugins();
return oldMethod && oldMethod.apply(this, [id]);
});
},
unloadPlugin(oldMethod) {
return dedupe(`${MONKEY_KEY}unloadPlugin`, oldMethod, async function (id: string) {
// console.debug('Call PluginManager.unloadPlugin');
const currentVault = plugin.app.appId;
// Load the plugin lists
plugin.loadInUsePlugins();
// Remove current vault from unloaded plugin
if (plugin.inUsePlugins[id] && Array.isArray(plugin.inUsePlugins[id]) && plugin.inUsePlugins[id].contains(currentVault)) {
plugin.inUsePlugins[id].remove(currentVault);
// Cleanup in use if last vault stops using it
if (plugin.inUsePlugins[id].length <= 0) {
delete plugin.inUsePlugins[id];
}
}
plugin.saveInUsePlugins();
return oldMethod && oldMethod.apply(this, [id]);
});
},
});

View file

@ -1,30 +0,0 @@
import { CommunityPluginsSettingTab, SettingsModal, SettingTab } from 'obsidian';
import MyPlugin from '../main';
import { around, dedupe } from 'monkey-around';
import { PLUGIN_ID } from 'src/constants';
import patchCommunityPluginsSettingTab from './patch.CommunityPluginsSettingTab';
const MONKEY_KEY = `${PLUGIN_ID}-SettingsModal-`;
export default (plugin: MyPlugin) => around(plugin.app.setting as SettingsModal, {
openTab(oldMethod) {
return dedupe(`${MONKEY_KEY}openTab`, oldMethod, function (tab: SettingTab) {
// console.debug('Call SettingsModal.openTab');
if (tab.id === 'community-plugins') {
if (!plugin.uninstallCommunityPluginsSettingTabPatch) {
// console.debug('Patch CommunityPluginsSettingTab');
plugin.uninstallCommunityPluginsSettingTabPatch = patchCommunityPluginsSettingTab(tab as CommunityPluginsSettingTab, plugin);
}
}
if (plugin.uninstallSettingsModalPatch && plugin.uninstallCommunityPluginsSettingTabPatch) {
// console.debug('Uninstall SettingsModal patch');
plugin.uninstallSettingsModalPatch();
plugin.uninstallSettingsModalPatch = undefined;
}
return oldMethod && oldMethod.apply(this, [tab]);
});
},
});

View file

@ -1,69 +0,0 @@
import MyPlugin from '../main';
import { around, dedupe } from 'monkey-around';
import { ThemeManifest } from 'obsidian';
import { PLUGIN_ID } from 'src/constants';
const MONKEY_KEY = `${PLUGIN_ID}-StyleManager-installTheme`;
export default (plugin: MyPlugin) => around(plugin.app.customCss, {
installTheme(oldMethod) {
return dedupe(`${MONKEY_KEY}installTheme`, oldMethod, async function (manifest: ThemeManifest, version: string) {
// console.debug('Call StyleManager.installTheme');
const result = oldMethod && await oldMethod.apply(this, [manifest, version]);
// Load the theme lists
plugin.loadKnownThemes();
// Add Theme to known list if not already
if (!plugin.knownThemes.contains(manifest.name)) {
plugin.knownThemes.push(manifest.name);
}
plugin.saveKnownThemes();
return result;
});
},
setTheme(oldMethod) {
return dedupe(`${MONKEY_KEY}setTheme`, oldMethod, function (name: string) {
// console.debug('Call StyleManager.setTheme', this);
const currentVault = plugin.app.appId;
// Load the theme lists
plugin.loadInUseThemes();
// Remove current vault from replaced theme
const oldTheme = this.theme;
if (plugin.inUseThemes[oldTheme] && Array.isArray(plugin.inUseThemes[oldTheme]) && plugin.inUseThemes[oldTheme].contains(currentVault)) {
plugin.inUseThemes[oldTheme].remove(currentVault);
// Cleanup in use if last vault stops using it
if (plugin.inUseThemes[oldTheme].length <= 0) {
delete plugin.inUseThemes[oldTheme];
}
}
const result = oldMethod && oldMethod.apply(this, [name]);
// Ignore the default theme
if (name === '') {
plugin.saveInUseThemes();
return result;
}
// Add the theme to the in use list with this vault as user
if (!plugin.inUseThemes[name] || !Array.isArray(plugin.inUseThemes[name])) {
plugin.inUseThemes[name] = [];
}
if (!plugin.inUseThemes[name].contains(currentVault)) {
plugin.inUseThemes[name].push(currentVault);
}
plugin.saveInUseThemes();
return result;
});
},
});

195
src/types.d.ts vendored
View file

@ -1,200 +1,37 @@
import { } from 'obsidian';
declare module 'obsidian' {
interface App {
appId: string;
setting: SettingsModal;
plugins: PluginManager;
customCss: StyleManager;
}
interface ThemeManifest {
author: string;
authorUrl: string;
dir: string;
minAppVersion: string;
name: string;
version: string;
}
interface CommunityItem {
id: string;
// ...
}
interface CommunityModal extends Modal {
sortOrder: string;
items: unknown;
itemsVisible: unknown;
selectedItemId: string | null;
dimBackground: unknown;
onCloseCallback: () => void;
interface CommunityModal {
update(): void;
returnToGridView(): void;
selectItem(id: string | null): unknown;
showSortMenu(event: unknown): void;
sortItems(items: unknown): unknown;
scrollIntoView(): unknown;
addCustomControls(): unknown;
updateItems(): CommunityItem[];
showItem(item: CommunityItem): Promise<void>;
}
showItem(item: CommunityItem): void;
interface CommunityThemeModal extends CommunityModal {
// ...
}
interface CommunityPluginModal extends CommunityModal {
setAutoOpen(id: string): CommunityPluginModal;
loadItems(): unknown;
}
interface SettingsModal extends Modal {
openTab(settingTab: SettingTab): void;
}
interface SettingTab {
// Additional from this plugin
/**
* This is the backup of the original ``updateItems()`` function. Do not use otherwise than restoring!
*/
originalUpdateItems?(): CommunityItem[];
/**
* Used to identify the tab
*
* @private reverse engineered
* This is the backup of the original ``showItem()`` function. Do not use otherwise than restoring!
*/
id: string;
setting: unknown;
navEl: HTMLElement;
originalShowItem?(item: CommunityItem): void;
}
interface CommunityPluginsSettingTab extends SettingTab {
id: 'community-plugins';
installedPlugins: {
listEl: HTMLElement
};
render(e: boolean): void;
renderInstalledPlugin(plugin: CommunityItem, containerEl: HTMLElement, n: unknown, i: unknown, r: unknown): void;
updateSearch(e: unknown): void
}
interface HotkeysSettingTab extends SettingTab {
name: string;
id: 'hotkeys';
setHotkeyScope: unknown;
commandBeingCustomized: unknown;
filterHotkeyScope: unknown;
filterHotkeyEl: unknown;
filterHotkeyActiveEl: unknown;
hotkeyListContainerEl: unknown;
hotkeyFilter: unknown;
}
interface StyleManager extends Component {
app: App;
styleEl: HTMLStyleElement;
extraStyleEls: HTMLStyleElement[];
boundRaw: () => unknown;
oldThemes: unknown[];
interface Modal {
// Additional form this plugin
/**
* Holds the manifest of the installed themes in this vault
*
* @private reverse engineered
* This is the backup of the original ``open()`` function. Do not use otherwise than restoring!
*/
themes: Record<string, ThemeManifest>;
updates: Record<string, { themeInfo: unknown, version: string }>;
snippets: string[];
enabledSnippets: Set<string>;
csscache: Map<number, unknown>;
/**
* Holds the id of the enabled theme
*
* @private reverse engineered
*/
theme: string;
requestLoadTheme: () => unknown;
requestLoadSnippets: () => unknown;
requestReadThemes: () => unknown;
queue: unknown;
onload(): Promise<unknown>;
loadData(): void;
readThemes(e: unknown): unknown;
readSnippets(e: unknown): unknown;
onRaw(e: unknown): void;
loadTheme(e: undefined): unknown;
loadSnippets(): unknown;
loadCss(path: string): Promise<unknown>;
setCssEnabledStatus(e: unknown, t: unknown): void;
setTheme(name: string): void;
setTranslucency(e: unknown): void;
enableTranslucency(): void;
disableTranslucency(): void;
getThemeFolder(): string;
getSnippetsFolder(): string;
getThemePath(e: unknown): string;
getSnippetPath(e: unknown): string;
removeTheme(e: unknown): Promise<unknown>;
downloadLegacyTheme(e: unknown): Promise<unknown>;
isThemeInstalled(e: unknown): unknown;
hasUpdates(): unknown;
checkForUpdate(e: unknown): Promise<unknown>;
checkForUpdates(): Promise<unknown>;
installLegacyTheme(e: unknown): Promise<unknown>;
installTheme(manifest: ThemeManifest, version: string): Promise<unknown>;
getManifest(e: unknown): Promise<unknown>;
}
interface PluginManager {
/**
* Holds the manifest of the installed plugins in this vault
*
* @private reverse engineered
*/
manifests: Record<string, PluginManifest>;
/**
* Holds references to the loaded plugins identified by there id
*
* @private reverse engineered
*/
plugins: Record<string, Plugin>;
/**
* Holds a the ids of the enabled plugins in this vault
*
* @private reverse engineered
*/
enabledPlugins: Set<string>;
/**
* Holds the available updates identified by there id with information about the update
*
* @private reverse engineered
*/
updates: Record<string, { repo: string, version: string, manifest: PluginManifest }>;
requestSaveConfig: () => unknown;
app: App;
onRaw(e: unknown): void;
loadManifests(): Promise<unknown>;
loadManifest(path: string): Promise<unknown>;
loadPlugin(id: string): Promise<unknown>;
unloadPlugin(id: string): Promise<unknown>;
initialize(): Promise<unknown>;
getPluginFolder(): string;
enablePlugin(id: string): Promise<unknown>;
disablePlugin(id: string): Promise<unknown>;
enablePluginAndSave(id: string): Promise<unknown>;
disablePluginAndSave(id: string): Promise<unknown>;
uninstallPlugin(id: string): Promise<unknown>;
getPlugin(id: string): unknown | null;
saveConfig(): Promise<unknown>;
isEnabled(): boolean;
setEnable(e: unknown): Promise<unknown>;
checkForDeprecations(): Promise<unknown>;
isDeprecated(e: unknown): unknown;
installPlugin(repo: string, version: string, manifest: PluginManifest): Promise<unknown>;
checkForUpdates(): Promise<unknown>;
originalOpen?(): void;
}
}

View file

@ -1,11 +0,0 @@
export default class ActiveFlair {
constructor(parentEl: HTMLElement) {
if (parentEl) {
const flair = Array.from(parentEl.getElementsByClassName('flair')).find(el => el.textContent?.trim().toLowerCase() === 'active');
if (flair) {
flair.setAttribute('aria-label', 'Active theme in this vault');
}
}
}
}

View file

@ -1,7 +0,0 @@
import Flair from './Flair';
export default class FavoriteFlair extends Flair {
constructor(parentEl: HTMLElement) {
super(parentEl, 'Favorite', 'Marked as favorite');
}
}

View file

@ -1,13 +0,0 @@
export default class Flair {
constructor(parentEl: HTMLElement, text: string, ariaLabel: string | null = null) {
if (parentEl) {
const flair = parentEl.createSpan({
cls: 'flair',
text: text,
});
if (ariaLabel) {
flair.setAttribute('aria-label', ariaLabel);
}
}
}
}

View file

@ -1,7 +0,0 @@
import Flair from './Flair';
export default class InUseFlair extends Flair {
constructor(parentEl: HTMLElement) {
super(parentEl, 'In Use', 'Enabled in at least one vault');
}
}

View file

@ -1,11 +0,0 @@
export default class InstalledFlair {
constructor(parentEl: HTMLElement) {
if (parentEl) {
const flair = Array.from(parentEl.getElementsByClassName('flair')).find(el => el.textContent?.trim().toLowerCase() === 'installed');
if (flair) {
flair.setAttribute('aria-label', 'Installed in this vault');
}
}
}
}

View file

@ -1,7 +0,0 @@
import Flair from './Flair';
export default class KnownFlair extends Flair {
constructor(parentEl: HTMLElement) {
super(parentEl, 'Known', 'Installed at least once in a vault');
}
}

View file

@ -1,60 +0,0 @@
/**
* Credits: https://github.com/TfTHacker/obsidian42-brat
*/
import { request } from 'obsidian';
export interface CommunityEntry {
name: string;
author: string;
repo: string;
}
export interface CommunityPlugin extends CommunityEntry {
id: string;
description: string;
}
export interface CommunityTheme extends CommunityEntry {
screenshot: string;
modes: string[];
legacy?: boolean;
}
/**
* Fetch all community plugin entries.
* @returns A list of community plugins
*/
export async function fetchCommunityPluginList(): Promise<CommunityPlugin[] | undefined> {
const URL = 'https://raw.githubusercontent.com/obsidianmd/obsidian-releases/HEAD/community-plugins.json';
try {
// Do a request to the url
const response = await request({ url: URL });
// Process the response
return (await JSON.parse(response)) as CommunityPlugin[];
}
catch (e) {
(e as Error).message = 'Failed to fetch community plugin list! ' + (e as Error).message;
console.error(e);
}
}
/**
* Fetch all community theme entries.
* @returns A list of community themes
*/
export async function fetchCommunityThemeList(): Promise<CommunityTheme[] | undefined> {
const URL = 'https://raw.githubusercontent.com/obsidianmd/obsidian-releases/refs/heads/master/community-css-themes.json';
try {
// Do a request to the url
const response = await request({ url: URL });
// Process the response
return (await JSON.parse(response)) as CommunityTheme[];
}
catch (e) {
(e as Error).message = 'Failed to fetch community plugin list! ' + (e as Error).message;
console.error(e);
}
}

View file

@ -1,5 +0,0 @@
.community-item-name {
display: flex;
flex-wrap: wrap;
align-items: baseline;
}

View file

@ -1,11 +1,9 @@
import { version as manifestVersion, minAppVersion as manifestMinAppVersion } from '../manifest.json';
import { version as packageVersion } from '../package.json';
import { version as packageLockVersion } from '../package.json';
import versions from '../versions.json';
test('Versions matching', () => {
expect(manifestVersion).toBe(packageVersion);
expect(manifestVersion).toBe(packageLockVersion);
expect(Object.keys(versions).find(value => value === manifestVersion)).toBe(manifestVersion);
expect(Object.entries(versions).find(value => value[0] === manifestVersion)?.[1]).toBe(manifestMinAppVersion);
});

View file

@ -1,6 +1,5 @@
import { execSync } from 'child_process';
import { readFileSync, writeFileSync } from 'fs';
import { valid } from 'semver';
import { readFileSync, writeFileSync } from "fs";
import { valid } from "semver";
const newVersion = process.argv.find(value => value.startsWith('--new_version'));
if (!newVersion) {
@ -15,30 +14,23 @@ if (!valid(targetVersion)) {
}
// read minAppVersion from manifest.json
let manifestFile = JSON.parse(readFileSync('manifest.json', 'utf8'));
let manifestFile = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifestFile;
if (!minAppVersion || minAppVersion === '') {
throw Error(`Missing minAppVersion in 'manifest.json'`);
if (!minAppVersion || minAppVersion === "") {
throw Error('Missing minAppVersion in "manifest.json"');
}
// update version to target version
manifestFile.version = targetVersion;
writeFileSync('manifest.json', JSON.stringify(manifestFile, null, '\t'));
writeFileSync("manifest.json", JSON.stringify(manifestFile, null, "\t"));
// update version in package
let packageFile = JSON.parse(readFileSync('package.json', 'utf8'));
let packageFile = JSON.parse(readFileSync("package.json", "utf8"));
packageFile.version = targetVersion;
writeFileSync('package.json', JSON.stringify(packageFile, null, '\t'));
// update version in package-lock
try {
execSync('npm install', { stdio: 'inherit' });
} catch (error) {
throw Error('npm installed failed: ' + error);
}
writeFileSync("package.json", JSON.stringify(packageFile, null, "\t"));
// read versions file
let versionsFile = JSON.parse(readFileSync('versions.json', 'utf8'));
let versionsFile = JSON.parse(readFileSync("versions.json", "utf8"));
let keys = Object.keys(versionsFile);
// remove existing versions with same minAppVersion
@ -50,4 +42,4 @@ keys.forEach(key => {
// update versions.json with target version and minAppVersion from manifest.json
versionsFile[targetVersion] = minAppVersion;
writeFileSync('versions.json', JSON.stringify(versionsFile, null, '\t'));
writeFileSync("versions.json", JSON.stringify(versionsFile, null, "\t"));

View file

@ -1,3 +1,3 @@
{
"1.4.0": "1.8.10"
"1.0.0": "1.8.10"
}