mirror of
https://github.com/alberti42/obsidian-plugins-annotations.git
synced 2026-07-22 10:10:24 +00:00
Compare commits
No commits in common. "main" and "1.7.8" have entirely different histories.
56 changed files with 165 additions and 560 deletions
3
.eslintignore
Normal file
3
.eslintignore
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
50
.github/workflows/release.yml
vendored
50
.github/workflows/release.yml
vendored
|
|
@ -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
6
.gitignore
vendored
|
|
@ -18,9 +18,3 @@ data.json
|
|||
|
||||
# Exclude macOS Finder (System Explorer) View States
|
||||
.DS_Store
|
||||
|
||||
# iCLoud
|
||||
*.nosync
|
||||
|
||||
# Sublime
|
||||
*sublime-workspace
|
||||
|
|
|
|||
3
.npmrc
3
.npmrc
|
|
@ -1,2 +1 @@
|
|||
tag-version-prefix=""
|
||||
message=build: version bump to %s
|
||||
tag-version-prefix=""
|
||||
|
|
@ -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'
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
11
package-lock.json
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -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.
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -1 +0,0 @@
|
|||
|
||||
|
|
@ -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.
|
||||
|
|
@ -1 +0,0 @@
|
|||
Small fix correcting a typo.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Change log
|
||||
|
||||
## Added:
|
||||
|
||||
- Editing notes as simple text, Markdown, and HTML.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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!
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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`.
|
||||
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Change log
|
||||
|
||||
## Bug fixes
|
||||
|
||||
- Fixed bug where buttons resetting to default conditions did not properly save the new setting.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Change log
|
||||
|
||||
## Bug fix
|
||||
|
||||
- Fix mobile annotation focus and touch handling (refs #24, PR #25)
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Change log
|
||||
|
||||
## Bug fix
|
||||
|
||||
- Correctly identify Obsidian appearance (light/dark theme) for displaying GitHub icon matched to the theme
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -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).
|
||||
|
|
@ -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.
|
||||
|
|
@ -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.
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
# Change log
|
||||
|
||||
## Bug fix
|
||||
|
||||
- Fix community plugins hook for Obsidian 1.11.5 (refs #26)
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
289
src/main.ts
289
src/main.ts
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
14
src/types/obsidian-augment.d.ts
vendored
14
src/types/obsidian-augment.d.ts
vendored
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue