Compare commits

...

2 commits

Author SHA1 Message Date
4Source
2a09294e56 Apply version 1.4.0 2026-03-17 22:36:41 +00:00
4Source
e0c6469396
[FEAT] In Use/Known lists (#19)
* Refactor patching

* Preparation for InUse/Known lists

* Remove "debug" from allowed console methods in ESLint configuration

* Move Monkey keys  from constants to files

* Merge patch files into single file per class

* Patch install, enable, disable for plugins/themes

* Update lists based on state of vault

* Disable debug logs

* Add aria labels to flairs + remove duplicated flair code

* Update README.md

* Fix Security Audit

* Fix Command names
2026-03-17 23:36:19 +01:00
41 changed files with 1332 additions and 523 deletions

View file

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

View file

@ -3,7 +3,9 @@
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. 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 # Features
- **Global Favorites for Plugins & Themes** - Mark your go to plugins and themes once and use them in all vaults - **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 - **Ease of Use** - Favorite/unfavorite directly inside the Obsidian's
- Plugin browser - Plugin browser
- Community plugins tab - Community plugins tab
@ -21,67 +23,99 @@ Stop re-searching the same plugins or themes in every vault. With one central li
For plugins you can also favorite already installed plugins directly from the Community plugins tab. For plugins you can also favorite already installed plugins directly from the Community plugins tab.
# Screenshots # Screenshots
<img width="1104" height="1004" alt="image" src="https://github.com/user-attachments/assets/61b41e7b-4569-4509-95f3-d5e8a667d653" /> ![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" /> <img width="1100" height="1000" alt="image" src="https://github.com/user-attachments/assets/a0b35f98-b194-433f-bd9e-66eecb5dd111" />
<img width="1104" height="1004" alt="image" src="https://github.com/user-attachments/assets/960eac0d-fea2-48e1-8faf-e54a7504149e" />
# Commands # Commands
If you prefer managing your favorites list with commands here are the available commands If you prefer managing your favorites list with commands here are the available commands
- [Add plugin to favorite list](#add-plugin-to-favorite-list) - [Search and add plugin to favorite list](#search-and-add-plugin-to-favorite-list)
- [Add theme to favorite list](#add-theme-to-favorite-list) - [Search and add theme to favorite list](#search-and-add-theme-to-favorite-list)
- [Remove plugin from favorite list](#remove-plugin-from-favorite-list) - [Search and remove plugin from favorite list](#search-and-remove-plugin-from-favorite-list)
- [Remove theme from favorite list](#remove-theme-from-favorite-list) - [Search and remove theme from favorite list](#search-and-remove-theme-from-favorite-list)
- [Clear the plugin favorites lists](#clear-the-plugin-favorites-lists) - [Clear the plugin favorites list](#clear-the-plugin-favorites-list)
- [Clear the theme favorites lists](#clear-the-theme-favorites-lists) - [Clear the plugin in use list](#clear-the-plugin-in-use-list)
- [Manually load the favorites lists](#manually-load-the-favorites-lists) - [Clear the plugin known list](#clear-the-plugin-known-list)
- [Manually save the favorites lists](#manually-save-the-favorites-lists) - [Clear the theme favorites list](#clear-the-theme-favorites-list)
- [Export favorite lists to file](#export-favorite-lists-to-file) - [Clear the theme in use list](#clear-the-theme-in-use-list)
- [Import favorite lists from file](#import-favorite-lists-from-file) - [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)
## Add plugin to favorite list ## 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 select a community plugin to add it to your favorites list. The list is fetched from the community plugin registry.
## Add theme to favorite list ## 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 select a community theme to add it to your favorites list. The list is fetched from the community theme registry.
## Remove plugin from favorite list ## Search and remove plugin from favorite list
Search and select from your current favorite plugins to remove it from your favorites list. Search and select from your current favorite plugins to remove it from your favorites list.
## Remove theme from favorite list ## Search and remove theme from favorite list
Search and select from your current favorite themes to remove it from your favorites list. Search and select from your current favorite themes to remove it from your favorites list.
## Clear the plugin favorites lists ## Clear the plugin favorites list
Permanently removes ***all*** plugins from your favorites list after confirmation. Permanently removes ***all*** plugins from your favorites list after confirmation.
> [!WARNING] > [!WARNING]
> This action cannot be undone. > This action cannot be undone.
## Clear the theme favorites lists ## 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. Permanently removes ***all*** themes from your favorites list after confirmation.
> [!WARNING] > [!WARNING]
> This action cannot be undone. > This action cannot be undone.
## Manually load the favorites lists ## Clear the theme in use list
Reloads the saved favorite plugins and themes. Use this to restore your favorites if they were changed externally or need to be refreshed. 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] > [!NOTE]
> This is normally not necessary because the plugin handles this internally. > This is normally not necessary because the plugin handles this internally.
## Manually save the favorites lists ## Manually save the lists
Forces the plugin to immediately save your current favorite plugins and themes. Use this if you want to ensure your favorites are persistent. 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] > [!WARNING]
> This could overwrite changes from another running Obsidian instance if the changes from there are not loaded beforehand. > This could overwrite changes from another running Obsidian instance if the changes from there are not loaded beforehand.
> [!NOTE] > [!NOTE]
> This is normally not necessary because the plugin handles this internally. > This is normally not necessary because the plugin handles this internally.
## Export favorite lists to file ## Export lists to file
Creates a backup file containing your current favorite plugins and themes. The backup is saved as a timestamped JSON file inside the plugins backup folder within your vault configuration directory. 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 favorite lists from file ## Import lists from file
Restores favorite 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 favorites will replaces your current favorites with the contents of the selected backup. 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] > [!WARNING]
> This will overwrite current favorite lists and can't be undone. > This will overwrite current lists and can't be undone.
# Contribution # Contribution
Feel free to contribute. Feel free to contribute.

View file

@ -40,7 +40,11 @@ const context = await esbuild.context({
outfile: "main.js", outfile: "main.js",
define: { define: {
'process.env.FAVORITE_PLUGINS_KEY': JSON.stringify(prod ? 'favorite-plugins' : 'dev-favorite-plugins'), '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.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'),
} }
}); });

View file

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

13
package-lock.json generated
View file

@ -1,12 +1,12 @@
{ {
"name": "favorites", "name": "favorites",
"version": "1.3.2", "version": "1.4.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "favorites", "name": "favorites",
"version": "1.3.2", "version": "1.4.0",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@stylistic/eslint-plugin": "^3.1.0", "@stylistic/eslint-plugin": "^3.1.0",
@ -3503,10 +3503,11 @@
} }
}, },
"node_modules/flatted": { "node_modules/flatted": {
"version": "3.3.3", "version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true "dev": true,
"license": "ISC"
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.4", "version": "4.0.4",

View file

@ -33,5 +33,5 @@
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
}, },
"version": "1.3.2" "version": "1.4.0"
} }

View file

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

View file

@ -0,0 +1,21 @@
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

@ -0,0 +1,21 @@
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

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

View file

@ -0,0 +1,21 @@
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

@ -0,0 +1,21 @@
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,10 +0,0 @@
import { Command } from 'obsidian';
import CurrentPlugin from '../main';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'export-favorite-lists',
name: 'Export favorite lists to file',
callback: () => { plugin.exportFavorites(); },
};
}

