make it work well

This commit is contained in:
2026-01-04 20:42:36 -05:00
parent e5552e2d4c
commit 83846e7602

574
main.ts
View File

@@ -1,12 +1,12 @@
import { App, Notice, Plugin, PluginSettingTab, Setting, parseYaml } from "obsidian"; import { App, Notice, Plugin, PluginSettingTab, Setting, parseYaml, moment } from "obsidian";
import { RangeSetBuilder } from "@codemirror/state"; import { RangeSetBuilder } from "@codemirror/state";
import { import {
Decoration, Decoration,
DecorationSet, DecorationSet,
EditorView, EditorView,
ViewPlugin, ViewPlugin,
ViewUpdate, ViewUpdate,
WidgetType, WidgetType,
} from "@codemirror/view"; } from "@codemirror/view";
/* ========================= /* =========================
@@ -22,73 +22,114 @@ interface DateCalcSettings {
const DEFAULT_SETTINGS: DateCalcSettings = { const DEFAULT_SETTINGS: DateCalcSettings = {
debug: false, debug: false,
hideResultWhileCursorInside: true, hideResultWhileCursorInside: true,
verbose: false, // default to concise verbose: false,
}; };
/* ========================= /* =========================
Helpers Moment helpers
========================= */ ========================= */
function zeroTime(d: Date): Date { type M = moment.Moment;
const nd = new Date(d);
nd.setHours(0, 0, 0, 0);
return nd;
}
function parseDate(input: any): Date | null {
if (!input) return null;
const d = new Date(input);
if (isNaN(d.getTime())) return null;
return d;
}
function humanizeDuration(ms: number): string {
const abs = Math.abs(ms);
let seconds = Math.floor(abs / 1000);
let minutes = Math.floor(seconds / 60);
let hours = Math.floor(minutes / 60);
let days = Math.floor(hours / 24);
const years = Math.floor(days / 365);
days = days % 365;
const months = Math.floor(days / 30);
days = days % 30;
hours = hours % 24;
minutes = minutes % 60;
seconds = seconds % 60;
const parts: string[] = [];
if (years) parts.push(`${years} year${years !== 1 ? "s" : ""}`);
if (months) parts.push(`${months} month${months !== 1 ? "s" : ""}`);
if (days) parts.push(`${days} day${days !== 1 ? "s" : ""}`);
if (hours && parts.length < 3) parts.push(`${hours} hour${hours !== 1 ? "s" : ""}`);
if (minutes && parts.length < 3) parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
if (seconds && parts.length < 3) parts.push(`${seconds} second${seconds !== 1 ? "s" : ""}`);
if (!parts.length) return "0 seconds";
return parts.join(", ");
}
function isLivePreviewEditor(cm: EditorView): boolean { function isLivePreviewEditor(cm: EditorView): boolean {
// In Obsidian, the editor is inside .markdown-source-view
// Live Preview adds .is-live-preview on that container.
const container = cm.dom.closest(".markdown-source-view"); const container = cm.dom.closest(".markdown-source-view");
return !!container?.classList.contains("is-live-preview"); return !!container?.classList.contains("is-live-preview");
} }
/**
* Parse various inputs into a Moment.
* - "YYYY-MM-DD" => local startOf("day")
* - YAML Date objects that represent UTC midnight => keep same calendar date locally
* - Other date-time inputs => moment(...) as-is
*/
export function parseMoment(input: unknown): M | null {
if (input == null || input === "") return null;
// YAML can produce Date objects (often representing UTC midnight for date-only scalars)
if (input instanceof Date) {
if (Number.isNaN(input.getTime())) return null;
const isUtcMidnight =
input.getUTCHours() === 0 &&
input.getUTCMinutes() === 0 &&
input.getUTCSeconds() === 0 &&
input.getUTCMilliseconds() === 0;
if (isUtcMidnight) {
return moment({
year: input.getUTCFullYear(),
month: input.getUTCMonth(),
day: input.getUTCDate(),
}).startOf("day");
}
return moment(input);
}
const s = String(input).trim();
// Strict date-only => local midnight
const dateOnly = moment(s, "YYYY-MM-DD", true);
if (dateOnly.isValid()) return dateOnly.startOf("day");
// Fallback: let Moment interpret other formats (ISO with time/offset, etc.)
const any = moment(s);
return any.isValid() ? any : null;
}
function formatNiceDate(m: M): string {
return m.format("MMMM Do, YYYY");
}
/**
* Calendar-aware breakdown (years, months, days, hours) with remainder behavior.
* Returns a formatted string (verbose or concise).
*/
function formatSpan(from: M, to: M, verbose: boolean): { text: string; isNegative: boolean } {
let a = from.clone();
let b = to.clone();
const isNegative = b.isBefore(a);
if (isNegative) [a, b] = [b, a];
const years = b.diff(a, "years"); a.add(years, "years");
const months = b.diff(a, "months"); a.add(months, "months");
const days = b.diff(a, "days"); a.add(days, "days");
const hours = b.diff(a, "hours");
if (verbose) {
const parts: string[] = [];
if (years) parts.push(`${years} year${years === 1 ? "" : "s"}`);
if (months) parts.push(`${months} month${months === 1 ? "" : "s"}`);
if (days) parts.push(`${days} day${days === 1 ? "" : "s"}`);
if (hours && parts.length < 3) parts.push(`${hours} hour${hours === 1 ? "" : "s"}`);
return { text: parts.join(", ") || "0 days", isNegative };
} else {
const parts: string[] = [];
if (years) parts.push(`${years}y`);
if (months) parts.push(`${months}mo`);
if (days) parts.push(`${days}d`);
if (hours && parts.length < 3) parts.push(`${hours}h`);
return { text: parts.join(" ") || "0d", isNegative };
}
}
/* ========================= /* =========================
Inline processor Inline args parsing
========================= */ ========================= */
function parseKv(params: string): Record<string, string> { function parseKv(params: string): Record<string, string> {
const cfg: Record<string, string> = {}; const cfg: Record<string, string> = {};
// key=value where value can be "..." or '...' or a bare token
const rx = /(\w+)=("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/g; const rx = /(\w+)=("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/g;
let m: RegExpExecArray | null; let m: RegExpExecArray | null;
while ((m = rx.exec(params)) !== null) { while ((m = rx.exec(params)) !== null) {
const key = m[1]; const key = m[1];
let val = m[2]; let val = m[2];
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) { if (
(val.startsWith('"') && val.endsWith('"')) ||
(val.startsWith("'") && val.endsWith("'"))
) {
val = val.slice(1, -1); val = val.slice(1, -1);
} }
cfg[key] = val; cfg[key] = val;
@@ -97,125 +138,166 @@ function parseKv(params: string): Record<string, string> {
} }
function parseInlineArgs(params: string): any { function parseInlineArgs(params: string): any {
// 1) If it looks like key=value syntax, parse that first (fast + reliable) if (!params) return {};
// Prefer key=value (fast, predictable)
if (params.includes("=")) return parseKv(params); if (params.includes("=")) return parseKv(params);
// 2) Otherwise try YAML (for `{type: diff, from: ..., to: ...}` and similar) // Otherwise attempt YAML (supports `{ type: diff, from: ..., to: ... }`)
try { try {
const v = parseYaml(params); const v = parseYaml(params);
// If YAML returns a string, just return it (supports `date-calc: birthday`)
return v ?? {}; return v ?? {};
} catch { } catch {
// Last resort: empty // Fall back to raw string type alias like: `date-calc: birthday`
return {}; return params.trim();
} }
} }
function normalizeType(paramsRaw: string, cfg: any): { type: string; cfg: any } {
const params = paramsRaw.trim();
let type = (cfg?.type ?? "").toString().toLowerCase().trim();
// `date-calc: birthday`
if (!type && typeof cfg === "string" && /^[A-Za-z][A-Za-z-]*$/.test(cfg.trim())) {
type = cfg.trim().toLowerCase();
cfg = {};
}
if (!type && /^[A-Za-z][A-Za-z-]*$/.test(params)) {
type = params.toLowerCase();
cfg = {};
}
// Infer from fields
if (!type && cfg && typeof cfg === "object") {
if (cfg.birthday || cfg.birthdate) type = "birthday";
else if (cfg.to || cfg.until) type = "countdown";
else if (cfg.since) type = "since";
else if (cfg.from && cfg.to) type = "diff";
}
// Aliases
if (type === "bday") type = "birthday";
if (type === "until") type = "countdown";
if (type === "difference") type = "diff";
return { type, cfg };
}
/* =========================
Inline processor
========================= */
// raw should be WITHOUT backticks: "date-calc: birthday=1992-08-16" // raw should be WITHOUT backticks: "date-calc: birthday=1992-08-16"
function processInlineDateCalc(raw: string, app: App, sourcePath: string): string { function processInlineDateCalc(
if (!/^date-calc\s*:/.test(raw)) return ""; raw: string,
app: App,
sourcePath: string,
settings: DateCalcSettings
): { text: string; tooltip?: string } {
if (!/^date-calc\s*:/.test(raw)) return { text: "" };
const params = raw.replace(/^date-calc\s*:/, "").trim(); const paramsRaw = raw.replace(/^date-calc\s*:/, "").trim();
let cfg: any = params ? parseInlineArgs(params) : {}; let cfg: any = parseInlineArgs(paramsRaw);
const norm = normalizeType(paramsRaw, cfg);
const type = norm.type;
cfg = norm.cfg;
// Support `date-calc: birthday` if (!type) return { text: "" };
let type = (cfg?.type ?? "").toString().toLowerCase().trim();
if (!type && typeof cfg === "string" && /^[A-Za-z][A-Za-z-]*$/.test(cfg.trim())) {
type = cfg.trim().toLowerCase();
cfg = {};
}
if (!type && /^[A-Za-z][A-Za-z-]*$/.test(params)) type = params.toLowerCase();
// Infer const verbose = settings.verbose;
if (!type) {
if (cfg.birthday || cfg.birthdate) type = "birthday";
else if (cfg.to || cfg.until) type = "countdown";
else if (cfg.since) type = "since";
else if (cfg.from && cfg.to) type = "diff";
}
// Aliases try {
if (type === "bday") type = "birthday"; switch (type) {
if (type === "until") type = "countdown"; case "birthday": {
if (type === "difference") type = "diff"; const fileCache = app.metadataCache.getCache(sourcePath);
if (!type) return ""; const fm = (fileCache?.frontmatter ?? {}) as any;
try { const bstr =
switch (type) { cfg.birthday ||
case "birthday": { cfg.birthdate ||
const fileCache = app.metadataCache.getCache(sourcePath); cfg.date ||
const fm = (fileCache?.frontmatter ?? {}) as any; fm?.birthday ||
const bstr = cfg.birthday || cfg.birthdate || cfg.date || fm?.birthday || fm?.birthdate; fm?.birthdate;
const birthDate = parseDate(bstr);
if (!birthDate) return `date-calc: Missing or invalid "birthday" date.`;
const today = zeroTime(new Date()); const bd = parseMoment(bstr)?.startOf("day");
const bd = zeroTime(birthDate); if (!bd) return { text: `date-calc: Missing or invalid "birthday" date.` };
let age = today.getFullYear() - bd.getFullYear(); const today = moment().startOf("day");
const m = today.getMonth() - bd.getMonth();
if (m < 0 || (m === 0 && today.getDate() < bd.getDate())) age--;
const nextBirthday = zeroTime(new Date(today.getFullYear(), bd.getMonth(), bd.getDate())); const age = today.diff(bd, "years");
if (today > nextBirthday) nextBirthday.setFullYear(today.getFullYear() + 1);
const oneDay = 24 * 60 * 60 * 1000; const next = bd.clone().year(today.year());
const isBirthdayToday = today.getMonth() === bd.getMonth() && today.getDate() === bd.getDate(); if (next.isBefore(today, "day")) next.add(1, "year");
const daysUntil = isBirthdayToday ? 0 : Math.round((nextBirthday.getTime() - today.getTime()) / oneDay);
let msg = ""; const daysUntil = next.diff(today, "days");
if (daysUntil > 31) {
let monthsUntil =
(nextBirthday.getFullYear() - today.getFullYear()) * 12 +
(nextBirthday.getMonth() - today.getMonth());
if (today.getDate() > nextBirthday.getDate()) monthsUntil--;
msg = `Next birthday in ${monthsUntil} months.`;
} else if (daysUntil === 0) msg = "Wish them Happy Birthday!";
else if (daysUntil === 1) msg = "Their birthday is tomorrow!";
else msg = `Next birthday in ${daysUntil} days.`;
return `Age: ${age} years. ${msg}`; let msg: string;
} if (daysUntil === 0) msg = verbose ? "Wish them Happy Birthday!" : "Birthday!";
else if (daysUntil === 1) msg = verbose ? "Their birthday is tomorrow!" : "Tomorrow!";
else if (daysUntil > 31) {
const monthsUntil = next.diff(today, "months");
msg = verbose ? `Next birthday in ${monthsUntil} months.` : `Next in ${monthsUntil}mo`;
} else {
msg = verbose ? `Next birthday in ${daysUntil} days.` : `Next in ${daysUntil}d`;
}
case "countdown": { const ageStr = verbose ? `${age} years` : `${age}y`;
const toStr = cfg.to || cfg.date || cfg.until; return {
const toDate = parseDate(toStr); text: `Age: ${ageStr}. ${msg}`,
if (!toDate) return `date-calc: Missing or invalid "to" date.`; tooltip: formatNiceDate(bd),
};
}
const fromDate = parseDate(cfg.from) || new Date(); case "countdown": {
const diffMs = toDate.getTime() - fromDate.getTime(); const to = parseMoment(cfg.to || cfg.date || cfg.until);
const label = cfg.label ? `${cfg.label}: ` : ""; if (!to) return { text: `date-calc: Missing or invalid "to" date.` };
return diffMs >= 0 const from = parseMoment(cfg.from) ?? moment();
? `${label}Countdown: ${humanizeDuration(diffMs)}` const label = cfg.label ? `${cfg.label}: ` : "";
: `${label}Event passed ${humanizeDuration(-diffMs)} ago`;
}
case "diff": { const span = formatSpan(from, to, verbose);
const fromD = parseDate(cfg.from || cfg.start);
const toD = parseDate(cfg.to || cfg.end);
if (!fromD || !toD) return `date-calc: Provide valid "from" and "to" dates.`;
const diffMs = toD.getTime() - fromD.getTime(); const text = !span.isNegative
return `Difference: ${humanizeDuration(Math.abs(diffMs))}${diffMs < 0 ? " (to is before from)" : ""}`; ? `${label}${verbose ? "Countdown: " : ""}${span.text}`
} : `${label}${verbose ? "Event passed " : ""}${span.text}${verbose ? " ago" : ""}`;
case "since": { return { text };
const sinceDate = parseDate(cfg.since || cfg.from || cfg.date); }
if (!sinceDate) return `date-calc: Missing or invalid "since" date.`;
const diffMs = Date.now() - sinceDate.getTime(); case "diff": {
return diffMs >= 0 ? `Since: ${humanizeDuration(diffMs)} ago` : `In: ${humanizeDuration(-diffMs)}`; const from = parseMoment(cfg.from || cfg.start);
} const to = parseMoment(cfg.to || cfg.end);
if (!from || !to) return { text: `date-calc: Provide valid "from" and "to" dates.` };
default: const span = formatSpan(from, to, verbose);
return "date-calc: Unknown type. Supported: birthday, countdown, diff, since";
} const text = verbose
} catch (e: any) { ? `Difference: ${span.text}${span.isNegative ? " (to is before from)" : ""}`
return `date-calc error: ${e?.message || e}`; : `Diff: ${span.text}${span.isNegative ? " (reverse)" : ""}`;
}
return { text };
}
case "since": {
const since = parseMoment(cfg.since || cfg.from || cfg.date);
if (!since) return { text: `date-calc: Missing or invalid "since" date.` };
const now = moment();
const span = formatSpan(since, now, verbose);
const text = !span.isNegative
? `Since: ${span.text}${verbose ? " ago" : ""}`
: `In: ${span.text}`;
return { text };
}
default:
return { text: "date-calc: Unknown type. Supported: birthday, countdown, diff, since" };
}
} catch (e: any) {
return { text: `date-calc error: ${e?.message || e}` };
}
} }
/* ========================= /* =========================
@@ -223,22 +305,40 @@ function processInlineDateCalc(raw: string, app: App, sourcePath: string): strin
========================= */ ========================= */
class DateCalcWidget extends WidgetType { class DateCalcWidget extends WidgetType {
constructor(private text: string) { super(); } constructor(
eq(other: DateCalcWidget) { return other.text === this.text; } private text: string,
private tooltip: string | undefined,
private from: number,
private to: number
) { super(); }
eq(other: DateCalcWidget) {
return other.text === this.text && other.tooltip === this.tooltip;
}
toDOM() { toDOM() {
const span = document.createElement("span"); const span = document.createElement("span");
span.className = "date-calc-inline"; span.className = "date-calc-inline";
span.textContent = this.text; span.textContent = this.text;
span.setAttribute("contenteditable", "false"); span.setAttribute("contenteditable", "false");
// store source range for click-to-edit
span.dataset.dcFrom = String(this.from);
span.dataset.dcTo = String(this.to);
if (this.tooltip) {
span.className += " has-tooltip";
span.setAttribute("aria-label", this.tooltip);
}
return span; return span;
} }
ignoreEvent() { return false; } // important for interaction [web:64]
} }
export function dateCalcLivePreview(app: any, getSettings: () => { debug: boolean }) { export function dateCalcLivePreview(app: App, getSettings: () => DateCalcSettings) {
// Match only inline-code expressions (backticked)
const re = /`date-calc:[^`]*`/g; const re = /`date-calc:[^`]*`/g;
// Key fix: scan beyond visibleRanges so matches don't get missed at viewport boundaries // Scan past viewport boundaries so matches don't flicker/miss near edges
const margin = 2000; const margin = 2000;
return ViewPlugin.fromClass( return ViewPlugin.fromClass(
@@ -256,18 +356,19 @@ export function dateCalcLivePreview(app: any, getSettings: () => { debug: boolea
} }
build(view: EditorView): DecorationSet { build(view: EditorView): DecorationSet {
// IMPORTANT: do nothing in Source mode so raw markdown stays visible // Do nothing in Source mode so raw markdown stays visible
if (!isLivePreviewEditor(view)) return Decoration.none; if (!isLivePreviewEditor(view)) return Decoration.none;
const b = new RangeSetBuilder<Decoration>(); const settings = getSettings();
const sourcePath = app.workspace.getActiveFile()?.path ?? ""; const sourcePath = app.workspace.getActiveFile()?.path ?? "";
const head = view.state.selection.main.head; const head = view.state.selection.main.head;
const b = new RangeSetBuilder<Decoration>();
const used = new Set<string>();
let matchesSeen = 0; let matchesSeen = 0;
let replaced = 0; let replaced = 0;
const used = new Set<string>();
for (const r of view.visibleRanges) { for (const r of view.visibleRanges) {
const scanFrom = Math.max(0, r.from - margin); const scanFrom = Math.max(0, r.from - margin);
const scanTo = Math.min(view.state.doc.length, r.to + margin); const scanTo = Math.min(view.state.doc.length, r.to + margin);
@@ -282,46 +383,67 @@ export function dateCalcLivePreview(app: any, getSettings: () => { debug: boolea
const absStart = scanFrom + m.index; const absStart = scanFrom + m.index;
const absEnd = absStart + m[0].length; const absEnd = absStart + m[0].length;
// de-dupe overlaps from overlapping scan windows // De-dupe overlaps from overlapping scan windows
const key = `${absStart}:${absEnd}`; const key = `${absStart}:${absEnd}`;
if (used.has(key)) continue; if (used.has(key)) continue;
used.add(key); used.add(key);
// If cursor is inside, don't replace (so user can edit the raw inline code) // Optional: keep raw inline code visible while editing it
if (head >= absStart && head <= absEnd) continue; const sel = view.state.selection.main;
const intersects = sel.from <= absEnd && sel.to >= absStart;
if (settings.hideResultWhileCursorInside && intersects) continue;
const inner = m[0].slice(1, -1).trim(); // remove backticks const inner = m[0].slice(1, -1).trim(); // remove backticks
const result = processInlineDateCalc(inner, app, sourcePath); const result = processInlineDateCalc(inner, app, sourcePath, settings);
if (!result) continue; if (!result.text) continue;
// Replace the entire backticked expression with a widget
b.add( b.add(
absStart, absStart,
absEnd, absEnd,
Decoration.replace({ Decoration.replace({
widget: new DateCalcWidget(result), widget: new DateCalcWidget(result.text, result.tooltip, absStart, absEnd),
inclusive: false, inclusive: false,
}) })
); );
replaced++; replaced++;
} }
} }
if (getSettings().debug) { if (settings.debug) {
console.log("[date-calc] replace scan", { matchesSeen, replaced, sourcePath }); console.log("[date-calc] replace scan", { matchesSeen, replaced, sourcePath });
} }
return b.finish(); return b.finish();
} }
}, },
{ {
decorations: (v) => v.decorations, decorations: (v) => v.decorations,
// Important for replace widgets: treat them as “atomic” so cursor navigation behaves sanely eventHandlers: {
// (same pattern as CodeMirrors decoration examples) mousedown: (e, view) => {
provide: (plugin) => const el = (e.target as HTMLElement | null)?.closest?.(".date-calc-inline") as HTMLElement | null;
EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none), if (!el) return false;
}
const from = Number(el.dataset.dcFrom);
const to = Number(el.dataset.dcTo);
if (!Number.isFinite(from) || !Number.isFinite(to)) return false;
// Select the whole backticked expression so it immediately “reveals” for editing
view.dispatch({
selection: { anchor: from, head: to },
scrollIntoView: true,
});
view.focus();
return true; // CodeMirror will preventDefault when handler returns true [web:64]
},
},
provide: (plugin) =>
EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none),
}
); );
} }
@@ -330,31 +452,38 @@ export function dateCalcLivePreview(app: any, getSettings: () => { debug: boolea
========================= */ ========================= */
export default class DateCalcPlugin extends Plugin { export default class DateCalcPlugin extends Plugin {
settings: DateCalcSettings; settings: DateCalcSettings;
async onload() { async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
console.log("[date-calc] loaded"); if (this.settings.debug) console.log("[date-calc] loaded");
new Notice("Date Calc loaded (Live Preview)"); new Notice("Date Calc loaded (Live Preview)");
// Live Preview requires editor extensions + decorations this.registerEditorExtension(dateCalcLivePreview(this.app, () => this.settings));
this.registerEditorExtension(dateCalcLivePreview(this.app, () => this.settings)); // [page:2][page:1]
this.addCommand({
id: "date-calc-toggle-debug",
name: "Date Calc: Toggle debug",
callback: async () => {
this.settings.debug = !this.settings.debug;
await this.saveData(this.settings);
new Notice(`Date Calc debug: ${this.settings.debug ? "ON" : "OFF"}`);
},
});
this.addCommand({ this.addCommand({
id: "date-calc-toggle-debug", id: "date-calc-toggle-verbose",
name: "Date Calc: Toggle debug", name: "Date Calc: Toggle verbose output",
callback: async () => { callback: async () => {
this.settings.debug = !this.settings.debug; this.settings.verbose = !this.settings.verbose;
await this.saveData(this.settings); await this.saveData(this.settings);
new Notice(`Date Calc debug: ${this.settings.debug ? "ON" : "OFF"}`); new Notice(`Date Calc verbose: ${this.settings.verbose ? "ON" : "OFF"}`);
console.log("[date-calc] debug =", this.settings.debug); },
}, });
});
this.addSettingTab(new DateCalcSettingTab(this.app, this)); this.addSettingTab(new DateCalcSettingTab(this.app, this));
} }
} }
/* ========================= /* =========================
@@ -362,47 +491,46 @@ export default class DateCalcPlugin extends Plugin {
========================= */ ========================= */
class DateCalcSettingTab extends PluginSettingTab { class DateCalcSettingTab extends PluginSettingTab {
constructor(app: App, private plugin: DateCalcPlugin) { constructor(app: App, private plugin: DateCalcPlugin) {
super(app, plugin); super(app, plugin);
} }
display(): void { display(): void {
const { containerEl } = this; const { containerEl } = this;
containerEl.empty(); containerEl.empty();
containerEl.createEl("h2", { text: "Date Calc" }); containerEl.createEl("h2", { text: "Date Calc" });
new Setting(containerEl) new Setting(containerEl)
.setName("Debug logging") .setName("Debug logging")
.setDesc("Logs how many `date-calc:` inline-code matches were seen and decorated.") .setDesc("Logs how many `date-calc:` inline-code matches were seen and decorated.")
.addToggle((t) => .addToggle((t) =>
t.setValue(this.plugin.settings.debug).onChange(async (v) => { t.setValue(this.plugin.settings.debug).onChange(async (v) => {
this.plugin.settings.debug = v; this.plugin.settings.debug = v;
await this.plugin.saveData(this.plugin.settings); await this.plugin.saveData(this.plugin.settings);
}) })
); );
new Setting(containerEl) new Setting(containerEl)
.setName("Hide result while cursor inside inline code") .setName("Hide result while cursor inside inline code")
.setDesc("Prevents the widget from showing while you edit the inline backticks.") .setDesc("Prevents the widget from showing while you edit the inline backticks.")
.addToggle((t) => .addToggle((t) =>
t.setValue(this.plugin.settings.hideResultWhileCursorInside).onChange(async (v) => { t.setValue(this.plugin.settings.hideResultWhileCursorInside).onChange(async (v) => {
this.plugin.settings.hideResultWhileCursorInside = v; this.plugin.settings.hideResultWhileCursorInside = v;
await this.plugin.saveData(this.plugin.settings); await this.plugin.saveData(this.plugin.settings);
}) })
); );
new Setting(containerEl) new Setting(containerEl)
.setName("Verbose output") .setName("Verbose output")
.setDesc("When enabled, show the longer/wordier messages (your current output).") .setDesc("When enabled, show longer/wordier messages.")
.addToggle(t => .addToggle((t) =>
t.setValue(this.plugin.settings.verbose).onChange(async (v) => { t.setValue(this.plugin.settings.verbose).onChange(async (v) => {
this.plugin.settings.verbose = v; this.plugin.settings.verbose = v;
await this.plugin.saveData(this.plugin.settings); await this.plugin.saveData(this.plugin.settings);
}) })
); );
containerEl.createEl("p", { text: 'Example: `date-calc: birthday=1992-08-16`' });
containerEl.createEl("p", { text: 'Example: `date-calc: birthday=1992-08-16`' }); }
}
} }