chore: restore settings

This commit is contained in:
Aleix Soler 2025-05-24 14:14:40 +02:00
parent 632f7d8f7d
commit 23118c613c
4 changed files with 372 additions and 359 deletions

View file

@ -0,0 +1,119 @@
import { App } from 'obsidian';
import { Status } from '../../models/types';
import NoteStatus from 'main';
import { StatusService } from 'services/status-service';
import { NoteStatusSettingsUI, SettingsUICallbacks } from './settings-ui';
/**
* Controller for settings UI, handles business logic and plugin state updates
*/
export class NoteStatusSettingsController implements SettingsUICallbacks {
private app: App;
private plugin: NoteStatus;
private statusService: StatusService;
private ui: NoteStatusSettingsUI;
constructor(app: App, plugin: NoteStatus, statusService: StatusService) {
this.app = app;
this.plugin = plugin;
this.statusService = statusService;
this.ui = new NoteStatusSettingsUI(this);
}
/**
* Renders the settings interface
*/
display(containerEl: HTMLElement): void {
this.ui.render(containerEl, this.plugin.settings);
}
/**
* Handles template enable/disable toggle
*/
async onTemplateToggle(templateId: string, enabled: boolean): Promise<void> {
if (enabled) {
if (!this.plugin.settings.enabledTemplates.includes(templateId)) {
this.plugin.settings.enabledTemplates.push(templateId);
}
} else {
this.plugin.settings.enabledTemplates = this.plugin.settings.enabledTemplates.filter(
(id: string) => id !== templateId
);
}
await this.plugin.saveSettings();
}
/**
* Handles general setting changes
*/
async onSettingChange(key: string, value: any): Promise<void> {
(this.plugin.settings as any)[key] = value;
await this.plugin.saveSettings();
}
/**
* Handles custom status field changes
*/
async onCustomStatusChange(index: number, field: string, value: any): Promise<void> {
const status = this.plugin.settings.customStatuses[index];
if (!status) return;
if (field === 'name') {
const oldName = status.name;
status.name = value;
if (oldName !== status.name) {
this.plugin.settings.statusColors[status.name] =
this.plugin.settings.statusColors[oldName];
delete this.plugin.settings.statusColors[oldName];
}
} else if (field === 'color') {
this.plugin.settings.statusColors[status.name] = value;
} else {
(status as any)[field] = value;
}
await this.plugin.saveSettings();
}
/**
* Handles custom status removal
*/
async onCustomStatusRemove(index: number): Promise<void> {
const status = this.plugin.settings.customStatuses[index];
if (!status) return;
this.plugin.settings.customStatuses.splice(index, 1);
delete this.plugin.settings.statusColors[status.name];
await this.plugin.saveSettings();
// Re-render the custom statuses section
const statusList = document.querySelector('.custom-status-list') as HTMLElement;
if (statusList) {
this.ui.renderCustomStatuses(statusList, this.plugin.settings);
}
}
/**
* Handles adding new custom status
*/
async onCustomStatusAdd(): Promise<void> {
const newStatus: Status = {
name: `status${this.plugin.settings.customStatuses.length + 1}`,
icon: '⭐'
};
this.plugin.settings.customStatuses.push(newStatus);
this.plugin.settings.statusColors[newStatus.name] = '#ffffff';
await this.plugin.saveSettings();
// Re-render the custom statuses section
const statusList = document.querySelector('.custom-status-list') as HTMLElement;
if (statusList) {
this.ui.renderCustomStatuses(statusList, this.plugin.settings);
}
}
}

View file

