Compare commits

..

No commits in common. "main" and "1.7.8" have entirely different histories.
main ... 1.7.8

56 changed files with 165 additions and 560 deletions

3
.eslintignore Normal file
View file

@ -0,0 +1,3 @@
node_modules/
main.js

View file

@ -1,50 +0,0 @@
name: Release
on:
push:
tags:
- '[0-9]+.[0-9]+.[0-9]+'
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run build
- name: Prepare release notes
run: |
VERSION="${GITHUB_REF_NAME}"
NOTES="release-notes/release-${VERSION}.md"
if [ ! -f "$NOTES" ]; then
echo "No release notes provided for version ${VERSION}." > "$NOTES"
fi
PREV_TAG=$(git tag --sort=-version:refname | grep -A1 "^${VERSION}$" | tail -1)
if [ -n "$PREV_TAG" ] && [ "$PREV_TAG" != "$VERSION" ]; then
echo "" >> "$NOTES"
echo "**Full changelog**: https://github.com/${{ github.repository }}/compare/${PREV_TAG}...${VERSION}" >> "$NOTES"
fi
- uses: softprops/action-gh-release@v2
with:
name: ${{ github.ref_name }}
body_path: release-notes/release-${{ github.ref_name }}.md
make_latest: true
draft: true
prerelease: false
files: |
dist/main.js
dist/manifest.json
dist/styles.css

6
.gitignore vendored
View file

@ -18,9 +18,3 @@ data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
# iCLoud
*.nosync
# Sublime
*sublime-workspace

3
.npmrc
View file

@ -1,2 +1 @@
tag-version-prefix=""
message=build: version bump to %s
tag-version-prefix=""

View file

@ -1,43 +0,0 @@
module.exports = {
env: {
node: true, // Add this line to define Node.js global variables like `module`
es6: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended'
],
ignorePatterns: ['node_modules', 'dist', 'coverage', '**/*.d.ts', 'tests'],
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
sourceType: 'module'
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/naming-convention': [
'error',
{
selector: 'interface',
format: ['PascalCase'],
custom: {
regex: '^I[A-Z]',
match: true
}
}
],
'@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-use-before-define': 'off',
'@typescript-eslint/quotes': [
'error',
'single',
{ avoidEscape: true, allowTemplateLiterals: false }
],
curly: ['error', 'all'],
eqeqeq: 'error',
'prefer-arrow-callback': 'error'
}
};

View file

@ -1,11 +1,11 @@
{
"id": "plugins-annotations",
"name": "Plugins Annotations",
"version": "1.7.12",
"minAppVersion": "1.13.0",
"version": "1.7.7",
"minAppVersion": "1.5.0",
"description": "Allows adding personal comments to each installed plugin.",
"author": "Andrea Alberti",
"authorUrl": "https://www.linkedin.com/in/dr-andrea-alberti/",
"fundingUrl": "https://buymeacoffee.com/alberti",
"isDesktopOnly": false
}
}

View file

@ -1,29 +0,0 @@
{
"folders": [
{
"path": ".",
"folder_exclude_patterns": [".git", "node_modules", "dist"]
}
],
"settings": {
"tab_size": 4,
"translate_tabs_to_spaces": true,
"typescript_tsdk": "./node_modules/typescript/lib"
},
"build_systems": [
{
"name": "TypeScript Build",
"shell_cmd": "npm run build",
"working_dir": "${folder}", // Use ${folder} to point to the project root
"file_regex": "^\\s*(.+?\\.ts)\\((\\d+),(\\d+)\\):\\s*(.*)$",
"selector": "source.ts"
},
{
"name": "TypeScript Dev",
"shell_cmd": "npm run dev",
"working_dir": "${folder}", // Use ${folder} to point to the project root
"file_regex": "^\\s*(.+?\\.ts):(\\d+):(\\d+):\\s*(.*)$",
"selector": "source.ts"
}
]
}