View file

@ -0,0 +1,37 @@
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,10 +0,0 @@
import { Command } from 'obsidian';
import CurrentPlugin from '../main';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'import-favorite-lists',
name: 'Import favorite lists from file',
callback: () => { plugin.importFavorites(); },
};
}

View file

@ -0,0 +1,51 @@
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

@ -3,11 +3,11 @@ import CurrentPlugin from '../main';
export default function (plugin: CurrentPlugin): Command { export default function (plugin: CurrentPlugin): Command {
return { return {
id: 'save-favorites-lists', id: 'load-lists',
name: 'Manually save the favorites lists', name: 'Manually load the lists',
callback: () => { callback: () => {
plugin.saveFavorites(); plugin.loadLists();
new Notice('Saved favorite lists'); new Notice('Loaded lists');
}, },
}; };
} }

View file

@ -3,11 +3,11 @@ import CurrentPlugin from '../main';
export default function (plugin: CurrentPlugin): Command { export default function (plugin: CurrentPlugin): Command {
return { return {
id: 'load-favorites-lists', id: 'save-lists',
name: 'Manually load the favorites lists', name: 'Manually save the lists',
callback: () => { callback: () => {
plugin.loadFavorites(); plugin.saveLists();
new Notice('Loaded favorite lists'); new Notice('Saved lists');
}, },
}; };
} }

View file