@ -1,352 +1,23 @@
import { App, PluginSettingTab, Setting, Notice } from 'obsidian';
import { Status } from '../../models/types';
import { PREDEFINED_TEMPLATES } from '../../constants/status-templates';
import { App, PluginSettingTab } from 'obsidian';
import NoteStatus from 'main';
import { StatusService } from 'services/status-service';
import { NoteStatusSettingsController } from './settings-controller';
/**
* Settings tab for the Note Status plugin
* Settings tab for the Note Status plugin - delegates to controller
*/
export class NoteStatusSettingTab extends PluginSettingTab {
plugin: NoteStatus;
statusService: StatusService
private controller: NoteStatusSettingsController;
constructor(app: App, plugin: any, statusService: StatusService) {
super(app, plugin);
this.plugin = plugin;
this.statusService = statusService;
}
constructor(app: App, plugin: NoteStatus, statusService: StatusService) {
super(app, plugin);
this.controller = new NoteStatusSettingsController(app, plugin, statusService);
}
display(): void {
const { containerEl } = this;
containerEl.empty();
// Status Template section
this.displayTemplateSettings(containerEl.createDiv());
// UI section
new Setting(containerEl).setName('User interface').setHeading();
// Status bar settings
new Setting(containerEl)
.setName('Show status bar')
.setDesc('Display the status bar')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showStatusBar)
.onChange(async (value) => {
this.plugin.settings.showStatusBar = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Auto-hide status bar')
.setDesc('Hide the status bar when status is unknown')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.autoHideStatusBar)
.onChange(async (value) => {
this.plugin.settings.autoHideStatusBar = value;
await this.plugin.saveSettings();
}));
// File explorer settings
new Setting(containerEl)
.setName('Show status icons in file explorer')
.setDesc('Display status icons in the file explorer')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.showStatusIconsInExplorer)
.onChange(async (value) => {
this.plugin.settings.showStatusIconsInExplorer = value;
await this.plugin.saveSettings();
// Explicitly refresh explorer icons
if (value) {
this.plugin.explorerIntegration.updateAllFileExplorerIcons();
} else {
this.plugin.explorerIntegration.removeAllFileExplorerIcons();
}
}));
// NEW SETTING: Hide unknown status in explorer
new Setting(containerEl)
.setName('Hide unknown status in file explorer')
.setDesc('Hide status icons for files with unknown status in the file explorer')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.hideUnknownStatusInExplorer || false)
.onChange(async (value) => {
this.plugin.settings.hideUnknownStatusInExplorer = value;
await this.plugin.saveSettings();
// Refresh explorer icons when this setting changes
this.plugin.explorerIntegration.updateAllFileExplorerIcons();
}));
// Compact view setting
new Setting(containerEl)
.setName('Default to compact view')
.setDesc('Start the status pane in compact view by default')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.compactView || false)
.onChange(async (value) => {
this.plugin.settings.compactView = value;
await this.plugin.saveSettings();
}));
// Add option to exclude unknown status in pane view for performance
new Setting(containerEl)
.setName('Exclude unassigned notes from status pane')
.setDesc('Improves performance by excluding notes with no assigned status from the status pane. Recommended for large vaults.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.excludeUnknownStatus || false)
.onChange(async (value) => {
this.plugin.settings.excludeUnknownStatus = value;
await this.plugin.saveSettings();
// Refresh the status pane if open
const statusPane = this.app.workspace.getLeavesOfType('status-pane')[0];
if (statusPane && statusPane.view) {
this.statusService.refreshUI([]);
}
}));
new Setting(containerEl).setName('Status tag').setHeading();
// Option to use multiple statuses
new Setting(containerEl)
.setName('Enable multiple statuses')
.setDesc('Allow notes to have multiple statuses at the same time')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.useMultipleStatuses)
.onChange(async (value) => {
this.plugin.settings.useMultipleStatuses = value;
await this.plugin.saveSettings();
// Refresh UI to show multi-select or single-select options
this.statusService.refreshUI([]);
}));
// Status tag prefix
new Setting(containerEl)
.setName('Status tag prefix')
.setDesc('The YAML frontmatter tag name used for status (default: obsidian-note-status)')
.addText(text => text
.setValue(this.plugin.settings.tagPrefix)
.onChange(async (value) => {
// Don't allow empty tag prefix
if (!value.trim()) {
return;
}
this.plugin.settings.tagPrefix = value.trim();
await this.plugin.saveSettings();
this.statusService.refreshUI([]);
}));
// Status management section
new Setting(containerEl).setName('Custom statuses').setHeading();
// Option to use only custom statuses
new Setting(containerEl)
.setName('Use only custom statuses')
.setDesc('Ignore template statuses and use only the custom statuses defined below')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.useCustomStatusesOnly || false)
.onChange(async (value) => {
this.plugin.settings.useCustomStatusesOnly = value;
await this.plugin.saveSettings();
// Refresh the UI to show/hide template statuses
this.statusService.refreshUI([]);
this.display();
}));
// Custom statuses list
const statusList = containerEl.createDiv({ cls: 'custom-status-list' });
const renderStatuses = () => {
statusList.empty();
this.plugin.settings.customStatuses.forEach((status: Status, index: number) => {
const setting = new Setting(statusList)
.setName(status.name)
.setClass('status-item');
// Name field - now properly implemented
setting.addText(text => text
.setPlaceholder('Name')
.setValue(status.name)
.onChange(async (value) => {
const oldName = status.name;
status.name = value || 'unnamed';
// Update color mapping when name changes
if (oldName !== status.name) {
this.plugin.settings.statusColors[status.name] =
this.plugin.settings.statusColors[oldName];
delete this.plugin.settings.statusColors[oldName];
}
await this.plugin.saveSettings();
}));
// Icon field
setting.addText(text => text
.setPlaceholder('Icon')
.setValue(status.icon)
.onChange(async (value) => {
status.icon = value || '❓';
await this.plugin.saveSettings();
}));
// Color picker
setting.addColorPicker(colorPicker => colorPicker
.setValue(this.plugin.settings.statusColors[status.name] || '#ffffff')
.onChange(async (value) => {
this.plugin.settings.statusColors[status.name] = value;
await this.plugin.saveSettings();
}));
// Description field
setting.addText(text => text
.setPlaceholder('Description')
.setValue(status.description || '')
.onChange(async (value) => {
status.description = value;
await this.plugin.saveSettings();
}));
// Remove button
setting.addButton(button => button
.setButtonText('Remove')
.setClass('status-remove-button')
.setWarning()
.onClick(async () => {
this.plugin.settings.customStatuses.splice(index, 1);
delete this.plugin.settings.statusColors[status.name];
await this.plugin.saveSettings();
renderStatuses();
}));
});
};
renderStatuses();
// Add new status
new Setting(containerEl)
.setName('Add new status')
.setDesc('Add a custom status with a name, icon, and color')
.addButton(button => button
.setButtonText('Add Status')
.setCta()
.onClick(async () => {
const newStatus = {
name: `status${this.plugin.settings.customStatuses.length + 1}`,
icon: '⭐'
};
this.plugin.settings.customStatuses.push(newStatus);
this.plugin.settings.statusColors[newStatus.name] = '#ffffff'; // Initial white color
await this.plugin.saveSettings();
renderStatuses();
}));
}
/**
* Display template settings section
*/
private displayTemplateSettings(containerEl: HTMLElement): void {
new Setting(containerEl).setName('Status templates').setHeading();
containerEl.createEl('p', {
text: 'Enable predefined templates to quickly add common status workflows',
cls: 'setting-item-description'
});
// Create templates container
const templatesContainer = containerEl.createDiv({ cls: 'templates-container' });
// List each template with checkbox and preview
PREDEFINED_TEMPLATES.forEach(template => {
const templateEl = templatesContainer.createDiv({ cls: 'template-item' });
// Template header with checkbox and name
const headerEl = templateEl.createDiv({ cls: 'template-header' });
// Checkbox for enabling/disabling template
const isEnabled = this.plugin.settings.enabledTemplates.includes(template.id);
const checkbox = headerEl.createEl('input', {
type: 'checkbox',
cls: 'template-checkbox'
});
checkbox.checked = isEnabled;
checkbox.addEventListener('change', async () => {
if (checkbox.checked) {
// Enable template
if (!this.plugin.settings.enabledTemplates.includes(template.id)) {
this.plugin.settings.enabledTemplates.push(template.id);
}
} else {
// Disable template
this.plugin.settings.enabledTemplates = this.plugin.settings.enabledTemplates.filter(
(id: string) => id !== template.id
);
}
await this.plugin.saveSettings();
this.statusService.refreshUI([]);
});
// Template name
headerEl.createEl('span', {
text: template.name,
cls: 'template-name'
});
// Template description
templateEl.createEl('div', {
text: template.description,
cls: 'template-description'
});
// Preview statuses
const statusesEl = templateEl.createDiv({ cls: 'template-statuses' });
template.statuses.forEach(status => {
const statusEl = statusesEl.createEl('div', { cls: 'template-status-chip' });
const colorDot = statusEl.createEl('span', { cls: 'status-color-dot' });
colorDot.style.setProperty('--dot-color', status.color || '#ffffff')
// Status icon and name
statusEl.createSpan({ text: `${status.icon} ${status.name}` });
});
});
// Import/Export buttons for templates
const buttonsContainer = containerEl.createDiv({ cls: 'template-buttons' });
// Import button
const importButton = buttonsContainer.createEl('button', {
text: 'Import template',
cls: 'mod-cta'
});
importButton.addEventListener('click', () => {
// Show notification about upcoming feature
new Notice('Template import functionality coming soon!');
});
// Export button
const exportButton = buttonsContainer.createEl('button', {
text: 'Export templates',
cls: ''
});
exportButton.addEventListener('click', () => {
// Show notification about upcoming feature
new Notice('Template export functionality coming soon!');
});
}
/**
* Displays the settings interface
*/
display(): void {
this.controller.display(this.containerEl);
}
}

View file

@ -0,0 +1,227 @@
import { Setting } from 'obsidian';
import { Status } from '../../models/types';
import { PREDEFINED_TEMPLATES } from '../../constants/status-templates';
/**
* Callbacks interface for settings UI interactions
*/
export interface SettingsUICallbacks {
/** Handle template enable/disable toggle */
onTemplateToggle: (templateId: string, enabled: boolean) => Promise<void>;
/** Handle general setting changes */
onSettingChange: (key: string, value: any) => Promise<void>;
/** Handle custom status field changes */
onCustomStatusChange: (index: number, field: string, value: any) => Promise<void>;
/** Handle custom status removal */
onCustomStatusRemove: (index: number) => Promise<void>;
/** Handle adding new custom status */
onCustomStatusAdd: () => Promise<void>;
}
/**
* Pure UI component for rendering settings interface
*/
export class NoteStatusSettingsUI {
private callbacks: SettingsUICallbacks;
constructor(callbacks: SettingsUICallbacks) {
this.callbacks = callbacks;
}
/**
* Renders the complete settings interface
*/
render(containerEl: HTMLElement, settings: any): void {
containerEl.empty();
this.renderTemplateSettings(containerEl, settings);
this.renderUISettings(containerEl, settings);
this.renderTagSettings(containerEl, settings);
this.renderCustomStatusSettings(containerEl, settings);
}
/**
* Renders the status templates section
*/
private renderTemplateSettings(containerEl: HTMLElement, settings: any): void {
new Setting(containerEl).setName('Status templates').setHeading();
containerEl.createEl('p', {
text: 'Enable predefined templates to quickly add common status workflows',
cls: 'setting-item-description'
});
const templatesContainer = containerEl.createDiv({ cls: 'templates-container' });
PREDEFINED_TEMPLATES.forEach(template => {
const templateEl = templatesContainer.createDiv({ cls: 'template-item' });
const headerEl = templateEl.createDiv({ cls: 'template-header' });
const isEnabled = settings.enabledTemplates.includes(template.id);
const checkbox = headerEl.createEl('input', {
type: 'checkbox',
cls: 'template-checkbox'
});
checkbox.checked = isEnabled;
checkbox.addEventListener('change', () => {
this.callbacks.onTemplateToggle(template.id, checkbox.checked);
});
headerEl.createEl('span', {
text: template.name,
cls: 'template-name'
});
templateEl.createEl('div', {
text: template.description,
cls: 'template-description'
});
const statusesEl = templateEl.createDiv({ cls: 'template-statuses' });
template.statuses.forEach(status => {
const statusEl = statusesEl.createEl('div', { cls: 'template-status-chip' });
const colorDot = statusEl.createEl('span', { cls: 'status-color-dot' });
colorDot.style.setProperty('--dot-color', status.color || '#ffffff');
statusEl.createSpan({ text: `${status.icon} ${status.name}` });
});
});
}
/**
* Renders the UI display settings section
*/
private renderUISettings(containerEl: HTMLElement, settings: any): void {
new Setting(containerEl).setName('User interface').setHeading();
new Setting(containerEl)
.setName('Show status bar')
.setDesc('Display the status bar')
.addToggle(toggle => toggle
.setValue(settings.showStatusBar)
.onChange(value => this.callbacks.onSettingChange('showStatusBar', value)));
new Setting(containerEl)
.setName('Auto-hide status bar')
.setDesc('Hide the status bar when status is unknown')
.addToggle(toggle => toggle
.setValue(settings.autoHideStatusBar)
.onChange(value => this.callbacks.onSettingChange('autoHideStatusBar', value)));
new Setting(containerEl)
.setName('Show status icons in file explorer')
.setDesc('Display status icons in the file explorer')
.addToggle(toggle => toggle
.setValue(settings.showStatusIconsInExplorer)
.onChange(value => this.callbacks.onSettingChange('showStatusIconsInExplorer', value)));
new Setting(containerEl)
.setName('Hide unknown status in file explorer')
.setDesc('Hide status icons for files with unknown status in the file explorer')
.addToggle(toggle => toggle
.setValue(settings.hideUnknownStatusInExplorer || false)
.onChange(value => this.callbacks.onSettingChange('hideUnknownStatusInExplorer', value)));
new Setting(containerEl)
.setName('Default to compact view')
.setDesc('Start the status pane in compact view by default')
.addToggle(toggle => toggle
.setValue(settings.compactView || false)
.onChange(value => this.callbacks.onSettingChange('compactView', value)));
new Setting(containerEl)
.setName('Exclude unassigned notes from status pane')
.setDesc('Improves performance by excluding notes with no assigned status from the status pane. Recommended for large vaults.')
.addToggle(toggle => toggle
.setValue(settings.excludeUnknownStatus || false)
.onChange(value => this.callbacks.onSettingChange('excludeUnknownStatus', value)));
}
/**
* Renders the status tag configuration section
*/
private renderTagSettings(containerEl: HTMLElement, settings: any): void {
new Setting(containerEl).setName('Status tag').setHeading();
new Setting(containerEl)
.setName('Enable multiple statuses')
.setDesc('Allow notes to have multiple statuses at the same time')
.addToggle(toggle => toggle
.setValue(settings.useMultipleStatuses)
.onChange(value => this.callbacks.onSettingChange('useMultipleStatuses', value)));
new Setting(containerEl)
.setName('Status tag prefix')
.setDesc('The YAML frontmatter tag name used for status (default: obsidian-note-status)')
.addText(text => text
.setValue(settings.tagPrefix)
.onChange(value => {
if (value.trim()) {
this.callbacks.onSettingChange('tagPrefix', value.trim());
}
}));
}
/**
* Renders the custom status management section
*/
private renderCustomStatusSettings(containerEl: HTMLElement, settings: any): void {
new Setting(containerEl).setName('Custom statuses').setHeading();
new Setting(containerEl)
.setName('Use only custom statuses')
.setDesc('Ignore template statuses and use only the custom statuses defined below')
.addToggle(toggle => toggle
.setValue(settings.useCustomStatusesOnly || false)
.onChange(value => this.callbacks.onSettingChange('useCustomStatusesOnly', value)));
const statusList = containerEl.createDiv({ cls: 'custom-status-list' });
this.renderCustomStatuses(statusList, settings);
new Setting(containerEl)
.setName('Add new status')
.setDesc('Add a custom status with a name, icon, and color')
.addButton(button => button
.setButtonText('Add Status')
.setCta()
.onClick(() => this.callbacks.onCustomStatusAdd()));
}
/**
* Renders the list of custom statuses with edit controls
*/
renderCustomStatuses(statusList: HTMLElement, settings: any): void {
statusList.empty();
settings.customStatuses.forEach((status: Status, index: number) => {
const setting = new Setting(statusList)
.setName(status.name)
.setClass('status-item');
setting.addText(text => text
.setPlaceholder('Name')
.setValue(status.name)
.onChange(value => this.callbacks.onCustomStatusChange(index, 'name', value || 'unnamed')));
setting.addText(text => text
.setPlaceholder('Icon')
.setValue(status.icon)
.onChange(value => this.callbacks.onCustomStatusChange(index, 'icon', value || '❓')));
setting.addColorPicker(colorPicker => colorPicker
.setValue(settings.statusColors[status.name] || '#ffffff')
.onChange(value => this.callbacks.onCustomStatusChange(index, 'color', value)));
setting.addText(text => text
.setPlaceholder('Description')
.setValue(status.description || '')
.onChange(value => this.callbacks.onCustomStatusChange(index, 'description', value)));
setting.addButton(button => button
.setButtonText('Remove')
.setClass('status-remove-button')
.setWarning()
.onClick(() => this.callbacks.onCustomStatusRemove(index)));
});
}
}

