Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 141121878f | |||
| 6a89660cf4 | |||
| 44c2dd2abc | |||
| 49c8da9863 |
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "waypoint-sidebar",
|
||||
"name": "Waypoint Sidebar",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.1",
|
||||
"minAppVersion": "0.16.3",
|
||||
"description": "Calendar, recent files, and custom bookmarks sidebar.",
|
||||
"author": "Olivier",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "waypoint",
|
||||
"version": "1.3.0",
|
||||
"version": "1.4.1",
|
||||
"description": "Calendar, recent files, and custom bookmarks sidebar.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
+53
-2
@@ -16,12 +16,14 @@ import { BookmarkItem, WaypointData } from 'src/models/bookmark';
|
||||
|
||||
const DEFAULT_DATA: WaypointData = {
|
||||
bookmarks: [],
|
||||
recentFiles: [],
|
||||
};
|
||||
|
||||
export default class WaypointPlugin extends Plugin {
|
||||
public settings: WaypointSettings;
|
||||
public waypointData: WaypointData;
|
||||
public recentFiles: { path: string; basename: string }[] = [];
|
||||
private recentFilesSaveTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
console.debug('Waypoint: loading plugin v' + this.manifest.version);
|
||||
@@ -41,7 +43,10 @@ export default class WaypointPlugin extends Plugin {
|
||||
this.app,
|
||||
this,
|
||||
this.settings,
|
||||
() => this.redrawAll(),
|
||||
() => {
|
||||
this.enforceRecentFilesLimit();
|
||||
this.redrawAll();
|
||||
},
|
||||
));
|
||||
|
||||
// ── Commands ──
|
||||
@@ -206,14 +211,47 @@ export default class WaypointPlugin extends Plugin {
|
||||
const saved = await this.loadData() as Record<string, unknown> | null;
|
||||
const d = (saved?.waypointData || {}) as Partial<WaypointData>;
|
||||
this.waypointData = Object.assign({}, DEFAULT_DATA, d);
|
||||
|
||||
// Load persisted recent files
|
||||
this.recentFiles = this.waypointData.recentFiles || [];
|
||||
|
||||
// Apply current limit (in case maxItems was reduced since last save)
|
||||
if (this.recentFiles.length > this.settings.recentFiles.maxItems) {
|
||||
this.recentFiles = this.recentFiles.slice(0, this.settings.recentFiles.maxItems);
|
||||
this.waypointData.recentFiles = this.recentFiles;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim recent files to the current maxItems limit and persist.
|
||||
* Called when the maxItems setting changes.
|
||||
*/
|
||||
enforceRecentFilesLimit(): void {
|
||||
if (this.recentFiles.length > this.settings.recentFiles.maxItems) {
|
||||
this.recentFiles = this.recentFiles.slice(0, this.settings.recentFiles.maxItems);
|
||||
this.persistRecentFiles();
|
||||
}
|
||||
}
|
||||
|
||||
async saveWaypointData(): Promise<void> {
|
||||
// Sync recentFiles into waypointData before saving
|
||||
this.waypointData.recentFiles = this.recentFiles;
|
||||
const all = (await this.loadData()) as Record<string, unknown> || {};
|
||||
all.waypointData = this.waypointData;
|
||||
await this.saveData(all);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist recent files to disk (debounced to avoid excessive writes on rapid opens).
|
||||
*/
|
||||
persistRecentFiles(): void {
|
||||
this.waypointData.recentFiles = this.recentFiles;
|
||||
if (this.recentFilesSaveTimer) clearTimeout(this.recentFilesSaveTimer);
|
||||
this.recentFilesSaveTimer = setTimeout(() => {
|
||||
this.saveWaypointData();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// ── Recent Files ──
|
||||
|
||||
private onFileOpen(file: TFile): void {
|
||||
@@ -225,14 +263,26 @@ export default class WaypointPlugin extends Plugin {
|
||||
}
|
||||
|
||||
addToRecentFiles(file: TFile): void {
|
||||
// Apply omitted paths filter
|
||||
if (this.settings.recentFiles.omittedPaths.length > 0) {
|
||||
for (const pattern of this.settings.recentFiles.omittedPaths) {
|
||||
try {
|
||||
if (new RegExp(pattern).test(file.path)) return;
|
||||
} catch {
|
||||
// Invalid regex, skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.recentFiles = this.recentFiles.filter(f => f.path !== file.path);
|
||||
this.recentFiles.unshift({ path: file.path, basename: file.basename });
|
||||
|
||||
// Prune
|
||||
// Apply max items limit
|
||||
if (this.recentFiles.length > this.settings.recentFiles.maxItems) {
|
||||
this.recentFiles = this.recentFiles.slice(0, this.settings.recentFiles.maxItems);
|
||||
}
|
||||
|
||||
this.persistRecentFiles();
|
||||
this.broadcastRedraw();
|
||||
}
|
||||
|
||||
@@ -241,6 +291,7 @@ export default class WaypointPlugin extends Plugin {
|
||||
if (entry) {
|
||||
entry.path = file.path;
|
||||
entry.basename = (file as TFile).basename || file.name.replace(/\.[^/.]+$/, '');
|
||||
this.persistRecentFiles();
|
||||
this.broadcastRedraw();
|
||||
}
|
||||
|
||||
|
||||
@@ -13,4 +13,5 @@ export interface BookmarkItem {
|
||||
|
||||
export interface WaypointData {
|
||||
bookmarks: BookmarkItem[];
|
||||
recentFiles: { path: string; basename: string }[];
|
||||
}
|
||||
|
||||
+55
-3
@@ -1,11 +1,11 @@
|
||||
import { Setting, PluginSettingTab, App, Plugin } from 'obsidian';
|
||||
import { Setting, PluginSettingTab, App, Plugin, setIcon } from 'obsidian';
|
||||
import { WaypointSettings, DEFAULT_SETTINGS, PeriodNoteSettings } from 'src/settings';
|
||||
|
||||
export class WaypointSettingTab extends PluginSettingTab {
|
||||
private plugin: Plugin;
|
||||
private settings: WaypointSettings;
|
||||
private onSettingsChange: () => void;
|
||||
private activeTab: 'calendar' | 'periodic' | 'recent' | 'display' = 'calendar';
|
||||
private activeTab: 'calendar' | 'periodic' | 'recent' | 'display' | 'about' = 'calendar';
|
||||
|
||||
constructor(app: App, plugin: Plugin, settings: WaypointSettings, onSettingsChange: () => void) {
|
||||
super(app, plugin);
|
||||
@@ -25,6 +25,7 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
{ key: 'periodic' as const, label: 'Periodic Notes' },
|
||||
{ key: 'recent' as const, label: 'Recent Files' },
|
||||
{ key: 'display' as const, label: 'Display' },
|
||||
{ key: 'about' as const, label: 'About' },
|
||||
];
|
||||
|
||||
for (const tab of tabs) {
|
||||
@@ -53,6 +54,9 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
case 'display':
|
||||
this.renderDisplayTab(tabContent);
|
||||
break;
|
||||
case 'about':
|
||||
this.renderAboutTab(tabContent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +298,55 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
}
|
||||
|
||||
private async saveAndRefresh(): Promise<void> {
|
||||
await this.plugin.saveData(this.settings);
|
||||
await (this.plugin as any).saveSettings();
|
||||
this.onSettingsChange();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════
|
||||
// About tab
|
||||
// ═══════════════════════════════
|
||||
|
||||
private renderAboutTab(container: HTMLElement): void {
|
||||
const version = this.plugin.manifest.version;
|
||||
|
||||
const header = container.createDiv();
|
||||
header.style.display = 'flex';
|
||||
header.style.alignItems = 'center';
|
||||
header.style.gap = '12px';
|
||||
header.style.marginBottom = '16px';
|
||||
|
||||
const icon = header.createDiv();
|
||||
icon.style.display = 'flex';
|
||||
icon.style.alignItems = 'center';
|
||||
icon.style.justifyContent = 'center';
|
||||
icon.style.width = '48px';
|
||||
icon.style.height = '48px';
|
||||
icon.style.borderRadius = '12px';
|
||||
icon.style.background = 'var(--interactive-accent)';
|
||||
icon.style.color = 'var(--text-on-accent)';
|
||||
icon.style.fontSize = '24px';
|
||||
setIcon(icon, 'compass');
|
||||
|
||||
const titleGroup = header.createDiv();
|
||||
const title = titleGroup.createEl('h2', { text: 'Waypoint Sidebar' });
|
||||
title.style.margin = '0';
|
||||
title.style.lineHeight = '1.2';
|
||||
const subtitle = titleGroup.createDiv({ text: `v${version}` });
|
||||
subtitle.style.color = 'var(--text-muted)';
|
||||
subtitle.style.fontSize = 'var(--font-ui-small)';
|
||||
|
||||
const desc = container.createDiv();
|
||||
desc.style.marginBottom = '20px';
|
||||
desc.style.lineHeight = '1.6';
|
||||
desc.style.color = 'var(--text-normal)';
|
||||
desc.innerHTML = [
|
||||
`<p><strong>Waypoint</strong> is a sidebar plugin that brings three essential panels into one view:</p>`,
|
||||
`<ul style="padding-left: 20px; margin: 8px 0;">`,
|
||||
`<li><strong>Calendar</strong> — a monthly grid for your periodic notes (daily, weekly, monthly, quarterly, yearly). Click any day, week, or month to open or create the corresponding note.</li>`,
|
||||
`<li><strong>Recent Files</strong> — a list of recently opened files with type filtering, drag-and-drop, and right-click actions.</li>`,
|
||||
`<li><strong>Bookmarks</strong> — custom bookmarks with icons, groups, nesting, and drag-and-drop reordering. Separate from Obsidian's native bookmarks.</li>`,
|
||||
`</ul>`,
|
||||
`<p style="color: var(--text-muted); font-size: var(--font-ui-small);">Made by Olivier. Licensed under MIT.</p>`,
|
||||
].join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ export class WaypointView extends ItemView {
|
||||
removeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.plugin.recentFiles = this.plugin.recentFiles.filter(f => f.path !== file.path);
|
||||
this.plugin.saveSettings();
|
||||
this.plugin.persistRecentFiles();
|
||||
this.redraw();
|
||||
});
|
||||
setTooltip(navFile, file.path);
|
||||
@@ -384,7 +384,7 @@ export class WaypointView extends ItemView {
|
||||
} else {
|
||||
new Notice('Cannot find file');
|
||||
this.plugin.recentFiles = this.plugin.recentFiles.filter(f => f.path !== file.path);
|
||||
this.plugin.saveSettings();
|
||||
this.plugin.persistRecentFiles();
|
||||
this.redraw();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user