Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 695141383f | |||
| a0a3b50d94 | |||
| 621bef0d29 |
+1
-1
@@ -178,7 +178,7 @@ Configured systems live in `settings.dateSystems`, where array order is menu ord
|
||||
| `journal` | Journal | `periodic/journal` | `YYYY-MM-DD - [Journal]` | green (`#22c55e`) | one |
|
||||
| `meetings` | Meeting | `periodic/meetings` | `YYYY-MM-DD - {title}` | violet (`#a855f7`) | many |
|
||||
|
||||
The default calendar mode is `systems`, while `any` preserves the older one-neutral-dot behaviour. Template file fields in both Periodic Notes and Date systems use a native `datalist` populated from every Markdown path in the vault (without the optional `.md` extension), so templates stored outside a conventional `Templates/` folder remain discoverable.
|
||||
The default calendar mode is `systems`, while `any` preserves the older one-neutral-dot behaviour. Existing installs that silently persisted the pre-`systems` default of `any` are promoted to `systems` exactly once, on load, via a `data.json`-level `indicatorModeMigrated` flag; a value chosen after that flag is set (including `any`) is never touched again. Template file fields in both Periodic Notes and Date systems use `TemplateFileSuggest`, an `AbstractInputSuggest<TFile>` (the same base class Templater's own template pickers use — Obsidian's native fuzzy-suggest popup, not an unstyled `<datalist>`), searching every Markdown path in the vault. `AbstractInputSuggest` requires Obsidian ≥1.4.10, hence `manifest.json`'s `minAppVersion`.
|
||||
|
||||
### Templater folder triggers
|
||||
|
||||
|
||||
@@ -80,8 +80,9 @@
|
||||
- [ ] Change Daily/Journal/Meeting indicator colours in Settings → the visible calendar refreshes immediately
|
||||
- [ ] Add a custom date system with its own colour and a matching note → its dot appears in system order
|
||||
- [ ] Create, delete, or rename a date-system note → coloured dots refresh without changing months
|
||||
- [ ] Start typing in any Template file field → matching vault Markdown paths appear as native autocomplete suggestions
|
||||
|
||||
- [ ] An install that previously had **Single dot** as its silently-defaulted, never-touched setting shows **Colour by note system** after upgrading and reloading the plugin
|
||||
- [ ] After that one-time change, manually selecting **Single dot** and reloading again keeps **Single dot** — the migration never re-fires
|
||||
- [ ] Typing in any Template file field opens Obsidian's native fuzzy-suggest popup (matching the look of Templater's own template pickers), not a plain browser dropdown
|
||||
|
||||
## Calendar: Date systems
|
||||
|
||||
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "waypoint-sidebar",
|
||||
"name": "Waypoint Sidebar",
|
||||
"version": "1.7.0",
|
||||
"minAppVersion": "1.4.4",
|
||||
"version": "1.7.1",
|
||||
"minAppVersion": "1.4.10",
|
||||
"description": "Calendar, recent files, and custom bookmarks sidebar.",
|
||||
"author": "Olivier",
|
||||
"isDesktopOnly": false
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "waypoint",
|
||||
"version": "1.7.0",
|
||||
"version": "1.7.1",
|
||||
"description": "Calendar, recent files, and custom bookmarks sidebar.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
+34
-3
@@ -61,14 +61,26 @@ export default class WaypointPlugin extends Plugin {
|
||||
/** Results for the displayed month; invalidated by markdown-file changes. */
|
||||
private dateSystemIndicators: Map<string, DateSystemIndicator[]> | null = null;
|
||||
private dateSystemIndicatorKey = '';
|
||||
/**
|
||||
* True once `data.json` carries the post-migration schema. Set on every
|
||||
* load and included in every save, so a single migration run is durable
|
||||
* even if this session never triggers another settings write.
|
||||
*/
|
||||
private indicatorModeMigrated = false;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
console.debug('Waypoint: loading plugin v' + this.manifest.version);
|
||||
|
||||
// Load persisted data — data.json is read exactly once here.
|
||||
const saved = await this.loadData() as Record<string, unknown> | null;
|
||||
this.applySettings(saved);
|
||||
const migrated = this.applySettings(saved);
|
||||
this.applyWaypointData(saved);
|
||||
if (migrated) {
|
||||
// Persist immediately: an install that only ever reads data.json
|
||||
// (never changes a setting or bookmark this session) must still
|
||||
// keep the migrated value instead of re-migrating every load.
|
||||
void this.persistAll();
|
||||
}
|
||||
|
||||
// Register the sidebar view
|
||||
this.registerView(
|
||||
@@ -245,9 +257,11 @@ export default class WaypointPlugin extends Plugin {
|
||||
/**
|
||||
* Merge persisted settings over the defaults. Nested objects are merged
|
||||
* individually so existing configs keep their values while picking up
|
||||
* fields added in newer versions.
|
||||
* fields added in newer versions. Returns true the one time a one-off
|
||||
* migration (see `indicatorModeMigrated` below) actually changes a value,
|
||||
* so the caller can persist that change immediately.
|
||||
*/
|
||||
private applySettings(saved: Record<string, unknown> | null): void {
|
||||
private applySettings(saved: Record<string, unknown> | null): boolean {
|
||||
const s = (saved?.settings || {}) as Partial<WaypointSettings>;
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, s);
|
||||
this.settings.recentFiles = Object.assign({}, DEFAULT_SETTINGS.recentFiles, s.recentFiles || {});
|
||||
@@ -267,6 +281,22 @@ export default class WaypointPlugin extends Plugin {
|
||||
return Object.assign({}, builtIn || DEFAULT_DATE_SYSTEM, sys);
|
||||
})
|
||||
: DEFAULT_SETTINGS.dateSystems.map(sys => Object.assign({}, sys));
|
||||
|
||||
// `indicatorMode` shipped with a default of 'any' before per-system
|
||||
// colours existed, so every install that loaded before that default
|
||||
// changed silently persisted 'any' to disk as if it were a deliberate
|
||||
// choice — changing DEFAULT_SETTINGS alone can never reach an install
|
||||
// that already has an explicit value on disk. Promote that one-time
|
||||
// default to the current default exactly once; any choice made after
|
||||
// this flag is set is real and must never be touched again.
|
||||
const alreadyMigrated = saved?.indicatorModeMigrated === true;
|
||||
let didMigrateValue = false;
|
||||
if (!alreadyMigrated && s.calendar?.indicatorMode === 'any') {
|
||||
this.settings.calendar.indicatorMode = 'systems';
|
||||
didMigrateValue = true;
|
||||
}
|
||||
this.indicatorModeMigrated = true;
|
||||
return didMigrateValue;
|
||||
}
|
||||
|
||||
private applyWaypointData(saved: Record<string, unknown> | null): void {
|
||||
@@ -301,6 +331,7 @@ export default class WaypointPlugin extends Plugin {
|
||||
return this.saveData({
|
||||
settings: this.settings,
|
||||
waypointData: this.waypointData,
|
||||
indicatorModeMigrated: this.indicatorModeMigrated,
|
||||
});
|
||||
});
|
||||
// Keep the queue usable after a failed write without leaving an
|
||||
|
||||
+39
-35
@@ -1,8 +1,36 @@
|
||||
import { Setting, PluginSettingTab, App, setIcon } from 'obsidian';
|
||||
import { Setting, PluginSettingTab, App, setIcon, AbstractInputSuggest, TFile } from 'obsidian';
|
||||
import type WaypointPlugin from 'src/main';
|
||||
import { WaypointSettings, DEFAULT_SETTINGS, DEFAULT_DATE_SYSTEM, PeriodNoteSettings } from 'src/settings';
|
||||
import { formatHasDateToken } from 'src/utils/date-systems';
|
||||
|
||||
/**
|
||||
* Vault-wide Markdown-file suggester for template-path fields, matching
|
||||
* Obsidian's native suggest popup (same base class Templater's own template
|
||||
* pickers use) rather than the browser's unstyled `<datalist>` dropdown.
|
||||
*/
|
||||
class TemplateFileSuggest extends AbstractInputSuggest<TFile> {
|
||||
constructor(app: App, private inputEl: HTMLInputElement) {
|
||||
super(app, inputEl);
|
||||
}
|
||||
|
||||
getSuggestions(query: string): TFile[] {
|
||||
const q = query.toLowerCase();
|
||||
return this.app.vault.getMarkdownFiles()
|
||||
.filter(file => file.path.toLowerCase().includes(q))
|
||||
.slice(0, 200);
|
||||
}
|
||||
|
||||
renderSuggestion(file: TFile, el: HTMLElement): void {
|
||||
el.setText(file.path.replace(/\.md$/, ''));
|
||||
}
|
||||
|
||||
selectSuggestion(file: TFile): void {
|
||||
this.setValue(file.path.replace(/\.md$/, ''));
|
||||
this.inputEl.trigger('input');
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
export class WaypointSettingTab extends PluginSettingTab {
|
||||
private plugin: WaypointPlugin;
|
||||
private settings: WaypointSettings;
|
||||
@@ -129,20 +157,14 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
// ═══════════════════════════════
|
||||
|
||||
private renderPeriodicTab(container: HTMLElement): void {
|
||||
const templateSuggestionsId = this.createTemplateSuggestions(container);
|
||||
this.addPeriodNoteSettings(container, 'Daily', this.settings.daily, templateSuggestionsId);
|
||||
this.addPeriodNoteSettings(container, 'Weekly', this.settings.weekly, templateSuggestionsId);
|
||||
this.addPeriodNoteSettings(container, 'Monthly', this.settings.monthly, templateSuggestionsId);
|
||||
this.addPeriodNoteSettings(container, 'Quarterly', this.settings.quarterly, templateSuggestionsId);
|
||||
this.addPeriodNoteSettings(container, 'Yearly', this.settings.yearly, templateSuggestionsId);
|
||||
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);
|
||||
}
|
||||
|
||||
private addPeriodNoteSettings(
|
||||
container: HTMLElement,
|
||||
label: string,
|
||||
period: PeriodNoteSettings,
|
||||
templateSuggestionsId: string,
|
||||
): void {
|
||||
private addPeriodNoteSettings(container: HTMLElement, label: string, period: PeriodNoteSettings): void {
|
||||
new Setting(container).setHeading().setName(label);
|
||||
|
||||
new Setting(container)
|
||||
@@ -175,7 +197,7 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
.addText((text) => {
|
||||
text.setPlaceholder('Templates/Daily note');
|
||||
text.setValue(period.templateFile);
|
||||
text.inputEl.setAttr('list', templateSuggestionsId);
|
||||
new TemplateFileSuggest(this.app, text.inputEl);
|
||||
text.onChange((value) => {
|
||||
period.templateFile = value;
|
||||
this.saveAndRefresh();
|
||||
@@ -211,8 +233,6 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
.setName('Date systems')
|
||||
.setDesc(intro);
|
||||
|
||||
const templateSuggestionsId = this.createTemplateSuggestions(container);
|
||||
|
||||
this.settings.dateSystems.forEach((system, i, arr) => {
|
||||
new Setting(container)
|
||||
.setHeading()
|
||||
@@ -279,7 +299,7 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
|
||||
this.addSystemTextSetting(container,
|
||||
'Template file', 'Path to the template file. The .md extension is optional.',
|
||||
system, 'templateFile', 'resources/template/journal', templateSuggestionsId,
|
||||
system, 'templateFile', 'resources/template/journal', true,
|
||||
);
|
||||
this.addSystemTextSetting(container,
|
||||
'Type property', `Fallback value for the 'type' frontmatter property, used when no template is found.`,
|
||||
@@ -327,7 +347,7 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
obj: Record<K, string>,
|
||||
key: K,
|
||||
placeholder: string,
|
||||
templateSuggestionsId?: string,
|
||||
suggestTemplates?: boolean,
|
||||
): Setting {
|
||||
return new Setting(container)
|
||||
.setName(name)
|
||||
@@ -335,7 +355,7 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
.addText((text) => {
|
||||
text.setPlaceholder(placeholder);
|
||||
text.setValue(obj[key]);
|
||||
if (templateSuggestionsId) text.inputEl.setAttr('list', templateSuggestionsId);
|
||||
if (suggestTemplates) new TemplateFileSuggest(this.app, text.inputEl);
|
||||
text.onChange((value) => {
|
||||
obj[key] = value;
|
||||
this.saveAndRefresh();
|
||||
@@ -343,22 +363,6 @@ export class WaypointSettingTab extends PluginSettingTab {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Native datalist keeps template paths searchable without another custom
|
||||
* modal, and accepts templates stored anywhere in the vault.
|
||||
*/
|
||||
private createTemplateSuggestions(container: HTMLElement): string {
|
||||
const id = 'waypoint-template-suggestions';
|
||||
const list = container.createEl('datalist', { attr: { id } });
|
||||
const paths = this.app.vault.getMarkdownFiles()
|
||||
.map(file => file.path.replace(/\.md$/, ''))
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
for (const path of paths) {
|
||||
list.createEl('option', { value: path });
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════
|
||||
// Recent Files tab
|
||||
// ═══════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user