765c6f3b47
View: - Chevron moved from left side to right side of bookmark rows - Display settings applied as CSS variables on the view element Settings: - Tabbed UI with 4 tabs: Calendar, Periodic Notes, Recent Files, Display - Display tab: row size, row spacing, indent size, font size, icon size, calendar cell size - All settings use sliders with live preview and dynamic tooltips - Reset to defaults button CSS: - Bookmark items use CSS variables (--wp-row-size, --wp-row-spacing, etc.) - Calendar cells use --wp-cal-cell-size for configurable height - Settings tab styling with active indicator
301 lines
9.2 KiB
TypeScript
301 lines
9.2 KiB
TypeScript
import { Setting, PluginSettingTab, App, Plugin } from 'obsidian';
|
|
import { WaypointSettings, DEFAULT_SETTINGS, PeriodNoteSettings } from 'src/settings';
|
|
|
|
export class WaypointSettingTab extends PluginSettingTab {
|
|
private plugin: Plugin;
|
|
private settings: WaypointSettings;
|
|
private onSettingsChange: () => void;
|
|
private activeTab: 'calendar' | 'periodic' | 'recent' | 'display' = 'calendar';
|
|
|
|
constructor(app: App, plugin: Plugin, settings: WaypointSettings, onSettingsChange: () => void) {
|
|
super(app, plugin);
|
|
this.plugin = plugin;
|
|
this.settings = settings;
|
|
this.onSettingsChange = onSettingsChange;
|
|
}
|
|
|
|
display(): void {
|
|
const { containerEl } = this;
|
|
containerEl.empty();
|
|
|
|
// ── Tab bar ──
|
|
const tabBar = containerEl.createDiv({ cls: 'waypoint-settings-tabs' });
|
|
const tabs = [
|
|
{ key: 'calendar' as const, label: 'Calendar' },
|
|
{ key: 'periodic' as const, label: 'Periodic Notes' },
|
|
{ key: 'recent' as const, label: 'Recent Files' },
|
|
{ key: 'display' as const, label: 'Display' },
|
|
];
|
|
|
|
for (const tab of tabs) {
|
|
const btn = tabBar.createEl('button', {
|
|
cls: `waypoint-settings-tab${this.activeTab === tab.key ? ' is-active' : ''}`,
|
|
text: tab.label,
|
|
});
|
|
btn.addEventListener('click', () => {
|
|
this.activeTab = tab.key;
|
|
this.display();
|
|
});
|
|
}
|
|
|
|
const tabContent = containerEl.createDiv({ cls: 'waypoint-settings-content' });
|
|
|
|
switch (this.activeTab) {
|
|
case 'calendar':
|
|
this.renderCalendarTab(tabContent);
|
|
break;
|
|
case 'periodic':
|
|
this.renderPeriodicTab(tabContent);
|
|
break;
|
|
case 'recent':
|
|
this.renderRecentTab(tabContent);
|
|
break;
|
|
case 'display':
|
|
this.renderDisplayTab(tabContent);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ═══════════════════════════════
|
|
// Calendar tab
|
|
// ═══════════════════════════════
|
|
|
|
private renderCalendarTab(container: HTMLElement): void {
|
|
new Setting(container)
|
|
.setName('First day of week')
|
|
.setDesc('Which day the calendar week starts on.')
|
|
.addDropdown((dropdown) => {
|
|
dropdown
|
|
.addOption('0', 'Sunday')
|
|
.addOption('1', 'Monday')
|
|
.setValue(String(this.settings.calendar.firstDayOfWeek))
|
|
.onChange((value) => {
|
|
this.settings.calendar.firstDayOfWeek = parseInt(value, 10);
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
|
|
new Setting(container)
|
|
.setName('Show note indicators')
|
|
.setDesc('Show a dot on days that have existing notes.')
|
|
.addToggle((toggle) => {
|
|
toggle
|
|
.setValue(this.settings.calendar.showNoteIndicators)
|
|
.onChange((value) => {
|
|
this.settings.calendar.showNoteIndicators = value;
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
}
|
|
|
|
// ═══════════════════════════════
|
|
// Periodic Notes tab
|
|
// ═══════════════════════════════
|
|
|
|
private renderPeriodicTab(container: HTMLElement): void {
|
|
this.addPeriodNoteSettings(container, 'Daily', this.settings.daily);
|
|
this.addPeriodNoteSettings(container, 'Weekly', this.settings.weekly);
|
|
this.addPeriodNoteSettings(container, 'Monthly', this.settings.monthly);
|
|
this.addPeriodNoteSettings(container, 'Quarterly', this.settings.quarterly);
|
|
this.addPeriodNoteSettings(container, 'Yearly', this.settings.yearly);
|
|
}
|
|
|
|
private addPeriodNoteSettings(container: HTMLElement, label: string, period: PeriodNoteSettings): void {
|
|
new Setting(container).setHeading().setName(label);
|
|
|
|
new Setting(container)
|
|
.setName('Folder')
|
|
.setDesc(`Folder path for ${label.toLowerCase()} notes.`)
|
|
.addText((text) => {
|
|
text.setPlaceholder('periodic/daily');
|
|
text.setValue(period.folder);
|
|
text.onChange((value) => {
|
|
period.folder = value;
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
|
|
new Setting(container)
|
|
.setName('Name format')
|
|
.setDesc(`Date format for ${label.toLowerCase()} note filenames (moment.js format).`)
|
|
.addText((text) => {
|
|
text.setPlaceholder('yyyy-MM-dd');
|
|
text.setValue(period.nameFormat);
|
|
text.onChange((value) => {
|
|
period.nameFormat = value;
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
|
|
new Setting(container)
|
|
.setName('Template file')
|
|
.setDesc(`Path to the template file (without .md extension).`)
|
|
.addText((text) => {
|
|
text.setPlaceholder('Templates/Daily note');
|
|
text.setValue(period.templateFile);
|
|
text.onChange((value) => {
|
|
period.templateFile = value;
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
|
|
new Setting(container)
|
|
.setName('Type property')
|
|
.setDesc(`Value for the 'type' frontmatter property.`)
|
|
.addText((text) => {
|
|
text.setPlaceholder('daily-note');
|
|
text.setValue(period.typeProperty);
|
|
text.onChange((value) => {
|
|
period.typeProperty = value;
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
}
|
|
|
|
// ═══════════════════════════════
|
|
// Recent Files tab
|
|
// ═══════════════════════════════
|
|
|
|
private renderRecentTab(container: HTMLElement): void {
|
|
new Setting(container)
|
|
.setName('Max items')
|
|
.setDesc('Maximum number of recent files to track.')
|
|
.addText((text) => {
|
|
text.inputEl.setAttr('type', 'number');
|
|
text.inputEl.setAttr('placeholder', '50');
|
|
text.setValue(String(this.settings.recentFiles.maxItems));
|
|
text.inputEl.onblur = () => {
|
|
const val = parseInt(text.getValue(), 10);
|
|
if (!isNaN(val) && val > 0) {
|
|
this.settings.recentFiles.maxItems = val;
|
|
this.saveAndRefresh();
|
|
}
|
|
};
|
|
});
|
|
|
|
new Setting(container)
|
|
.setName('Update on')
|
|
.setDesc('When to add a file to the recent list.')
|
|
.addDropdown((dropdown) => {
|
|
dropdown
|
|
.addOption('file-open', 'File opened')
|
|
.addOption('file-edit', 'File changed')
|
|
.setValue(this.settings.recentFiles.updateOn)
|
|
.onChange((value: 'file-open' | 'file-edit') => {
|
|
this.settings.recentFiles.updateOn = value;
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
|
|
const pathDesc = new DocumentFragment();
|
|
pathDesc.appendText('Regex patterns for paths to exclude. One per line.');
|
|
new Setting(container)
|
|
.setName('Omitted paths')
|
|
.setDesc(pathDesc)
|
|
.addTextArea((text) => {
|
|
text.inputEl.setAttr('rows', 4);
|
|
text.setPlaceholder('^archives/\n\\.png$');
|
|
text.setValue(this.settings.recentFiles.omittedPaths.join('\n'));
|
|
text.inputEl.onblur = () => {
|
|
this.settings.recentFiles.omittedPaths = text.getValue().split('\n').filter(p => p.trim());
|
|
this.saveAndRefresh();
|
|
};
|
|
});
|
|
|
|
const tagDesc = new DocumentFragment();
|
|
tagDesc.appendText('Regex patterns for frontmatter tags to exclude. One per line.');
|
|
new Setting(container)
|
|
.setName('Omitted tags')
|
|
.setDesc(tagDesc)
|
|
.addTextArea((text) => {
|
|
text.inputEl.setAttr('rows', 4);
|
|
text.setPlaceholder('ignore\narchive');
|
|
text.setValue(this.settings.recentFiles.omittedTags.join('\n'));
|
|
text.inputEl.onblur = () => {
|
|
this.settings.recentFiles.omittedTags = text.getValue().split('\n').filter(t => t.trim());
|
|
this.saveAndRefresh();
|
|
};
|
|
});
|
|
}
|
|
|
|
// ═══════════════════════════════
|
|
// Display tab
|
|
// ═══════════════════════════════
|
|
|
|
private renderDisplayTab(container: HTMLElement): void {
|
|
new Setting(container).setHeading().setName('Bookmarks');
|
|
|
|
this.addSliderSetting(container,
|
|
'Row size', 'Height of bookmark items.',
|
|
this.settings.display, 'rowSize', 18, 40, 1, 'px',
|
|
);
|
|
this.addSliderSetting(container,
|
|
'Row spacing', 'Gap between bookmark items.',
|
|
this.settings.display, 'rowSpacing', 0, 12, 1, 'px',
|
|
);
|
|
this.addSliderSetting(container,
|
|
'Indent size', 'Indent per nesting depth.',
|
|
this.settings.display, 'indentSize', 8, 32, 2, 'px',
|
|
);
|
|
this.addSliderSetting(container,
|
|
'Font size', 'Label font size.',
|
|
this.settings.display, 'fontSize', 10, 18, 1, 'px',
|
|
);
|
|
this.addSliderSetting(container,
|
|
'Icon size', 'Bookmark icon size.',
|
|
this.settings.display, 'iconSize', 12, 24, 1, 'px',
|
|
);
|
|
|
|
new Setting(container).setHeading().setName('Calendar');
|
|
|
|
this.addSliderSetting(container,
|
|
'Cell size', 'Height of calendar day cells.',
|
|
this.settings.display, 'calendarCellSize', 20, 48, 2, 'px',
|
|
);
|
|
|
|
new Setting(container)
|
|
.addButton((btn) =>
|
|
btn
|
|
.setButtonText('Reset to defaults')
|
|
.onClick(() => {
|
|
this.settings.display = { ...DEFAULT_SETTINGS.display };
|
|
this.saveAndRefresh();
|
|
this.display();
|
|
}),
|
|
);
|
|
}
|
|
|
|
private addSliderSetting(
|
|
container: HTMLElement,
|
|
name: string,
|
|
desc: string,
|
|
obj: any,
|
|
key: string,
|
|
min: number,
|
|
max: number,
|
|
step: number,
|
|
unit: string,
|
|
): void {
|
|
const setting = new Setting(container)
|
|
.setName(name)
|
|
.setDesc(`${desc} (${obj[key]}${unit})`);
|
|
|
|
setting.addSlider((slider) => {
|
|
slider
|
|
.setLimits(min, max, step)
|
|
.setValue(obj[key])
|
|
.setDynamicTooltip()
|
|
.onChange((value) => {
|
|
obj[key] = value;
|
|
setting.setDesc(`${desc} (${value}${unit})`);
|
|
this.saveAndRefresh();
|
|
});
|
|
});
|
|
}
|
|
|
|
private async saveAndRefresh(): Promise<void> {
|
|
await this.plugin.saveData(this.settings);
|
|
this.onSettingsChange();
|
|
}
|
|
}
|