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 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> {
+18
View File
@@ -19,6 +19,15 @@ export interface RecentFilesSettings {
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 {
calendar: CalendarSettings;
daily: PeriodNoteSettings;
@@ -27,6 +36,7 @@ export interface WaypointSettings {
quarterly: PeriodNoteSettings;
yearly: PeriodNoteSettings;
recentFiles: RecentFilesSettings;
display: DisplaySettings;
}
export const DEFAULT_SETTINGS: WaypointSettings = {
@@ -70,4 +80,12 @@ export const DEFAULT_SETTINGS: WaypointSettings = {
omittedPaths: [],
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.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.renderRecentFiles();
this.renderCalendar();
@@ -646,6 +655,11 @@ export class WaypointView extends ItemView {
// ── Group: chevron + icon + label ──
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' });
setIcon(chevron, 'chevron-down');
chevron.addEventListener('click', (event: MouseEvent) => {
@@ -653,11 +667,6 @@ export class WaypointView extends ItemView {
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
rowEl.addEventListener('click', (event: MouseEvent) => {
if (item.filePath) {
@@ -691,6 +700,11 @@ export class WaypointView extends ItemView {
// ── File: icon + label ──
} 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
if (item.children && item.children.length > 0) {
const chevron = rowEl.createDiv({ cls: 'waypoint-bm-chevron' });
@@ -704,11 +718,6 @@ export class WaypointView extends ItemView {
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);
rowEl.addEventListener('click', (event: MouseEvent) => {
@@ -823,12 +832,6 @@ export class WaypointView extends ItemView {
.setIcon('image')
.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.addItem((i) =>
i
+45 -4
View File
@@ -154,10 +154,14 @@
.waypoint-calendar .waypoint-day {
cursor: var(--cursor);
padding: 6px 0;
min-height: var(--wp-cal-cell-size, 32px);
padding: 2px 0;
border-radius: 6px;
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.waypoint-calendar .waypoint-day:hover {
@@ -256,9 +260,10 @@
.waypoint-favorites .waypoint-bookmark-item {
display: flex;
align-items: center;
gap: 4px;
gap: var(--wp-row-spacing, 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);
border-radius: 4px;
user-select: none;
@@ -273,6 +278,8 @@
display: flex;
align-items: center;
flex-shrink: 0;
width: var(--wp-icon-size, 16px);
height: var(--wp-icon-size, 16px);
}
.waypoint-favorites .waypoint-bookmark-item .waypoint-bm-label {
@@ -297,7 +304,7 @@
}
.waypoint-favorites .waypoint-bookmark-children {
padding-left: 16px;
padding-left: var(--wp-indent-size, 16px);
}
.waypoint-favorites .waypoint-bookmark-children.collapsed {
@@ -351,3 +358,37 @@
cursor: grab;
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;
}