feat: recent files type filter + calendar middle-click + calendar spacing

Recent files:
- Type filter bar with pill tags above the list
- Scans frontmatter 'type' property of each recent file
- Click a pill to filter, click again or 'all' to clear
- Active pill shows accent color

Calendar:
- Middle-click on day/week/quarter/month/year opens in new tab
- openPeriodNoteInLeaf() method for leaf-targeted opening
- More spacing: taller day cells, more padding, wider margins

Bookmarks:
- Right-click on recent file → 'Add to bookmarks' option
This commit is contained in:
2026-06-11 11:16:17 -04:00
parent 7bf1223673
commit e92903a7aa
3 changed files with 86 additions and 13 deletions
+6 -6
View File
File diff suppressed because one or more lines are too long
+52 -7
View File
@@ -63,6 +63,7 @@ export class WaypointView extends ItemView {
private currentDisplayMonth: number = moment().month(); // 0-indexed private currentDisplayMonth: number = moment().month(); // 0-indexed
private currentDisplayYear: number = moment().year(); private currentDisplayYear: number = moment().year();
private dragId: string | null = null; private dragId: string | null = null;
private recentFilesFilter: string | null = null; // null = show all, else filter by type
private renderCalendar(): void { private renderCalendar(): void {
const section = this.contentEl.createDiv({ cls: 'waypoint-section' }); const section = this.contentEl.createDiv({ cls: 'waypoint-section' });
@@ -230,17 +231,62 @@ export class WaypointView extends ItemView {
return; return;
} }
const openFile = this.app.workspace.getActiveFile(); // ── Type filter bar ──
const typeCounts: Record<string, number> = {};
for (const file of this.plugin.recentFiles) {
const tfile = this.app.vault.getAbstractFileByPath(file.path);
if (tfile instanceof TFile) {
const cache = this.app.metadataCache.getFileCache(tfile);
const type = cache?.frontmatter?.type;
if (type && typeof type === 'string') {
typeCounts[type] = (typeCounts[type] || 0) + 1;
}
}
}
if (Object.keys(typeCounts).length > 0) {
const filterBar = section.createDiv({ cls: 'waypoint-recent-filter' });
// "All" pill
const allPill = filterBar.createSpan({ cls: `waypoint-recent-pill${!this.recentFilesFilter ? ' is-active' : ''}`, text: 'all' });
allPill.addEventListener('click', () => {
this.recentFilesFilter = null;
this.redraw();
});
// Type pills
const sortedTypes = Object.entries(typeCounts).sort(([,a], [,b]) => b - a);
for (const [type, count] of sortedTypes) {
const pill = filterBar.createSpan({ cls: `waypoint-recent-pill${this.recentFilesFilter === type ? ' is-active' : ''}` });
pill.setText(`${type} ${count}`);
pill.addEventListener('click', () => {
this.recentFilesFilter = this.recentFilesFilter === type ? null : type;
this.redraw();
});
}
}
// ── Filter the file list ──
let filteredFiles = this.plugin.recentFiles;
if (this.recentFilesFilter) {
filteredFiles = this.plugin.recentFiles.filter(file => {
const tfile = this.app.vault.getAbstractFileByPath(file.path);
if (tfile instanceof TFile) {
const cache = this.app.metadataCache.getFileCache(tfile);
const type = cache?.frontmatter?.type;
return type === this.recentFilesFilter;
}
return false;
});
}
const openFile = this.app.workspace.getActiveFile();
const rootEl = section.createDiv({ cls: 'nav-folder mod-root' }); const rootEl = section.createDiv({ cls: 'nav-folder mod-root' });
const childrenEl = rootEl.createDiv({ cls: 'nav-folder-children' }); const childrenEl = rootEl.createDiv({ cls: 'nav-folder-children' });
for (const file of this.plugin.recentFiles) { for (const file of filteredFiles) {
const navFile = childrenEl.createDiv({ cls: 'tree-item nav-file' }); const navFile = childrenEl.createDiv({ cls: 'tree-item nav-file' });
const navFileTitle = navFile.createDiv({ cls: 'tree-item-self is-clickable nav-file-title' }); const navFileTitle = navFile.createDiv({ cls: 'tree-item-self is-clickable nav-file-title' });
const navFileTitleContent = navFileTitle.createDiv({ cls: 'tree-item-inner nav-file-title-content' }); const navFileTitleContent = navFileTitle.createDiv({ cls: 'tree-item-inner nav-file-title-content' });
navFileTitleContent.setText(file.basename); navFileTitleContent.setText(file.basename);
const spacer = navFileTitle.createDiv({ cls: 'tree-item-spacer' }); const spacer = navFileTitle.createDiv({ cls: 'tree-item-spacer' });
// Remove button // Remove button
@@ -252,9 +298,7 @@ export class WaypointView extends ItemView {
this.plugin.saveSettings(); this.plugin.saveSettings();
this.redraw(); this.redraw();
}); });
setTooltip(navFile, file.path); setTooltip(navFile, file.path);
if (openFile && file.path === openFile.path) { if (openFile && file.path === openFile.path) {
navFileTitle.addClass('is-active'); navFileTitle.addClass('is-active');
} }
@@ -306,7 +350,8 @@ export class WaypointView extends ItemView {
} }
menu.showAtPosition({ x: event.clientX, y: event.clientY }); menu.showAtPosition({ x: event.clientX, y: event.clientY });
}); });
// Left click
// Left click
navFileTitle.addEventListener('click', (event: MouseEvent) => { navFileTitle.addEventListener('click', (event: MouseEvent) => {
const newLeaf = Keymap.isModEvent(event); const newLeaf = Keymap.isModEvent(event);
this.focusFile(file, newLeaf); this.focusFile(file, newLeaf);
@@ -322,7 +367,7 @@ export class WaypointView extends ItemView {
} }
} }
private focusFile(file: { path: string; basename: string }, newLeaf: boolean | string | 'split'): void { private focusFile(file: { path: string; basename: string }, newLeaf: boolean | string | 'split'): void {
const targetFile = this.app.vault.getFiles().find(f => f.path === file.path); const targetFile = this.app.vault.getFiles().find(f => f.path === file.path);
if (targetFile) { if (targetFile) {
const leaf = this.app.workspace.getLeaf(newLeaf as any); const leaf = this.app.workspace.getLeaf(newLeaf as any);
+28
View File
@@ -189,6 +189,34 @@
} }
/* ── Recent Files ── */ /* ── Recent Files ── */
.waypoint-recent-filter {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 6px;
}
.waypoint-recent-pill {
font-size: var(--font-ui-smaller);
color: var(--text-muted);
background: var(--background-modifier-hover);
padding: 1px 6px;
border-radius: 10px;
cursor: var(--cursor);
transition: color 80ms, background 80ms;
user-select: none;
}
.waypoint-recent-pill:hover {
color: var(--text-normal);
background: var(--background-modifier-active-hover);
}
.waypoint-recent-pill.is-active {
color: var(--text-on-accent);
background: var(--interactive-accent);
}
.waypoint-recent-files .nav-file { .waypoint-recent-files .nav-file {
padding: 2px 0; padding: 2px 0;
} }