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:
+186
-64
@@ -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> {
|
||||
|
||||
@@ -19,6 +19,15 @@ export interface RecentFilesSettings {
|
||||
omittedTags: string[];
|
||||
}
|
||||
|
||||
export interface DisplaySettings {
|
||||
rowSize: number; // px, base height of bookmark items (20–40)
|
||||
rowSpacing: number; // px, gap between items (0–12)
|
||||
indentSize: number; // px, indent per depth level (8–32)
|
||||
fontSize: number; // px, font size for bookmark labels (10–18)
|
||||
iconSize: number; // px, icon size (12–24)
|
||||
calendarCellSize: number; // px, calendar day cell height (20–48)
|
||||
}
|
||||
|
||||
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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user