feat: chevron on right + tabbed settings + display customization

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
This commit is contained in:
2026-06-12 12:56:27 -04:00
parent 846726a5c7
commit 765c6f3b47
5 changed files with 281 additions and 97 deletions
+186 -64
View File
@@ -5,6 +5,7 @@ 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);
@@ -17,10 +18,50 @@ export class WaypointSettingTab extends PluginSettingTab {
const { containerEl } = this;
containerEl.empty();
// ── Calendar Section ──
new Setting(containerEl).setHeading().setName('Calendar');
// ── 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' },
];
new Setting(containerEl)
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) => {
@@ -34,7 +75,7 @@ export class WaypointSettingTab extends PluginSettingTab {
});
});
new Setting(containerEl)
new Setting(container)
.setName('Show note indicators')
.setDesc('Show a dot on days that have existing notes.')
.addToggle((toggle) => {
@@ -45,20 +86,78 @@ export class WaypointSettingTab extends PluginSettingTab {
this.saveAndRefresh();
});
});
}
// ── Periodic Note Settings ──
new Setting(containerEl).setHeading().setName('Periodic Note Paths');
// ═══════════════════════════════
// Periodic Notes tab
// ═══════════════════════════════
this.addPeriodNoteSettings('Daily', this.settings.daily);
this.addPeriodNoteSettings('Weekly', this.settings.weekly);
this.addPeriodNoteSettings('Monthly', this.settings.monthly);
this.addPeriodNoteSettings('Quarterly', this.settings.quarterly);
this.addPeriodNoteSettings('Yearly', this.settings.yearly);
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);
}
// ── Recent Files Section ──
new Setting(containerEl).setHeading().setName('Recent Files');
private addPeriodNoteSettings(container: HTMLElement, label: string, period: PeriodNoteSettings): void {
new Setting(container).setHeading().setName(label);
new Setting(containerEl)
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) => {
@@ -74,7 +173,7 @@ export class WaypointSettingTab extends PluginSettingTab {
};
});
new Setting(containerEl)
new Setting(container)
.setName('Update on')
.setDesc('When to add a file to the recent list.')
.addDropdown((dropdown) => {
@@ -90,7 +189,7 @@ export class WaypointSettingTab extends PluginSettingTab {
const pathDesc = new DocumentFragment();
pathDesc.appendText('Regex patterns for paths to exclude. One per line.');
new Setting(containerEl)
new Setting(container)
.setName('Omitted paths')
.setDesc(pathDesc)
.addTextArea((text) => {
@@ -105,7 +204,7 @@ export class WaypointSettingTab extends PluginSettingTab {
const tagDesc = new DocumentFragment();
tagDesc.appendText('Regex patterns for frontmatter tags to exclude. One per line.');
new Setting(containerEl)
new Setting(container)
.setName('Omitted tags')
.setDesc(tagDesc)
.addTextArea((text) => {
@@ -119,56 +218,79 @@ export class WaypointSettingTab extends PluginSettingTab {
});
}
private addPeriodNoteSettings(label: string, period: PeriodNoteSettings): void {
new Setting(this.containerEl).setHeading().setName(label);
// ═══════════════════════════════
// Display tab
// ═══════════════════════════════
new Setting(this.containerEl)
.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;
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();
});
});
new Setting(this.containerEl)
.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(this.containerEl)
.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(this.containerEl)
.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();
});
});
});
}
private async saveAndRefresh(): Promise<void> {