28
main.ts
View file

@ -80,7 +80,7 @@ export default class NoteStatus extends Plugin {
});
// Añadir pestaña de configuración
// this.addSettingTab(new NoteStatusSettingTab(this.app, this, this.statusService));
this.addSettingTab(new NoteStatusSettingTab(this.app, this, this.statusService));
}
private initializeIntegrations() {
@ -240,25 +240,21 @@ export default class NoteStatus extends Plugin {
async saveSettings() {
// await this.saveData(this.settings);
// // Actualizar servicios
// this.statusService.updateSettings(this.settings);
// this.styleService.updateSettings(this.settings);
// Actualizar servicios
this.statusService.updateSettings(this.settings);
this.styleService.updateSettings(this.settings);
// // Actualizar integraciones
// this.updateIntegrationsSettings();
}
private updateIntegrationsSettings() {
// // Actualizar configuración en todas las integraciones
// this.explorerIntegration.updateSettings(this.settings);
// this.fileContextMenuIntegration.updateSettings(this.settings);
// Actualizar integraciones
this.explorerIntegration.updateSettings(this.settings);
this.fileContextMenuIntegration.updateSettings(this.settings);
// this.editorIntegration.updateSettings(this.settings);
// this.toolbarIntegration.updateSettings(this.settings);
// this.metadataIntegration.updateSettings(this.settings);
// this.workspaceIntegration.updateSettings(this.settings);
//this.metadataIntegration.updateSettings(this.settings);
this.toolbarIntegration.updateSettings(this.settings);
this.workspaceIntegration.updateSettings(this.settings);
// // Actualizar componentes UI
// this.statusBar.updateSettings(this.settings);
this.statusBar.updateSettings(this.settings);
this.statusDropdown.updateSettings(this.settings)
}
onunload() {