mirror of
https://github.com/alberti42/obsidian-plugins-annotations.git
synced 2026-07-22 10:10:24 +00:00
362 lines
14 KiB
TypeScript
362 lines
14 KiB
TypeScript
// settings_tab.ts
|
|
|
|
import PluginsAnnotations from "main";
|
|
import { handleMarkdownFilePathChange } from "manageAnnotations";
|
|
import { App, Platform, PluginSettingTab, Setting, TextComponent } from "obsidian";
|
|
import { PluginAnnotationDict } from "types";
|
|
import { parseFilePath, FileSuggestion } from "utils";
|
|
|
|
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<void> {
|
|
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();
|
|
|
|
/* ====== Personal annotations ====== */
|
|
|
|
new Setting(containerEl).setName('Personal annotations').setHeading();
|
|
const instructions = 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 dispalyed 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:\
|
|
<ul>\
|
|
<li>markdown:</li>\
|
|
<li>html:</li>\
|
|
<li>text:</li>\
|
|
</ul>\
|
|
If the first line of annotation text contains none of the options above, the default <em>markdown:</em> 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 <em>${label}</em> to display the <em>annotation label</em> at the chosen location."
|
|
div.appendChild(p4);
|
|
|
|
frag.appendChild(div);
|
|
});
|
|
|
|
// Append instructions right after the Annotations heading
|
|
const instructions_div = containerEl.createDiv();
|
|
instructions_div.classList.add('setting-item');
|
|
instructions_div.appendChild(instructions);
|
|
|
|
// 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('Path to the markdown file where the plugins\' annotations will be stored.');
|
|
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 vault_files = this.app.vault.getFiles().filter((f) => f.extension === "md");
|
|
|
|
const inputEl = text.inputEl;
|
|
new FileSuggestion(this.app, inputEl, vault_files);
|
|
|
|
inputEl.addEventListener('keydown', (event) => {
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
inputEl.dispatchEvent(new Event('change'));
|
|
}
|
|
});
|
|
|
|
// Use change explicitly instead of onChange because onChange
|
|
// reacts to events of type `input` instead of `change`
|
|
inputEl.addEventListener('change', async (event: Event) => {
|
|
|
|
if(processingChange) {
|
|
return;
|
|
} else {
|
|
processingChange = true;
|
|
}
|
|
|
|
const filepath = inputEl.value;
|
|
|
|
if(filepath!==this.plugin.settings.markdown_file_path) { // if the path has changed
|
|
|
|
if (parseFilePath(filepath).ext !== '.md') {
|
|
md_filepath_error_div.style.display = '';
|
|
this.plugin.settings.markdown_file_path = '';
|
|
return;
|
|
}
|
|
|
|
md_filepath_error_div.style.display = 'none';
|
|
if(await handleMarkdownFilePathChange(this.plugin, filepath)) {
|
|
this.plugin.settings.markdown_file_path = filepath;
|
|
this.plugin.debouncedSaveAnnotations();
|
|
} else {
|
|
text.setValue(this.plugin.settings.markdown_file_path);
|
|
}
|
|
}
|
|
|
|
processingChange = false;
|
|
});
|
|
});
|
|
|
|
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('Backup Annotations')
|
|
.setHeading();
|
|
|
|
// Create Backup Button
|
|
new Setting(containerEl)
|
|
.setName('Create Backup')
|
|
.setDesc('Create a new backup of your current annotations.')
|
|
.addButton(button => button
|
|
.setButtonText('Create Backup')
|
|
.setCta()
|
|
.onClick(async () => {
|
|
const backupName = prompt('Enter a name for this backup:');
|
|
if (backupName) {
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
const {backups:_,...currentSettings} = this.plugin.settings;
|
|
this.plugin.settings.backups.push({
|
|
name: backupName,
|
|
date: new Date(),
|
|
settings: { ...currentSettings }
|
|
});
|
|
this.plugin.debouncedSaveAnnotations();
|
|
this.display(); // Refresh the display to show the new backup
|
|
}
|
|
})
|
|
);
|
|
|
|
// List Existing Backups
|
|
if (this.plugin.settings.backups.length > 0) {
|
|
new Setting(containerEl)
|
|
.setName('Restore Backup')
|
|
.setDesc('Select a backup to restore your annotations.');
|
|
|
|
this.plugin.settings.backups.forEach((backup, index) => {
|
|
new Setting(containerEl)
|
|
.setName(backup.name)
|
|
.setDesc(`Created on: ${backup.date.toLocaleString()}`)
|
|
.addButton(button => button
|
|
.setButtonText('Restore')
|
|
.setCta()
|
|
.onClick(async () => {
|
|
this.plugin.settings = { backups: this.plugin.settings.backups, ...backup.settings };
|
|
this.plugin.debouncedSaveAnnotations();
|
|
alert(`Annotations restored from backup: ${backup.name}`);
|
|
this.display(); // Refresh the display to reflect the restored annotations
|
|
})
|
|
)
|
|
.addButton(button => button
|
|
.setButtonText('Delete')
|
|
.setCta()
|
|
.onClick(async () => {
|
|
this.plugin.settings.backups.splice(index, 1);
|
|
this.plugin.debouncedSaveAnnotations();
|
|
this.display(); // Refresh the display to remove the deleted backup
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|