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": [
"warn",
"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.
# 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
- Plugin browser
- 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.
# 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="1104" height="1004" alt="image" src="https://github.com/user-attachments/assets/960eac0d-fea2-48e1-8faf-e54a7504149e" />
# 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)
- [Add theme to favorite list](#add-theme-to-favorite-list)
- [Remove plugin from favorite list](#remove-plugin-from-favorite-list)
- [Remove theme from favorite list](#remove-theme-from-favorite-list)
- [Clear the plugin favorites lists](#clear-the-plugin-favorites-lists)
- [Clear the theme favorites lists](#clear-the-theme-favorites-lists)
- [Manually load the favorites lists](#manually-load-the-favorites-lists)
- [Manually save the favorites lists](#manually-save-the-favorites-lists)
- [Export favorite lists to file](#export-favorite-lists-to-file)
- [Import favorite lists from file](#import-favorite-lists-from-file)
- [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)
## 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.
## 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.
## 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.
## 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.
## Clear the plugin favorites lists
## Clear the plugin favorites list
Permanently removes ***all*** plugins from your favorites list after confirmation.
> [!WARNING]
> 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.
> [!WARNING]
> This action cannot be undone.
## Manually load the favorites lists
Reloads the saved favorite plugins and themes. Use this to restore your favorites if they were changed externally or need to be refreshed.
## 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 favorites lists
Forces the plugin to immediately save your current favorite plugins and themes. Use this if you want to ensure your favorites are persistent.
## 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 favorite 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.
## 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 favorite 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.
## 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 favorite lists and can't be undone.
> This will overwrite current lists and can't be undone.
# Contribution
Feel free to contribute.

View file

@ -40,7 +40,11 @@ const context = await esbuild.context({
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.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",
"isDesktopOnly": false,
"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",
"version": "1.3.2",
"version": "1.4.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "favorites",
"version": "1.3.2",
"version": "1.4.0",
"license": "MIT",
"devDependencies": {
"@stylistic/eslint-plugin": "^3.1.0",
@ -3503,10 +3503,11 @@
}
},
"node_modules/flatted": {
"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
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
"node_modules/form-data": {
"version": "4.0.4",

View file

@ -33,5 +33,5 @@
"tslib": "2.4.0",
"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 {
return {
id: 'clear-plugin-favorites-lists',
name: 'Clear the plugin favorites lists',
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.saveFavoritesPlugins();
plugin.saveFavoritePlugins();
new Notice(`Cleared ${numberClear} 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 {
return {
id: 'clear-theme-favorites-lists',
name: 'Clear the theme favorites lists',
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.saveFavoritesThemes();
plugin.saveFavoriteThemes();
new Notice(`Cleared ${numberClear} 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 {
return {
id: 'save-favorites-lists',
name: 'Manually save the favorites lists',
id: 'load-lists',
name: 'Manually load the lists',
callback: () => {
plugin.saveFavorites();
new Notice('Saved favorite lists');
plugin.loadLists();
new Notice('Loaded lists');
},
};
}

View file

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

View file

@ -6,7 +6,7 @@ import { CommunityPlugin, fetchCommunityPluginList } from 'src/util/GitHub';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-add-plugin-to-favorite-list',
name: 'Add plugin to favorite list',
name: 'Search and add plugin to favorite list',
callback: async () => {
let items = await fetchCommunityPluginList();
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) => {
plugin.favoritePlugins.push(result.id);
plugin.saveFavoritesPlugins();
plugin.saveFavoritePlugins();
new Notice(`Added ${result.name} to favorite list`);
}).open();
},

View file

@ -6,7 +6,7 @@ import { CommunityTheme, fetchCommunityThemeList } from 'src/util/GitHub';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-add-theme-to-favorite-list',
name: 'Add theme to favorite list',
name: 'Search and add theme to favorite list',
callback: async () => {
let items = await fetchCommunityThemeList();
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) => {
plugin.favoriteThemes.push(result.name);
plugin.saveFavoritesThemes();
plugin.saveFavoriteThemes();
new Notice(`Added ${result.name} to favorite list`);
}).open();
},

View file

@ -6,7 +6,7 @@ import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-remove-plugin-from-favorite-list',
name: 'Remove plugin from favorite list',
name: 'Search and remove plugin from favorite list',
callback: async () => {
plugin.loadFavoritePlugins();
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) => {
plugin.favoritePlugins.remove(result.id);
plugin.saveFavoritesPlugins();
plugin.saveFavoritePlugins();
new Notice(`Removed ${result.name} from favorite list`);
}).open();
},

View file

@ -6,7 +6,7 @@ import { CommunitySuggestModal } from 'src/modals/CommunitySuggestModal';
export default function (plugin: CurrentPlugin): Command {
return {
id: 'search-and-remove-theme-from-favorite-list',
name: 'Remove theme from favorite list',
name: 'Search and remove theme from favorite list',
callback: async () => {
plugin.loadFavoriteThemes();
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) => {
plugin.favoriteThemes.remove(result.name);
plugin.saveFavoritesThemes();
plugin.saveFavoriteThemes();
new Notice(`Removed ${result.name} from favorite list`);
}).open();
},

View file

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

View file

@ -1,9 +1,2 @@
export const PLUGIN_ID = 'favorites';
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 { 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 { 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';
export default class FavoritesPlugin extends Plugin {
pluginsKey: string;
themesKey: string;
pluginFavoritesKey: string;
favoritePlugins: string[];
themeFavoritesKey: string;
favoriteThemes: string[];
uninstallModalOpen?: () => void;
uninstallSettingsModalOpenTab?: () => void;
uninstallPluginBrowserModalUpdateItems?: () => void;
uninstallPluginBrowserModalShowItem?: () => void;
uninstallThemeBrowserModalUpdateItems?: () => void;
uninstallThemeBrowserModalShowItem?: () => void;
uninstallCommunityPluginsSettingTabRenderInstalledPlugin?: () => void;
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;
onload() {
if (process.env.FAVORITE_PLUGINS_KEY) {
this.pluginsKey = process.env.FAVORITE_PLUGINS_KEY;
}
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();
async onload() {
this.loadStorageKeys();
this.loadLists();
// Register
this.registerDomEvent(window, 'storage', (event) => {
if (event.key === this.pluginsKey) {
console.debug('Plugins key content changed', event);
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);
});
},
});
// Patch SettingsModal
if (!this.uninstallSettingsModalPatch) {
// console.debug('Patch SettingsModal');
this.uninstallSettingsModalPatch = patchSettingsModal(this);
}
if (!this.uninstallModalOpen) {
console.debug('Patch Modal.open');
this.uninstallModalOpen = around(Modal.prototype, {
open(oldMethod) {
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;
});
},
});
// Patch Modal
if (!this.uninstallModalPatch) {
// console.debug('Patch Modal');
this.uninstallModalPatch = patchModal(this);
}
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
addAllCommands(this);
}
onunload() {
async onunload() {
try {
// Restore the original functions
if (this.uninstallModalOpen) {
console.debug('Uninstall Modal.open');
this.uninstallModalOpen();
this.uninstallModalOpen = undefined;
if (this.uninstallCommunityPluginModalPatch) {
// console.debug('Uninstall CommunityPluginModal patch');
this.uninstallCommunityPluginModalPatch();
this.uninstallCommunityPluginModalPatch = undefined;
}
if (this.uninstallSettingsModalOpenTab) {
console.debug('Uninstall SettingsModal.openTab');
this.uninstallSettingsModalOpenTab();
this.uninstallSettingsModalOpenTab = undefined;
if (this.uninstallCommunityPluginsSettingTabPatch) {
// console.debug('Uninstall CommunityPluginsSettingTab patch');
this.uninstallCommunityPluginsSettingTabPatch();
this.uninstallCommunityPluginsSettingTabPatch = undefined;
}
if (this.uninstallPluginBrowserModalUpdateItems) {
console.debug('Uninstall PluginBrowserModal.updateItem');
this.uninstallPluginBrowserModalUpdateItems();
this.uninstallPluginBrowserModalUpdateItems = undefined;
if (this.uninstallCommunityThemeModalPatch) {
// console.debug('Uninstall CommunityThemeModal patch');
this.uninstallCommunityThemeModalPatch();
this.uninstallCommunityThemeModalPatch = undefined;
}
if (this.uninstallPluginBrowserModalShowItem) {
console.debug('Uninstall PluginBrowserModal.showItem');
this.uninstallPluginBrowserModalShowItem();
this.uninstallPluginBrowserModalShowItem = undefined;
if (this.uninstallModalPatch) {
// console.debug('Uninstall Modal patch');
this.uninstallModalPatch();
this.uninstallModalPatch = undefined;
}
if (this.uninstallThemeBrowserModalUpdateItems) {
console.debug('Uninstall ThemeBrowserModal.updateItem');
this.uninstallThemeBrowserModalUpdateItems();
this.uninstallThemeBrowserModalUpdateItems = undefined;
if (this.uninstallPluginManagerPatch) {
// console.debug('Uninstall PluginManager patch');
this.uninstallPluginManagerPatch();
this.uninstallPluginManagerPatch = undefined;
}
if (this.uninstallThemeBrowserModalShowItem) {
console.debug('Uninstall ThemeBrowserModal.showItem');
this.uninstallThemeBrowserModalShowItem();
this.uninstallThemeBrowserModalShowItem = undefined;
if (this.uninstallSettingsModalPatch) {
// console.debug('Uninstall SettingsModal patch');
this.uninstallSettingsModalPatch();
this.uninstallSettingsModalPatch = undefined;
}
if (this.uninstallCommunityPluginsSettingTabRenderInstalledPlugin) {
console.debug('Uninstall CommunityPluginsSettingTab.renderInstalledPlugin');
this.uninstallCommunityPluginsSettingTabRenderInstalledPlugin();
this.uninstallCommunityPluginsSettingTabRenderInstalledPlugin = 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}`);
// console.debug(`onFavoritePluginsChanged: ${newValue}`);
if (newValue) {
this.favoritePlugins = JSON.parse(newValue);
}
@ -390,7 +201,7 @@ export default class FavoritesPlugin extends Plugin {
}
onFavoriteThemesChanged(newValue: string | null) {
console.debug(`onFavoriteThemesChanged: ${newValue}`);
// console.debug(`onFavoriteThemesChanged: ${newValue}`);
if (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() {
// Load the favorite plugins
this.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginsKey) || '[]');
this.favoritePlugins = JSON.parse(localStorage.getItem(this.pluginFavoritesKey) || '[]');
}
loadFavoriteThemes() {
// 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.loadFavoriteThemes();
this.loadInUsePlugins();
this.loadInUseThemes();
this.loadKnownPlugins();
this.loadKnownThemes();
}
saveFavoritesPlugins() {
saveFavoritePlugins() {
if (this.favoritePlugins.length > 0) {
localStorage.setItem(this.pluginsKey, JSON.stringify(this.favoritePlugins));
localStorage.setItem(this.pluginFavoritesKey, JSON.stringify(this.favoritePlugins));
}
else {
localStorage.removeItem(this.pluginsKey);
localStorage.removeItem(this.pluginFavoritesKey);
}
}
saveFavoritesThemes() {
saveFavoriteThemes() {
if (this.favoriteThemes.length > 0) {
localStorage.setItem(this.themesKey, JSON.stringify(this.favoriteThemes));
localStorage.setItem(this.themeFavoritesKey, JSON.stringify(this.favoriteThemes));
}
else {
localStorage.removeItem(this.themesKey);
localStorage.removeItem(this.themeFavoritesKey);
}
}
saveFavorites() {
this.saveFavoritesPlugins();
this.saveFavoritesThemes();
saveInUsePlugins() {
if (Object.keys(this.inUsePlugins).length > 0) {
localStorage.setItem(this.pluginInUseKey, JSON.stringify(this.inUsePlugins));
}
else {
localStorage.removeItem(this.pluginInUseKey);
}
}
async exportFavorites() {
if (this.favoritePlugins.length <= 0 && this.favoriteThemes.length <= 0) {
new Notice('No favorite lists to backup');
return;
saveInUseThemes() {
if (Object.keys(this.inUseThemes).length > 0) {
localStorage.setItem(this.themeInUseKey, JSON.stringify(this.inUseThemes));
}
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() {
const backupPath = normalizePath(`${this.app.vault.configDir}${PLUGIN_BACKUP_BASE_PATH}`);
if (!(await this.app.vault.adapter.exists(backupPath))) {
// Backup path does not exist
new Notice(`Backup path does not exist: ${backupPath}`);
return;
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);
}
}
const backupFiles = (await this.app.vault.adapter.list(backupPath)).files;
if (backupFiles.length <= 0) {
// Backup path is empty
new Notice('Backup path is empty');
return;
// 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.inUsePlugins[id].contains(currentVault)) {
this.inUsePlugins[id].push(currentVault);
}
}
new StringSuggestModal(this.app, 'Select backup file to load from', backupFiles, async (result) => {
const content = await this.app.vault.adapter.read(result);
const json = JSON.parse(content);
const loadedPlugins = json.plugins || [];
const loadedThemes = json.themes || [];
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();
// 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);
new Notice('Loaded favorites backup successfully');
}, () => {
new Notice('Canceled loading favorites backup by user');
}, 'Overwrite', true, 'Cancel', false).open();
}).open();
// 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

@ -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' {
interface App {
appId: string;
setting: SettingsModal;
plugins: {
manifests: Record<string, PluginManifest>;
};
customCss: {
themes: Record<string, ThemeManifest>;
}
plugins: PluginManager;
customCss: StyleManager;
}
interface ThemeManifest {
@ -24,26 +21,47 @@ declare module 'obsidian' {
id: string;
}
interface CommunityModal {
interface CommunityModal extends Modal {
sortOrder: string;
items: unknown;
itemsVisible: unknown;
selectedItemId: string | null;
dimBackground: unknown;
onCloseCallback: () => void;
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): void;
showItem(item: CommunityItem): Promise<void>;
}
interface Modal {
interface CommunityThemeModal extends CommunityModal {
}
interface SettingsModal {
openTab(settingTab: SettingTab): unknown;
interface CommunityPluginModal extends CommunityModal {
setAutoOpen(id: string): CommunityPluginModal;
loadItems(): unknown;
}
interface SettingsModal extends Modal {
openTab(settingTab: SettingTab): void;
}
interface SettingTab {
/**
* Used to identify the tab
*
* @private reverse engineered
*/
id: string;
setting: unknown;
containerEl: HTMLElement;
navEl: HTMLElement;
app: App;
}
interface CommunityPluginsSettingTab extends SettingTab {
@ -52,9 +70,131 @@ declare module 'obsidian' {
listEl: HTMLElement
};
display(): 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
}
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"
}