11
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "plugins-annotations",
"version": "1.7.12",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "plugins-annotations",
"version": "1.7.12",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"monkey-around": "^3.0.0"
@ -1871,11 +1871,10 @@
}
},
"node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"peer": true,
"dependencies": {
"brace-expansion": "^1.1.7"

View file

@ -1,6 +1,6 @@
{
"name": "plugins-annotations",
"version": "1.7.12",
"version": "1.0.0",
"description": "Allows adding personal comments to each installed plugin.",
"main": "main.js",
"scripts": {

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1,12 +0,0 @@
# Changelog
## [1.0.12] - 2024-06-23
### Bug fixes
- **Mobile Optimization**:
- Resolved the issue where the caret and keyboard were not shown when clicking on placeholder text in mobile devices.
- Clicking on a placeholder text now selects the entire text, allowing users to easily start typing.
### Bug Fixes
- **Annotation Deletion**:
- Fixed an issue where annotations were not correctly deleted when the text was removed.

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1 +0,0 @@

View file

@ -1,7 +0,0 @@
# Changelog version 1.1.0
## Added features
- Setting panels for configuring plugin annotations.
- Option to hide annotations when they are empty, improving the cleanliness of the interface.
- Enhanced the user experience by allowing the removal of annotations for uninstalled plugins from memory.

View file

@ -1 +0,0 @@
Small fix correcting a typo.

View file

@ -1,5 +0,0 @@
# Change log
## Added:
- Editing notes as simple text, Markdown, and HTML.

View file

@ -1,5 +0,0 @@
# Change log
## Fixes
- Trim annotations to remove empty lines and spaces. If the user wants to enforce empty spaces, HTML-formatted code can be used.

View file

@ -1,5 +0,0 @@
# Change log
## New features
- Added a lock button to lock annotations and prevent empty annotation fields from showing up when hovering over with the mouse.

View file

@ -1,5 +0,0 @@
# Change log
# New minor feature
- Added customization of the placeholder label appearing when no user annotation is provided. It supports HTML-formatted text.

View file

@ -1,14 +0,0 @@
# Changelog for Version 1.4.0
## New Features
- Added an option to automatically remove annotations when a plugin is uninstalled.
- Displaying the names of uninstalled plugins, allowing users to manually remove stored annotations if desired.
## Bug Fixes
- Fixed an issue where personal annotations were not displayed when filtering installed plugins using the search field.
## Under the Hood Improvements
- Enhanced data format to store the names of plugins.
- Refactored code for better performance and maintainability.
This release includes significant improvements, bug fixes, and new features to enhance your experience with the plugin. Enjoy the updates!

View file

@ -1,12 +0,0 @@
# Change log
## New features
- Possible to provide a Markdown file (for example in `00 Meta/Misc/Plugins annotations.md`), where the annotations are stored. This allows editing the annotations directly from the Obsidian editor. Most importantly, it allows one to use Obsidian links and having the links automatically updated when the linked files in the vault are renamed.
- Added to the preference panes a place to manage backups of the annotations.
- Added possibility to export and import annotations as json file.
- Many other improvements under the hood and refactored the code.
## Changes
- The default type for the plugin annotations has now changed from `text` to `markdown`.

View file

@ -1,12 +0,0 @@
# Change log
## New features
- Possible to provide a Markdown file (for example in `00 Meta/Misc/Plugins annotations.md`), where the annotations are stored. This allows editing the annotations directly from the Obsidian editor. Most importantly, it allows one to use Obsidian links and having the links automatically updated when the linked files in the vault are renamed.
- Added to the preference panes a place to manage backups of the annotations.
- Added possibility to export and import annotations as json file.
- Many other improvements under the hood and refactored the code.
## Changes
- The default type for the plugin annotations has now changed from `text` to `markdown`.

View file

@ -1,12 +0,0 @@
# Change log
## New features
- Possible to provide a Markdown file (for example in `00 Meta/Misc/Plugins annotations.md`), where the annotations are stored. This allows editing the annotations directly from the Obsidian editor. Most importantly, it allows one to use Obsidian links and having the links automatically updated when the linked files in the vault are renamed.
- Added to the preference panes a place to manage backups of the annotations.
- Added possibility to export and import annotations as json file.
- Many other improvements under the hood and refactored the code.
## Changes
- The default type for the plugin annotations has now changed from `text` to `markdown`.

View file

@ -1,6 +0,0 @@
# Change log
## Minor improvements
- Improved explanation of backup functionality in the preference pane
- Improve visualization of the preference pane for mobile devices (optimized for with small screens).

View file

@ -1,16 +0,0 @@
# Change log
## New features
- Added backup of annotations
## Bugs and improvements
- Corrected a bug where the links were not clickable if the notes were not editable.
- Corrected a bug where the lock icon was not correctly displayed the first time the pane was opened.
- Refactoring the code, which improves clarity and allows more extensions in the future.
- Improved explanations in the preference pane.
## Removed features
- HTML and text annotations have been discontinued. It was too much of an effort to maintain them, while there is no advantage over using Markdown. You can still use HTML in Markdown.

View file

@ -1,17 +0,0 @@
# Change log
## New features
- Added backup of annotations
- Notes exported to the markdown file are sorted out alphabetically.
## Bugs and improvements
- Corrected a bug where the links were not clickable if the notes were not editable.
- Corrected a bug where the lock icon was not correctly displayed the first time the pane was opened.
- Refactoring the code, which improves clarity and allows more extensions in the future.
- Improved explanations in the preference pane.
## Removed features
- HTML and text annotations have been discontinued. It was too much of an effort to maintain them, while there is no advantage over using Markdown. You can still use HTML in Markdown.

View file

@ -1,11 +0,0 @@
# Change log
## New features
- Added restore buttons to the settings for default configurations
- Added the possibility to edit annotations of uninstalled plugins in the preference pane.
- Improved explanation in the preference pane.
## Bug fixes
- Many bugs corrected (still testing import / export / backup functions).

View file

@ -1,6 +0,0 @@
# Change log
## Bug fixes and performance improvements
- Improved debounced saving functions.
- Fixed a bug when switching between the preference pane and the community plugins pane, where the latest changes in the settings were lost.

View file

@ -1,5 +0,0 @@
# Change log
## Bug fixes
- Fixed bug where buttons resetting to default conditions did not properly save the new setting.

View file

@ -1,6 +0,0 @@
# Change log
## Bug fixes
- When disabling the plugin, the lock icon is now removed.
- This should fix the bug of multiple lock icons being created when the plugin `Divide & Conquer` was used. The plugin disables and re-enables plugins. Every time this plugin was reloaded, a new icon was created because the previous one was never removed.

View file

@ -1,5 +0,0 @@
# Change log
## Bug fixes
- Fixed bug where annotations could not be added when some text is provided in the search field to restrict the range of annotations displayed.

View file

@ -1,6 +0,0 @@
# Change log
## New features
- Added GitHub icons linking to the plugin's GitHub page. A configuration setting allows one to decide whether to show or not these icons.
- Various improvements under the hood.

View file

@ -1,7 +0,0 @@
# Change log
## New features
- Added GitHub icons linking to the plugin's GitHub page. A configuration setting allows one to decide whether to show or not these icons.
- Various improvements under the hood.
- Some fixes resolving minor bugs in 1.7.0

View file

@ -1,5 +0,0 @@
# Change log
## Bug fix
- Fix mobile annotation focus and touch handling (refs #24, PR #25)

View file

@ -1,5 +0,0 @@
# Change log
## Bug fix
- Correctly identify Obsidian appearance (light/dark theme) for displaying GitHub icon matched to the theme

View file

@ -1,9 +0,0 @@
# Change log
## Bug fix
- Restore personal annotations in the **Community plugins** settings tab. Obsidian 1.13 redesigned that tab, which stopped annotations (and the lock icon and GitHub icons) from appearing. The plugin now hooks into the new settings UI.
## Compatibility
- Requires Obsidian **1.13.0** or newer. Users on older Obsidian versions keep receiving 1.7.11.

View file

@ -1,7 +0,0 @@
# Change log
## New features
- Added GitHub icons linking to the plugin's GitHub page. A configuration setting allows one to decide whether to show or not these icons.
- Various improvements under the hood.
- Some fixes resolving minor bugs in 1.7.1

View file

@ -1,7 +0,0 @@
# Change log
## Bug fixes
- Fixed bug where a warning was shown when a new plugin was installed.
- Fixed small bugs where the code is now more efficient, i.e., avoids repeating unnecessary operations
- Corrected a bug where, when enabling this plugin, the personal annotations were not immediately shown in the preference pane. Instead, it required to exit the preference pane and enter it again. Now, this is no longer necessary.

View file

@ -1,5 +0,0 @@
# Change log
## Bug fixes
- Fixed a bug that caused a conflict with [Hotkey Helper plugin](https://github.com/pjeby/hotkey-helper) and possibly could have conflicted with other plugins that make use of `openTab` Obsidian function for opening preference panes.

View file

@ -1,5 +0,0 @@
# Change log
## Bug fixes
- Streamlined code and remove potential weak spot where `openTab` function was called with an asynchronous mechanism. See discussion on the GitHub [page](https://github.com/alberti42/obsidian-plugins-annotations/issues/14).

View file

@ -1,5 +0,0 @@
# Change log
## New features
- Detect changes to `data.json` made by the synchronisation engine (Obsidian or Google Drive or Dropbox etc) or any external editor. If the Community Preferences window is active, the changes will be shown immediately. Similarly, if changes are made using an external editor to the MarkDown file that stores the personal annotations, these changes will be detected and the preferences pane will be updated immediately to reflect the new changes.

View file

@ -1,5 +0,0 @@
# Change log
## New features
- Detect changes to data.json which can be caused by the synchronization engine or an external editor and update the setting pane automatically.

View file

@ -1,5 +0,0 @@
# Change log
## Bug fix
- Fix community plugins hook for Obsidian 1.11.5 (refs #26)

View file

@ -55,20 +55,13 @@ export class AnnotationControl {
}
addEventListeners() {
const linkInteractionHandler = (event: MouseEvent | TouchEvent) => {
const target = event.target as HTMLElement | null;
// Use closest('a') so clicks on nested elements (e.g., spans inside links) still register as link clicks.
// This scenario is likely never occurring, but closest('a') keeps link detection reliable (just in case).
if (target && target.closest('a')) {
this.annotation_div.addEventListener('mousedown', (event:MouseEvent) => {
if (event.target && (event.target as HTMLElement).tagName === 'A') {
this.clickedLink = true;
} else {
this.clickedLink = false;
}
};
this.annotation_div.addEventListener('mousedown', linkInteractionHandler);
this.annotation_div.addEventListener('touchstart', linkInteractionHandler, { passive: true });
});
// Prevent click event propagation to parent
this.annotation_div.addEventListener('click', (event:MouseEvent) => {
@ -76,10 +69,6 @@ export class AnnotationControl {
return;
} else {
event.stopPropagation();
if (!this.clickedLink) {
// Explicitly focus to help mobile keyboards appear, especially on Android.
this.annotation_div.focus();
}
}
});

View file

@ -2,7 +2,6 @@
import {
Plugin,
Setting,
SettingTab,
Platform,
Plugins,
@ -39,14 +38,14 @@ export default class PluginsAnnotations extends Plugin {
private community_plugins = {} as CommunityPluginInfoDict;
// MutationObserver watching document.body class changes to detect Obsidian theme switches
private themeObserver: MutationObserver | null = null;
private handleThemeChange: ((event: MediaQueryListEvent) => void) | null = null;
private colorSchemeMedia: MediaQueryList | null = null;
// Declare class methods that will be initialized in the constructor
debouncedSaveAnnotations: (callback?: () => void) => void;
waitForSaveToComplete: () => Promise<void>;
private communityPluginSettingTabPatched = false;
private communityPluginSettingTabPatched = false;
private listGitHubIcons:HTMLDivElement[] = [];
@ -105,26 +104,25 @@ export default class PluginsAnnotations extends Plugin {
}
}
async onLayoutReady() {
// Load settings
const loadSettingsPromise = this.loadSettings();
// Load the big json file containing the GitHub address of all community plugins
const loadCommunityPluginsJsonPromise = this.loadCommunityPluginsJson();
async onLayoutReady() {
// Load settings
const loadSettingsPromise = this.loadSettings();
// Load the big json file containing the GitHub address of all community plugins
const loadCommunityPluginsJsonPromise = this.loadCommunityPluginsJson();
// Store a reference to the community plugin tab
this.communityPluginTab = this.app.setting.settingTabs.find((tab:SettingTab):boolean => tab.id === "community-plugins");
// Store a reference to the community plugin tab
this.communityPluginTab = this.app.setting.settingTabs.find((tab:SettingTab):boolean => tab.id === "community-plugins");
// Patch the rendering function of the community plugin preference pane
if(this.communityPluginTab) {
this.patchCommunityPluginSettingTab(this.communityPluginTab);
} else {
console.warn('[Plugins Annotations] Community plugins tab not found; hook not installed.');
}
// Patch the rendering function of the community plugin preference pane
if(this.communityPluginTab) this.patchCommunityPluginSettingTab(this.communityPluginTab);
// Monkey-patch functions to detect when community plugins are installed and uninstalled.
this.hookOnInstallAndUninstallPlugins();
// Detect color scheme
this.colorSchemeMedia = matchMedia('(prefers-color-scheme: dark)');
// Install listener to theme changes
if(this.communityPluginTab && this.communityPluginTab.containerEl) this.listenForThemeChange(this.communityPluginTab.containerEl);
@ -408,32 +406,49 @@ export default class PluginsAnnotations extends Plugin {
return invertedMap;
}
patchCommunityPluginSettingTab(tab:SettingTab) {
if(this.communityPluginSettingTabPatched) return;
patchCommunityPluginSettingTab(tab:SettingTab) {
if(this.communityPluginSettingTabPatched) return;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
// eslint-disable-next-line @typescript-eslint/no-this-alias
const self = this;
// Monkey patch the per-plugin renderer of the community-plugins tab.
// Obsidian's declarative settings tab calls `renderInstalledPlugin(setting, manifest)`
// for every plugin row, on both the initial render and every later re-render, so this
// is the single hook we need for injecting annotations and the lock icon.
const removeMonkeyPatchForRender = around(tab, {
renderInstalledPlugin: (next: SettingTab['renderInstalledPlugin']) => {
// Monkey patch for uninstallPlugin
const removeMonkeyPatchForRender = around(tab, {
renderInstalledPlugin: (next: (
pluginManifest: PluginManifest,
containerEl:HTMLElement,
nameMatch: boolean | null,
authorMatch: boolean | null,
descriptionMatch: boolean | null
) => void ) => {
return function (this: SettingTab, setting: Setting, manifest: PluginManifest): void {
// Render the plugin row using Obsidian's own implementation first.
next.call(this, setting, manifest);
return function (this: SettingTab,
pluginManifest: PluginManifest,
containerEl: HTMLElement,
nameMatch: boolean | null,
authorMatch: boolean | null,
descriptionMatch: boolean | null
): void {
next.call(this, pluginManifest, containerEl, nameMatch, authorMatch, descriptionMatch);
const settingEl = setting.settingEl;
if (settingEl instanceof HTMLElement) {
self.addAnnotation(settingEl, manifest.id);
// (Re)add the lock icon to the group heading. It is idempotent.
self.addLockIcon(this.containerEl);
}
};
}
});
// Custom code for personal annotations here
if(containerEl && containerEl.lastElementChild) self.addAnnotation(containerEl.lastElementChild);
};
},
// Patch for `render` method
render: (next: (
isInitialRender: boolean
) => void) => {
return function (this: SettingTab, isInitialRender: boolean): void {
self.listGitHubIcons = [];
// Call the original `render` function
next.call(this, isInitialRender);
self.addLockIcon(this.containerEl);
};
}
});
// Register the patch to ensure it gets cleaned up
this.register(removeMonkeyPatchForRender);
@ -486,44 +501,46 @@ export default class PluginsAnnotations extends Plugin {
}
listenForThemeChange(tabContainer: HTMLElement) {
// If observer is already installed, return immediately to avoid duplicates
if (this.themeObserver) return;
// If listener is already install, we can directly return
if(this.handleThemeChange) return;
// Obsidian sets 'theme-dark' or 'theme-light' on document.body; observe class
// attribute changes so GitHub icons update whenever the user switches themes,
// regardless of the OS-level color scheme.
this.themeObserver = new MutationObserver(() => {
const isDarkMode = document.body.classList.contains('theme-dark');
const githubIcons = tabContainer.querySelectorAll(
'div.setting-item > div.setting-item-control > div.github-icon'
);
// Update each icon to match the new theme
githubIcons.forEach((icon) => {
icon.innerHTML = isDarkMode ? svg_github_dark : svg_github_light;
});
});
// Check if the color scheme was detected
if (this.colorSchemeMedia===null) {
console.warn("Color scheme could not be determined.");
return;
}
// Watch only the 'class' attribute to minimise observer overhead
this.themeObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
}
// Create the event listener with the correct signature
this.handleThemeChange = (event: MediaQueryListEvent): void => {
const pluginsContainer = tabContainer.querySelector('.installed-plugins-container');
// Locate the control area of the "Installed plugins" group heading, where the lock
// icon lives. This tab renders several group headings, so we target the installed-plugins
// group (`.setting-group.mod-list`) specifically rather than the first heading found.
private findInstalledPluginsHeadingControl(containerEl: HTMLElement): Element | null {
const group = containerEl.querySelector('.setting-group.mod-list');
return group ? group.querySelector('.setting-item-heading .setting-item-control') : null;
const isDarkMode = event.matches; // true means dark mode is active
if (pluginsContainer) {
const githubIcons = pluginsContainer.querySelectorAll(
'div.setting-item > div.setting-item-control > div.github-icon'
);
// Iterate over each github icon and set the appropriate SVG
githubIcons.forEach((icon) => {
if (isDarkMode) {
icon.innerHTML = svg_github_dark;
} else {
icon.innerHTML = svg_github_light;
}
});
}
}
// Add an event listener for changes to the appearance mode
this.colorSchemeMedia.addEventListener("change", this.handleThemeChange);
}
async addLockIcon(containerEl: HTMLElement) {
// Add new icon to the existing icons container
const headingContainer = this.findInstalledPluginsHeadingControl(containerEl);
const headingContainer = containerEl.querySelector('.setting-item-heading .setting-item-control');
if (headingContainer) {
// Idempotent: skip if a lock icon is already present in this heading.
if (this.lockIcon && this.lockIcon.isConnected && headingContainer.contains(this.lockIcon)) {
return;
}
this.lockIcon = document.createElement('div');
const lockIcon = this.lockIcon;
@ -593,18 +610,9 @@ export default class PluginsAnnotations extends Plugin {
}
removeGitHubIcons() {
// Remove any GitHub icons currently in the tab. We query the DOM (rather than rely
// solely on the tracked list) so icons re-created by Obsidian's render reconciliation
// are also cleaned up.
if (this.communityPluginTab) {
this.communityPluginTab.containerEl.querySelectorAll('.github-icon').forEach((iconEl:Element) => {
iconEl.remove();
});
}
this.listGitHubIcons.forEach((iconEl:Element) => {
iconEl.remove();
});
this.listGitHubIcons = [];
})
}
async loadCommunityPluginsJson() {
@ -637,74 +645,53 @@ export default class PluginsAnnotations extends Plugin {
}
}
// Extract the plugin name from a `.setting-item-name` element. Newer Obsidian appends
// version/author <span>s to this element, so we read only the leading text node(s)
// rather than `textContent` (which would include the version and author).
private extractPluginName(nameEl: Element): string {
let text = '';
nameEl.childNodes.forEach((node: ChildNode) => {
if (node.nodeType === Node.TEXT_NODE) {
text += node.textContent ?? '';
}
});
return text.trim();
}
addAnnotation(pluginDOMElement: Element) {
const pluginNameDiv = pluginDOMElement.querySelector('.setting-item-name');
const pluginName = pluginNameDiv ? pluginNameDiv.textContent : null;
addAnnotation(pluginDOMElement: Element, pluginId?: string) {
if (!pluginId) {
// No id supplied (e.g. bulk re-scan): derive it from the displayed plugin name.
const pluginNameDiv = pluginDOMElement.querySelector('.setting-item-name');
const pluginName = pluginNameDiv ? this.extractPluginName(pluginNameDiv) : null;
if (!pluginName) {
console.warn('Plugin name not found');
return;
}
if (!pluginName) {
console.warn('Plugin name not found');
return;
}
const pluginId = this.pluginNameToIdMap[pluginName];
if (!pluginId) {
console.warn(`Plugin ID not found for plugin name: ${pluginName}`);
return;
}
pluginId = this.pluginNameToIdMap[pluginName];
}
if (!pluginId) {
console.warn('Plugin ID not found for the plugin row.');
return;
}
// Use the canonical name from the installed manifests (falls back to the id).
const pluginName = this.pluginIdToNameMap[pluginId] ?? pluginId;
const settingItemInfo = pluginDOMElement.querySelector('.setting-item-info');
if (settingItemInfo) {
const descriptionDiv = settingItemInfo.querySelector('.setting-item-description');
if (descriptionDiv) {
const commentDiv = descriptionDiv.querySelector('.plugin-comment');
if (!commentDiv) {
const annotation_container = document.createElement('div');
annotation_container.className = 'plugin-comment';
const settingItemInfo = pluginDOMElement.querySelector('.setting-item-info');
if (settingItemInfo) {
const descriptionDiv = settingItemInfo.querySelector('.setting-item-description');
if (descriptionDiv) {
const commentDiv = descriptionDiv.querySelector('.plugin-comment');
if (!commentDiv) {
const annotation_container = document.createElement('div');
annotation_container.className = 'plugin-comment';
const annotationControl = new AnnotationControl(this,annotation_container,pluginId,pluginName);
descriptionDiv.appendChild(annotation_container);
if(this.settings.show_github_icons) {
// Get the repository of the plugin
const community_plugins = this.community_plugins[pluginId];
const repo = community_plugins ? community_plugins.repo : undefined;
if (repo) {
const controlDiv = pluginDOMElement.querySelector('.setting-item-control');
if(controlDiv) {
{
// Read Obsidian's active theme from document.body at render time
const isDarkMode = document.body.classList.contains('theme-dark');
const gitHubIcon = annotationControl.addGitHubIcon(controlDiv,repo, isDarkMode);
if(gitHubIcon) this.listGitHubIcons.push(gitHubIcon);
}
}
}
}
}
}
}
}
if(this.settings.show_github_icons) {
// Get the repository of the plugin
const community_plugins = this.community_plugins[pluginId];
const repo = community_plugins ? community_plugins.repo : undefined;
if (repo) {
const controlDiv = pluginDOMElement.querySelector('.setting-item-control');
if(controlDiv) {
if(this.colorSchemeMedia) {
const isDarkMode = this.colorSchemeMedia.matches;
const gitHubIcon = annotationControl.addGitHubIcon(controlDiv,repo, isDarkMode);
if(gitHubIcon) this.listGitHubIcons.push(gitHubIcon);
}
}
}
}
}
}
}
}
addAnnotations() {
if(this.communityPluginTab===undefined) {
@ -712,17 +699,13 @@ export default class PluginsAnnotations extends Plugin {
return;
}
const containerEl = this.communityPluginTab.containerEl;
// The installed plugins are rendered inside `.setting-group.mod-list`; its
// `.setting-items` element holds the individual plugin rows.
const pluginsContainer = containerEl.querySelector('.setting-group.mod-list .setting-items');
const pluginsContainer = this.communityPluginTab.containerEl.querySelector('.installed-plugins-container');
if (!pluginsContainer) {
console.warn("Annotations could not be added because the installed-plugins list was not detected.")
console.warn("Annotations could not be added because installed-plugins-container was not detected.")
return;
}
// Only iterate direct plugin rows, skipping the group heading (also a `.setting-item`).
const pluginDOMElements = pluginsContainer.querySelectorAll(':scope > .setting-item:not(.setting-item-heading)');
const pluginDOMElements = pluginsContainer.querySelectorAll('.setting-item');
pluginDOMElements.forEach(pluginDOMElement => {
this.addAnnotation(pluginDOMElement);
});
@ -738,10 +721,11 @@ export default class PluginsAnnotations extends Plugin {
}
removeHandleThemeChangeListener() {
// Disconnect and release the MutationObserver used for theme detection
if (this.themeObserver) {
this.themeObserver.disconnect();
this.themeObserver = null;
// Remove listeners
if (this.handleThemeChange) {
if (this.colorSchemeMedia) {
this.colorSchemeMedia.removeEventListener("change", this.handleThemeChange);
}
}
}
@ -776,3 +760,4 @@ export default class PluginsAnnotations extends Plugin {
return uninstalledPlugins;
}
}

View file

@ -18,9 +18,15 @@ declare module "obsidian" {
id: string;
name: string;
navEl: HTMLElement;
// Per-plugin row renderer of the community-plugins tab (Obsidian >= 1.13):
// `setting` is the row's Setting component (exposes `settingEl`), `manifest` its plugin manifest.
renderInstalledPlugin(setting: Setting, manifest: PluginManifest): void;
// updateSearch(e: string): void;
render(isInitialRender:boolean):void;
renderInstalledPlugin(
pluginManifest: PluginManifest,
containerEl: HTMLElement,
nameMatch: boolean | null,
authorMatch: boolean | null,
descriptionMatch: boolean | null
): void;
}
interface Setting {
onOpen(): void;
@ -64,4 +70,4 @@ declare module "obsidian" {
textInputEl: HTMLInputElement;
}
}
}

View file

@ -1,6 +1,3 @@
{
"1.0.0": "0.15.0",
"1.7.10": "1.5.0",
"1.7.11": "1.5.0",
"1.7.12": "1.13.0"
}
"1.0.0": "0.15.0"
}