diff --git a/src/defaults.ts b/src/defaults.ts
index 002e5d0..ba40836 100644
--- a/src/defaults.ts
+++ b/src/defaults.ts
@@ -3,22 +3,22 @@
import { AnnotationType, PluginsAnnotationsSettings } from './types';
export const DEFAULT_SETTINGS: PluginsAnnotationsSettings = {
- annotations: {
- "plugins-annotations": {
- "name": "Plugins Annotations",
- "desc": "Allows writing annotations (just like this one) about the community plugins installed in the vault.",
- "type": AnnotationType.markdown,
- }
- },
- plugins_annotations_uuid: 'BC56AB7B-A46F-4ACF-9BA1-3A4461F74C79',
- hide_placeholders: false,
- delete_placeholder_string_on_insertion: false,
- label_mobile: 'Annotation: ',
- label_desktop: 'Personal annotation: ',
- label_placeholder : "Add your personal comment about ${plugin_name} here...",
- editable: true,
- automatic_remove: false,
- markdown_file_path: '',
- backups: [],
- compatibility: '1.5.0',
+ annotations: {
+ "plugins-annotations": {
+ "name": "Plugins Annotations",
+ "desc": "Allows writing annotations (just like this one) about the community plugins installed in the vault.",
+ "type": AnnotationType.markdown,
+ }
+ },
+ plugins_annotations_uuid: 'BC56AB7B-A46F-4ACF-9BA1-3A4461F74C79',
+ hide_placeholders: false,
+ delete_placeholder_string_on_insertion: false,
+ label_mobile: 'Annotation: ',
+ label_desktop: 'Personal annotation: ',
+ label_placeholder : "Add your personal comment about ${plugin_name} here...",
+ editable: true,
+ automatic_remove: false,
+ markdown_file_path: '',
+ backups: [],
+ compatibility: '1.5.0',
}
diff --git a/src/defaults_legacy.ts b/src/defaults_legacy.ts
index c96363d..b82f86a 100644
--- a/src/defaults_legacy.ts
+++ b/src/defaults_legacy.ts
@@ -10,9 +10,9 @@ const { annotations:_, markdown_file_path:__, compatibility:___, backups:____, .
export const DEFAULT_SETTINGS_1_4_0: PluginsAnnotationsSettings_1_4_0 = {
- ...theRestingSettings_1_4_0,
- annotations: {},
- plugins_annotations_uuid: 'B265C5B2-A6AD-4194-9E4C-C1327DB1EA18',
+ ...theRestingSettings_1_4_0,
+ annotations: {},
+ plugins_annotations_uuid: 'B265C5B2-A6AD-4194-9E4C-C1327DB1EA18',
}
diff --git a/src/main.ts b/src/main.ts
index 1df7b0d..f8f27d1 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,17 +1,17 @@
// main.ts
import {
- Plugin,
- Setting,
- SettingTab,
- Platform,
- MarkdownRenderer,
- Plugins,
- PluginManifest,
- FileSystemAdapter,
- TAbstractFile,
- // PluginSettingTab,
- // App,
+ Plugin,
+ Setting,
+ SettingTab,
+ Platform,
+ MarkdownRenderer,
+ Plugins,
+ PluginManifest,
+ FileSystemAdapter,
+ TAbstractFile,
+ // PluginSettingTab,
+ // App,
} from 'obsidian';
import { around } from 'monkey-around';
import { AnnotationType, isPluginAnnotation, isPluginsAnnotationsSettings, parseAnnotation, PluginAnnotationDict, PluginBackup, PluginsAnnotationsSettings } from './types';
@@ -24,481 +24,481 @@ import { readAnnotationsFromMdFile, writeAnnotationsToMdFile } from 'manageAnnot
import { sortPluginAnnotationsByName } from 'utils';
export default class PluginsAnnotations extends Plugin {
- settings: PluginsAnnotationsSettings = structuredClone(DEFAULT_SETTINGS);
- pluginNameToIdMap: Record = {};
- pluginIdToNameMap: Record = {};
+ settings: PluginsAnnotationsSettings = structuredClone(DEFAULT_SETTINGS);
+ pluginNameToIdMap: Record = {};
+ pluginIdToNameMap: Record = {};
- private mutationObserver: MutationObserver | null = null;
- private saveTimeout: number | null = null;
- private observedTab: SettingTab | null = null;
- private vaultPath: string | null = null;
+ private mutationObserver: MutationObserver | null = null;
+ private saveTimeout: number | null = null;
+ private observedTab: SettingTab | null = null;
+ private vaultPath: string | null = null;
- async onload() {
+ async onload() {
- // console.clear();
-
- // console.log('Loading Plugins Annotations');
+ // console.clear();
+
+ // console.log('Loading Plugins Annotations');
- // Add settings tab. It avoids loading the setting at this stage
- // because the cache about the files in the vault is not created yet.
- this.addSettingTab(new PluginsAnnotationsSettingTab(this.app, this));
-
- this.app.workspace.onLayoutReady(() => {
- this.patchSettings();
+ // Add settings tab. It avoids loading the setting at this stage
+ // because the cache about the files in the vault is not created yet.
+ this.addSettingTab(new PluginsAnnotationsSettingTab(this.app, this));
+
+ this.app.workspace.onLayoutReady(() => {
+ this.patchSettings();
- const activeTab = this.app.setting.activeTab;
- if (activeTab && activeTab.id === 'community-plugins') {
- this.observeTab(activeTab);
- }
- });
+ const activeTab = this.app.setting.activeTab;
+ if (activeTab && activeTab.id === 'community-plugins') {
+ this.observeTab(activeTab);
+ }
+ });
- this.app.vault.on('modify', (modifiedFile: TAbstractFile) => {
- if(this.settings.markdown_file_path !== '') {
- if (modifiedFile.path === this.settings.markdown_file_path) {
- readAnnotationsFromMdFile(this);
- }
- }
- });
- }
+ this.app.vault.on('modify', (modifiedFile: TAbstractFile) => {
+ if(this.settings.markdown_file_path !== '') {
+ if (modifiedFile.path === this.settings.markdown_file_path) {
+ readAnnotationsFromMdFile(this);
+ }
+ }
+ });
+ }
- /* Load settings for different versions */
- async importSettings(data: unknown): Promise<{importedSettings: unknown, wasUpdated: boolean}> {
+ /* Load settings for different versions */
+ async importSettings(data: unknown): Promise<{importedSettings: unknown, wasUpdated: boolean}> {
- // Set to true when the settings are updated to the new format
- let wasUpdated = false;
-
- // Nested function to handle different versions of settings
- const getSettingsFromData = async (data: unknown): Promise => {
-
- if(data === null) { // if the file is empty
- return data;
- } else if (isPluginsAnnotationsSettings(data)) {
- const settings: PluginsAnnotationsSettings = data;
- return settings;
- } else if (isSettingsFormat_1_4_0(data)) { // previous versions where the name of the plugins was not stored
- // Make a backup
- await this.backupSettings('Settings before upgrade from 1.4 to 1.5',data);
+ // Set to true when the settings are updated to the new format
+ let wasUpdated = false;
+
+ // Nested function to handle different versions of settings
+ const getSettingsFromData = async (data: unknown): Promise => {
+
+ if(data === null) { // if the file is empty
+ return data;
+ } else if (isPluginsAnnotationsSettings(data)) {
+ const settings: PluginsAnnotationsSettings = data;
+ return settings;
+ } else if (isSettingsFormat_1_4_0(data)) { // previous versions where the name of the plugins was not stored
+ // Make a backup
+ await this.backupSettings('Settings before upgrade from 1.4 to 1.5',data);
- const default_settings = DEFAULT_SETTINGS;
+ const default_settings = DEFAULT_SETTINGS;
- // Upgrade annotations format
- const upgradedAnnotations: PluginAnnotationDict = {};
- for (const pluginId in data.annotations) {
- const annotation = data.annotations[pluginId];
- const {type,content} = parseAnnotation_1_4_0(annotation.anno);
- upgradedAnnotations[pluginId] = {
- name: annotation.name,
- desc: content,
- type: type,
- };
- }
+ // Upgrade annotations format
+ const upgradedAnnotations: PluginAnnotationDict = {};
+ for (const pluginId in data.annotations) {
+ const annotation = data.annotations[pluginId];
+ const {type,content} = parseAnnotation_1_4_0(annotation.anno);
+ upgradedAnnotations[pluginId] = {
+ name: annotation.name,
+ desc: content,
+ type: type,
+ };
+ }
- const oldSettings: PluginsAnnotationsSettings_1_4_0 = data;
+ const oldSettings: PluginsAnnotationsSettings_1_4_0 = data;
- // Update the data with the new format
- const newSettings: PluginsAnnotationsSettings = {
- ...oldSettings,
- annotations: upgradedAnnotations,
- plugins_annotations_uuid: default_settings.plugins_annotations_uuid,
- backups: this.settings.backups,
- compatibility: default_settings.compatibility,
- markdown_file_path: default_settings.markdown_file_path
- };
- wasUpdated = true;
+ // Update the data with the new format
+ const newSettings: PluginsAnnotationsSettings = {
+ ...oldSettings,
+ annotations: upgradedAnnotations,
+ plugins_annotations_uuid: default_settings.plugins_annotations_uuid,
+ backups: this.settings.backups,
+ compatibility: default_settings.compatibility,
+ markdown_file_path: default_settings.markdown_file_path
+ };
+ wasUpdated = true;
- return await getSettingsFromData(newSettings);
- } else if (isSettingsFormat_1_3_0(data)) { // previous versions where the name of the plugins was not stored
- // Make a backup
- await this.backupSettings('Settings before upgrade from 1.3 to 1.4',data);
+ return await getSettingsFromData(newSettings);
+ } else if (isSettingsFormat_1_3_0(data)) { // previous versions where the name of the plugins was not stored
+ // Make a backup
+ await this.backupSettings('Settings before upgrade from 1.3 to 1.4',data);
- const default_settings_1_4_0 = DEFAULT_SETTINGS_1_4_0
+ const default_settings_1_4_0 = DEFAULT_SETTINGS_1_4_0
- // Upgrade annotations format
- const upgradedAnnotations: PluginAnnotationDict_1_4_0 = {};
-
- for (const pluginId in data.annotations) {
- const annotation = data.annotations[pluginId];
- upgradedAnnotations[pluginId] = {
- name: this.pluginIdToNameMap[pluginId] || pluginId,
- anno: annotation,
- };
- }
- const oldSettings: PluginsAnnotationsSettings_1_3_0 = data;
+ // Upgrade annotations format
+ const upgradedAnnotations: PluginAnnotationDict_1_4_0 = {};
+
+ for (const pluginId in data.annotations) {
+ const annotation = data.annotations[pluginId];
+ upgradedAnnotations[pluginId] = {
+ name: this.pluginIdToNameMap[pluginId] || pluginId,
+ anno: annotation,
+ };
+ }
+ const oldSettings: PluginsAnnotationsSettings_1_3_0 = data;
- // Update the data with the new format
- const newSettings: PluginsAnnotationsSettings_1_4_0 = {
- ...oldSettings,
- annotations: upgradedAnnotations,
- plugins_annotations_uuid: default_settings_1_4_0.plugins_annotations_uuid,
- };
- wasUpdated = true;
- return await getSettingsFromData(newSettings);
- } else {
- // Make a backup
- await this.backupSettings('Settings before upgrade from 1.0 to 1.3',data);
+ // Update the data with the new format
+ const newSettings: PluginsAnnotationsSettings_1_4_0 = {
+ ...oldSettings,
+ annotations: upgradedAnnotations,
+ plugins_annotations_uuid: default_settings_1_4_0.plugins_annotations_uuid,
+ };
+ wasUpdated = true;
+ return await getSettingsFromData(newSettings);
+ } else {
+ // Make a backup
+ await this.backupSettings('Settings before upgrade from 1.0 to 1.3',data);
- const default_settings_1_3_0 = structuredClone(DEFAULT_SETTINGS_1_3_0);
+ const default_settings_1_3_0 = structuredClone(DEFAULT_SETTINGS_1_3_0);
- // Very first version of the plugin 1.0 -- no options were stored, only the dictionary of annotations
- const newSettings: PluginsAnnotationsSettings_1_3_0 = default_settings_1_3_0;
- newSettings.annotations = isPluginAnnotationDictFormat_1_3_0(data) ? data : default_settings_1_3_0.annotations;
- wasUpdated = true;
- return await getSettingsFromData(newSettings);
- }
- };
+ // Very first version of the plugin 1.0 -- no options were stored, only the dictionary of annotations
+ const newSettings: PluginsAnnotationsSettings_1_3_0 = default_settings_1_3_0;
+ newSettings.annotations = isPluginAnnotationDictFormat_1_3_0(data) ? data : default_settings_1_3_0.annotations;
+ wasUpdated = true;
+ return await getSettingsFromData(newSettings);
+ }
+ };
- const importedSettings = await getSettingsFromData(data);
+ const importedSettings = await getSettingsFromData(data);
- return {importedSettings, wasUpdated};
- }
+ return {importedSettings, wasUpdated};
+ }
- async backupSettings(backupName: string, settings: unknown) {
- // Ensure settings is an object
- if (typeof settings !== 'object' || settings === null) return;
+ async backupSettings(backupName: string, settings: unknown) {
+ // Ensure settings is an object
+ if (typeof settings !== 'object' || settings === null) return;
- let settingsWithoutBackup;
+ let settingsWithoutBackup;
- // Remove the backups field from the settings to be backed up
- if (settings.hasOwnProperty('backups')) {
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { backups: _, ...rest } = settings as { backups: unknown };
- settingsWithoutBackup = rest;
- } else {
- settingsWithoutBackup = settings;
- }
+ // Remove the backups field from the settings to be backed up
+ if (settings.hasOwnProperty('backups')) {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ const { backups: _, ...rest } = settings as { backups: unknown };
+ settingsWithoutBackup = rest;
+ } else {
+ settingsWithoutBackup = settings;
+ }
- // Deep copy
- const deepCopiedSettings = structuredClone(settingsWithoutBackup);
+ // Deep copy
+ const deepCopiedSettings = structuredClone(settingsWithoutBackup);
- // Add the backup with the deep-copied settings
- this.settings.backups.push({
- name: backupName,
- date: new Date(),
- settings: deepCopiedSettings
- });
+ // Add the backup with the deep-copied settings
+ this.settings.backups.push({
+ name: backupName,
+ date: new Date(),
+ settings: deepCopiedSettings
+ });
- await this.saveSettings();
- }
+ await this.saveSettings();
+ }
- async loadSettings(data?: unknown, forceSave?: boolean): Promise {
-
- // Create a mapping of names to IDs for the installed plugins
- this.pluginNameToIdMap = this.constructPluginNameToIdMap();
- this.pluginIdToNameMap = this.generateInvertedMap(this.pluginNameToIdMap);
-
- if(data === undefined) {
- data = await this.loadData();
- }
+ async loadSettings(data?: unknown, forceSave?: boolean): Promise {
+
+ // Create a mapping of names to IDs for the installed plugins
+ this.pluginNameToIdMap = this.constructPluginNameToIdMap();
+ this.pluginIdToNameMap = this.generateInvertedMap(this.pluginNameToIdMap);
+
+ if(data === undefined) {
+ data = await this.loadData();
+ }
- if(forceSave === undefined) {
- forceSave = false;
- }
+ if(forceSave === undefined) {
+ forceSave = false;
+ }
- if (!data || typeof data !== 'object') {
- console.error('Invalid settings.');
- return;
- }
+ if (!data || typeof data !== 'object') {
+ console.error('Invalid settings.');
+ return;
+ }
- const {importedSettings, wasUpdated} = await this.importSettings(data);
+ const {importedSettings, wasUpdated} = await this.importSettings(data);
- // Merge loaded settings with default settings
- this.settings = Object.assign({}, structuredClone(DEFAULT_SETTINGS), importedSettings);
-
- if (this.settings.backups) {
- this.settings.backups.forEach((backup: PluginBackup) => {
- backup.date = new Date(backup.date); // Convert the date string to a Date object
- });
- }
+ // Merge loaded settings with default settings
+ this.settings = Object.assign({}, structuredClone(DEFAULT_SETTINGS), importedSettings);
+
+ if (this.settings.backups) {
+ this.settings.backups.forEach((backup: PluginBackup) => {
+ backup.date = new Date(backup.date); // Convert the date string to a Date object
+ });
+ }
- if(forceSave || wasUpdated) { // if it requires to store the new settings, the .md file will be overwritten
- await this.saveSettings();
- } else { // otherwise read from the md file
- if(this.settings.markdown_file_path!=='') {
- await readAnnotationsFromMdFile(this);
- }
- }
- }
+ if(forceSave || wasUpdated) { // if it requires to store the new settings, the .md file will be overwritten
+ await this.saveSettings();
+ } else { // otherwise read from the md file
+ if(this.settings.markdown_file_path!=='') {
+ await readAnnotationsFromMdFile(this);
+ }
+ }
+ }
- // Store the path to the vault
- getVaultPath():string {
- if(this.vaultPath) return this.vaultPath;
+ // Store the path to the vault
+ getVaultPath():string {
+ if(this.vaultPath) return this.vaultPath;
- if (Platform.isDesktopApp) {
- // store the vault path
- const adapter = this.app.vault.adapter;
- if (!(adapter instanceof FileSystemAdapter)) {
- throw new Error("The vault folder could not be determined.");
- }
- // Normalize to POSIX-style path
- this.vaultPath = adapter.getBasePath().split(path.sep).join(path.posix.sep);
-
- return this.vaultPath;
- } else return "";
- }
-
- async saveSettings() {
- try {
- await this.saveData(this.settings);
- } catch (error) {
- console.error('Failed to save annotations:', error);
- }
- if(this.settings.markdown_file_path!=='') {
- try {
- await writeAnnotationsToMdFile(this);
- } catch (error) {
- console.error('Failed to save annotations to md file:', error);
- }
- }
- }
+ if (Platform.isDesktopApp) {
+ // store the vault path
+ const adapter = this.app.vault.adapter;
+ if (!(adapter instanceof FileSystemAdapter)) {
+ throw new Error("The vault folder could not be determined.");
+ }
+ // Normalize to POSIX-style path
+ this.vaultPath = adapter.getBasePath().split(path.sep).join(path.posix.sep);
+
+ return this.vaultPath;
+ } else return "";
+ }
+
+ async saveSettings() {
+ try {
+ await this.saveData(this.settings);
+ } catch (error) {
+ console.error('Failed to save annotations:', error);
+ }
+ if(this.settings.markdown_file_path!=='') {
+ try {
+ await writeAnnotationsToMdFile(this);
+ } catch (error) {
+ console.error('Failed to save annotations to md file:', error);
+ }
+ }
+ }
- constructPluginNameToIdMap(): Record < string, string > {
- const map: Record < string, string > = {};
- for (const pluginId in this.app.plugins.manifests) {
- const plugin = this.app.plugins.manifests[pluginId];
- if (plugin) {
- map[plugin.name] = plugin.id;
- }
- }
- return map;
- }
+ constructPluginNameToIdMap(): Record < string, string > {
+ const map: Record < string, string > = {};
+ for (const pluginId in this.app.plugins.manifests) {
+ const plugin = this.app.plugins.manifests[pluginId];
+ if (plugin) {
+ map[plugin.name] = plugin.id;
+ }
+ }
+ return map;
+ }
- // Function to generate the inverted map
- generateInvertedMap(originalMap: Record < string, string >) {
- const invertedMap: Record < string, string > = {};
- for (const key in originalMap) {
- if (originalMap.hasOwnProperty(key)) {
- const value = originalMap[key];
- invertedMap[value] = key;
- }
- }
- return invertedMap;
- }
+ // Function to generate the inverted map
+ generateInvertedMap(originalMap: Record < string, string >) {
+ const invertedMap: Record < string, string > = {};
+ for (const key in originalMap) {
+ if (originalMap.hasOwnProperty(key)) {
+ const value = originalMap[key];
+ invertedMap[value] = key;
+ }
+ }
+ return invertedMap;
+ }
- patchSettings() {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
-
- // Patch openTab to detect when a tab is opened
- const removeMonkeyPatchForSetting = around(this.app.setting, {
- openTab: (next: (tab: SettingTab) => void) => {
- return function(this: Setting, tab: SettingTab) {
- next.call(this, tab);
- if (tab && tab.id === 'community-plugins') {
- if(self.observedTab!==tab)
- {
- self.observeTab(tab);
- }
- }
- };
- },
- onClose: (next: () => void) => {
- return function (this: Setting) {
- const result = next.call(this);
- // closing settings pane
- self.disconnectObservers();
- return result;
- };
- }
- });
+ patchSettings() {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const self = this;
+
+ // Patch openTab to detect when a tab is opened
+ const removeMonkeyPatchForSetting = around(this.app.setting, {
+ openTab: (next: (tab: SettingTab) => void) => {
+ return function(this: Setting, tab: SettingTab) {
+ next.call(this, tab);
+ if (tab && tab.id === 'community-plugins') {
+ if(self.observedTab!==tab)
+ {
+ self.observeTab(tab);
+ }
+ }
+ };
+ },
+ onClose: (next: () => void) => {
+ return function (this: Setting) {
+ const result = next.call(this);
+ // closing settings pane
+ self.disconnectObservers();
+ return result;
+ };
+ }
+ });
- // Register the cleanup for openTab patch
- this.register(removeMonkeyPatchForSetting);
+ // Register the cleanup for openTab patch
+ this.register(removeMonkeyPatchForSetting);
- // Monkey patch for uninstallPlugin
- const removeMonkeyPatchForPlugins = around(this.app.plugins, {
- uninstallPlugin: (next: (pluginId: string) => Promise) => {
- return async function (this: Plugins, pluginId: string): Promise {
- await next.call(this, pluginId);
- // Triggered when pluginId has been uninstalled
- if (self.settings.automatic_remove && self.settings.annotations.hasOwnProperty(pluginId)) {
- // If automatic_remove is enabled and there is an annotation, remove the annotation
- delete self.settings.annotations[pluginId];
- self.debouncedSaveAnnotations();
- }
- };
- },
- });
+ // Monkey patch for uninstallPlugin
+ const removeMonkeyPatchForPlugins = around(this.app.plugins, {
+ uninstallPlugin: (next: (pluginId: string) => Promise) => {
+ return async function (this: Plugins, pluginId: string): Promise {
+ await next.call(this, pluginId);
+ // Triggered when pluginId has been uninstalled
+ if (self.settings.automatic_remove && self.settings.annotations.hasOwnProperty(pluginId)) {
+ // If automatic_remove is enabled and there is an annotation, remove the annotation
+ delete self.settings.annotations[pluginId];
+ self.debouncedSaveAnnotations();
+ }
+ };
+ },
+ });
- // Register the patch to ensure it gets cleaned up
- this.register(removeMonkeyPatchForPlugins);
- }
+ // Register the patch to ensure it gets cleaned up
+ this.register(removeMonkeyPatchForPlugins);
+ }
- async observeTab(tab: SettingTab) {
- // eslint-disable-next-line @typescript-eslint/no-this-alias
- const self = this;
+ async observeTab(tab: SettingTab) {
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
+ const self = this;
- // Monkey patch for uninstallPlugin
- const removeMonkeyPatchForUpdateSearch = around(tab, {
- renderInstalledPlugin: (next: (
- pluginManifest: PluginManifest,
- containerEl:HTMLElement,
- nameMatch: boolean | null,
- authorMatch: boolean | null,
- descriptionMatch: boolean | null
- ) => void ) => {
+ // Monkey patch for uninstallPlugin
+ const removeMonkeyPatchForUpdateSearch = around(tab, {
+ renderInstalledPlugin: (next: (
+ pluginManifest: PluginManifest,
+ containerEl:HTMLElement,
+ nameMatch: boolean | null,
+ authorMatch: boolean | null,
+ descriptionMatch: boolean | null
+ ) => void ) => {
- 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);
+ 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);
- // Add your custom code for personal annotations here
- const annotation = self.settings.annotations[pluginManifest.id];
- if (annotation) {
- if(containerEl && containerEl.lastElementChild)
- {
- self.addAnnotation(containerEl.lastElementChild)
- }
- }
- };
- }
- });
+ // Add your custom code for personal annotations here
+ const annotation = self.settings.annotations[pluginManifest.id];
+ if (annotation) {
+ if(containerEl && containerEl.lastElementChild)
+ {
+ self.addAnnotation(containerEl.lastElementChild)
+ }
+ }
+ };
+ }
+ });
- // // Register the patch to ensure it gets cleaned up
- this.register(removeMonkeyPatchForUpdateSearch);
+ // // Register the patch to ensure it gets cleaned up
+ this.register(removeMonkeyPatchForUpdateSearch);
- if(!this.mutationObserver) {
- this.observedTab = tab;
+ if(!this.mutationObserver) {
+ this.observedTab = tab;
- const observer = new MutationObserver(async () => {
+ const observer = new MutationObserver(async () => {
// force reload - this is convenient because since the loading of the plugin
// there could be changes in the settings due to synchronization among devices
// which only happens after the plugin is loaded
await this.loadSettings();
- this.addIcon(tab);
- this.addAnnotations(tab);
- });
+ this.addIcon(tab);
+ this.addAnnotations(tab);
+ });
- observer.observe(tab.containerEl, { childList: true, subtree: false });
- this.mutationObserver = observer;
- }
+ observer.observe(tab.containerEl, { childList: true, subtree: false });
+ this.mutationObserver = observer;
+ }
// force reload - this is convenient because since the loading of the plugin
// there could be changes in the settings due to synchronization among devices
// which only happens after the plugin is loaded
await this.loadSettings();
- // Initial call to add comments to already present plugins
- this.addIcon(tab);
- this.addAnnotations(tab);
- }
+ // Initial call to add comments to already present plugins
+ this.addIcon(tab);
+ this.addAnnotations(tab);
+ }
- disconnectObservers() {
- if (this.mutationObserver) {
- this.mutationObserver.disconnect();
- this.mutationObserver = null;
- this.observedTab = null;
- }
- }
+ disconnectObservers() {
+ if (this.mutationObserver) {
+ this.mutationObserver.disconnect();
+ this.mutationObserver = null;
+ this.observedTab = null;
+ }
+ }
- // Helper function to parse links and add click listeners
- handleAnnotationLinks(element: HTMLElement) {
- const links = element.querySelectorAll('a');
- links.forEach(link => {
+ // Helper function to parse links and add click listeners
+ handleAnnotationLinks(element: HTMLElement) {
+ const links = element.querySelectorAll('a');
+ links.forEach(link => {
console.log(element);
- link.addEventListener('click', (event) => {
- event.preventDefault();
- const href = link.getAttribute('href');
+ link.addEventListener('click', (event) => {
+ event.preventDefault();
+ const href = link.getAttribute('href');
if (href) {
- this.app.workspace.openLinkText(href, '', false);
- this.app.setting.close(); // Close the settings pane when a link is clicked
- }
- });
- });
- }
+ this.app.workspace.openLinkText(href, '', false);
+ this.app.setting.close(); // Close the settings pane when a link is clicked
+ }
+ });
+ });
+ }
- create_label(): HTMLSpanElement | null {
- const label = Platform.isMobile ? this.settings.label_mobile : this.settings.label_desktop;
- if(label.trim() === "") {
- return null;
- } else {
- const span = document.createElement('span');
- span.innerHTML = label;
- span.classList.add('plugin-comment-label');
- return span;
- }
- }
+ create_label(): HTMLSpanElement | null {
+ const label = Platform.isMobile ? this.settings.label_mobile : this.settings.label_desktop;
+ if(label.trim() === "") {
+ return null;
+ } else {
+ const span = document.createElement('span');
+ span.innerHTML = label;
+ span.classList.add('plugin-comment-label');
+ return span;
+ }
+ }
- async renderAnnotation(annotation_div: HTMLElement, annoType:AnnotationType, desc:string) {
- annotation_div.innerText = '';
- switch(annoType) {
- case AnnotationType.text: {
- const p = document.createElement('p');
- p.dir = 'auto';
- const label = this.create_label();
- if(label) {
- p.appendChild(label);
- p.appendText(desc);
- }
- else {
- p.innerText = desc;
- }
- annotation_div.appendChild(p);
- break;
- }
- case AnnotationType.html: {
- const label = Platform.isMobile ? this.settings.label_mobile : this.settings.label_desktop;
- const desc_with_label = desc.replace(/\$\{label\}/g, label);
- annotation_div.innerHTML = desc_with_label;
- this.handleAnnotationLinks(annotation_div);
- break;
- }
- case AnnotationType.markdown: {
- const label = Platform.isMobile ? this.settings.label_mobile : this.settings.label_desktop;
- const desc_with_label = label + desc;
- await MarkdownRenderer.renderMarkdown(desc_with_label, annotation_div, '', this);
- this.handleAnnotationLinks(annotation_div);
- break;
- }
- }
- }
+ async renderAnnotation(annotation_div: HTMLElement, annoType:AnnotationType, desc:string) {
+ annotation_div.innerText = '';
+ switch(annoType) {
+ case AnnotationType.text: {
+ const p = document.createElement('p');
+ p.dir = 'auto';
+ const label = this.create_label();
+ if(label) {
+ p.appendChild(label);
+ p.appendText(desc);
+ }
+ else {
+ p.innerText = desc;
+ }
+ annotation_div.appendChild(p);
+ break;
+ }
+ case AnnotationType.html: {
+ const label = Platform.isMobile ? this.settings.label_mobile : this.settings.label_desktop;
+ const desc_with_label = desc.replace(/\$\{label\}/g, label);
+ annotation_div.innerHTML = desc_with_label;
+ this.handleAnnotationLinks(annotation_div);
+ break;
+ }
+ case AnnotationType.markdown: {
+ const label = Platform.isMobile ? this.settings.label_mobile : this.settings.label_desktop;
+ const desc_with_label = label + desc;
+ await MarkdownRenderer.renderMarkdown(desc_with_label, annotation_div, '', this);
+ this.handleAnnotationLinks(annotation_div);
+ break;
+ }
+ }
+ }
- configureAnnotation(annotation_container:HTMLDivElement,annotation_div:HTMLDivElement,pluginId:string,pluginName:string) {
-
- if(this.settings.editable) {
- annotation_div.contentEditable = 'true';
- annotation_div.classList.add('plugin-comment-annotation-editable');
- } else {
- annotation_div.contentEditable = 'false';
- annotation_div.classList.remove('plugin-comment-annotation-editable');
- }
+ configureAnnotation(annotation_container:HTMLDivElement,annotation_div:HTMLDivElement,pluginId:string,pluginName:string) {
+
+ if(this.settings.editable) {
+ annotation_div.contentEditable = 'true';
+ annotation_div.classList.add('plugin-comment-annotation-editable');
+ } else {
+ annotation_div.contentEditable = 'false';
+ annotation_div.classList.remove('plugin-comment-annotation-editable');
+ }
- const placeholder = (this.settings.label_placeholder).replace(/\$\{plugin_name\}/g, pluginName);
+ const placeholder = (this.settings.label_placeholder).replace(/\$\{plugin_name\}/g, pluginName);
- let isPlaceholder = this.settings.annotations[pluginId] ? false : true;
- let annotationDesc:string;
- let annoType:AnnotationType;
-
- if(!isPlaceholder && isPluginAnnotation(this.settings.annotations[pluginId])) {
- const annotation = this.settings.annotations[pluginId];
- annotationDesc = annotation.desc;
- annoType = annotation.type;
- } else {
- annotationDesc = placeholder.trim();
- annoType = AnnotationType.html;
-
- annotation_div.classList.add('plugin-comment-placeholder');
- if (this.settings.hide_placeholders) { // if it is a placeholder
- if(this.settings.editable) { // if fields can be edited, set the placeholder tag
- annotation_container.classList.add('plugin-comment-placeholder');
- } else { // if fields cannot be edited, just simply hide placeholders
- annotation_container.classList.add('plugin-comment-hidden');
- }
- }
- }
+ let isPlaceholder = this.settings.annotations[pluginId] ? false : true;
+ let annotationDesc:string;
+ let annoType:AnnotationType;
+
+ if(!isPlaceholder && isPluginAnnotation(this.settings.annotations[pluginId])) {
+ const annotation = this.settings.annotations[pluginId];
+ annotationDesc = annotation.desc;
+ annoType = annotation.type;
+ } else {
+ annotationDesc = placeholder.trim();
+ annoType = AnnotationType.html;
+
+ annotation_div.classList.add('plugin-comment-placeholder');
+ if (this.settings.hide_placeholders) { // if it is a placeholder
+ if(this.settings.editable) { // if fields can be edited, set the placeholder tag
+ annotation_container.classList.add('plugin-comment-placeholder');
+ } else { // if fields cannot be edited, just simply hide placeholders
+ annotation_container.classList.add('plugin-comment-hidden');
+ }
+ }
+ }
- // Initial render
- this.renderAnnotation(annotation_div,annoType,annotationDesc);
+ // Initial render
+ this.renderAnnotation(annotation_div,annoType,annotationDesc);
- // Handle mousedown event to check if a link was clicked
+ // Handle mousedown event to check if a link was clicked
let clickedLink = false;
- annotation_div.addEventListener('mousedown', (event:MouseEvent) => {
+ annotation_div.addEventListener('mousedown', (event:MouseEvent) => {
console.log("MOUSE DOWN");
if(!this.settings.editable) { return; }
if (event.target && (event.target as HTMLElement).tagName === 'A') {
@@ -516,258 +516,258 @@ export default class PluginsAnnotations extends Plugin {
event.stopPropagation();
});
- // Remove placeholder class when user starts typing
- annotation_div.addEventListener('focus', (event:FocusEvent) => {
+ // Remove placeholder class when user starts typing
+ annotation_div.addEventListener('focus', (event:FocusEvent) => {
console.log("FOCUS");
- if(!this.settings.editable) { return; }
- if (isPlaceholder) {
- if (this.settings.delete_placeholder_string_on_insertion) {
- annotation_div.innerText = '';
- }
- annotation_div.classList.remove('plugin-comment-placeholder');
- if (this.settings.hide_placeholders) {
- // we remove 'plugin-comment-placeholder' only when 'this.settings.hide_placeholders' is true
- // when 'this.settings.hide_placeholders' is false, the class is not set and does not need to be removed.
- annotation_container.classList.remove('plugin-comment-placeholder');
- }
-
- const text = annotation_div.innerText; // text without html markup
- annotation_div.innerText = text; // this removes all html markup for editing
+ if(!this.settings.editable) { return; }
+ if (isPlaceholder) {
+ if (this.settings.delete_placeholder_string_on_insertion) {
+ annotation_div.innerText = '';
+ }
+ annotation_div.classList.remove('plugin-comment-placeholder');
+ if (this.settings.hide_placeholders) {
+ // we remove 'plugin-comment-placeholder' only when 'this.settings.hide_placeholders' is true
+ // when 'this.settings.hide_placeholders' is false, the class is not set and does not need to be removed.
+ annotation_container.classList.remove('plugin-comment-placeholder');
+ }
+
+ const text = annotation_div.innerText; // text without html markup
+ annotation_div.innerText = text; // this removes all html markup for editing
- // Force a DOM reflow by reading the offsetHeight (or another property)
- annotation_div.offsetHeight;
+ // Force a DOM reflow by reading the offsetHeight (or another property)
+ annotation_div.offsetHeight;
- const range = document.createRange();
- range.selectNodeContents(annotation_div);
- const selection = window.getSelection();
- if (selection) {
- selection.removeAllRanges();
- selection.addRange(range);
- }
- } else {
- // Only update annotation_div.innerText if not clicking on a link
- if (!clickedLink) {
- let preamble;
- switch(annoType) {
- case AnnotationType.html:
- preamble = 'html:';
- break;
- case AnnotationType.markdown:
- preamble = 'markdown:';
- break;
- case AnnotationType.text:
- preamble = 'text:';
- break;
- default:
- preamble = 'markdown:';
- }
+ const range = document.createRange();
+ range.selectNodeContents(annotation_div);
+ const selection = window.getSelection();
+ if (selection) {
+ selection.removeAllRanges();
+ selection.addRange(range);
+ }
+ } else {
+ // Only update annotation_div.innerText if not clicking on a link
+ if (!clickedLink) {
+ let preamble;
+ switch(annoType) {
+ case AnnotationType.html:
+ preamble = 'html:';
+ break;
+ case AnnotationType.markdown:
+ preamble = 'markdown:';
+ break;
+ case AnnotationType.text:
+ preamble = 'text:';
+ break;
+ default:
+ preamble = 'markdown:';
+ }
- annotation_div.innerText = preamble + '\n' + annotationDesc;
- }
- }
- });
+ annotation_div.innerText = preamble + '\n' + annotationDesc;
+ }
+ }
+ });
- // Save the comment on input change and update inputTriggered status
- annotation_div.addEventListener('input', (event: Event) => {
- if(!this.settings.editable) return;
- isPlaceholder = false;
- });
+ // Save the comment on input change and update inputTriggered status
+ annotation_div.addEventListener('input', (event: Event) => {
+ if(!this.settings.editable) return;
+ isPlaceholder = false;
+ });
- // Add placeholder class back if no changes are made
- annotation_div.addEventListener('blur', (event:FocusEvent) => {
- if(!this.settings.editable) { return; }
+ // Add placeholder class back if no changes are made
+ annotation_div.addEventListener('blur', (event:FocusEvent) => {
+ if(!this.settings.editable) { return; }
- const {annoType: type, annoDesc: content} = parseAnnotation(annotation_div.innerText.trim());
+ const {annoType: type, annoDesc: content} = parseAnnotation(annotation_div.innerText.trim());
- if (isPlaceholder || content === '') { // placeholder
- annotation_div.innerHTML = placeholder;
- delete this.settings.annotations[pluginId];
- annotation_div.classList.add('plugin-comment-placeholder');
- if (this.settings.hide_placeholders) {
- annotation_container.classList.add('plugin-comment-placeholder');
- }
- isPlaceholder = true;
- annotationDesc = '';
- annoType = AnnotationType.html;
- } else {
- isPlaceholder = false;
+ if (isPlaceholder || content === '') { // placeholder
+ annotation_div.innerHTML = placeholder;
+ delete this.settings.annotations[pluginId];
+ annotation_div.classList.add('plugin-comment-placeholder');
+ if (this.settings.hide_placeholders) {
+ annotation_container.classList.add('plugin-comment-placeholder');
+ }
+ isPlaceholder = true;
+ annotationDesc = '';
+ annoType = AnnotationType.html;
+ } else {
+ isPlaceholder = false;
- annotationDesc = content.trim();
- annoType = type;
-
- this.settings.annotations[pluginId] = {
- desc: annotationDesc,
- name: pluginName,
- type: type,
- };
- annotation_div.classList.remove('plugin-comment-placeholder');
+ annotationDesc = content.trim();
+ annoType = type;
+
+ this.settings.annotations[pluginId] = {
+ desc: annotationDesc,
+ name: pluginName,
+ type: type,
+ };
+ annotation_div.classList.remove('plugin-comment-placeholder');
- this.renderAnnotation(annotation_div,type,content);
- }
- this.debouncedSaveAnnotations();
- });
- }
+ this.renderAnnotation(annotation_div,type,content);
+ }
+ this.debouncedSaveAnnotations();
+ });
+ }
- async addIcon(tab: SettingTab) {
- // Add new icon to the existing icons container
- const headingContainer = tab.containerEl.querySelector('.setting-item-heading .setting-item-control');
- if (headingContainer) {
- const svg_unlocked = '';
- const svg_locked ='';
+ async addIcon(tab: SettingTab) {
+ // Add new icon to the existing icons container
+ const headingContainer = tab.containerEl.querySelector('.setting-item-heading .setting-item-control');
+ if (headingContainer) {
+ const svg_unlocked = '';
+ const svg_locked ='';
- const newIcon = document.createElement('div');
- newIcon.classList.add('clickable-icon', 'extra-setting-button');
+ const newIcon = document.createElement('div');
+ newIcon.classList.add('clickable-icon', 'extra-setting-button');
console.log('EDITABLE',this.settings.editable);
- if(this.settings.editable) {
- newIcon.setAttribute('aria-label', 'Click to lock personal annotations');
- newIcon.innerHTML = svg_unlocked;
- } else {
- newIcon.setAttribute('aria-label', 'Click to be able to edit personal annotations');
- newIcon.innerHTML = svg_locked;
- }
+ if(this.settings.editable) {
+ newIcon.setAttribute('aria-label', 'Click to lock personal annotations');
+ newIcon.innerHTML = svg_unlocked;
+ } else {
+ newIcon.setAttribute('aria-label', 'Click to be able to edit personal annotations');
+ newIcon.innerHTML = svg_locked;
+ }
- newIcon.addEventListener('click', (event:MouseEvent) => {
+ newIcon.addEventListener('click', (event:MouseEvent) => {
this.settings.editable = !this.settings.editable;
this.debouncedSaveAnnotations();
- if(this.settings.editable) {
- newIcon.setAttribute('aria-label', 'Click to lock personal annotations');
- newIcon.innerHTML = svg_unlocked;
- } else {
- newIcon.setAttribute('aria-label', 'Click to unlock personal annotations');
- newIcon.innerHTML = svg_locked;
+ if(this.settings.editable) {
+ newIcon.setAttribute('aria-label', 'Click to lock personal annotations');
+ newIcon.innerHTML = svg_unlocked;
+ } else {
+ newIcon.setAttribute('aria-label', 'Click to unlock personal annotations');
+ newIcon.innerHTML = svg_locked;
- }
- const plugins = tab.containerEl.querySelectorAll('.plugin-comment-annotation');
- plugins.forEach((div:Element) => {
- if (div instanceof HTMLDivElement) {
- if(this.settings.editable) {
- div.contentEditable = 'true';
- div.classList.add('plugin-comment-annotation-editable');
- } else {
- div.contentEditable = 'false';
- div.classList.remove('plugin-comment-annotation-editable');
- }
- }
- });
+ }
+ const plugins = tab.containerEl.querySelectorAll('.plugin-comment-annotation');
+ plugins.forEach((div:Element) => {
+ if (div instanceof HTMLDivElement) {
+ if(this.settings.editable) {
+ div.contentEditable = 'true';
+ div.classList.add('plugin-comment-annotation-editable');
+ } else {
+ div.contentEditable = 'false';
+ div.classList.remove('plugin-comment-annotation-editable');
+ }
+ }
+ });
- // Select all div elements that have both 'plugin-comment' and 'plugin-comment-placeholder' classes
- const placeholders = document.querySelectorAll(!this.settings.editable ? 'div.plugin-comment.plugin-comment-placeholder' : 'div.plugin-comment.plugin-comment-hidden');
+ // Select all div elements that have both 'plugin-comment' and 'plugin-comment-placeholder' classes
+ const placeholders = document.querySelectorAll(!this.settings.editable ? 'div.plugin-comment.plugin-comment-placeholder' : 'div.plugin-comment.plugin-comment-hidden');
- // Loop through each plugin for which the placeholder is shown
- placeholders.forEach((el) => {
- if(this.settings.editable) {
- // Add the 'plugin-comment-placeholder' class
- el.classList.add('plugin-comment-placeholder');
- // Remove the 'plugin-comment-hidden' class
- el.classList.remove('plugin-comment-hidden');
- } else {
- // Add the 'plugin-comment-hidden' class
- el.classList.add('plugin-comment-hidden');
- // Remove the 'plugin-comment-placeholder' class
- el.classList.remove('plugin-comment-placeholder');
- }
-
- });
- });
+ // Loop through each plugin for which the placeholder is shown
+ placeholders.forEach((el) => {
+ if(this.settings.editable) {
+ // Add the 'plugin-comment-placeholder' class
+ el.classList.add('plugin-comment-placeholder');
+ // Remove the 'plugin-comment-hidden' class
+ el.classList.remove('plugin-comment-hidden');
+ } else {
+ // Add the 'plugin-comment-hidden' class
+ el.classList.add('plugin-comment-hidden');
+ // Remove the 'plugin-comment-placeholder' class
+ el.classList.remove('plugin-comment-placeholder');
+ }
+
+ });
+ });
- headingContainer.appendChild(newIcon);
- }
- }
+ headingContainer.appendChild(newIcon);
+ }
+ }
- addAnnotation(plugin: Element) {
- const settingItemInfo = plugin.querySelector('.setting-item-info');
- if (settingItemInfo) {
- const pluginNameDiv = plugin.querySelector('.setting-item-name');
- const pluginName = pluginNameDiv ? pluginNameDiv.textContent : null;
+ addAnnotation(plugin: Element) {
+ const settingItemInfo = plugin.querySelector('.setting-item-info');
+ if (settingItemInfo) {
+ const pluginNameDiv = plugin.querySelector('.setting-item-name');
+ const pluginName = pluginNameDiv ? pluginNameDiv.textContent : 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;
- }
+ const pluginId = this.pluginNameToIdMap[pluginName];
+ if (!pluginId) {
+ console.warn(`Plugin ID not found for plugin name: ${pluginName}`);
+ return;
+ }
- 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 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 annotation_div = document.createElement('div');
- annotation_div.className = 'plugin-comment-annotation';
+ const annotation_div = document.createElement('div');
+ annotation_div.className = 'plugin-comment-annotation';
- this.configureAnnotation(annotation_container,annotation_div,pluginId,pluginName);
+ this.configureAnnotation(annotation_container,annotation_div,pluginId,pluginName);
- annotation_container.appendChild(annotation_div);
- descriptionDiv.appendChild(annotation_container);
- }
- }
- }
- }
+ annotation_container.appendChild(annotation_div);
+ descriptionDiv.appendChild(annotation_container);
+ }
+ }
+ }
+ }
- addAnnotations(tab: SettingTab) {
- const pluginsContainer = tab.containerEl.querySelector('.installed-plugins-container');
- if (!pluginsContainer) return;
+ addAnnotations(tab: SettingTab) {
+ const pluginsContainer = tab.containerEl.querySelector('.installed-plugins-container');
+ if (!pluginsContainer) return;
- const plugins = pluginsContainer.querySelectorAll('.setting-item');
- plugins.forEach(plugin => {
- this.addAnnotation(plugin);
- });
- }
+ const plugins = pluginsContainer.querySelectorAll('.setting-item');
+ plugins.forEach(plugin => {
+ this.addAnnotation(plugin);
+ });
+ }
- debouncedSaveAnnotations(timeout_ms = 250) {
- if (this.saveTimeout) {
- clearTimeout(this.saveTimeout);
- }
-
- this.saveTimeout = window.setTimeout(() => {
- this.saveSettings();
- this.saveTimeout = null;
- }, timeout_ms);
- }
+ debouncedSaveAnnotations(timeout_ms = 250) {
+ if (this.saveTimeout) {
+ clearTimeout(this.saveTimeout);
+ }
+
+ this.saveTimeout = window.setTimeout(() => {
+ this.saveSettings();
+ this.saveTimeout = null;
+ }, timeout_ms);
+ }
- removeCommentsFromTab() {
- if (this.observedTab) {
- const commentElements = this.observedTab.containerEl.querySelectorAll('.plugin-comment');
- commentElements.forEach(element => {
- element.remove();
- });
- }
- }
+ removeCommentsFromTab() {
+ if (this.observedTab) {
+ const commentElements = this.observedTab.containerEl.querySelectorAll('.plugin-comment');
+ commentElements.forEach(element => {
+ element.remove();
+ });
+ }
+ }
- onunload() {
- // console.log('Unloading Plugins Annotations');
+ onunload() {
+ // console.log('Unloading Plugins Annotations');
- // Remove all comments
- this.removeCommentsFromTab();
+ // Remove all comments
+ this.removeCommentsFromTab();
- // Just in case, disconnect observers if they still exist
- this.disconnectObservers();
- }
+ // Just in case, disconnect observers if they still exist
+ this.disconnectObservers();
+ }
- getUninstalledPlugins(): PluginAnnotationDict {
- const installedPluginIds = new Set(Object.keys(this.app.plugins.manifests));
- const uninstalledPlugins: PluginAnnotationDict = {};
+ getUninstalledPlugins(): PluginAnnotationDict {
+ const installedPluginIds = new Set(Object.keys(this.app.plugins.manifests));
+ const uninstalledPlugins: PluginAnnotationDict = {};
- for (const pluginId of sortPluginAnnotationsByName(this.settings.annotations)) {
- if (!installedPluginIds.has(pluginId)) {
- uninstalledPlugins[pluginId] = this.settings.annotations[pluginId];
- }
- }
- return uninstalledPlugins;
- }
+ for (const pluginId of sortPluginAnnotationsByName(this.settings.annotations)) {
+ if (!installedPluginIds.has(pluginId)) {
+ uninstalledPlugins[pluginId] = this.settings.annotations[pluginId];
+ }
+ }
+ return uninstalledPlugins;
+ }
}
diff --git a/src/manageAnnotations.ts b/src/manageAnnotations.ts
index b6baeab..673ec83 100644
--- a/src/manageAnnotations.ts
+++ b/src/manageAnnotations.ts
@@ -9,133 +9,133 @@ import { PluginAnnotationDict } from "types";
let isWriting = false;
export async function handleMarkdownFilePathChange(plugin: PluginsAnnotations, filepath: string): Promise {
- const file = plugin.app.vault.getAbstractFileByPath(filepath);
+ const file = plugin.app.vault.getAbstractFileByPath(filepath);
- const {base} = parseFilePath(filepath);
+ const {base} = parseFilePath(filepath);
- if (!file) {
- const message = createFragment((frag) => {
- frag.appendText('The file ');
+ if (!file) {
+ const message = createFragment((frag) => {
+ frag.appendText('The file ');
- frag.createEl('strong', {
- text: base
- });
+ frag.createEl('strong', {
+ text: base
+ });
- frag.appendText(' does not exist. Do you want to create it?');
- });
+ frag.appendText(' does not exist. Do you want to create it?');
+ });
- // File doesn't exist, ask user if they want to create it
- const createFile = await showConfirmationDialog(plugin.app, 'Create file', message);
- if (!createFile) return false;
- } else {
- // File exists, ask user if they want to overwrite it
+ // File doesn't exist, ask user if they want to create it
+ const createFile = await showConfirmationDialog(plugin.app, 'Create file', message);
+ if (!createFile) return false;
+ } else {
+ // File exists, ask user if they want to overwrite it
- const {base} = parseFilePath(filepath);
-
- const message = createFragment((frag) => {
- frag.appendText('The file ');
+ const {base} = parseFilePath(filepath);
+
+ const message = createFragment((frag) => {
+ frag.appendText('The file ');
- if (Platform.isDesktopApp) {
- const fileLink = frag.createEl('a', {
- text: base,
- href: '#',
- });
- fileLink.addEventListener('click', (e) => {
- e.preventDefault(); // Prevent the default anchor behavior
- // Open the folder in the system's default file explorer
+ if (Platform.isDesktopApp) {
+ const fileLink = frag.createEl('a', {
+ text: base,
+ href: '#',
+ });
+ fileLink.addEventListener('click', (e) => {
+ e.preventDefault(); // Prevent the default anchor behavior
+ // Open the folder in the system's default file explorer
- window.require('electron').remote.shell.showItemInFolder(makePosixPathOScompatible(joinPaths(plugin.getVaultPath(),filepath))); // Adjust as necessary
- });
- } else {
- frag.createEl('strong', {
- text: base
- });
- }
- frag.appendText(' already exists. Do you want to replace the file with your personal annotations about the installed plugins? If you reply yes, the existing file is moved to the trash.');
- });
-
- const overwriteFile = await showConfirmationDialog(plugin.app, 'Overwrite file', message);
- if (!overwriteFile) return false;
- await plugin.app.vault.adapter.trashSystem(file.path);
- }
- return true;
+ window.require('electron').remote.shell.showItemInFolder(makePosixPathOScompatible(joinPaths(plugin.getVaultPath(),filepath))); // Adjust as necessary
+ });
+ } else {
+ frag.createEl('strong', {
+ text: base
+ });
+ }
+ frag.appendText(' already exists. Do you want to replace the file with your personal annotations about the installed plugins? If you reply yes, the existing file is moved to the trash.');
+ });
+
+ const overwriteFile = await showConfirmationDialog(plugin.app, 'Overwrite file', message);
+ if (!overwriteFile) return false;
+ await plugin.app.vault.adapter.trashSystem(file.path);
+ }
+ return true;
}
export async function readAnnotationsFromMdFile(plugin: PluginsAnnotations): Promise {
- if(isWriting) return;
+ if(isWriting) return;
- const filePath = plugin.settings.markdown_file_path;
+ const filePath = plugin.settings.markdown_file_path;
- const file = plugin.app.vault.getFileByPath(filePath);
+ const file = plugin.app.vault.getFileByPath(filePath);
- if(!file) {
- // If the file does not exist but we have annotation in memory, write them down
- writeAnnotationsToMdFile(plugin);
- return;
- }
+ if(!file) {
+ // If the file does not exist but we have annotation in memory, write them down
+ writeAnnotationsToMdFile(plugin);
+ return;
+ }
- try {
- const md_content = await plugin.app.vault.read(file);
+ try {
+ const md_content = await plugin.app.vault.read(file);
- try {
- const md_content_parsed = parse(md_content) as PluginAnnotationDict;
- plugin.settings.annotations = md_content_parsed;
- } catch(error) {
- if (error instanceof SyntaxError) {
- console.error("Syntax error:", error);
- } else {
- console.error("Unexpected error:", error);
- }
- }
- } catch (error) {
- console.error('Failed to read annotations from file:', error);
- return;
- }
+ try {
+ const md_content_parsed = parse(md_content) as PluginAnnotationDict;
+ plugin.settings.annotations = md_content_parsed;
+ } catch(error) {
+ if (error instanceof SyntaxError) {
+ console.error("Syntax error:", error);
+ } else {
+ console.error("Unexpected error:", error);
+ }
+ }
+ } catch (error) {
+ console.error('Failed to read annotations from file:', error);
+ return;
+ }
}
export async function writeAnnotationsToMdFile(plugin: PluginsAnnotations) {
- if(!plugin.pluginNameToIdMap) return;
- const filePath = plugin.settings.markdown_file_path;
- if(filePath === "") return;
- const annotations = plugin.settings.annotations;
- // if(Object.keys(annotations).length === 0) return;
+ if(!plugin.pluginNameToIdMap) return;
+ const filePath = plugin.settings.markdown_file_path;
+ if(filePath === "") return;
+ const annotations = plugin.settings.annotations;
+ // if(Object.keys(annotations).length === 0) return;
- const header = 'Make changes only within the annotation blocks marked by and . Changes made anywhere else will be overwritten.\n'
+ const header = 'Make changes only within the annotation blocks marked by and . Changes made anywhere else will be overwritten.\n'
- const content: string[] = [header];
- for (const pluginId in annotations) {
- content.push(`# ${annotations[pluginId].name}\n\n\n\n\n${annotations[pluginId].desc}\n\n`);
- }
- const content_concatenated = content.join('\n');
+ const content: string[] = [header];
+ for (const pluginId in annotations) {
+ content.push(`# ${annotations[pluginId].name}\n\n\n\n\n${annotations[pluginId].desc}\n\n`);
+ }
+ const content_concatenated = content.join('\n');
- try {
- let file = await getFileCaseInsensitive(plugin.app.vault,filePath);
-
- if (file!==null && file.path !== filePath) {
- // remove the existing file, whose name is written with different case
- // the md file will be recreated with the correct name
- await plugin.app.vault.adapter.remove(file.path);
- file = null;
- }
+ try {
+ let file = await getFileCaseInsensitive(plugin.app.vault,filePath);
+
+ if (file!==null && file.path !== filePath) {
+ // remove the existing file, whose name is written with different case
+ // the md file will be recreated with the correct name
+ await plugin.app.vault.adapter.remove(file.path);
+ file = null;
+ }
- if (!file) {
- try {
- const {dir} = parseFilePath(filePath);
- await createFolderIfNotExists(plugin.app.vault,dir);
- } catch (error) {
- console.error('Failed to create folder for Markdown file with annotations:', error);
- return;
- }
- isWriting = true;
- file = await plugin.app.vault.create(filePath, content_concatenated);
- isWriting = false;
- } else {
- isWriting = true;
- await plugin.app.vault.modify(file as TFile, content_concatenated);
- isWriting = false;
- }
- } catch (error) {
- console.error('Failed to write Markdown file with annotations:', error);
- return;
- }
+ if (!file) {
+ try {
+ const {dir} = parseFilePath(filePath);
+ await createFolderIfNotExists(plugin.app.vault,dir);
+ } catch (error) {
+ console.error('Failed to create folder for Markdown file with annotations:', error);
+ return;
+ }
+ isWriting = true;
+ file = await plugin.app.vault.create(filePath, content_concatenated);
+ isWriting = false;
+ } else {
+ isWriting = true;
+ await plugin.app.vault.modify(file as TFile, content_concatenated);
+ isWriting = false;
+ }
+ } catch (error) {
+ console.error('Failed to write Markdown file with annotations:', error);
+ return;
+ }
}
diff --git a/src/settings_tab.ts b/src/settings_tab.ts
index c0ab1e1..9d223de 100644
--- a/src/settings_tab.ts
+++ b/src/settings_tab.ts
@@ -9,501 +9,501 @@ import { parseFilePath, FileSuggestion, downloadJson, showConfirmationDialog } f
declare const moment: typeof import('moment');
export class PluginsAnnotationsSettingTab extends PluginSettingTab {
- plugin: PluginsAnnotations;
-
- constructor(app: App, plugin: PluginsAnnotations) {
- super(app, plugin);
- this.plugin = plugin;
- }
-
- createUninstalledPluginSettings(containerEl: HTMLElement) {
- const uninstalledPlugins:PluginAnnotationDict = this.plugin.getUninstalledPlugins();
-
- const heading = new Setting(containerEl).setName('Personal annotations of no longer installed community plugins').setHeading();
- const headingEl = heading.settingEl;
-
- new Setting(containerEl)
- .setName('Automatically remove personal annotations of uninstalled plugins:')
- .setDesc('If this option is enabled, whenever a plugin is uninstalled, the \
- attached personal annotation is automatically removed. \
- If this option is disabled, you can still manually remove the personal \
- annotations of any plugin that is no longer installed. \
- The list of the no longer installed plugins is shown below, when the list is not empty.')
- .addToggle(toggle => toggle
- .setValue(this.plugin.settings.automatic_remove)
- .onChange(async (value: boolean) => {
- this.plugin.settings.automatic_remove = value;
- this.plugin.debouncedSaveAnnotations();
- }));
-
- // Check if uninstalledPlugins is empty
- if (Object.keys(uninstalledPlugins).length === 0) return;
-
- const list_uninstalled_label = new Setting(containerEl)
- .setName('List of no longer installed plugins:')
- .setDesc('If you plan to reinstall the plugin in the future, \
- it is recommended not to remove your annotations, as you can reuse them later.');
-
- // Iterate over uninstalled plugins and add settings to the new subcontainer
- Object.keys({...uninstalledPlugins}).forEach(pluginId => {
- const pluginSetting = new Setting(containerEl)
- .setName(`Plugin ${uninstalledPlugins[pluginId].name}`)
- .addButton(button => button
- .setButtonText('Delete')
- .setCta()
- .onClick(async () => {
- delete this.plugin.settings.annotations[pluginId];
- delete uninstalledPlugins[pluginId];
- pluginSetting.settingEl.remove();
- this.plugin.debouncedSaveAnnotations();
-
- // If no more uninstalled plugins, remove the section container
- if (Object.keys(uninstalledPlugins).length === 0) {
- headingEl.remove();
- list_uninstalled_label.settingEl.remove();
- }
- }));
- // Render the annotation inside the temporary div
- this.plugin.renderAnnotation(pluginSetting.descEl, uninstalledPlugins[pluginId].type, uninstalledPlugins[pluginId].desc);
- pluginSetting.descEl.classList.add('plugin-comment-annotation');
- pluginSetting.settingEl.classList.add('plugin-comment-uninstalled');
- });
- }
-
- async display(): Promise {
- const createPluginsPaneFragment = (): DocumentFragment => {
- return createFragment((frag) => {
- const em = frag.createEl('em');
- const link = frag.createEl('a', { href: '#', text: 'Community plugins'});
- link.onclick = () => {
- this.app.setting.openTabById('community-plugins');
- };
- em.appendChild(link);
- });
- };
-
- // Load annotations first
- await this.plugin.loadSettings();
-
- // Clean container in the preference pane
- const containerEl = this.containerEl;
- containerEl.empty();
-
- /* ====== Instructions ====== */
-
- new Setting(containerEl).setName('Instructions').setHeading();
-
- const instructions_frag = createFragment((frag) => {
- const div = document.createElement('div');
- div.classList.add('plugin-comment-instructions');
-
- const p1 = document.createElement('p');
- p1.appendText('To add or edit your personal annotations for the installed plugins, go to the ');
- p1.appendChild(createPluginsPaneFragment());
- p1.appendText(' pane and click over the annotation fields to edit their content');
- div.appendChild(p1);
-
- const p2 = document.createElement('p2');
- p2.innerHTML = "You can enter rich text notes using Markdown (recommended) and HTML. \
- Markdown annotations will be displayed as Obsidian renders Markdown text. \
- The annotation type can be selected by starting the annotation text with a line containing one \
- of the following options:\
-
\
-
markdown:
\
-
html:
\
-
text:
\
-
\
- If the first line of annotation text contains none of the options above, \
- the default markdown: is assumed.";
- div.appendChild(p2);
-
- const p3 = document.createElement('p');
- p3.innerHTML = "In Markdown annotations, you can directly link notes inside your \
- vault by adding links such as [[My notes/Review of plugin XYZ|my plugin note]].";
- div.appendChild(p3);
-
- const p4 = document.createElement('p');
- p4.innerHTML = "When editing HTML annotations, use the placeholder ${label} to \
- display the annotation label at the chosen location."
- div.appendChild(p4);
-
- frag.appendChild(div);
- });
-
- new Setting(containerEl).setName(instructions_frag);
-
- /* ==== Storage ==== */
-
- new Setting(containerEl).setName('Storage').setHeading();
-
- // Add new setting for storing annotations in a Markdown file
- const toggle_md_file = new Setting(containerEl)
- .setName('Store annotations in a Markdown file:')
- .setDesc('With this option enabled, you can select a Markdown file in your vault to \
- contain your personal annotations for the installed plugins. This feature is intended \
- for power users who prefer to edit annotations directly from a Markdown file. \
- A second advantage of this mode is that if you use links to some of your notes in \
- the vault, those links will be automatically updated if your notes are later renamed.');
-
- let file_path_field_control: TextComponent;
- let md_filepath_error_div: HTMLDivElement;
- // Add new setting for markdown file path
- const file_path_field = new Setting(containerEl)
- .setName('Markdown File Path')
- .setDesc(createFragment((frag) => {
- frag.appendText('Markdown file where the plugins\' annotations are stored (e.g, 00 Meta/Misc/Plugins annotations.md).');
- md_filepath_error_div = frag.createDiv({text: 'Error: the filename must end with .md extension.', cls: "mod-warning" });
- md_filepath_error_div.style.display = 'none';
- }))
- .addText(text => {
-
- let processingChange = false;
-
- file_path_field_control = text;
-
- text.setPlaceholder('E.g.: 00 Meta/Plugins annotations.md');
- text.setValue(this.plugin.settings.markdown_file_path);
-
- const inputEl = text.inputEl;
- const fileSuggestion = new FileSuggestion(this.app, inputEl);
-
- const updateVaultFiles = () => {
- if(fileSuggestion) {
- fileSuggestion.setSuggestions(this.app.vault.getFiles().filter((f) => f.extension === "md"));
- }
- }
-
- updateVaultFiles();
-
- const onChangeHandler = async (event: Event) => {
- if(processingChange) {
- return;
- } else {
- processingChange = true;
- }
-
- let filepath = inputEl.value;
-
- if(filepath!==this.plugin.settings.markdown_file_path) { // if the path has changed
-
- if(filepath.trim()==='') {
- this.plugin.settings.markdown_file_path = '';
- text.setValue(this.plugin.settings.markdown_file_path);
- processingChange = false;
- this.plugin.debouncedSaveAnnotations();
- return;
- }
-
- filepath = normalizePath(filepath);
-
- if (parseFilePath(filepath).ext !== '.md') {
- md_filepath_error_div.style.display = '';
- text.setValue(this.plugin.settings.markdown_file_path);
- processingChange = false;
- return;
- }
-
- md_filepath_error_div.style.display = 'none';
- const answer = await handleMarkdownFilePathChange(this.plugin, filepath);
- if(answer) {
- this.plugin.settings.markdown_file_path = filepath;
- await this.plugin.saveSettings();
- updateVaultFiles();
- }
- text.setValue(this.plugin.settings.markdown_file_path);
- }
-
- processingChange = false;
- };
-
- inputEl.addEventListener('keydown', (event) => {
- if (event.key === 'Enter') {
- event.preventDefault();
- onChangeHandler(event);
- }
- });
-
- inputEl.addEventListener('blur', onChangeHandler);
-
- // Use change explicitly instead of onChange because onChange
- // reacts to events of type `input` instead of `change`
- inputEl.addEventListener('change', onChangeHandler);
- });
-
- file_path_field.settingEl.style.display = this.plugin.settings.markdown_file_path === '' ? 'none' : '';
-
- toggle_md_file.addToggle(toggle => toggle
- .setValue(this.plugin.settings.markdown_file_path !== '')
- .onChange(async (value: boolean) => {
- if (value) {
- file_path_field.settingEl.style.display = '';
- this.plugin.settings.markdown_file_path = file_path_field_control.getValue();
- } else {
- file_path_field.settingEl.style.display = 'none';
- this.plugin.settings.markdown_file_path = '';
- }
- this.plugin.debouncedSaveAnnotations();
- }));
-
- // Append the settings
- containerEl.appendChild(toggle_md_file.settingEl);
- containerEl.appendChild(file_path_field.settingEl);
-
- /* ==== Display heading ==== */
-
- new Setting(containerEl).setName('Display').setHeading();
-
- if (Platform.isMobile) {
- new Setting(containerEl)
- .setName('Annotation label:')
- .setDesc('Choose the annotation label for the mobile version of Obsidian. \
- Use HTML code if you want to format it. Enter an empty string if \
- you want to hide the label.')
- .addText(text => {
- text.setPlaceholder('Annotation label');
- text.setValue(this.plugin.settings.label_mobile);
- text.onChange(async (value: string) => {
- this.plugin.settings.label_mobile = value;
- this.plugin.debouncedSaveAnnotations();
- })});
- } else {
- new Setting(containerEl)
- .setName('Annotation label:')
- .setDesc('Choose the annotation label for the desktop version of Obsidian. \
- Use HTML code if you want to format it. Enter an empty string if you want \
- to hide the label.')
- .addText(text => {
- text.setPlaceholder('Annotation label');
- text.setValue(this.plugin.settings.label_desktop);
- text.onChange(async (value: string) => {
- this.plugin.settings.label_desktop = value;
- this.plugin.debouncedSaveAnnotations();
- })});
- }
-
- new Setting(containerEl)
- .setName('Placeholder label:')
- .setDesc(createFragment((frag) => {
- frag.appendText('Choose the label appearing where no user annotation is provied yet. Use ');
- frag.createEl('em').appendText('${plugin_name}');
- frag.appendText(' to refer to the plugin name.')}))
- .addText(text => {
- text.setPlaceholder('Annotation label');
- text.setValue(this.plugin.settings.label_placeholder);
- text.onChange(async (value: string) => {
- this.plugin.settings.label_placeholder = value;
- this.plugin.debouncedSaveAnnotations();
- })});
-
- new Setting(containerEl)
- .setName('Hide empty annotations:')
- .setDesc(createFragment((frag) => {
- frag.appendText('If this option is enabled, only annotations set by the user \
- will be shown. If you want to insert an annotation to a plugin for the first \
- time, hover with the mouse over the chosen plugin in the ');
- frag.appendChild(createPluginsPaneFragment());
- frag.appendText(' pane. The annotation field will appear automatically.');
-
- if (Platform.isMobile) {
- const p = frag.createEl('p');
- const warning = p.createEl('span', {
- text: 'On mobile devices, you can hover over plugins with your finger instead of using the mouse.',
- });
- warning.classList.add('mod-warning');
- frag.appendChild(p);
- }
- }))
- .addToggle(toggle => toggle
- .setValue(this.plugin.settings.hide_placeholders)
- .onChange(async (value: boolean) => {
- this.plugin.settings.hide_placeholders = value;
- this.plugin.debouncedSaveAnnotations();
- }));
-
- new Setting(containerEl)
- .setName('Delete placeholder text when inserting a new annotation:')
- .setDesc('If this option is enabled, the placeholder text will be deleted \
- automatically when you start typing a new annotation. If disabled, \
- the placeholder text will be selected for easier replacement. \
- This is a minor customization.')
- .addToggle(toggle => toggle
- .setValue(this.plugin.settings.delete_placeholder_string_on_insertion)
- .onChange(async (value: boolean) => {
- this.plugin.settings.delete_placeholder_string_on_insertion = value;
- this.plugin.debouncedSaveAnnotations();
- }));
-
-
- /* ====== Backups ====== */
- this.createBackupManager(containerEl);
-
- /* ====== Personal annotations of no longer installed community plugins ====== */
- this.createUninstalledPluginSettings(containerEl);
- }
-
- createBackupManager(containerEl: HTMLElement) {
- new Setting(containerEl)
- .setName('Backups')
- .setHeading();
-
- const export_label = (Platform.isDesktopApp) ? ' Use the export and import buttons \
- to copy the current settings and annnotations to an external file and, vicevera, \
- to restore them from an external file.' : '';
-
- const backup_settings = new Setting(containerEl)
- .setName('Create a backup copy of your current settings and annotations:')
- .setDesc('Use the backup button to create an internal backup copy. \
- You can customize the names of existing backups by clicking on their names once you have created them.'
- + export_label)
- .addButton(button => button
- .setButtonText('Create Backup')
- .setCta()
- .onClick(async () => {
- const backupName = 'Untitled backup';
- await this.plugin.backupSettings(backupName,this.plugin.settings);
- this.display();
- })
- );
-
- backup_settings.controlEl.classList.add('plugin-comment-export-buttons');
-
- if (Platform.isDesktopApp) {
- backup_settings.addButton(button => button
- .setButtonText('Export')
- .setCta()
- .onClick(async () => {
- downloadJson({...this.plugin.settings, backups: []});
- })
- );
-
- backup_settings.addButton(button => button
- .setButtonText('Import')
- .setCta()
- .onClick(async () => {
- // Create an input element to upload a file
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.json'; // Only allow JSON files
-
- input.onchange = async () => {
- const file = input.files?.[0];
- if (file) {
- // Read the file as text
- const reader = new FileReader();
- reader.onload = async (event) => {
- try {
- // Parse the JSON file
- const importedData = JSON.parse(event.target?.result as string);
-
- // Validate and merge the imported settings
- if (importedData && typeof importedData === 'object') {
- const forceSave = true;
- await this.plugin.loadSettings(importedData,forceSave);
- new Notice('Settings successfully imported.');
- this.display(); // Refresh the display to reflect the imported annotations
- } else {
- alert('Invalid settings file.');
- }
- } catch (error) {
- console.error('Error importing settings:', error);
- alert('Failed to import settings. Please ensure the file is valid.');
- }
- };
- reader.readAsText(file);
- }
- };
-
- // Trigger the file input click
- input.click();
- })
- );
- }
-
- // List Existing Backups
- if (this.plugin.settings.backups.length > 0) {
-
- // Sort the backups by date (most recent first)
- this.plugin.settings.backups.sort((a, b) => b.date.getTime() - a.date.getTime());
-
- // Create a wrapper div for the table
- const backupTableContainer = containerEl.createDiv({ cls: 'setting-item' });
-
- const tableDiv = backupTableContainer.createDiv({ cls: 'plugin-comment-backup-table' });
-
- // Create the header row
- const headerRow = tableDiv.createDiv({ cls: 'plugin-comment-backup-table-row header' });
- headerRow.createDiv({ cls: 'plugin-comment-backup-table-cell', text: 'Backup name' + (Platform.isMobileApp ? '' : ' (click to edit)') });
- headerRow.createDiv({ cls: 'plugin-comment-backup-table-cell', text: 'Created on' });
- headerRow.createDiv({ cls: 'plugin-comment-backup-table-cell', text: '' });
-
- this.plugin.settings.backups.forEach((backup, index) => {
- const rowDiv = tableDiv.createDiv({ cls: 'plugin-comment-backup-table-row' });
-
- // Backup name cell
- const nameCell = rowDiv.createDiv({ cls: 'plugin-comment-backup-table-cell plugin-comment-backup-name' });
- const nameDiv = nameCell.createDiv({ text: backup.name, attr: { contenteditable: 'true' } });
-
- // Handle saving the updated name when editing is complete
- nameDiv.addEventListener('blur', async () => {
- const newName = nameDiv.textContent?.trim() || 'Unnamed Backup';
- this.plugin.settings.backups[index].name = newName;
- this.plugin.debouncedSaveAnnotations();
- });
-
- // Handle the Enter key to finish editing
- nameDiv.addEventListener('keydown', (event) => {
- if (event.key === 'Enter') {
- event.preventDefault();
- nameDiv.blur(); // Trigger the blur event to save the name
- }
- });
-
- // Created on cell
- const dateCell = rowDiv.createDiv({ cls: 'plugin-comment-backup-table-cell' });
- dateCell.setText(moment(backup.date).format('YYYY-MM-DD HH:mm:ss'));
-
- // Add Restore and Delete buttons to the last cell
- const actionCell = rowDiv.createDiv({ cls: 'plugin-comment-backup-table-cell plugin-comment-backup-buttons' });
- actionCell.createEl('button', { text: 'Restore', cls: 'mod-cta' })
- .addEventListener('click', async () => {
- const answer = await showConfirmationDialog(this.plugin.app, 'Delete backup',
- createFragment((frag) => {
- frag.appendText('You are about to restore the settings from the backup named ');
- frag.createEl('strong',{text: this.plugin.settings.backups[index].name});
- frag.appendText(' created on ');
- frag.createEl('strong',{text: moment(this.plugin.settings.backups[index].date).format('YYYY-MM-DD HH:mm:ss')});
- frag.appendText('. If you proceed, the current settings will be overwritten with those from the backup. \
- If you want to keep a copy of the current settings, make a backup before proceeding.\
- Do you want to proceed restoring the seettings from the backup?');
- }));
- if(answer) {
- const backups = [...this.plugin.settings.backups]; // store a copy of the backups before restoring the old settings
- await this.plugin.loadSettings(structuredClone(this.plugin.settings.backups[index].settings));
- this.plugin.settings.backups = backups; // restore the copy of the backups
- await this.plugin.saveSettings(); // save the restored settings with the backups
- new Notice(`Annotations restored from backup "${backup.name}"`);
- this.display(); // Refresh the display to reflect the restored annotations
- }
- });
-
- actionCell.createEl('button', { text: 'Delete', cls: 'mod-cta' })
- .addEventListener('click', async () => {
- const answer = await showConfirmationDialog(this.plugin.app, 'Delete backup',
- createFragment((frag) => {
- frag.appendText('You are about to delete the backup named ');
- frag.createEl('strong',{text: this.plugin.settings.backups[index].name});
- frag.appendText(' created on ');
- frag.createEl('strong',{text: moment(this.plugin.settings.backups[index].date).format('YYYY-MM-DD HH:mm:ss')});
- frag.appendText('. Do you want to continue?');
- }));
- if(answer) {
- this.plugin.settings.backups.splice(index, 1);
- await this.plugin.saveSettings();
- this.display(); // Refresh the display to remove the deleted backup
- }
- });
- });
- }
- }
+ plugin: PluginsAnnotations;
+
+ constructor(app: App, plugin: PluginsAnnotations) {
+ super(app, plugin);
+ this.plugin = plugin;
+ }
+
+ createUninstalledPluginSettings(containerEl: HTMLElement) {
+ const uninstalledPlugins:PluginAnnotationDict = this.plugin.getUninstalledPlugins();
+
+ const heading = new Setting(containerEl).setName('Personal annotations of no longer installed community plugins').setHeading();
+ const headingEl = heading.settingEl;
+
+ new Setting(containerEl)
+ .setName('Automatically remove personal annotations of uninstalled plugins:')
+ .setDesc('If this option is enabled, whenever a plugin is uninstalled, the \
+ attached personal annotation is automatically removed. \
+ If this option is disabled, you can still manually remove the personal \
+ annotations of any plugin that is no longer installed. \
+ The list of the no longer installed plugins is shown below, when the list is not empty.')
+ .addToggle(toggle => toggle
+ .setValue(this.plugin.settings.automatic_remove)
+ .onChange(async (value: boolean) => {
+ this.plugin.settings.automatic_remove = value;
+ this.plugin.debouncedSaveAnnotations();
+ }));
+
+ // Check if uninstalledPlugins is empty
+ if (Object.keys(uninstalledPlugins).length === 0) return;
+
+ const list_uninstalled_label = new Setting(containerEl)
+ .setName('List of no longer installed plugins:')
+ .setDesc('If you plan to reinstall the plugin in the future, \
+ it is recommended not to remove your annotations, as you can reuse them later.');
+
+ // Iterate over uninstalled plugins and add settings to the new subcontainer
+ Object.keys({...uninstalledPlugins}).forEach(pluginId => {
+ const pluginSetting = new Setting(containerEl)
+ .setName(`Plugin ${uninstalledPlugins[pluginId].name}`)
+ .addButton(button => button
+ .setButtonText('Delete')
+ .setCta()
+ .onClick(async () => {
+ delete this.plugin.settings.annotations[pluginId];
+ delete uninstalledPlugins[pluginId];
+ pluginSetting.settingEl.remove();
+ this.plugin.debouncedSaveAnnotations();
+
+ // If no more uninstalled plugins, remove the section container
+ if (Object.keys(uninstalledPlugins).length === 0) {
+ headingEl.remove();
+ list_uninstalled_label.settingEl.remove();
+ }
+ }));
+ // Render the annotation inside the temporary div
+ this.plugin.renderAnnotation(pluginSetting.descEl, uninstalledPlugins[pluginId].type, uninstalledPlugins[pluginId].desc);
+ pluginSetting.descEl.classList.add('plugin-comment-annotation');
+ pluginSetting.settingEl.classList.add('plugin-comment-uninstalled');
+ });
+ }
+
+ async display(): Promise {
+ const createPluginsPaneFragment = (): DocumentFragment => {
+ return createFragment((frag) => {
+ const em = frag.createEl('em');
+ const link = frag.createEl('a', { href: '#', text: 'Community plugins'});
+ link.onclick = () => {
+ this.app.setting.openTabById('community-plugins');
+ };
+ em.appendChild(link);
+ });
+ };
+
+ // Load annotations first
+ await this.plugin.loadSettings();
+
+ // Clean container in the preference pane
+ const containerEl = this.containerEl;
+ containerEl.empty();
+
+ /* ====== Instructions ====== */
+
+ new Setting(containerEl).setName('Instructions').setHeading();
+
+ const instructions_frag = createFragment((frag) => {
+ const div = document.createElement('div');
+ div.classList.add('plugin-comment-instructions');
+
+ const p1 = document.createElement('p');
+ p1.appendText('To add or edit your personal annotations for the installed plugins, go to the ');
+ p1.appendChild(createPluginsPaneFragment());
+ p1.appendText(' pane and click over the annotation fields to edit their content');
+ div.appendChild(p1);
+
+ const p2 = document.createElement('p2');
+ p2.innerHTML = "You can enter rich text notes using Markdown (recommended) and HTML. \
+ Markdown annotations will be displayed as Obsidian renders Markdown text. \
+ The annotation type can be selected by starting the annotation text with a line containing one \
+ of the following options:\
+
\
+
markdown:
\
+
html:
\
+
text:
\
+
\
+ If the first line of annotation text contains none of the options above, \
+ the default markdown: is assumed.";
+ div.appendChild(p2);
+
+ const p3 = document.createElement('p');
+ p3.innerHTML = "In Markdown annotations, you can directly link notes inside your \
+ vault by adding links such as [[My notes/Review of plugin XYZ|my plugin note]].";
+ div.appendChild(p3);
+
+ const p4 = document.createElement('p');
+ p4.innerHTML = "When editing HTML annotations, use the placeholder ${label} to \
+ display the annotation label at the chosen location."
+ div.appendChild(p4);
+
+ frag.appendChild(div);
+ });
+
+ new Setting(containerEl).setName(instructions_frag);
+
+ /* ==== Storage ==== */
+
+ new Setting(containerEl).setName('Storage').setHeading();
+
+ // Add new setting for storing annotations in a Markdown file
+ const toggle_md_file = new Setting(containerEl)
+ .setName('Store annotations in a Markdown file:')
+ .setDesc('With this option enabled, you can select a Markdown file in your vault to \
+ contain your personal annotations for the installed plugins. This feature is intended \
+ for power users who prefer to edit annotations directly from a Markdown file. \
+ A second advantage of this mode is that if you use links to some of your notes in \
+ the vault, those links will be automatically updated if your notes are later renamed.');
+
+ let file_path_field_control: TextComponent;
+ let md_filepath_error_div: HTMLDivElement;
+ // Add new setting for markdown file path
+ const file_path_field = new Setting(containerEl)
+ .setName('Markdown File Path')
+ .setDesc(createFragment((frag) => {
+ frag.appendText('Markdown file where the plugins\' annotations are stored (e.g, 00 Meta/Misc/Plugins annotations.md).');
+ md_filepath_error_div = frag.createDiv({text: 'Error: the filename must end with .md extension.', cls: "mod-warning" });
+ md_filepath_error_div.style.display = 'none';
+ }))
+ .addText(text => {
+
+ let processingChange = false;
+
+ file_path_field_control = text;
+
+ text.setPlaceholder('E.g.: 00 Meta/Plugins annotations.md');
+ text.setValue(this.plugin.settings.markdown_file_path);
+
+ const inputEl = text.inputEl;
+ const fileSuggestion = new FileSuggestion(this.app, inputEl);
+
+ const updateVaultFiles = () => {
+ if(fileSuggestion) {
+ fileSuggestion.setSuggestions(this.app.vault.getFiles().filter((f) => f.extension === "md"));
+ }
+ }
+
+ updateVaultFiles();
+
+ const onChangeHandler = async (event: Event) => {
+ if(processingChange) {
+ return;
+ } else {
+ processingChange = true;
+ }
+
+ let filepath = inputEl.value;
+
+ if(filepath!==this.plugin.settings.markdown_file_path) { // if the path has changed
+
+ if(filepath.trim()==='') {
+ this.plugin.settings.markdown_file_path = '';
+ text.setValue(this.plugin.settings.markdown_file_path);
+ processingChange = false;
+ this.plugin.debouncedSaveAnnotations();
+ return;
+ }
+
+ filepath = normalizePath(filepath);
+
+ if (parseFilePath(filepath).ext !== '.md') {
+ md_filepath_error_div.style.display = '';
+ text.setValue(this.plugin.settings.markdown_file_path);
+ processingChange = false;
+ return;
+ }
+
+ md_filepath_error_div.style.display = 'none';
+ const answer = await handleMarkdownFilePathChange(this.plugin, filepath);
+ if(answer) {
+ this.plugin.settings.markdown_file_path = filepath;
+ await this.plugin.saveSettings();
+ updateVaultFiles();
+ }
+ text.setValue(this.plugin.settings.markdown_file_path);
+ }
+
+ processingChange = false;
+ };
+
+ inputEl.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ onChangeHandler(event);
+ }
+ });
+
+ inputEl.addEventListener('blur', onChangeHandler);
+
+ // Use change explicitly instead of onChange because onChange
+ // reacts to events of type `input` instead of `change`
+ inputEl.addEventListener('change', onChangeHandler);
+ });
+
+ file_path_field.settingEl.style.display = this.plugin.settings.markdown_file_path === '' ? 'none' : '';
+
+ toggle_md_file.addToggle(toggle => toggle
+ .setValue(this.plugin.settings.markdown_file_path !== '')
+ .onChange(async (value: boolean) => {
+ if (value) {
+ file_path_field.settingEl.style.display = '';
+ this.plugin.settings.markdown_file_path = file_path_field_control.getValue();
+ } else {
+ file_path_field.settingEl.style.display = 'none';
+ this.plugin.settings.markdown_file_path = '';
+ }
+ this.plugin.debouncedSaveAnnotations();
+ }));
+
+ // Append the settings
+ containerEl.appendChild(toggle_md_file.settingEl);
+ containerEl.appendChild(file_path_field.settingEl);
+
+ /* ==== Display heading ==== */
+
+ new Setting(containerEl).setName('Display').setHeading();
+
+ if (Platform.isMobile) {
+ new Setting(containerEl)
+ .setName('Annotation label:')
+ .setDesc('Choose the annotation label for the mobile version of Obsidian. \
+ Use HTML code if you want to format it. Enter an empty string if \
+ you want to hide the label.')
+ .addText(text => {
+ text.setPlaceholder('Annotation label');
+ text.setValue(this.plugin.settings.label_mobile);
+ text.onChange(async (value: string) => {
+ this.plugin.settings.label_mobile = value;
+ this.plugin.debouncedSaveAnnotations();
+ })});
+ } else {
+ new Setting(containerEl)
+ .setName('Annotation label:')
+ .setDesc('Choose the annotation label for the desktop version of Obsidian. \
+ Use HTML code if you want to format it. Enter an empty string if you want \
+ to hide the label.')
+ .addText(text => {
+ text.setPlaceholder('Annotation label');
+ text.setValue(this.plugin.settings.label_desktop);
+ text.onChange(async (value: string) => {
+ this.plugin.settings.label_desktop = value;
+ this.plugin.debouncedSaveAnnotations();
+ })});
+ }
+
+ new Setting(containerEl)
+ .setName('Placeholder label:')
+ .setDesc(createFragment((frag) => {
+ frag.appendText('Choose the label appearing where no user annotation is provied yet. Use ');
+ frag.createEl('em').appendText('${plugin_name}');
+ frag.appendText(' to refer to the plugin name.')}))
+ .addText(text => {
+ text.setPlaceholder('Annotation label');
+ text.setValue(this.plugin.settings.label_placeholder);
+ text.onChange(async (value: string) => {
+ this.plugin.settings.label_placeholder = value;
+ this.plugin.debouncedSaveAnnotations();
+ })});
+
+ new Setting(containerEl)
+ .setName('Hide empty annotations:')
+ .setDesc(createFragment((frag) => {
+ frag.appendText('If this option is enabled, only annotations set by the user \
+ will be shown. If you want to insert an annotation to a plugin for the first \
+ time, hover with the mouse over the chosen plugin in the ');
+ frag.appendChild(createPluginsPaneFragment());
+ frag.appendText(' pane. The annotation field will appear automatically.');
+
+ if (Platform.isMobile) {
+ const p = frag.createEl('p');
+ const warning = p.createEl('span', {
+ text: 'On mobile devices, you can hover over plugins with your finger instead of using the mouse.',
+ });
+ warning.classList.add('mod-warning');
+ frag.appendChild(p);
+ }
+ }))
+ .addToggle(toggle => toggle
+ .setValue(this.plugin.settings.hide_placeholders)
+ .onChange(async (value: boolean) => {
+ this.plugin.settings.hide_placeholders = value;
+ this.plugin.debouncedSaveAnnotations();
+ }));
+
+ new Setting(containerEl)
+ .setName('Delete placeholder text when inserting a new annotation:')
+ .setDesc('If this option is enabled, the placeholder text will be deleted \
+ automatically when you start typing a new annotation. If disabled, \
+ the placeholder text will be selected for easier replacement. \
+ This is a minor customization.')
+ .addToggle(toggle => toggle
+ .setValue(this.plugin.settings.delete_placeholder_string_on_insertion)
+ .onChange(async (value: boolean) => {
+ this.plugin.settings.delete_placeholder_string_on_insertion = value;
+ this.plugin.debouncedSaveAnnotations();
+ }));
+
+
+ /* ====== Backups ====== */
+ this.createBackupManager(containerEl);
+
+ /* ====== Personal annotations of no longer installed community plugins ====== */
+ this.createUninstalledPluginSettings(containerEl);
+ }
+
+ createBackupManager(containerEl: HTMLElement) {
+ new Setting(containerEl)
+ .setName('Backups')
+ .setHeading();
+
+ const export_label = (Platform.isDesktopApp) ? ' Use the export and import buttons \
+ to copy the current settings and annnotations to an external file and, vicevera, \
+ to restore them from an external file.' : '';
+
+ const backup_settings = new Setting(containerEl)
+ .setName('Create a backup copy of your current settings and annotations:')
+ .setDesc('Use the backup button to create an internal backup copy. \
+ You can customize the names of existing backups by clicking on their names once you have created them.'
+ + export_label)
+ .addButton(button => button
+ .setButtonText('Create Backup')
+ .setCta()
+ .onClick(async () => {
+ const backupName = 'Untitled backup';
+ await this.plugin.backupSettings(backupName,this.plugin.settings);
+ this.display();
+ })
+ );
+
+ backup_settings.controlEl.classList.add('plugin-comment-export-buttons');
+
+ if (Platform.isDesktopApp) {
+ backup_settings.addButton(button => button
+ .setButtonText('Export')
+ .setCta()
+ .onClick(async () => {
+ downloadJson({...this.plugin.settings, backups: []});
+ })
+ );
+
+ backup_settings.addButton(button => button
+ .setButtonText('Import')
+ .setCta()
+ .onClick(async () => {
+ // Create an input element to upload a file
+ const input = document.createElement('input');
+ input.type = 'file';
+ input.accept = '.json'; // Only allow JSON files
+
+ input.onchange = async () => {
+ const file = input.files?.[0];
+ if (file) {
+ // Read the file as text
+ const reader = new FileReader();
+ reader.onload = async (event) => {
+ try {
+ // Parse the JSON file
+ const importedData = JSON.parse(event.target?.result as string);
+
+ // Validate and merge the imported settings
+ if (importedData && typeof importedData === 'object') {
+ const forceSave = true;
+ await this.plugin.loadSettings(importedData,forceSave);
+ new Notice('Settings successfully imported.');
+ this.display(); // Refresh the display to reflect the imported annotations
+ } else {
+ alert('Invalid settings file.');
+ }
+ } catch (error) {
+ console.error('Error importing settings:', error);
+ alert('Failed to import settings. Please ensure the file is valid.');
+ }
+ };
+ reader.readAsText(file);
+ }
+ };
+
+ // Trigger the file input click
+ input.click();
+ })
+ );
+ }
+
+ // List Existing Backups
+ if (this.plugin.settings.backups.length > 0) {
+
+ // Sort the backups by date (most recent first)
+ this.plugin.settings.backups.sort((a, b) => b.date.getTime() - a.date.getTime());
+
+ // Create a wrapper div for the table
+ const backupTableContainer = containerEl.createDiv({ cls: 'setting-item' });
+
+ const tableDiv = backupTableContainer.createDiv({ cls: 'plugin-comment-backup-table' });
+
+ // Create the header row
+ const headerRow = tableDiv.createDiv({ cls: 'plugin-comment-backup-table-row header' });
+ headerRow.createDiv({ cls: 'plugin-comment-backup-table-cell', text: 'Backup name' + (Platform.isMobileApp ? '' : ' (click to edit)') });
+ headerRow.createDiv({ cls: 'plugin-comment-backup-table-cell', text: 'Created on' });
+ headerRow.createDiv({ cls: 'plugin-comment-backup-table-cell', text: '' });
+
+ this.plugin.settings.backups.forEach((backup, index) => {
+ const rowDiv = tableDiv.createDiv({ cls: 'plugin-comment-backup-table-row' });
+
+ // Backup name cell
+ const nameCell = rowDiv.createDiv({ cls: 'plugin-comment-backup-table-cell plugin-comment-backup-name' });
+ const nameDiv = nameCell.createDiv({ text: backup.name, attr: { contenteditable: 'true' } });
+
+ // Handle saving the updated name when editing is complete
+ nameDiv.addEventListener('blur', async () => {
+ const newName = nameDiv.textContent?.trim() || 'Unnamed Backup';
+ this.plugin.settings.backups[index].name = newName;
+ this.plugin.debouncedSaveAnnotations();
+ });
+
+ // Handle the Enter key to finish editing
+ nameDiv.addEventListener('keydown', (event) => {
+ if (event.key === 'Enter') {
+ event.preventDefault();
+ nameDiv.blur(); // Trigger the blur event to save the name
+ }
+ });
+
+ // Created on cell
+ const dateCell = rowDiv.createDiv({ cls: 'plugin-comment-backup-table-cell' });
+ dateCell.setText(moment(backup.date).format('YYYY-MM-DD HH:mm:ss'));
+
+ // Add Restore and Delete buttons to the last cell
+ const actionCell = rowDiv.createDiv({ cls: 'plugin-comment-backup-table-cell plugin-comment-backup-buttons' });
+ actionCell.createEl('button', { text: 'Restore', cls: 'mod-cta' })
+ .addEventListener('click', async () => {
+ const answer = await showConfirmationDialog(this.plugin.app, 'Delete backup',
+ createFragment((frag) => {
+ frag.appendText('You are about to restore the settings from the backup named ');
+ frag.createEl('strong',{text: this.plugin.settings.backups[index].name});
+ frag.appendText(' created on ');
+ frag.createEl('strong',{text: moment(this.plugin.settings.backups[index].date).format('YYYY-MM-DD HH:mm:ss')});
+ frag.appendText('. If you proceed, the current settings will be overwritten with those from the backup. \
+ If you want to keep a copy of the current settings, make a backup before proceeding.\
+ Do you want to proceed restoring the seettings from the backup?');
+ }));
+ if(answer) {
+ const backups = [...this.plugin.settings.backups]; // store a copy of the backups before restoring the old settings
+ await this.plugin.loadSettings(structuredClone(this.plugin.settings.backups[index].settings));
+ this.plugin.settings.backups = backups; // restore the copy of the backups
+ await this.plugin.saveSettings(); // save the restored settings with the backups
+ new Notice(`Annotations restored from backup "${backup.name}"`);
+ this.display(); // Refresh the display to reflect the restored annotations
+ }
+ });
+
+ actionCell.createEl('button', { text: 'Delete', cls: 'mod-cta' })
+ .addEventListener('click', async () => {
+ const answer = await showConfirmationDialog(this.plugin.app, 'Delete backup',
+ createFragment((frag) => {
+ frag.appendText('You are about to delete the backup named ');
+ frag.createEl('strong',{text: this.plugin.settings.backups[index].name});
+ frag.appendText(' created on ');
+ frag.createEl('strong',{text: moment(this.plugin.settings.backups[index].date).format('YYYY-MM-DD HH:mm:ss')});
+ frag.appendText('. Do you want to continue?');
+ }));
+ if(answer) {
+ this.plugin.settings.backups.splice(index, 1);
+ await this.plugin.saveSettings();
+ this.display(); // Refresh the display to remove the deleted backup
+ }
+ });
+ });
+ }
+ }
}
diff --git a/src/types.ts b/src/types.ts
index fecf7e2..244ee48 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,13 +3,13 @@
import { DEFAULT_SETTINGS } from "defaults";
export interface PluginAnnotation {
- name: string; // extended name of the plugin
- desc: string; // personal annontation
- type: AnnotationType; // annotation type
+ name: string; // extended name of the plugin
+ desc: string; // personal annontation
+ type: AnnotationType; // annotation type
}
export interface PluginAnnotationDict {
- [pluginId: string]: PluginAnnotation;
+ [pluginId: string]: PluginAnnotation;
}
export interface PluginBackup {
@@ -19,41 +19,41 @@ export interface PluginBackup {
}
export interface PluginsAnnotationsSettings {
- annotations: PluginAnnotationDict;
- plugins_annotations_uuid: string;
- hide_placeholders: boolean;
- delete_placeholder_string_on_insertion: boolean;
- label_mobile: string;
- label_desktop: string;
- label_placeholder: string;
- editable: boolean;
- automatic_remove: boolean;
- markdown_file_path: string;
- compatibility: string;
- backups: PluginBackup[];
+ annotations: PluginAnnotationDict;
+ plugins_annotations_uuid: string;
+ hide_placeholders: boolean;
+ delete_placeholder_string_on_insertion: boolean;
+ label_mobile: string;
+ label_desktop: string;
+ label_placeholder: string;
+ editable: boolean;
+ automatic_remove: boolean;
+ markdown_file_path: string;
+ compatibility: string;
+ backups: PluginBackup[];
}
export function isPluginsAnnotationsSettings(s:unknown): s is PluginsAnnotationsSettings {
- if (typeof s !== 'object' || s === null) {
- return false;
- }
- return 'annotations' in s
- && 'compatibility' in s && (s as PluginsAnnotationsSettings).compatibility === '1.5.0'
- && 'plugins_annotations_uuid' in s
- && (s as PluginsAnnotationsSettings).plugins_annotations_uuid === DEFAULT_SETTINGS.plugins_annotations_uuid;
+ if (typeof s !== 'object' || s === null) {
+ return false;
+ }
+ return 'annotations' in s
+ && 'compatibility' in s && (s as PluginsAnnotationsSettings).compatibility === '1.5.0'
+ && 'plugins_annotations_uuid' in s
+ && (s as PluginsAnnotationsSettings).plugins_annotations_uuid === DEFAULT_SETTINGS.plugins_annotations_uuid;
}
export function isPluginAnnotation(anno:unknown): anno is PluginAnnotation {
- if (typeof anno !== 'object' || anno === null) {
- return false;
- }
- const obj = anno as Record;
+ if (typeof anno !== 'object' || anno === null) {
+ return false;
+ }
+ const obj = anno as Record;
- const hasName = typeof obj.name === 'string';
- const hasDesc = typeof obj.desc === 'string';
- const hasType = typeof obj.type === 'string' && Object.values(AnnotationType).includes(obj.type as AnnotationType);
+ const hasName = typeof obj.name === 'string';
+ const hasDesc = typeof obj.desc === 'string';
+ const hasType = typeof obj.type === 'string' && Object.values(AnnotationType).includes(obj.type as AnnotationType);
- return hasName && hasDesc && hasType;
+ return hasName && hasDesc && hasType;
}
// Function to render the annotation based on preamble
@@ -80,15 +80,15 @@ export function parseAnnotation(text: string): {annoType: AnnotationType, annoDe
}
export enum AnnotationType {
- text = 'text',
- html = 'html',
- markdown = 'markdown',
+ text = 'text',
+ html = 'html',
+ markdown = 'markdown',
}
export interface ParsedPath {
- dir: string,
- base: string,
- filename: string,
- ext: string,
- path: string
+ dir: string,
+ base: string,
+ filename: string,
+ ext: string,
+ path: string
}
diff --git a/src/types/obsidian-augment.d.ts b/src/types/obsidian-augment.d.ts
index 2cf25d4..ce9d060 100644
--- a/src/types/obsidian-augment.d.ts
+++ b/src/types/obsidian-augment.d.ts
@@ -3,69 +3,69 @@
import 'obsidian';
declare module "obsidian" {
- interface App {
- internalPlugins: InternalPlugins;
- plugins: Plugins;
- setting: Setting;
- }
- interface Plugin {
- _loaded: boolean;
- }
- interface PluginSettingTab {
- name: string;
- }
- interface SettingTab {
- id: string;
- name: string;
- navEl: HTMLElement;
- // updateSearch(e: string): void;
- renderInstalledPlugin(
- pluginManifest: PluginManifest,
- containerEl: HTMLElement,
- nameMatch: boolean | null,
- authorMatch: boolean | null,
- descriptionMatch: boolean | null
- ): void;
- }
- interface Setting {
- onOpen(): void;
- onClose(): void;
+ interface App {
+ internalPlugins: InternalPlugins;
+ plugins: Plugins;
+ setting: Setting;
+ }
+ interface Plugin {
+ _loaded: boolean;
+ }
+ interface PluginSettingTab {
+ name: string;
+ }
+ interface SettingTab {
+ id: string;
+ name: string;
+ navEl: HTMLElement;
+ // updateSearch(e: string): void;
+ renderInstalledPlugin(
+ pluginManifest: PluginManifest,
+ containerEl: HTMLElement,
+ nameMatch: boolean | null,
+ authorMatch: boolean | null,
+ descriptionMatch: boolean | null
+ ): void;
+ }
+ interface Setting {
+ onOpen(): void;
+ onClose(): void;
- openTabById(id: string): void;
- openTab(tab: SettingTab): void;
+ openTabById(id: string): void;
+ openTab(tab: SettingTab): void;
- closeActiveTab(tab: SettingTab): void;
+ closeActiveTab(tab: SettingTab): void;
- isPluginSettingTab(tab: SettingTab): boolean;
- addSettingTab(tab: SettingTab): void;
- removeSettingTab(tab: SettingTab): void;
+ isPluginSettingTab(tab: SettingTab): boolean;
+ addSettingTab(tab: SettingTab): void;
+ removeSettingTab(tab: SettingTab): void;
- activeTab: SettingTab;
- lastTabId: string;
+ activeTab: SettingTab;
+ lastTabId: string;
- pluginTabs: PluginSettingTab[];
- settingTabs: SettingTab[];
+ pluginTabs: PluginSettingTab[];
+ settingTabs: SettingTab[];
- tabContentContainer: HTMLDivElement;
- tabHeadersEl: HTMLDivElement;
+ tabContentContainer: HTMLDivElement;
+ tabHeadersEl: HTMLDivElement;
- close(): void;
- }
-
- interface Plugins {
- manifests: Record;
- plugins: Record;
- getPlugin(id: string): Plugin;
- uninstallPlugin(pluginId: string): Promise;
+ close(): void;
}
- interface InternalPlugins {
- plugins: Record;
- getPluginById(id: string): Plugin;
- }
+ interface Plugins {
+ manifests: Record;
+ plugins: Record;
+ getPlugin(id: string): Plugin;
+ uninstallPlugin(pluginId: string): Promise;
+ }
+
+ interface InternalPlugins {
+ plugins: Record;
+ getPluginById(id: string): Plugin;
+ }
- interface AbstractInputSuggest extends PopoverSuggest {
- textInputEl: HTMLInputElement;
- }
+ interface AbstractInputSuggest extends PopoverSuggest {
+ textInputEl: HTMLInputElement;
+ }
}
\ No newline at end of file
diff --git a/src/types_legacy.ts b/src/types_legacy.ts
index acff747..f5de2e8 100644
--- a/src/types_legacy.ts
+++ b/src/types_legacy.ts
@@ -6,57 +6,57 @@ import { AnnotationType, PluginAnnotation, PluginsAnnotationsSettings } from "ty
/* VERSION 1.4 */
export interface PluginAnnotation_1_4_0 extends Omit {
- anno: string; // personal annontation
+ anno: string; // personal annontation
}
export type PluginAnnotationDict_1_4_0 = {
- [pluginId: string]: PluginAnnotation_1_4_0;
+ [pluginId: string]: PluginAnnotation_1_4_0;
}
// Extend the original interface and override the annotations property
export interface PluginsAnnotationsSettings_1_4_0 extends Omit {
- annotations: PluginAnnotationDict_1_4_0;
+ annotations: PluginAnnotationDict_1_4_0;
}
export function isSettingsFormat_1_4_0(s:unknown): s is PluginsAnnotationsSettings_1_4_0 {
- if (typeof s !== 'object' || s === null) {
- return false;
- }
- return 'annotations' in s
- && 'plugins_annotations_uuid' in s
- && (s as PluginsAnnotationsSettings_1_4_0).plugins_annotations_uuid === DEFAULT_SETTINGS_1_4_0.plugins_annotations_uuid;
+ if (typeof s !== 'object' || s === null) {
+ return false;
+ }
+ return 'annotations' in s
+ && 'plugins_annotations_uuid' in s
+ && (s as PluginsAnnotationsSettings_1_4_0).plugins_annotations_uuid === DEFAULT_SETTINGS_1_4_0.plugins_annotations_uuid;
}
// Function to render the annotation based on preamble
export function parseAnnotation_1_4_0(text: string): {type:AnnotationType,content:string} {
- const lines = text.split('\n');
- const preamble = lines[0].toLowerCase();
- const sliced = lines.slice(1).join('\n');
-
- // annotation_div.innerHTML = '';
- if (preamble.startsWith('html:')) {
- return {type: AnnotationType.html, content: sliced};
- } else if (preamble.startsWith('markdown:')) {
- return {type: AnnotationType.markdown, content: sliced.replace(/\$\{label\}/g, '')};
- } else if (preamble.startsWith('text:')) {
- return {type: AnnotationType.text, content: sliced};
- } else {
- return {type: AnnotationType.text, content: text};
- }
+ const lines = text.split('\n');
+ const preamble = lines[0].toLowerCase();
+ const sliced = lines.slice(1).join('\n');
+
+ // annotation_div.innerHTML = '';
+ if (preamble.startsWith('html:')) {
+ return {type: AnnotationType.html, content: sliced};
+ } else if (preamble.startsWith('markdown:')) {
+ return {type: AnnotationType.markdown, content: sliced.replace(/\$\{label\}/g, '')};
+ } else if (preamble.startsWith('text:')) {
+ return {type: AnnotationType.text, content: sliced};
+ } else {
+ return {type: AnnotationType.text, content: text};
+ }
}
/* VERSION 1.3 */
// For backward compatibility only with version 1.3.0 'FAA70013-38E9-4FDF-B06A-F899F6487C19'
export function isPluginAnnotationDictFormat_1_3_0(d: unknown): d is PluginAnnotationDict_1_3_0 {
- if (typeof d !== 'object' || d === null) {
- return false;
- }
- return Object.values(d).every(value => typeof value === 'string');
+ if (typeof d !== 'object' || d === null) {
+ return false;
+ }
+ return Object.values(d).every(value => typeof value === 'string');
}
export interface PluginAnnotationDict_1_3_0 {
- [pluginId: string]: string;
+ [pluginId: string]: string;
}
// Extend the original interface and override the annotations property
@@ -65,13 +65,13 @@ export interface PluginsAnnotationsSettings_1_3_0 extends Omit {
- return new Promise((resolve) => {
- const modal = new Modal(app);
- modal.titleEl.setText(title);
+ return new Promise((resolve) => {
+ const modal = new Modal(app);
+ modal.titleEl.setText(title);
- if (typeof message === 'string') {
- modal.contentEl.setText(message);
- } else {
- modal.contentEl.appendChild(message);
- }
+ if (typeof message === 'string') {
+ modal.contentEl.setText(message);
+ } else {
+ modal.contentEl.appendChild(message);
+ }
- const buttonContainer = modal.contentEl.createDiv({ cls: 'modal-button-container' });
- buttonContainer.createEl('button', { text: 'Yes', cls: 'mod-cta' }).addEventListener('click', () => {
- resolve(true);
- modal.close();
- });
- buttonContainer.createEl('button', { text: 'No' }).addEventListener('click', () => {
- resolve(false);
- modal.close();
- });
+ const buttonContainer = modal.contentEl.createDiv({ cls: 'modal-button-container' });
+ buttonContainer.createEl('button', { text: 'Yes', cls: 'mod-cta' }).addEventListener('click', () => {
+ resolve(true);
+ modal.close();
+ });
+ buttonContainer.createEl('button', { text: 'No' }).addEventListener('click', () => {
+ resolve(false);
+ modal.close();
+ });
- modal.open();
- });
+ modal.open();
+ });
}
export function makePosixPathOScompatible(posixPath:string): string {
- return posixPath.split(path.posix.sep).join(path.sep);
+ return posixPath.split(path.posix.sep).join(path.sep);
}
// Joins multiple path segments into a single normalized path.
export function joinPaths(...paths: string[]): string {
- return paths.join('/');
+ return paths.join('/');
}
export function isInstanceOfFolder(file: TAbstractFile): file is TFolder {
- return file instanceof TFolder;
+ return file instanceof TFolder;
}
export function isInstanceOfFile(file: TAbstractFile): file is TFile {
- return file instanceof TFile;
+ return file instanceof TFile;
}
export function doesFolderExist(vault: Vault, relativePath: string): boolean {
- const file: TAbstractFile | null = vault.getAbstractFileByPath(relativePath);
- return !!file && isInstanceOfFolder(file);
+ const file: TAbstractFile | null = vault.getAbstractFileByPath(relativePath);
+ return !!file && isInstanceOfFolder(file);
}
export function doesFileExist(vault: Vault, relativePath: string): boolean {
- const file: TAbstractFile | null = vault.getAbstractFileByPath(relativePath);
- return !!file && isInstanceOfFile(file);
+ const file: TAbstractFile | null = vault.getAbstractFileByPath(relativePath);
+ return !!file && isInstanceOfFile(file);
}
export async function getFileCaseInsensitive(vault: Vault, filePath: string): Promise {
- // Check if the file exists (case-insensitive check)
- const fileExists = await vault.adapter.exists(filePath);
-
- if (!fileExists) {
- return null; // File does not exist
- }
+ // Check if the file exists (case-insensitive check)
+ const fileExists = await vault.adapter.exists(filePath);
+
+ if (!fileExists) {
+ return null; // File does not exist
+ }
- // Iterate over all files and find the one with a case-insensitive match
- const normalizedFilePath = filePath.toLowerCase();
+ // Iterate over all files and find the one with a case-insensitive match
+ const normalizedFilePath = filePath.toLowerCase();
- for (const file of vault.getFiles()) {
- if (file.path.toLowerCase() === normalizedFilePath) {
- return file; // Return the matched TFile object with the correct case
- }
- }
+ for (const file of vault.getFiles()) {
+ if (file.path.toLowerCase() === normalizedFilePath) {
+ return file; // Return the matched TFile object with the correct case
+ }
+ }
- return null; // No matching file found, this shouldn't happen if exists() returned true
+ return null; // No matching file found, this shouldn't happen if exists() returned true
}
export async function createFolderIfNotExists(vault: Vault, folderPath: string) {
- if(doesFolderExist(vault,folderPath)) return;
+ if(doesFolderExist(vault,folderPath)) return;
- try {
- await vault.createFolder(folderPath);
- } catch (error) {
- throw new Error(`Failed to create folder at ${folderPath}: ${error}`);
- }
+ try {
+ await vault.createFolder(folderPath);
+ } catch (error) {
+ throw new Error(`Failed to create folder at ${folderPath}: ${error}`);
+ }
}
/* File suggestions */
export class FileSuggestion extends AbstractInputSuggest {
- private files:TFile[] = [];
+ private files:TFile[] = [];
- constructor(app: App, inputEl: HTMLInputElement, private onSelectCallback: (file: TFile) => void = (v: TFile) => {}) {
- super(app, inputEl);
- }
+ constructor(app: App, inputEl: HTMLInputElement, private onSelectCallback: (file: TFile) => void = (v: TFile) => {}) {
+ super(app, inputEl);
+ }
- doFuzzySearch(target: string, maxResults = 20, minScore = -2): TFile[] {
- if (!target || target.length < 2) return [];
- const fuzzy = prepareFuzzySearch(target);
- const matches: [TFile, SearchResult | null][] = this.files.map((c) => [c, fuzzy(c.path)]);
- // Filter out the null matches
- const validMatches = matches.filter(([, result]) => result !== null && result.score > minScore);
- // Sort the valid matches by score
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
- validMatches.sort(([, a], [, b]) => b!.score - a!.score);
- return validMatches.map((c) => c[0]).slice(0, maxResults);
- }
+ doFuzzySearch(target: string, maxResults = 20, minScore = -2): TFile[] {
+ if (!target || target.length < 2) return [];
+ const fuzzy = prepareFuzzySearch(target);
+ const matches: [TFile, SearchResult | null][] = this.files.map((c) => [c, fuzzy(c.path)]);
+ // Filter out the null matches
+ const validMatches = matches.filter(([, result]) => result !== null && result.score > minScore);
+ // Sort the valid matches by score
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ validMatches.sort(([, a], [, b]) => b!.score - a!.score);
+ return validMatches.map((c) => c[0]).slice(0, maxResults);
+ }
- getSuggestions(inputStr: string): TFile[] {
- return this.doFuzzySearch(inputStr);
- }
+ getSuggestions(inputStr: string): TFile[] {
+ return this.doFuzzySearch(inputStr);
+ }
- renderSuggestion(file: TFile, el: HTMLElement): void {
- el.setText(file.path);
- }
+ renderSuggestion(file: TFile, el: HTMLElement): void {
+ el.setText(file.path);
+ }
- selectSuggestion(selection: TFile, evt: MouseEvent | KeyboardEvent): void {
- this.onSelectCallback(selection);
- this.textInputEl.value = selection.path;
-
- // Create a custom event with additional data
- this.textInputEl.dispatchEvent(new Event('change'));
- this.textInputEl.setSelectionRange(0, 1)
- this.textInputEl.setSelectionRange(this.textInputEl.value.length,this.textInputEl.value.length)
- this.textInputEl.focus()
- this.close();
- }
+ selectSuggestion(selection: TFile, evt: MouseEvent | KeyboardEvent): void {
+ this.onSelectCallback(selection);
+ this.textInputEl.value = selection.path;
+
+ // Create a custom event with additional data
+ this.textInputEl.dispatchEvent(new Event('change'));
+ this.textInputEl.setSelectionRange(0, 1)
+ this.textInputEl.setSelectionRange(this.textInputEl.value.length,this.textInputEl.value.length)
+ this.textInputEl.focus()
+ this.close();
+ }
- setSuggestions(files:TFile[]) {
- this.files = files;
- }
+ setSuggestions(files:TFile[]) {
+ this.files = files;
+ }
}
@@ -151,24 +151,24 @@ export class FileSuggestion extends AbstractInputSuggest {
// Function to sort PluginAnnotationDict based on the name field
export function sortPluginAnnotationsByName(annotations: PluginAnnotationDict): string[] {
- // Create an array of pairs [pluginId, name]
- const pluginArray = Object.entries(annotations).map(([pluginId, annotation]) => {
- return { pluginId, name: annotation.name };
- });
+ // Create an array of pairs [pluginId, name]
+ const pluginArray = Object.entries(annotations).map(([pluginId, annotation]) => {
+ return { pluginId, name: annotation.name };
+ });
- // Sort the array based on the 'name' field
- pluginArray.sort((a, b) => a.name.localeCompare(b.name));
+ // Sort the array based on the 'name' field
+ pluginArray.sort((a, b) => a.name.localeCompare(b.name));
- return pluginArray.map(item => item.pluginId);
+ return pluginArray.map(item => item.pluginId);
}
/* Download json settings */
export function downloadJson(data: unknown, filename = 'data.json') {
- if (typeof data !== 'object' || data === null) {
- return false;
- }
+ if (typeof data !== 'object' || data === null) {
+ return false;
+ }
// Step Convert data to JSON string
const jsonStr = JSON.stringify(data, null, 2); // Pretty print with 2-space indentation