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
+12 -12
View File
File diff suppressed because one or more lines are too long
+186 -64
View File
@@ -5,6 +5,7 @@ export class WaypointSettingTab extends PluginSettingTab {
private plugin: Plugin; private plugin: Plugin;
private settings: WaypointSettings; private settings: WaypointSettings;
private onSettingsChange: () => void; private onSettingsChange: () => void;
private activeTab: 'calendar' | 'periodic' | 'recent' | 'display' = 'calendar';
constructor(app: App, plugin: Plugin, settings: WaypointSettings, onSettingsChange: () => void) { constructor(app: App, plugin: Plugin, settings: WaypointSettings, onSettingsChange: () => void) {
super(app, plugin); super(app, plugin);
@@ -17,10 +18,50 @@ export class WaypointSettingTab extends PluginSettingTab {
const { containerEl } = this; const { containerEl } = this;
containerEl.empty(); containerEl.empty();
// ── Calendar Section ── // ── Tab bar ──
new Setting(containerEl).setHeading().setName('Calendar'); 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') .setName('First day of week')
.setDesc('Which day the calendar week starts on.') .setDesc('Which day the calendar week starts on.')
.addDropdown((dropdown) => { .addDropdown((dropdown) => {
@@ -34,7 +75,7 @@ export class WaypointSettingTab extends PluginSettingTab {
}); });
}); });
new Setting(containerEl) new Setting(container)
.setName('Show note indicators') .setName('Show note indicators')
.setDesc('Show a dot on days that have existing notes.') .setDesc('Show a dot on days that have existing notes.')
.addToggle((toggle) => { .addToggle((toggle) => {
@@ -45,20 +86,78 @@ export class WaypointSettingTab extends PluginSettingTab {
this.saveAndRefresh(); this.saveAndRefresh();
}); });
}); });
}
// ── Periodic Note Settings ── // ═══════════════════════════════
new Setting(containerEl).setHeading().setName('Periodic Note Paths'); // Periodic Notes tab
// ═══════════════════════════════
this.addPeriodNoteSettings('Daily', this.settings.daily); private renderPeriodicTab(container: HTMLElement): void {
this.addPeriodNoteSettings('Weekly', this.settings.weekly); this.addPeriodNoteSettings(container, 'Daily', this.settings.daily);
this.addPeriodNoteSettings('Monthly', this.settings.monthly); this.addPeriodNoteSettings(container, 'Weekly', this.settings.weekly);
this.addPeriodNoteSettings('Quarterly', this.settings.quarterly); this.addPeriodNoteSettings(container, 'Monthly', this.settings.monthly);
this.addPeriodNoteSettings('Yearly', this.settings.yearly); this.addPeriodNoteSettings(container, 'Quarterly', this.settings.quarterly);
this.addPeriodNoteSettings(container, 'Yearly', this.settings.yearly);
}
// ── Recent Files Section ── private addPeriodNoteSettings(container: HTMLElement, label: string, period: PeriodNoteSettings): void {
new Setting(containerEl).setHeading().setName('Recent Files'); 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') .setName('Max items')
.setDesc('Maximum number of recent files to track.') .setDesc('Maximum number of recent files to track.')
.addText((text) => { .addText((text) => {
@@ -74,7 +173,7 @@ export class WaypointSettingTab extends PluginSettingTab {
}; };
}); });
new Setting(containerEl) new Setting(container)
.setName('Update on') .setName('Update on')
.setDesc('When to add a file to the recent list.') .setDesc('When to add a file to the recent list.')
.addDropdown((dropdown) => { .addDropdown((dropdown) => {
@@ -90,7 +189,7 @@ export class WaypointSettingTab extends PluginSettingTab {
const pathDesc = new DocumentFragment(); const pathDesc = new DocumentFragment();
pathDesc.appendText('Regex patterns for paths to exclude. One per line.'); pathDesc.appendText('Regex patterns for paths to exclude. One per line.');
new Setting(containerEl) new Setting(container)
.setName('Omitted paths') .setName('Omitted paths')
.setDesc(pathDesc) .setDesc(pathDesc)
.addTextArea((text) => { .addTextArea((text) => {
@@ -105,7 +204,7 @@ export class WaypointSettingTab extends PluginSettingTab {
const tagDesc = new DocumentFragment(); const tagDesc = new DocumentFragment();
tagDesc.appendText('Regex patterns for frontmatter tags to exclude. One per line.'); tagDesc.appendText('Regex patterns for frontmatter tags to exclude. One per line.');
new Setting(containerEl) new Setting(container)
.setName('Omitted tags') .setName('Omitted tags')
.setDesc(tagDesc) .setDesc(tagDesc)
.addTextArea((text) => { .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) private renderDisplayTab(container: HTMLElement): void {
.setName('Folder') new Setting(container).setHeading().setName('Bookmarks');
.setDesc(`Folder path for ${label.toLowerCase()} notes.`)
.addText((text) => { this.addSliderSetting(container,
text.setPlaceholder('periodic/daily'); 'Row size', 'Height of bookmark items.',
text.setValue(period.folder); this.settings.display, 'rowSize', 18, 40, 1, 'px',
text.onChange((value) => { );
period.folder = value; 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(); 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> { private async saveAndRefresh(): Promise<void> {
+18
View File
@@ -19,6 +19,15 @@ export interface RecentFilesSettings {
omittedTags: string[]; omittedTags: string[];
} }
export interface DisplaySettings {
rowSize: number; // px, base height of bookmark items (2040)
rowSpacing: number; // px, gap between items (012)
indentSize: number; // px, indent per depth level (832)
fontSize: number; // px, font size for bookmark labels (1018)
iconSize: number; // px, icon size (1224)
calendarCellSize: number; // px, calendar day cell height (2048)
}
export interface WaypointSettings { export interface WaypointSettings {
calendar: CalendarSettings; calendar: CalendarSettings;
daily: PeriodNoteSettings; daily: PeriodNoteSettings;
@@ -27,6 +36,7 @@ export interface WaypointSettings {
quarterly: PeriodNoteSettings; quarterly: PeriodNoteSettings;
yearly: PeriodNoteSettings; yearly: PeriodNoteSettings;
recentFiles: RecentFilesSettings; recentFiles: RecentFilesSettings;
display: DisplaySettings;
} }
export const DEFAULT_SETTINGS: WaypointSettings = { export const DEFAULT_SETTINGS: WaypointSettings = {
@@ -70,4 +80,12 @@ export const DEFAULT_SETTINGS: WaypointSettings = {
omittedPaths: [], omittedPaths: [],
omittedTags: [], omittedTags: [],
}, },
display: {
rowSize: 26,
rowSpacing: 2,
indentSize: 16,
fontSize: 13,
iconSize: 16,
calendarCellSize: 32,
},
}; };
+19 -16
View File
@@ -51,6 +51,15 @@ export class WaypointView extends ItemView {
this.contentEl.empty(); this.contentEl.empty();
this.contentEl.addClass('waypoint-view'); this.contentEl.addClass('waypoint-view');
// Apply display settings as CSS variables
const d = this.plugin.settings.display;
this.contentEl.style.setProperty('--wp-row-size', d.rowSize + 'px');
this.contentEl.style.setProperty('--wp-row-spacing', d.rowSpacing + 'px');
this.contentEl.style.setProperty('--wp-indent-size', d.indentSize + 'px');
this.contentEl.style.setProperty('--wp-font-size', d.fontSize + 'px');
this.contentEl.style.setProperty('--wp-icon-size', d.iconSize + 'px');
this.contentEl.style.setProperty('--wp-cal-cell-size', d.calendarCellSize + 'px');
this.renderFavorites(); this.renderFavorites();
this.renderRecentFiles(); this.renderRecentFiles();
this.renderCalendar(); this.renderCalendar();
@@ -646,6 +655,11 @@ export class WaypointView extends ItemView {
// ── Group: chevron + icon + label ── // ── Group: chevron + icon + label ──
if (isGroup) { if (isGroup) {
const iconEl = rowEl.createDiv({ cls: 'waypoint-bm-icon' });
if (item.icon) setIcon(iconEl, item.icon);
const labelEl = rowEl.createDiv({ cls: 'waypoint-bm-label', text: item.label });
const chevron = rowEl.createDiv({ cls: 'waypoint-bm-chevron' }); const chevron = rowEl.createDiv({ cls: 'waypoint-bm-chevron' });
setIcon(chevron, 'chevron-down'); setIcon(chevron, 'chevron-down');
chevron.addEventListener('click', (event: MouseEvent) => { chevron.addEventListener('click', (event: MouseEvent) => {
@@ -653,11 +667,6 @@ export class WaypointView extends ItemView {
this.plugin.updateBookmark(item.id, { collapsed: !item.collapsed }); this.plugin.updateBookmark(item.id, { collapsed: !item.collapsed });
}); });
const iconEl = rowEl.createDiv({ cls: 'waypoint-bm-icon' });
if (item.icon) setIcon(iconEl, item.icon);
const labelEl = rowEl.createDiv({ cls: 'waypoint-bm-label', text: item.label });
// Click group row → open linked file (if any), otherwise toggle collapse // Click group row → open linked file (if any), otherwise toggle collapse
rowEl.addEventListener('click', (event: MouseEvent) => { rowEl.addEventListener('click', (event: MouseEvent) => {
if (item.filePath) { if (item.filePath) {
@@ -691,6 +700,11 @@ export class WaypointView extends ItemView {
// ── File: icon + label ── // ── File: icon + label ──
} else { } else {
const iconEl = rowEl.createDiv({ cls: 'waypoint-bm-icon' });
if (item.icon) setIcon(iconEl, item.icon);
const labelEl = rowEl.createDiv({ cls: 'waypoint-bm-label', text: item.label });
// Chevron for file bookmarks that have children // Chevron for file bookmarks that have children
if (item.children && item.children.length > 0) { if (item.children && item.children.length > 0) {
const chevron = rowEl.createDiv({ cls: 'waypoint-bm-chevron' }); const chevron = rowEl.createDiv({ cls: 'waypoint-bm-chevron' });
@@ -704,11 +718,6 @@ export class WaypointView extends ItemView {
chevron.style.transform = 'rotate(-90deg)'; chevron.style.transform = 'rotate(-90deg)';
} }
} }
const iconEl = rowEl.createDiv({ cls: 'waypoint-bm-icon' });
if (item.icon) setIcon(iconEl, item.icon);
const labelEl = rowEl.createDiv({ cls: 'waypoint-bm-label', text: item.label });
setTooltip(rowEl, item.filePath); setTooltip(rowEl, item.filePath);
rowEl.addEventListener('click', (event: MouseEvent) => { rowEl.addEventListener('click', (event: MouseEvent) => {
@@ -823,12 +832,6 @@ export class WaypointView extends ItemView {
.setIcon('image') .setIcon('image')
.onClick(() => this.promptIcon(item)), .onClick(() => this.promptIcon(item)),
); );
menu.addItem((i) =>
i
.setTitle(item.collapsed ? 'Expand' : 'Collapse')
.setIcon(item.collapsed ? 'chevron-down' : 'chevron-up')
.onClick(() => this.plugin.updateBookmark(item.id, { collapsed: !item.collapsed })),
);
menu.addSeparator(); menu.addSeparator();
menu.addItem((i) => menu.addItem((i) =>
i i
+46 -5
View File
@@ -154,10 +154,14 @@
.waypoint-calendar .waypoint-day { .waypoint-calendar .waypoint-day {
cursor: var(--cursor); cursor: var(--cursor);
padding: 6px 0; min-height: var(--wp-cal-cell-size, 32px);
padding: 2px 0;
border-radius: 6px; border-radius: 6px;
position: relative; position: relative;
width: 100%; width: 100%;
display: flex;
align-items: center;
justify-content: center;
} }
.waypoint-calendar .waypoint-day:hover { .waypoint-calendar .waypoint-day:hover {
@@ -256,9 +260,10 @@
.waypoint-favorites .waypoint-bookmark-item { .waypoint-favorites .waypoint-bookmark-item {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 4px; gap: var(--wp-row-spacing, 4px);
padding: 2px 8px 2px 4px; padding: 2px 8px 2px 4px;
font-size: var(--font-ui-small); font-size: var(--wp-font-size, 13px);
min-height: var(--wp-row-size, 26px);
cursor: var(--cursor); cursor: var(--cursor);
border-radius: 4px; border-radius: 4px;
user-select: none; user-select: none;
@@ -273,6 +278,8 @@
display: flex; display: flex;
align-items: center; align-items: center;
flex-shrink: 0; flex-shrink: 0;
width: var(--wp-icon-size, 16px);
height: var(--wp-icon-size, 16px);
} }
.waypoint-favorites .waypoint-bookmark-item .waypoint-bm-label { .waypoint-favorites .waypoint-bookmark-item .waypoint-bm-label {
@@ -297,7 +304,7 @@
} }
.waypoint-favorites .waypoint-bookmark-children { .waypoint-favorites .waypoint-bookmark-children {
padding-left: 16px; padding-left: var(--wp-indent-size, 16px);
} }
.waypoint-favorites .waypoint-bookmark-children.collapsed { .waypoint-favorites .waypoint-bookmark-children.collapsed {
@@ -350,4 +357,38 @@
border: none; border: none;
cursor: grab; cursor: grab;
background: transparent; background: transparent;
} }
/* ── Settings Tabs ── */
.waypoint-settings-tabs {
display: flex;
gap: 4px;
margin-bottom: 20px;
border-bottom: 1px solid var(--background-modifier-border);
padding-bottom: 8px;
}
.waypoint-settings-tab {
background: none;
border: none;
cursor: var(--cursor);
color: var(--text-muted);
padding: 6px 14px;
border-radius: 6px 6px 0 0;
font-size: var(--font-ui-small);
font-weight: var(--font-medium);
transition: color 100ms, background 100ms;
}
.waypoint-settings-tab:hover {
color: var(--text-normal);
background: var(--background-modifier-hover);
}
.waypoint-settings-tab.is-active {
color: var(--text-accent);
background: var(--background-modifier-active-hover);
border-bottom: 2px solid var(--text-accent);
margin-bottom: -9px;
}