@ -6,7 +6,7 @@ import { CommunityPlugin, fetchCommunityPluginList } from 'src/util/GitHub';
export default function (plugin: CurrentPlugin): Command { export default function (plugin: CurrentPlugin): Command {
return { return {
id: 'search-and-add-plugin-to-favorite-list', id: 'search-and-add-plugin-to-favorite-list',
name: 'Add plugin to favorite list', name: 'Search and add plugin to favorite list',
callback: async () => { callback: async () => {
let items = await fetchCommunityPluginList(); let items = await fetchCommunityPluginList();
if (!items) { if (!items) {
@ -25,7 +25,7 @@ export default function (plugin: CurrentPlugin): Command {
new CommunitySuggestModal<CommunityPlugin>(plugin.app, 'Select plugin which should be added to favorites list...', items, (result) => { new CommunitySuggestModal<CommunityPlugin>(plugin.app, 'Select plugin which should be added to favorites list...', items, (result) => {
plugin.favoritePlugins.push(result.id); plugin.favoritePlugins.push(result.id);
plugin.saveFavoritesPlugins(); plugin.saveFavoritePlugins();
new Notice(`Added ${result.name} to favorite list`); new Notice(`Added ${result.name} to favorite list`);
}).open(); }).open();
}, },

View file

@ -6,7 +6,7 @@ import { CommunityTheme, fetchCommunityThemeList } from 'src/util/GitHub';
export default function (plugin: CurrentPlugin): Command { export default function (plugin: CurrentPlugin): Command {
return { return {
id: 'search-and-add-theme-to-favorite-list', id: 'search-and-add-theme-to-favorite-list',
name: 'Add theme to favorite list', name: 'Search and add theme to favorite list',
callback: async () => { callback: async () => {
let items = await fetchCommunityThemeList(); let items = await fetchCommunityThemeList();
if (!items) { if (!items) {
@ -25,7 +25,7 @@ export default function (plugin: CurrentPlugin): Command {
new CommunitySuggestModal<CommunityTheme>(plugin.app, 'Select theme which should be added to favorites list...', items, (result) => { new CommunitySuggestModal<CommunityTheme>(plugin.app, 'Select theme which should be added to favorites list...', items, (result) => {
plugin.favoriteThemes.push(result.name); plugin.favoriteThemes.push(result.name);
plugin.saveFavoritesThemes(); plugin.saveFavoriteThemes();
new Notice(`Added ${result.name} to favorite list`); new Notice(`Added ${result.name} to favorite list`);
}).open(); }).open();
}, },

View file

@ -6,7 +6,7 @@ import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
export default function (plugin: CurrentPlugin): Command { export default function (plugin: CurrentPlugin): Command {
return { return {
id: 'search-and-remove-plugin-from-favorite-list', id: 'search-and-remove-plugin-from-favorite-list',
name: 'Remove plugin from favorite list', name: 'Search and remove plugin from favorite list',
callback: async () => { callback: async () => {
plugin.loadFavoritePlugins(); plugin.loadFavoritePlugins();
if (plugin.favoritePlugins.length <= 0) { if (plugin.favoritePlugins.length <= 0) {
@ -21,7 +21,7 @@ export default function (plugin: CurrentPlugin): Command {
new CommunitySuggestModal<CommunityPlugin>(plugin.app, 'Select plugin which should be removed from the favorites list...', items, (result) => { new CommunitySuggestModal<CommunityPlugin>(plugin.app, 'Select plugin which should be removed from the favorites list...', items, (result) => {
plugin.favoritePlugins.remove(result.id); plugin.favoritePlugins.remove(result.id);
plugin.saveFavoritesPlugins(); plugin.saveFavoritePlugins();
new Notice(`Removed ${result.name} from favorite list`); new Notice(`Removed ${result.name} from favorite list`);
}).open(); }).open();
}, },

View file

@ -6,7 +6,7 @@ import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
export default function (plugin: CurrentPlugin): Command { export default function (plugin: CurrentPlugin): Command {
return { return {
id: 'search-and-remove-theme-from-favorite-list', id: 'search-and-remove-theme-from-favorite-list',
name: 'Remove theme from favorite list', name: 'Search and remove theme from favorite list',
callback: async () => { callback: async () => {
plugin.loadFavoriteThemes(); plugin.loadFavoriteThemes();
if (plugin.favoriteThemes.length <= 0) { if (plugin.favoriteThemes.length <= 0) {
@ -21,7 +21,7 @@ export default function (plugin: CurrentPlugin): Command {
new CommunitySuggestModal<CommunityTheme>(plugin.app, 'Select theme which should be removed from the favorites list...', items, (result) => { new CommunitySuggestModal<CommunityTheme>(plugin.app, 'Select theme which should be removed from the favorites list...', items, (result) => {
plugin.favoriteThemes.remove(result.name); plugin.favoriteThemes.remove(result.name);
plugin.saveFavoritesThemes(); plugin.saveFavoriteThemes();
new Notice(`Removed ${result.name} from favorite list`); new Notice(`Removed ${result.name} from favorite list`);
}).open(); }).open();
}, },

View file

@ -1,10 +1,14 @@
import CurrentPlugin from '../main'; import CurrentPlugin from '../main';
import ClearPluginFavoritesLists from './ClearPluginFavoritesLists'; import ClearPluginFavoritesList from './ClearPluginFavoritesList';
import ClearThemeFavoritesLists from './ClearThemeFavoritesLists'; import ClearPluginInUseList from './ClearPluginInUseList';
import ExportFavoriteLists from './ExportFavoriteLists'; import ClearPluginKnownList from './ClearPluginKnownList';
import ImportFavoriteLists from './ImportFavoriteLists'; import ClearThemeFavoritesList from './ClearThemeFavoritesList';
import LoadFavoritesLists from './LoadFavoritesLists'; import ClearThemeInUseList from './ClearThemeInUseList';
import SaveFavoritesLists from './SaveFavoritesLists'; 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 SearchAndAddPluginToFavoriteList from './SearchAndAddPluginToFavoriteList';
import SearchAndAddThemeToFavoriteList from './SearchAndAddThemeToFavoriteList'; import SearchAndAddThemeToFavoriteList from './SearchAndAddThemeToFavoriteList';
import SearchAndRemovePluginFromFavoriteList from './SearchAndRemovePluginFromFavoriteList'; import SearchAndRemovePluginFromFavoriteList from './SearchAndRemovePluginFromFavoriteList';
@ -13,12 +17,16 @@ import SearchAndRemoveThemeFromFavoriteList from './SearchAndRemoveThemeFromFavo
export async function addAllCommands(plugin: CurrentPlugin) { export async function addAllCommands(plugin: CurrentPlugin) {
// TODO: Dynamically import all files in the commands folder during bundling time // TODO: Dynamically import all files in the commands folder during bundling time
plugin.addCommand(ClearPluginFavoritesLists(plugin)); plugin.addCommand(ClearPluginFavoritesList(plugin));
plugin.addCommand(ClearThemeFavoritesLists(plugin)); plugin.addCommand(ClearPluginInUseList(plugin));
plugin.addCommand(ExportFavoriteLists(plugin)); plugin.addCommand(ClearPluginKnownList(plugin));
plugin.addCommand(ImportFavoriteLists(plugin)); plugin.addCommand(ClearThemeFavoritesList(plugin));
plugin.addCommand(LoadFavoritesLists(plugin)); plugin.addCommand(ClearThemeInUseList(plugin));
plugin.addCommand(SaveFavoritesLists(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(SearchAndAddPluginToFavoriteList(plugin));
plugin.addCommand(SearchAndAddThemeToFavoriteList(plugin)); plugin.addCommand(SearchAndAddThemeToFavoriteList(plugin));
plugin.addCommand(SearchAndRemovePluginFromFavoriteList(plugin)); plugin.addCommand(SearchAndRemovePluginFromFavoriteList(plugin));

View file

@ -1,9 +1,2 @@
export const PLUGIN_ID = 'favorites'; export const PLUGIN_ID = 'favorites';
export const PLUGIN_BACKUP_BASE_PATH = `/plugins/${PLUGIN_ID}/backup`; export const PLUGIN_BACKUP_BASE_PATH = `/plugins/${PLUGIN_ID}/backup`;
export const MONKEY_KEY_MODAL_OPEN = `${PLUGIN_ID}-Modal-open`;
export const MONKEY_KEY_SETTINGS_MODAL_OPEN_TAB = `${PLUGIN_ID}-SettingsModal-openTab`;
export const MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS = `${PLUGIN_ID}-CommunityPluginModal-updateItems`;
export const MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS = `${PLUGIN_ID}-CommunityPluginModal-showItems`;
export const MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS = `${PLUGIN_ID}-ThemePluginModal-updateItems`;
export const MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS = `${PLUGIN_ID}-ThemePluginModal-showItems`;
export const MONKEY_KEY_COMMUNITY_PLUGIN_SETTINGS_TAB_RENDER_INSTALLED_PLUGIN = `${PLUGIN_ID}-CommunityPluginsSettingTab-renderInstalledPlugin`;

View file

@ -1,386 +1,197 @@
import { CommunityItem, CommunityPluginsSettingTab, Modal, normalizePath, Notice, Plugin, setIcon, SettingsModal, SettingTab, setTooltip } from 'obsidian'; import { Plugin } from 'obsidian';
import { dedupe, around } from 'monkey-around';
import { MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_MODAL_OPEN, MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, MONKEY_KEY_SETTINGS_MODAL_OPEN_TAB, MONKEY_KEY_COMMUNITY_PLUGIN_SETTINGS_TAB_RENDER_INSTALLED_PLUGIN, PLUGIN_BACKUP_BASE_PATH } from './constants';
import { DialogModal } from './modals/DialogModal';
import { StringSuggestModal } from './modals/StringSuggestModal';
import { addAllCommands } from './commands'; 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';
export default class FavoritesPlugin extends Plugin { export default class FavoritesPlugin extends Plugin {
pluginsKey: string; pluginFavoritesKey: string;
themesKey: string;
favoritePlugins: string[]; favoritePlugins: string[];
themeFavoritesKey: string;
favoriteThemes: string[]; favoriteThemes: string[];
uninstallModalOpen?: () => void; pluginInUseKey: string;
uninstallSettingsModalOpenTab?: () => void; inUsePlugins: Record<string, string[]>;
uninstallPluginBrowserModalUpdateItems?: () => void; themeInUseKey: string;
uninstallPluginBrowserModalShowItem?: () => void; inUseThemes: Record<string, string[]>;
uninstallThemeBrowserModalUpdateItems?: () => void; pluginKnownKey: string;
uninstallThemeBrowserModalShowItem?: () => void; knownPlugins: string[];
uninstallCommunityPluginsSettingTabRenderInstalledPlugin?: () => void; themeKnownKey: string;
knownThemes: string[];
uninstallCommunityPluginModalPatch?: () => void;
uninstallCommunityPluginsSettingTabPatch?: () => void;
uninstallCommunityThemeModalPatch?: () => void;
uninstallModalPatch?: () => void;
uninstallPluginManagerPatch?: () => void;
uninstallSettingsModalPatch?: () => void;
uninstallStyleManagerPatch?: () => void;
onload() { async onload() {
if (process.env.FAVORITE_PLUGINS_KEY) { this.loadStorageKeys();
this.pluginsKey = process.env.FAVORITE_PLUGINS_KEY; this.loadLists();
}
else {
throw Error('Missing environment variable \'FAVORITE_PLUGINS_KEY\'');
}
if (process.env.FAVORITE_THEMES_KEY) {
this.themesKey = process.env.FAVORITE_THEMES_KEY;
}
else {
throw Error('Missing environment variable \'FAVORITE_THEMES_KEY\'');
}
console.debug(`Plugins key: ${this.pluginsKey} Themes key: ${this.themesKey}`);
this.loadFavorites();
// Register // Patch SettingsModal
this.registerDomEvent(window, 'storage', (event) => { if (!this.uninstallSettingsModalPatch) {
if (event.key === this.pluginsKey) { // console.debug('Patch SettingsModal');
console.debug('Plugins key content changed', event); this.uninstallSettingsModalPatch = patchSettingsModal(this);
this.onFavoritePluginsChanged(event.newValue);
}
if (event.key === this.themesKey) {
console.debug('Themes key content changed', event);
this.onFavoriteThemesChanged(event.newValue);
}
});
// eslint-disable-next-line @typescript-eslint/no-this-alias -- Is required because the this context wil change inside the 'monkey-around' functions but the plugin is required to be accessible
const plugin = this;
// Patch the opening of SettingsModal
if (!this.uninstallSettingsModalOpenTab) {
console.debug('Patch SettingsModal.openTab');
this.uninstallSettingsModalOpenTab = around(this.app.setting as SettingsModal, {
openTab(oldMethod) {
return dedupe(MONKEY_KEY_SETTINGS_MODAL_OPEN_TAB, oldMethod, function (...args) {
console.debug('Call SettingsModal.openTab');
const tab = args[0] as SettingTab;
if (tab.id === 'community-plugins') {
if (!plugin.uninstallCommunityPluginsSettingTabRenderInstalledPlugin) {
console.debug('Patch CommunityPluginsSettingTab.renderInstalledPlugin');
plugin.uninstallCommunityPluginsSettingTabRenderInstalledPlugin = around((tab as CommunityPluginsSettingTab), {
renderInstalledPlugin(oldMethod) {
return dedupe(MONKEY_KEY_COMMUNITY_PLUGIN_SETTINGS_TAB_RENDER_INSTALLED_PLUGIN, oldMethod, function (...args) {
console.debug('Call CommunityPluginsSettingTab.renderInstalledPlugin');
oldMethod && oldMethod.apply(this, args);
// Load the favorite plugins
plugin.loadFavoritePlugins();
const selectedPluginID = args[0].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.saveFavoritesPlugins();
// Redraw
(tab as CommunityPluginsSettingTab).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]);
});
},
});
}
else {
console.debug('CommunityPluginsSettingTab.renderInstalledPlugin already patched!');
}
}
if (plugin.uninstallSettingsModalOpenTab && plugin.uninstallCommunityPluginsSettingTabRenderInstalledPlugin) {
console.debug('Uninstall SettingsModal.openTab');
plugin.uninstallSettingsModalOpenTab();
plugin.uninstallSettingsModalOpenTab = undefined;
}
return oldMethod && oldMethod.apply(this, args);
});
},
});
} }
if (!this.uninstallModalOpen) { // Patch Modal
console.debug('Patch Modal.open'); if (!this.uninstallModalPatch) {
this.uninstallModalOpen = around(Modal.prototype, { // console.debug('Patch Modal');
open(oldMethod) { this.uninstallModalPatch = patchModal(this);
return dedupe(MONKEY_KEY_MODAL_OPEN, oldMethod, function (...args) {
console.debug('Call Modal.open');
const result = oldMethod && oldMethod.apply(this, args);
// Check the opened Modal is the Community plugin browse modal
if (this.containerEl?.querySelector('.mod-community-plugin')) {
// Patch updateItems method
if (!plugin.uninstallPluginBrowserModalUpdateItems) {
console.debug('Patch PluginBrowserModal.updateItems');
plugin.uninstallPluginBrowserModalUpdateItems = around(this.constructor.prototype, {
updateItems(oldMethod) {
return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () {
console.debug('Call PluginBrowserModal.updateItems');
// Load the favorite plugins
plugin.loadFavoritePlugins();
const result = oldMethod && oldMethod.apply(this);
// Add to the favorite plugins a tag to visualize it for the user
plugin.favoritePlugins.forEach(id => {
if (this.items && this.items[id]?.nameEl) {
this.items[id].nameEl.createSpan({
cls: 'flair',
text: 'favorite',
});
}
});
return result;
});
},
});
}
else {
console.debug('PluginBrowserModal.updateItems already patched!');
}
// Patch showItem method
if (!plugin.uninstallPluginBrowserModalShowItem) {
console.debug('Patch PluginBrowserModal.showItem');
plugin.uninstallPluginBrowserModalShowItem = around(this.constructor.prototype, {
showItem(oldMethod) {
return dedupe(MONKEY_KEY_PLUGIN_BROWSER_MODAL_SHOW_ITEMS, oldMethod, function (...args: CommunityItem[]) {
console.debug('Call PluginBrowserModal.showItem');
return oldMethod && oldMethod.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];
// Load the favorite plugins
plugin.loadFavoritePlugins();
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.saveFavoritesPlugins();
// Redraw
infoEl.detach();
this.showItem(this.items[this.selectedItemId]);
this.update();
}));
return r;
});
});
},
});
}
else {
console.debug('PluginBrowserModal.showItem already patched!');
}
}
// Check the opened Modal is the Community themes browse modal
if (this.containerEl?.querySelector('.mod-community-theme')) {
// Patch updateItems method
if (!plugin.uninstallThemeBrowserModalUpdateItems) {
console.debug('Patch ThemeBrowserModal.updateItems');
plugin.uninstallThemeBrowserModalUpdateItems = around(this.constructor.prototype, {
updateItems(oldMethod) {
return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_UPDATE_ITEMS, oldMethod, function () {
console.debug('Call ThemeBrowserModal.updateItems');
// Load the favorite themes
plugin.loadFavoriteThemes();
const result = oldMethod && oldMethod.apply(this);
// Ignore the default theme
if (this.selectedItemId === '') {
return result;
}
// Add to the favorite themes a tag to visualize it for the user
plugin.favoriteThemes.forEach(id => {
if (this.items && this.items[id]?.nameEl) {
this.items[id].nameEl.createSpan({
cls: 'flair',
text: 'favorite',
});
}
});
return result;
});
},
});
}
else {
console.debug('ThemeBrowserModal.updateItems already patched!');
}
// Patch showItems method
if (!plugin.uninstallThemeBrowserModalShowItem) {
console.debug('Patch ThemeBrowserModal.showItem');
plugin.uninstallThemeBrowserModalShowItem = around(this.constructor.prototype, {
showItem(oldMethod) {
return dedupe(MONKEY_KEY_THEME_BROWSER_MODAL_SHOW_ITEMS, oldMethod, function (...args: CommunityItem[]) {
console.debug('Call ThemeBrowserModal.showItem');
return oldMethod && oldMethod.apply(this, args)
.then(async (r: void) => {
// Ignore the default theme
if (this.selectedItemId === '') {
return r;
}
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 favorite themes
plugin.loadFavoriteThemes();
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.saveFavoritesThemes();
// Redraw
infoEl.detach();
this.showItem(this.items[this.selectedItemId]);
this.update();
}));
return r;
});
});
},
});
}
else {
console.debug('ThemeBrowserModal.showItem already patched!');
}
}
if (plugin.uninstallModalOpen && plugin.uninstallPluginBrowserModalUpdateItems && plugin.uninstallPluginBrowserModalShowItem && plugin.uninstallThemeBrowserModalUpdateItems && plugin.uninstallThemeBrowserModalShowItem) {
console.debug('Uninstall Modal.open');
plugin.uninstallModalOpen();
plugin.uninstallModalOpen = undefined;
}
return result;
});
},
});
} }
else {
console.warn('Modal.open already patched!'); // 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 // Add all commands registered
addAllCommands(this); addAllCommands(this);
} }
onunload() { async onunload() {
try { try {
// Restore the original functions // Restore the original functions
if (this.uninstallModalOpen) { if (this.uninstallCommunityPluginModalPatch) {
console.debug('Uninstall Modal.open'); // console.debug('Uninstall CommunityPluginModal patch');
this.uninstallModalOpen(); this.uninstallCommunityPluginModalPatch();
this.uninstallModalOpen = undefined; this.uninstallCommunityPluginModalPatch = undefined;
} }
if (this.uninstallSettingsModalOpenTab) {
console.debug('Uninstall SettingsModal.openTab'); if (this.uninstallCommunityPluginsSettingTabPatch) {
this.uninstallSettingsModalOpenTab(); // console.debug('Uninstall CommunityPluginsSettingTab patch');
this.uninstallSettingsModalOpenTab = undefined; this.uninstallCommunityPluginsSettingTabPatch();
this.uninstallCommunityPluginsSettingTabPatch = undefined;
} }
if (this.uninstallPluginBrowserModalUpdateItems) {
console.debug('Uninstall PluginBrowserModal.updateItem'); if (this.uninstallCommunityThemeModalPatch) {
this.uninstallPluginBrowserModalUpdateItems(); // console.debug('Uninstall CommunityThemeModal patch');
this.uninstallPluginBrowserModalUpdateItems = undefined; this.uninstallCommunityThemeModalPatch();
this.uninstallCommunityThemeModalPatch = undefined;
} }
if (this.uninstallPluginBrowserModalShowItem) {
console.debug('Uninstall PluginBrowserModal.showItem'); if (this.uninstallModalPatch) {
this.uninstallPluginBrowserModalShowItem(); // console.debug('Uninstall Modal patch');
this.uninstallPluginBrowserModalShowItem = undefined; this.uninstallModalPatch();
this.uninstallModalPatch = undefined;
} }
if (this.uninstallThemeBrowserModalUpdateItems) {
console.debug('Uninstall ThemeBrowserModal.updateItem'); if (this.uninstallPluginManagerPatch) {
this.uninstallThemeBrowserModalUpdateItems(); // console.debug('Uninstall PluginManager patch');
this.uninstallThemeBrowserModalUpdateItems = undefined; this.uninstallPluginManagerPatch();
this.uninstallPluginManagerPatch = undefined;
} }
if (this.uninstallThemeBrowserModalShowItem) {
console.debug('Uninstall ThemeBrowserModal.showItem'); if (this.uninstallSettingsModalPatch) {
this.uninstallThemeBrowserModalShowItem(); // console.debug('Uninstall SettingsModal patch');
this.uninstallThemeBrowserModalShowItem = undefined; this.uninstallSettingsModalPatch();
this.uninstallSettingsModalPatch = undefined;
} }
if (this.uninstallCommunityPluginsSettingTabRenderInstalledPlugin) {
console.debug('Uninstall CommunityPluginsSettingTab.renderInstalledPlugin'); if (this.uninstallStyleManagerPatch) {
this.uninstallCommunityPluginsSettingTabRenderInstalledPlugin(); // console.debug('Uninstall StyleManager patch');
this.uninstallCommunityPluginsSettingTabRenderInstalledPlugin = undefined; this.uninstallStyleManagerPatch();
this.uninstallStyleManagerPatch = undefined;
} }
} }
catch (e) { catch (e) {
console.error('Failed uninstalling patch functions!');
console.error(e); 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) { onFavoritePluginsChanged(newValue: string | null) {
console.debug(`onFavoritePluginsChanged: ${newValue}`); // console.debug(`onFavoritePluginsChanged: ${newValue}`);
if (newValue) { if (newValue) {
this.favoritePlugins = JSON.parse(newValue); this.favoritePlugins = JSON.parse(newValue);
} }
@ -390,7 +201,7 @@ export default class FavoritesPlugin extends Plugin {
} }
onFavoriteThemesChanged(newValue: string | null) { onFavoriteThemesChanged(newValue: string | null) {
console.debug(`onFavoriteThemesChanged: ${newValue}`); // console.debug(`onFavoriteThemesChanged: ${newValue}`);
if (newValue) { if (newValue) {
this.favoriteThemes = JSON.parse(newValue); this.favoriteThemes = JSON.parse(newValue);
} }
@ -399,94 +210,220 @@ export default class FavoritesPlugin extends Plugin {
} }
} }
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() { loadFavoritePlugins() {
// Load the favorite plugins // Load the favorite plugins
this.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]'); this.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginFavoritesKey) || '[]');
} }
loadFavoriteThemes() { loadFavoriteThemes() {
// Load the favorite plugins // Load the favorite plugins
this.favoriteThemes = JSON.parse(localStorage.getItem(this.themesKey) || '[]'); this.favoriteThemes = JSON.parse(localStorage.getItem(this.themeFavoritesKey) || '[]');
} }
loadFavorites() { 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.loadFavoritePlugins();
this.loadFavoriteThemes(); this.loadFavoriteThemes();
this.loadInUsePlugins();
this.loadInUseThemes();
this.loadKnownPlugins();
this.loadKnownThemes();
} }
saveFavoritesPlugins() { saveFavoritePlugins() {
if (this.favoritePlugins.length > 0) { if (this.favoritePlugins.length > 0) {
localStorage.setItem(this.pluginsKey, JSON.stringify(this.favoritePlugins)); localStorage.setItem(this.pluginFavoritesKey, JSON.stringify(this.favoritePlugins));
} }
else { else {
localStorage.removeItem(this.pluginsKey); localStorage.removeItem(this.pluginFavoritesKey);
} }
} }
saveFavoritesThemes() { saveFavoriteThemes() {
if (this.favoriteThemes.length > 0) { if (this.favoriteThemes.length > 0) {
localStorage.setItem(this.themesKey, JSON.stringify(this.favoriteThemes)); localStorage.setItem(this.themeFavoritesKey, JSON.stringify(this.favoriteThemes));
} }
else { else {
localStorage.removeItem(this.themesKey); localStorage.removeItem(this.themeFavoritesKey);
} }
} }
saveFavorites() { saveInUsePlugins() {
this.saveFavoritesPlugins(); if (Object.keys(this.inUsePlugins).length > 0) {
this.saveFavoritesThemes(); localStorage.setItem(this.pluginInUseKey, JSON.stringify(this.inUsePlugins));
}
else {
localStorage.removeItem(this.pluginInUseKey);
}
} }
async exportFavorites() { saveInUseThemes() {
if (this.favoritePlugins.length <= 0 && this.favoriteThemes.length <= 0) { if (Object.keys(this.inUseThemes).length > 0) {
new Notice('No favorite lists to backup'); localStorage.setItem(this.themeInUseKey, JSON.stringify(this.inUseThemes));
return; }
else {
localStorage.removeItem(this.themeInUseKey);
} }
// Ensure backup path does exist
const backupPath = normalizePath(`${this.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`);
await this.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 this.app.vault.adapter.write(backupFilePath, JSON.stringify({ plugins: this.favoritePlugins, themes: this.favoriteThemes }));
new Notice(`Favorites list backup successful written to: ${backupFilePath}`);
} }
async importFavorites() { saveKnownPlugins() {
const backupPath = normalizePath(`${this.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`); if (this.knownPlugins.length > 0) {
if (!(await this.app.vault.adapter.exists(backupPath))) { localStorage.setItem(this.pluginKnownKey, JSON.stringify(this.knownPlugins));
// Backup path does not exist }
new Notice(`Backup path does not exist: ${backupPath}`); else {
return; 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);
}
} }
const backupFiles = (await this.app.vault.adapter.list(backupPath)).files; // Check the enabled plugins and update in use plugins list
if (backupFiles.length <= 0) { for (const id of this.app.plugins.enabledPlugins) {
// Backup path is empty // Add the plugin to the in use list with this vault as user
new Notice('Backup path is empty'); if (!this.inUsePlugins[id] || !Array.isArray(this.inUsePlugins[id])) {
return; this.inUsePlugins[id] = [];
}
if (!this.inUsePlugins[id].contains(currentVault)) {
this.inUsePlugins[id].push(currentVault);
}
} }
new StringSuggestModal(this.app, 'Select backup file to load from', backupFiles, async (result) => { // Check the in use plugins list does not contain old entries of this vault
const content = await this.app.vault.adapter.read(result); for (const id of Object.keys(this.inUsePlugins)) {
const json = JSON.parse(content); if (Array.isArray(this.inUsePlugins[id]) && this.inUsePlugins[id].contains(currentVault)) {
const loadedPlugins = json.plugins || []; if (!this.app.plugins.enabledPlugins.has(id)) {
const loadedThemes = json.themes || []; this.inUsePlugins[id].remove(currentVault);
new DialogModal(this.app, 'Are you sure you want to overwrite current favorite lists?', `The list of favorite plugins will change from ${this.favoritePlugins.length} items to ${loadedPlugins.length} items, and the list of favorite themes will change from ${this.favoriteThemes.length} items to ${loadedThemes.length} items.`, () => {
this.favoritePlugins = loadedPlugins;
this.favoriteThemes = loadedThemes;
this.saveFavorites();
new Notice('Loaded favorites backup successfully'); // Cleanup in use if last vault stops using it
}, () => { if (this.inUsePlugins[id].length <= 0) {
new Notice('Canceled loading favorites backup by user'); delete this.inUsePlugins[id];
}, 'Overwrite', true, 'Cancel', false).open(); }
}).open(); }
}
}
// 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

@ -0,0 +1,100 @@
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

@ -0,0 +1,50 @@
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

@ -0,0 +1,114 @@
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

@ -0,0 +1,43 @@
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

@ -0,0 +1,74 @@
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

@ -0,0 +1,30 @@
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

@ -0,0 +1,69 @@
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;
});
},
});

170
src/types.d.ts vendored
View file

@ -2,13 +2,10 @@ import { } from 'obsidian';
declare module 'obsidian' { declare module 'obsidian' {
interface App { interface App {
appId: string;
setting: SettingsModal; setting: SettingsModal;
plugins: { plugins: PluginManager;
manifests: Record<string, PluginManifest>; customCss: StyleManager;
};
customCss: {
themes: Record<string, ThemeManifest>;
}
} }
interface ThemeManifest { interface ThemeManifest {
@ -24,26 +21,47 @@ declare module 'obsidian' {
id: string; id: string;
} }
interface CommunityModal { interface CommunityModal extends Modal {
sortOrder: string;
items: unknown;
itemsVisible: unknown;
selectedItemId: string | null;
dimBackground: unknown;
onCloseCallback: () => void;
update(): void; update(): void;
returnToGridView(): void;
selectItem(id: string | null): unknown;
showSortMenu(event: unknown): void;
sortItems(items: unknown): unknown;
scrollIntoView(): unknown;
addCustomControls(): unknown;
updateItems(): CommunityItem[]; updateItems(): CommunityItem[];
showItem(item: CommunityItem): void; showItem(item: CommunityItem): Promise<void>;
} }
interface Modal { interface CommunityThemeModal extends CommunityModal {
} }
interface SettingsModal { interface CommunityPluginModal extends CommunityModal {
openTab(settingTab: SettingTab): unknown; setAutoOpen(id: string): CommunityPluginModal;
loadItems(): unknown;
}
interface SettingsModal extends Modal {
openTab(settingTab: SettingTab): void;
} }
interface SettingTab { interface SettingTab {
/**
* Used to identify the tab
*
* @private reverse engineered
*/
id: string; id: string;
setting: unknown; setting: unknown;
containerEl: HTMLElement;
navEl: HTMLElement; navEl: HTMLElement;
app: App;
} }
interface CommunityPluginsSettingTab extends SettingTab { interface CommunityPluginsSettingTab extends SettingTab {
@ -52,9 +70,131 @@ declare module 'obsidian' {
listEl: HTMLElement listEl: HTMLElement
}; };
display(): void;
render(e: boolean): void; render(e: boolean): void;
renderInstalledPlugin(plugin: CommunityItem, t: unknown, n: unknown, i: unknown, r: unknown): void; renderInstalledPlugin(plugin: CommunityItem, containerEl: HTMLElement, n: unknown, i: unknown, r: unknown): void;
updateSearch(e: 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[];
/**
* Holds the manifest of the installed themes in this vault
*
* @private reverse engineered
*/
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>;
}
} }

View file

@ -0,0 +1,11 @@
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

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

View file

@ -0,0 +1,13 @@
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

@ -0,0 +1,7 @@
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

@ -0,0 +1,11 @@
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

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

5
styles.css Normal file
View file

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

View file

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