chore: update dependencies and clean up styles
- Added @codemirror/language and @lezer/common as devDependencies in package.json and package-lock.json. - Removed inline date-calc styling and specific CodeMirror styles from styles.css for a cleaner design.
This commit is contained in:
@@ -21,16 +21,8 @@ const context = await esbuild.context({
|
|||||||
"obsidian",
|
"obsidian",
|
||||||
"electron",
|
"electron",
|
||||||
"@codemirror/autocomplete",
|
"@codemirror/autocomplete",
|
||||||
"@codemirror/collab",
|
"@codemirror/*",
|
||||||
"@codemirror/commands",
|
"@lezer/*",
|
||||||
"@codemirror/language",
|
|
||||||
"@codemirror/lint",
|
|
||||||
"@codemirror/search",
|
|
||||||
"@codemirror/state",
|
|
||||||
"@codemirror/view",
|
|
||||||
"@lezer/common",
|
|
||||||
"@lezer/highlight",
|
|
||||||
"@lezer/lr",
|
|
||||||
...builtins],
|
...builtins],
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
target: "es2018",
|
target: "es2018",
|
||||||
|
|||||||
796
main.ts
796
main.ts
@@ -1,18 +1,34 @@
|
|||||||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, parseYaml, Component, MarkdownPostProcessorContext, MarkdownRenderChild } from 'obsidian';
|
import { App, Notice, Plugin, PluginSettingTab, Setting, parseYaml } from "obsidian";
|
||||||
import { EditorView, ViewPlugin, ViewUpdate, Decoration, DecorationSet, WidgetType } from '@codemirror/view';
|
import { RangeSetBuilder } from "@codemirror/state";
|
||||||
import { RangeSetBuilder } from '@codemirror/state';
|
import {
|
||||||
|
Decoration,
|
||||||
|
DecorationSet,
|
||||||
|
EditorView,
|
||||||
|
ViewPlugin,
|
||||||
|
ViewUpdate,
|
||||||
|
WidgetType,
|
||||||
|
} from "@codemirror/view";
|
||||||
|
|
||||||
// Remember to rename these classes and interfaces!
|
/* =========================
|
||||||
|
Settings
|
||||||
|
========================= */
|
||||||
|
|
||||||
interface MyPluginSettings {
|
interface DateCalcSettings {
|
||||||
mySetting: string;
|
debug: boolean;
|
||||||
|
hideResultWhileCursorInside: boolean;
|
||||||
|
verbose: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
const DEFAULT_SETTINGS: DateCalcSettings = {
|
||||||
mySetting: 'default'
|
debug: false,
|
||||||
}
|
hideResultWhileCursorInside: true,
|
||||||
|
verbose: false, // default to concise
|
||||||
|
};
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Helpers
|
||||||
|
========================= */
|
||||||
|
|
||||||
// ===== Helper functions for date calculations =====
|
|
||||||
function zeroTime(d: Date): Date {
|
function zeroTime(d: Date): Date {
|
||||||
const nd = new Date(d);
|
const nd = new Date(d);
|
||||||
nd.setHours(0, 0, 0, 0);
|
nd.setHours(0, 0, 0, 0);
|
||||||
@@ -32,597 +48,361 @@ function humanizeDuration(ms: number): string {
|
|||||||
let minutes = Math.floor(seconds / 60);
|
let minutes = Math.floor(seconds / 60);
|
||||||
let hours = Math.floor(minutes / 60);
|
let hours = Math.floor(minutes / 60);
|
||||||
let days = Math.floor(hours / 24);
|
let days = Math.floor(hours / 24);
|
||||||
let years = Math.floor(days / 365);
|
const years = Math.floor(days / 365);
|
||||||
days = days % 365;
|
days = days % 365;
|
||||||
let months = Math.floor(days / 30);
|
const months = Math.floor(days / 30);
|
||||||
days = days % 30;
|
days = days % 30;
|
||||||
hours = hours % 24;
|
hours = hours % 24;
|
||||||
minutes = minutes % 60;
|
minutes = minutes % 60;
|
||||||
seconds = seconds % 60;
|
seconds = seconds % 60;
|
||||||
|
|
||||||
const parts: string[] = [];
|
const parts: string[] = [];
|
||||||
if (years) parts.push(`${years} year${years !== 1 ? 's' : ''}`);
|
if (years) parts.push(`${years} year${years !== 1 ? "s" : ""}`);
|
||||||
if (months) parts.push(`${months} month${months !== 1 ? 's' : ''}`);
|
if (months) parts.push(`${months} month${months !== 1 ? "s" : ""}`);
|
||||||
if (days) parts.push(`${days} day${days !== 1 ? 's' : ''}`);
|
if (days) parts.push(`${days} day${days !== 1 ? "s" : ""}`);
|
||||||
if (hours && parts.length < 3) parts.push(`${hours} hour${hours !== 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 (minutes && parts.length < 3) parts.push(`${minutes} minute${minutes !== 1 ? "s" : ""}`);
|
||||||
if (seconds && parts.length < 3) parts.push(`${seconds} second${seconds !== 1 ? 's' : ''}`);
|
if (seconds && parts.length < 3) parts.push(`${seconds} second${seconds !== 1 ? "s" : ""}`);
|
||||||
|
|
||||||
if (!parts.length) return '0 seconds';
|
if (!parts.length) return "0 seconds";
|
||||||
return parts.join(', ');
|
return parts.join(", ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shared function to process inline date-calc code
|
function isLivePreviewEditor(cm: EditorView): boolean {
|
||||||
function processInlineDateCalc(raw: string, app: App, sourcePath: string): string {
|
// In Obsidian, the editor is inside .markdown-source-view
|
||||||
if (!/^date-calc\s*:/.test(raw)) return '';
|
// Live Preview adds .is-live-preview on that container.
|
||||||
const params = raw.replace(/^date-calc\s*:/, '').trim();
|
const container = cm.dom.closest(".markdown-source-view");
|
||||||
let cfg: any = {};
|
return !!container?.classList.contains("is-live-preview");
|
||||||
if (params) {
|
}
|
||||||
// Try YAML first (supports inline map like {a: b, c: d} or multiline pasted)
|
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Inline processor
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
function parseKv(params: string): Record<string, string> {
|
||||||
|
const cfg: Record<string, string> = {};
|
||||||
|
// key=value where value can be "..." or '...' or a bare token
|
||||||
|
const rx = /(\w+)=("([^"\\]|\\.)*"|'([^'\\]|\\.)*'|\S+)/g;
|
||||||
|
let m: RegExpExecArray | null;
|
||||||
|
while ((m = rx.exec(params)) !== null) {
|
||||||
|
const key = m[1];
|
||||||
|
let val = m[2];
|
||||||
|
if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
|
||||||
|
val = val.slice(1, -1);
|
||||||
|
}
|
||||||
|
cfg[key] = val;
|
||||||
|
}
|
||||||
|
return cfg;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseInlineArgs(params: string): any {
|
||||||
|
// 1) If it looks like key=value syntax, parse that first (fast + reliable)
|
||||||
|
if (params.includes("=")) return parseKv(params);
|
||||||
|
|
||||||
|
// 2) Otherwise try YAML (for `{type: diff, from: ..., to: ...}` and similar)
|
||||||
try {
|
try {
|
||||||
cfg = parseYaml(params) || {};
|
const v = parseYaml(params);
|
||||||
|
// If YAML returns a string, just return it (supports `date-calc: birthday`)
|
||||||
|
return v ?? {};
|
||||||
} catch {
|
} catch {
|
||||||
// Fallback: parse key=value pairs separated by whitespace
|
// Last resort: empty
|
||||||
cfg = {};
|
return {};
|
||||||
params.split(/\s+/).forEach(pair => {
|
|
||||||
const eq = pair.indexOf('=');
|
|
||||||
if (eq > 0) {
|
|
||||||
const k = pair.slice(0, eq).trim();
|
|
||||||
const v = pair.slice(eq + 1).trim();
|
|
||||||
if (k) (cfg as any)[k] = v;
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Infer type if missing (also support bare-word like `date-calc:birthday`)
|
|
||||||
let type = (cfg as any)?.type ? (cfg.type as any).toString().toLowerCase().trim() : '';
|
|
||||||
if (!type) {
|
|
||||||
// If YAML parsed into a string, decide if it's a bare type or a key=value expression
|
|
||||||
if (typeof cfg === 'string') {
|
|
||||||
const s = cfg.trim();
|
|
||||||
if (/^[A-Za-z][A-Za-z-]*$/.test(s)) {
|
|
||||||
type = s.toLowerCase();
|
|
||||||
cfg = {};
|
|
||||||
} else if (s.includes('=')) {
|
|
||||||
// Parse as key=value pairs
|
|
||||||
const tmp: any = {};
|
|
||||||
s.split(/\s+/).forEach(pair => {
|
|
||||||
const eq = pair.indexOf('=');
|
|
||||||
if (eq > 0) {
|
|
||||||
const k = pair.slice(0, eq).trim();
|
|
||||||
const v = pair.slice(eq + 1).trim();
|
|
||||||
if (k) tmp[k] = v;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
cfg = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!type) {
|
|
||||||
// If params is a single bare word without separators, treat as type
|
|
||||||
if (/^[A-Za-z][A-Za-z-]*$/.test(params)) {
|
|
||||||
type = params.toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!type) {
|
|
||||||
if ((cfg as any).birthday || (cfg as any).birthdate) type = 'birthday';
|
|
||||||
else if ((cfg as any).to || (cfg as any).until) type = 'countdown';
|
|
||||||
else if ((cfg as any).since) type = 'since';
|
|
||||||
else if ((cfg as any).from && (cfg as any).to) type = 'diff';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize type aliases to match fenced code block processor
|
|
||||||
if (type === 'bday') type = 'birthday';
|
|
||||||
if (type === 'until') type = 'countdown';
|
|
||||||
if (type === 'difference') type = 'diff';
|
|
||||||
if (!type) return '';
|
|
||||||
|
|
||||||
// Render result similarly to the block processor
|
|
||||||
let out = '';
|
// raw should be WITHOUT backticks: "date-calc: birthday=1992-08-16"
|
||||||
|
function processInlineDateCalc(raw: string, app: App, sourcePath: string): string {
|
||||||
|
if (!/^date-calc\s*:/.test(raw)) return "";
|
||||||
|
|
||||||
|
const params = raw.replace(/^date-calc\s*:/, "").trim();
|
||||||
|
let cfg: any = params ? parseInlineArgs(params) : {};
|
||||||
|
|
||||||
|
// Support `date-calc: birthday`
|
||||||
|
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
|
||||||
|
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
|
||||||
|
if (type === "bday") type = "birthday";
|
||||||
|
if (type === "until") type = "countdown";
|
||||||
|
if (type === "difference") type = "diff";
|
||||||
|
if (!type) return "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'birthday': {
|
case "birthday": {
|
||||||
const fileCache = app.metadataCache.getCache(sourcePath);
|
const fileCache = app.metadataCache.getCache(sourcePath);
|
||||||
const fm = fileCache?.frontmatter || {} as any;
|
const fm = (fileCache?.frontmatter ?? {}) as any;
|
||||||
const bstr = cfg.birthday || cfg.birthdate || cfg.date || fm?.birthday || fm?.birthdate;
|
const bstr = cfg.birthday || cfg.birthdate || cfg.date || fm?.birthday || fm?.birthdate;
|
||||||
const birthDate = parseDate(bstr);
|
const birthDate = parseDate(bstr);
|
||||||
if (!birthDate) { out = 'date-calc: Missing or invalid "birthday" date.'; break; }
|
if (!birthDate) return `date-calc: Missing or invalid "birthday" date.`;
|
||||||
|
|
||||||
const today = zeroTime(new Date());
|
const today = zeroTime(new Date());
|
||||||
const bd = zeroTime(birthDate);
|
const bd = zeroTime(birthDate);
|
||||||
|
|
||||||
let age = today.getFullYear() - bd.getFullYear();
|
let age = today.getFullYear() - bd.getFullYear();
|
||||||
const m = today.getMonth() - bd.getMonth();
|
const m = today.getMonth() - bd.getMonth();
|
||||||
if (m < 0 || (m === 0 && today.getDate() < bd.getDate())) age--;
|
if (m < 0 || (m === 0 && today.getDate() < bd.getDate())) age--;
|
||||||
let nextBirthday = zeroTime(new Date(today.getFullYear(), bd.getMonth(), bd.getDate()));
|
|
||||||
|
const nextBirthday = zeroTime(new Date(today.getFullYear(), bd.getMonth(), bd.getDate()));
|
||||||
if (today > nextBirthday) nextBirthday.setFullYear(today.getFullYear() + 1);
|
if (today > nextBirthday) nextBirthday.setFullYear(today.getFullYear() + 1);
|
||||||
|
|
||||||
const oneDay = 24 * 60 * 60 * 1000;
|
const oneDay = 24 * 60 * 60 * 1000;
|
||||||
const isBirthdayToday = (today.getMonth() === bd.getMonth() && today.getDate() === bd.getDate());
|
const isBirthdayToday = today.getMonth() === bd.getMonth() && today.getDate() === bd.getDate();
|
||||||
const daysUntil = isBirthdayToday ? 0 : Math.round((nextBirthday.getTime() - today.getTime()) / oneDay);
|
const daysUntil = isBirthdayToday ? 0 : Math.round((nextBirthday.getTime() - today.getTime()) / oneDay);
|
||||||
let msg: string;
|
|
||||||
|
let msg = "";
|
||||||
if (daysUntil > 31) {
|
if (daysUntil > 31) {
|
||||||
let monthsUntil = (nextBirthday.getFullYear() - today.getFullYear()) * 12 + (nextBirthday.getMonth() - today.getMonth());
|
let monthsUntil =
|
||||||
|
(nextBirthday.getFullYear() - today.getFullYear()) * 12 +
|
||||||
|
(nextBirthday.getMonth() - today.getMonth());
|
||||||
if (today.getDate() > nextBirthday.getDate()) monthsUntil--;
|
if (today.getDate() > nextBirthday.getDate()) monthsUntil--;
|
||||||
const refDate = zeroTime(new Date(nextBirthday.getFullYear(), nextBirthday.getMonth() - monthsUntil, today.getDate()));
|
|
||||||
const partialMonthDays = Math.round((nextBirthday.getTime() - refDate.getTime()) / oneDay);
|
|
||||||
if (partialMonthDays > 15) monthsUntil += 0.5;
|
|
||||||
msg = `Next birthday in ${monthsUntil} months.`;
|
msg = `Next birthday in ${monthsUntil} months.`;
|
||||||
} else if (daysUntil === 0) {
|
} else if (daysUntil === 0) msg = "Wish them Happy Birthday!";
|
||||||
msg = 'Wish them Happy Birthday!';
|
else if (daysUntil === 1) msg = "Their birthday is tomorrow!";
|
||||||
} else if (daysUntil === 1) {
|
else msg = `Next birthday in ${daysUntil} days.`;
|
||||||
msg = 'Their birthday is tomorrow!';
|
|
||||||
} else {
|
return `Age: ${age} years. ${msg}`;
|
||||||
msg = `Next birthday in ${daysUntil} days.`;
|
|
||||||
}
|
}
|
||||||
out = `Age: ${age} years. ${msg}`;
|
|
||||||
break;
|
case "countdown": {
|
||||||
}
|
|
||||||
case 'countdown':
|
|
||||||
case 'until': {
|
|
||||||
const toStr = cfg.to || cfg.date || cfg.until;
|
const toStr = cfg.to || cfg.date || cfg.until;
|
||||||
const toDate = parseDate(toStr);
|
const toDate = parseDate(toStr);
|
||||||
if (!toDate) { out = 'date-calc: Missing or invalid "to" date.'; break; }
|
if (!toDate) return `date-calc: Missing or invalid "to" date.`;
|
||||||
|
|
||||||
const fromDate = parseDate(cfg.from) || new Date();
|
const fromDate = parseDate(cfg.from) || new Date();
|
||||||
const diffMs = toDate.getTime() - fromDate.getTime();
|
const diffMs = toDate.getTime() - fromDate.getTime();
|
||||||
const label = cfg.label ? `${cfg.label}: ` : '';
|
const label = cfg.label ? `${cfg.label}: ` : "";
|
||||||
out = diffMs >= 0 ? `${label}Countdown: ${humanizeDuration(diffMs)}` : `${label}Event passed ${humanizeDuration(-diffMs)} ago`;
|
|
||||||
break;
|
return diffMs >= 0
|
||||||
|
? `${label}Countdown: ${humanizeDuration(diffMs)}`
|
||||||
|
: `${label}Event passed ${humanizeDuration(-diffMs)} ago`;
|
||||||
}
|
}
|
||||||
case 'diff':
|
|
||||||
case 'difference': {
|
case "diff": {
|
||||||
const fromStr = cfg.from || cfg.start;
|
const fromD = parseDate(cfg.from || cfg.start);
|
||||||
const toStr = cfg.to || cfg.end;
|
const toD = parseDate(cfg.to || cfg.end);
|
||||||
const fromD = parseDate(fromStr);
|
if (!fromD || !toD) return `date-calc: Provide valid "from" and "to" dates.`;
|
||||||
const toD = parseDate(toStr);
|
|
||||||
if (!fromD || !toD) { out = 'date-calc: Provide valid "from" and "to" dates.'; break; }
|
|
||||||
const diffMs = toD.getTime() - fromD.getTime();
|
const diffMs = toD.getTime() - fromD.getTime();
|
||||||
out = `Difference: ${humanizeDuration(Math.abs(diffMs))}${diffMs < 0 ? ' (to is before from)' : ''}`;
|
return `Difference: ${humanizeDuration(Math.abs(diffMs))}${diffMs < 0 ? " (to is before from)" : ""}`;
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case 'since': {
|
|
||||||
const sinceStr = cfg.since || cfg.from || cfg.date;
|
case "since": {
|
||||||
const sinceDate = parseDate(sinceStr);
|
const sinceDate = parseDate(cfg.since || cfg.from || cfg.date);
|
||||||
if (!sinceDate) { out = 'date-calc: Missing or invalid "since" date.'; break; }
|
if (!sinceDate) return `date-calc: Missing or invalid "since" date.`;
|
||||||
const diffMs = new Date().getTime() - sinceDate.getTime();
|
|
||||||
out = diffMs >= 0 ? `Since: ${humanizeDuration(diffMs)} ago` : `In: ${humanizeDuration(-diffMs)}`;
|
const diffMs = Date.now() - sinceDate.getTime();
|
||||||
break;
|
return diffMs >= 0 ? `Since: ${humanizeDuration(diffMs)} ago` : `In: ${humanizeDuration(-diffMs)}`;
|
||||||
}
|
|
||||||
default: {
|
|
||||||
out = 'date-calc: Unknown type. Supported types: birthday, countdown, diff, since';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return "date-calc: Unknown type. Supported: birthday, countdown, diff, since";
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
out = `date-calc error: ${e?.message || e}`;
|
return `date-calc error: ${e?.message || e}`;
|
||||||
}
|
}
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Widget for displaying date-calc results in Live Preview
|
/* =========================
|
||||||
|
Live Preview decorations
|
||||||
|
========================= */
|
||||||
|
|
||||||
class DateCalcWidget extends WidgetType {
|
class DateCalcWidget extends WidgetType {
|
||||||
constructor(
|
constructor(private text: string) { super(); }
|
||||||
private result: string,
|
eq(other: DateCalcWidget) { return other.text === this.text; }
|
||||||
private app: App
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
eq(other: DateCalcWidget) {
|
|
||||||
return other.result === this.result;
|
|
||||||
}
|
|
||||||
|
|
||||||
toDOM() {
|
toDOM() {
|
||||||
const span = document.createElement('span');
|
const span = document.createElement("span");
|
||||||
span.classList.add('date-calc-inline');
|
span.className = "date-calc-inline";
|
||||||
span.setAttribute('contenteditable', 'false');
|
span.textContent = this.text;
|
||||||
span.textContent = this.result;
|
span.setAttribute("contenteditable", "false");
|
||||||
return span;
|
return span;
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreEvent() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Live Preview ViewPlugin for inline date-calc processing
|
export function dateCalcLivePreview(app: any, getSettings: () => { debug: boolean }) {
|
||||||
const dateCalcLivePreviewPlugin = (app: App) => ViewPlugin.fromClass(class {
|
// Match only inline-code expressions (backticked)
|
||||||
|
const re = /`date-calc:[^`]*`/g;
|
||||||
|
|
||||||
|
// Key fix: scan beyond visibleRanges so matches don't get missed at viewport boundaries
|
||||||
|
const margin = 2000;
|
||||||
|
|
||||||
|
return ViewPlugin.fromClass(
|
||||||
|
class {
|
||||||
decorations: DecorationSet = Decoration.none;
|
decorations: DecorationSet = Decoration.none;
|
||||||
|
|
||||||
constructor(view: EditorView) {
|
constructor(view: EditorView) {
|
||||||
this.buildDecorations(view);
|
this.decorations = this.build(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
update(update: ViewUpdate) {
|
update(u: ViewUpdate) {
|
||||||
if (update.docChanged || update.viewportChanged) {
|
if (u.docChanged || u.viewportChanged || u.selectionSet) {
|
||||||
this.buildDecorations(update.view);
|
this.decorations = this.build(u.view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buildDecorations(view: EditorView) {
|
build(view: EditorView): DecorationSet {
|
||||||
const builder = new RangeSetBuilder<Decoration>();
|
// IMPORTANT: do nothing in Source mode so raw markdown stays visible
|
||||||
const text = view.state.doc.toString();
|
if (!isLivePreviewEditor(view)) return Decoration.none;
|
||||||
|
|
||||||
// Find inline code spans that start with date-calc:
|
const b = new RangeSetBuilder<Decoration>();
|
||||||
// This regex looks for inline code between backticks
|
const sourcePath = app.workspace.getActiveFile()?.path ?? "";
|
||||||
const inlineCodeRegex = /`([^`]+)`/g;
|
const head = view.state.selection.main.head;
|
||||||
let match;
|
|
||||||
|
|
||||||
while ((match = inlineCodeRegex.exec(text)) !== null) {
|
let matchesSeen = 0;
|
||||||
const codeContent = match[1];
|
let replaced = 0;
|
||||||
if (/^date-calc\s*:/.test(codeContent)) {
|
|
||||||
const from = match.index;
|
|
||||||
const to = match.index + match[0].length;
|
|
||||||
|
|
||||||
// Get the active file path for processing
|
const used = new Set<string>();
|
||||||
const activeFile = app.workspace.getActiveFile();
|
|
||||||
const sourcePath = activeFile?.path || '';
|
|
||||||
|
|
||||||
// Process the date calculation
|
for (const r of view.visibleRanges) {
|
||||||
const result = processInlineDateCalc(codeContent, app, sourcePath);
|
const scanFrom = Math.max(0, r.from - margin);
|
||||||
|
const scanTo = Math.min(view.state.doc.length, r.to + margin);
|
||||||
|
|
||||||
if (result) {
|
const slice = view.state.doc.sliceString(scanFrom, scanTo);
|
||||||
// Create a decoration that shows the result after the inline code
|
re.lastIndex = 0;
|
||||||
const widget = Decoration.widget({
|
|
||||||
widget: new DateCalcWidget(` → ${result}`, app),
|
|
||||||
side: 1
|
|
||||||
});
|
|
||||||
builder.add(to, to, widget);
|
|
||||||
|
|
||||||
// Add a debug log to verify when a widget is being created
|
let m: RegExpExecArray | null;
|
||||||
console.log(`Live Preview widget created for: ${codeContent} with result: ${result}`);
|
while ((m = re.exec(slice)) !== null) {
|
||||||
|
matchesSeen++;
|
||||||
|
|
||||||
|
const absStart = scanFrom + m.index;
|
||||||
|
const absEnd = absStart + m[0].length;
|
||||||
|
|
||||||
|
// de-dupe overlaps from overlapping scan windows
|
||||||
|
const key = `${absStart}:${absEnd}`;
|
||||||
|
if (used.has(key)) continue;
|
||||||
|
used.add(key);
|
||||||
|
|
||||||
|
// If cursor is inside, don't replace (so user can edit the raw inline code)
|
||||||
|
if (head >= absStart && head <= absEnd) continue;
|
||||||
|
|
||||||
|
const inner = m[0].slice(1, -1).trim(); // remove backticks
|
||||||
|
const result = processInlineDateCalc(inner, app, sourcePath);
|
||||||
|
if (!result) continue;
|
||||||
|
|
||||||
|
// Replace the entire backticked expression with a widget
|
||||||
|
b.add(
|
||||||
|
absStart,
|
||||||
|
absEnd,
|
||||||
|
Decoration.replace({
|
||||||
|
widget: new DateCalcWidget(result),
|
||||||
|
inclusive: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
replaced++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (getSettings().debug) {
|
||||||
|
console.log("[date-calc] replace scan", { matchesSeen, replaced, sourcePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
this.decorations = builder.finish();
|
return b.finish();
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
decorations: v => v.decorations
|
{
|
||||||
});
|
decorations: (v) => v.decorations,
|
||||||
|
|
||||||
// DateCalc Inline Renderer - following dataview's component-based approach
|
// Important for replace widgets: treat them as “atomic” so cursor navigation behaves sanely
|
||||||
class DateCalcInlineRenderer extends MarkdownRenderChild {
|
// (same pattern as CodeMirror’s decoration examples)
|
||||||
constructor(
|
provide: (plugin) =>
|
||||||
public raw: string,
|
EditorView.atomicRanges.of((view) => view.plugin(plugin)?.decorations ?? Decoration.none),
|
||||||
containerEl: HTMLElement,
|
|
||||||
public target: HTMLElement,
|
|
||||||
public app: App,
|
|
||||||
public sourcePath: string
|
|
||||||
) {
|
|
||||||
super(containerEl);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* =========================
|
||||||
|
Plugin
|
||||||
|
========================= */
|
||||||
|
|
||||||
|
export default class DateCalcPlugin extends Plugin {
|
||||||
|
settings: DateCalcSettings;
|
||||||
|
|
||||||
async onload() {
|
async onload() {
|
||||||
await this.render();
|
|
||||||
}
|
|
||||||
|
|
||||||
async render() {
|
|
||||||
const result = processInlineDateCalc(this.raw, this.app, this.sourcePath);
|
|
||||||
if (result) {
|
|
||||||
console.log(`Rendering inline date-calc: ${this.raw} -> ${result}`);
|
|
||||||
|
|
||||||
// In Live Preview, just replace the code element directly
|
|
||||||
const span = document.createElement('span');
|
|
||||||
span.classList.add('date-calc-inline');
|
|
||||||
span.setAttribute('contenteditable', 'false');
|
|
||||||
span.textContent = result;
|
|
||||||
|
|
||||||
// Ensure we properly replace the target element
|
|
||||||
try {
|
|
||||||
this.target.parentNode?.replaceChild(span, this.target);
|
|
||||||
console.log('Successfully replaced code element with result');
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to replace code element:', e);
|
|
||||||
// Fallback: try the original method
|
|
||||||
this.target.replaceWith(span);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(`No result for inline date-calc: ${this.raw}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note: Removed CodeMirror live preview plugin to preserve editability
|
|
||||||
// Following dataview's approach of using only post-processors
|
|
||||||
|
|
||||||
export default class MyPlugin extends Plugin {
|
|
||||||
settings: MyPluginSettings;
|
|
||||||
|
|
||||||
async onload() {
|
|
||||||
await this.loadSettings();
|
|
||||||
|
|
||||||
// Add a status bar item showing the plugin is enabled
|
|
||||||
const statusBarItemEl = this.addStatusBarItem();
|
|
||||||
statusBarItemEl.setText('Date Calculator Enabled');
|
|
||||||
|
|
||||||
// Debugging command to test plugin functionality
|
|
||||||
this.addCommand({
|
|
||||||
id: 'date-calc-debug',
|
|
||||||
name: 'Debug Date Calculator',
|
|
||||||
callback: () => {
|
|
||||||
new Notice('Date Calculator plugin is working! Check console for more details.');
|
|
||||||
console.log('Date Calculator plugin debug info:');
|
|
||||||
console.log('Plugin version: 1.0.0');
|
|
||||||
console.log('Plugin enabled: true');
|
|
||||||
console.log('Current file:', this.app.workspace.getActiveFile()?.path);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Command to refresh inline calculations (useful for troubleshooting)
|
|
||||||
this.addCommand({
|
|
||||||
id: 'refresh-date-calc-inline',
|
|
||||||
name: 'Refresh Date Calculations',
|
|
||||||
callback: () => {
|
|
||||||
// Force refresh by triggering layout
|
|
||||||
this.app.workspace.trigger('layout-change');
|
|
||||||
new Notice('Date calculations refreshed!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
|
||||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
|
||||||
|
|
||||||
// CRITICAL: Register Live Preview extension FIRST and ensure it's properly loaded
|
|
||||||
console.log('Registering Live Preview editor extension for date-calc...');
|
|
||||||
this.registerEditorExtension(dateCalcLivePreviewPlugin(this.app));
|
|
||||||
console.log('Date Calculator Live Preview extension registered successfully!');
|
|
||||||
|
|
||||||
// Add more comprehensive debugging
|
|
||||||
new Notice('Date Calculator loaded with Live Preview support!');
|
|
||||||
|
|
||||||
// Register the `date-calc` code block processor
|
|
||||||
this.registerMarkdownCodeBlockProcessor('date-calc', (source, el, ctx) => {
|
|
||||||
let cfg: any = {};
|
|
||||||
try {
|
|
||||||
cfg = parseYaml(source) || {};
|
|
||||||
} catch (e: any) {
|
|
||||||
el.createEl('pre', { text: `date-calc: Invalid YAML: ${e?.message || e}` });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const type = (cfg.type || '').toString().toLowerCase().trim();
|
|
||||||
const container = el.createDiv({ cls: 'date-calc' });
|
|
||||||
|
|
||||||
try {
|
|
||||||
switch (type) {
|
|
||||||
case 'bday':
|
|
||||||
case 'birthday': {
|
|
||||||
// Accept from config or frontmatter fallback
|
|
||||||
const fileCache = this.app.metadataCache.getCache(ctx.sourcePath);
|
|
||||||
const fm = fileCache?.frontmatter || {} as any;
|
|
||||||
const bstr = cfg.birthday || cfg.birthdate || cfg.date || fm?.birthday || fm?.birthdate;
|
|
||||||
const birthDate = bstr ? new Date(bstr + "T00:00:00") : null;
|
|
||||||
|
|
||||||
if (!birthDate) {
|
|
||||||
container.setText('date-calc: Missing or invalid "birthday" date.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get name and gender from config or frontmatter
|
|
||||||
const name = cfg.name || fm?.name || "Person";
|
|
||||||
const gender = cfg.gender || fm?.gender || "neutral";
|
|
||||||
|
|
||||||
// Configurable label with default
|
|
||||||
const ageLabel = cfg.label || "Age:";
|
|
||||||
|
|
||||||
// Format birthday as "July 6, 1998"
|
|
||||||
const birthdayFormatted = birthDate.toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric'
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine pronouns based on gender
|
|
||||||
let pronoun = "their";
|
|
||||||
let possessive = "their";
|
|
||||||
if (gender.toLowerCase() === "male" || gender.toLowerCase() === "m") {
|
|
||||||
pronoun = "his";
|
|
||||||
possessive = "his";
|
|
||||||
} else if (gender.toLowerCase() === "female" || gender.toLowerCase() === "f") {
|
|
||||||
pronoun = "her";
|
|
||||||
possessive = "her";
|
|
||||||
}
|
|
||||||
|
|
||||||
const today = zeroTime(new Date());
|
|
||||||
const bd = zeroTime(birthDate);
|
|
||||||
|
|
||||||
let age = today.getFullYear() - bd.getFullYear();
|
|
||||||
const m = today.getMonth() - bd.getMonth();
|
|
||||||
if (m < 0 || (m === 0 && today.getDate() < bd.getDate())) age--;
|
|
||||||
|
|
||||||
let nextBirthday = zeroTime(new Date(today.getFullYear(), bd.getMonth(), bd.getDate()));
|
|
||||||
if (today > nextBirthday) nextBirthday.setFullYear(today.getFullYear() + 1);
|
|
||||||
|
|
||||||
const oneDay = 24 * 60 * 60 * 1000;
|
|
||||||
const daysUntil = Math.round((nextBirthday.getTime() - today.getTime()) / oneDay);
|
|
||||||
|
|
||||||
let msg: string;
|
|
||||||
if (daysUntil > 31) {
|
|
||||||
let monthsUntil = (nextBirthday.getFullYear() - today.getFullYear()) * 12 + (nextBirthday.getMonth() - today.getMonth());
|
|
||||||
if (today.getDate() > nextBirthday.getDate()) monthsUntil--;
|
|
||||||
|
|
||||||
const refDate = zeroTime(new Date(nextBirthday.getFullYear(), nextBirthday.getMonth() - monthsUntil, today.getDate()));
|
|
||||||
const partialMonthDays = Math.round((nextBirthday.getTime() - refDate.getTime()) / oneDay);
|
|
||||||
if (partialMonthDays > 15) monthsUntil += 0.5;
|
|
||||||
|
|
||||||
msg = `next one is in ${monthsUntil} months.`;
|
|
||||||
} else if (daysUntil === 0) {
|
|
||||||
msg = `today! Wish ${pronoun} Happy Birthday!`;
|
|
||||||
} else if (daysUntil === 1) {
|
|
||||||
msg = `that's tomorrow!`;
|
|
||||||
} else {
|
|
||||||
msg = `next one is in ${daysUntil} days.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.setText(`${name !== null ? name + ": " : ageLabel } ${age} years old. Birthday is ${birthdayFormatted}—${msg}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'countdown':
|
|
||||||
case 'until': {
|
|
||||||
const toStr = cfg.to || cfg.date || cfg.until;
|
|
||||||
const toDate = parseDate(toStr);
|
|
||||||
if (!toDate) { container.setText('date-calc: Missing or invalid "to" date.'); return; }
|
|
||||||
const fromDate = parseDate(cfg.from) || new Date();
|
|
||||||
const diffMs = toDate.getTime() - fromDate.getTime();
|
|
||||||
const label = cfg.label ? `${cfg.label}: ` : '';
|
|
||||||
container.setText(diffMs >= 0 ? `${label}Countdown: ${humanizeDuration(diffMs)}` : `${label}Event passed ${humanizeDuration(-diffMs)} ago`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'diff':
|
|
||||||
case 'difference': {
|
|
||||||
const fromStr = cfg.from || cfg.start;
|
|
||||||
const toStr = cfg.to || cfg.end;
|
|
||||||
const fromD = parseDate(fromStr);
|
|
||||||
const toD = parseDate(toStr);
|
|
||||||
if (!fromD || !toD) { container.setText('date-calc: Provide valid "from" and "to" dates.'); return; }
|
|
||||||
const diffMs = toD.getTime() - fromD.getTime();
|
|
||||||
container.setText(`Difference: ${humanizeDuration(Math.abs(diffMs))}${diffMs < 0 ? ' (to is before from)' : ''}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'since': {
|
|
||||||
const sinceStr = cfg.since || cfg.from || cfg.date;
|
|
||||||
const sinceDate = parseDate(sinceStr);
|
|
||||||
if (!sinceDate) { container.setText('date-calc: Missing or invalid "since" date.'); return; }
|
|
||||||
const diffMs = new Date().getTime() - sinceDate.getTime();
|
|
||||||
container.setText(diffMs >= 0 ? `Since: ${humanizeDuration(diffMs)} ago` : `In: ${humanizeDuration(-diffMs)}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
container.setText('date-calc: Unknown type. Supported types: birthday, countdown, diff, since');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
container.setText(`date-calc error: ${e?.message || e}`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Inline code support: `date-calc: key=value ...` or YAML/inline-map
|
|
||||||
// Following dataview's component-based approach - works in both reading view and live preview
|
|
||||||
this.registerMarkdownPostProcessor((el, ctx) => {
|
|
||||||
const codeNodes = el.querySelectorAll('code');
|
|
||||||
codeNodes.forEach((codeEl) => {
|
|
||||||
// Skip code inside of pre elements (fenced code blocks)
|
|
||||||
if (codeEl.parentElement && codeEl.parentElement.nodeName.toLowerCase() === 'pre') return;
|
|
||||||
|
|
||||||
const raw = codeEl.textContent?.trim() || '';
|
|
||||||
if (/^date-calc\s*:/.test(raw)) {
|
|
||||||
const renderer = new DateCalcInlineRenderer(raw, el, codeEl, this.app, ctx.sourcePath);
|
|
||||||
ctx.addChild(renderer);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onunload() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
async loadSettings() {
|
|
||||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||||
}
|
|
||||||
|
|
||||||
async saveSettings() {
|
console.log("[date-calc] loaded");
|
||||||
|
new Notice("Date Calc loaded (Live Preview)");
|
||||||
|
|
||||||
|
// Live Preview requires editor extensions + decorations
|
||||||
|
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);
|
await this.saveData(this.settings);
|
||||||
|
new Notice(`Date Calc debug: ${this.settings.debug ? "ON" : "OFF"}`);
|
||||||
|
console.log("[date-calc] debug =", this.settings.debug);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addSettingTab(new DateCalcSettingTab(this.app, this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SampleModal extends Modal {
|
/* =========================
|
||||||
constructor(app: App) {
|
Settings tab
|
||||||
super(app);
|
========================= */
|
||||||
}
|
|
||||||
|
|
||||||
onOpen() {
|
class DateCalcSettingTab extends PluginSettingTab {
|
||||||
const {contentEl} = this;
|
constructor(app: App, private plugin: DateCalcPlugin) {
|
||||||
contentEl.setText('Woah!');
|
|
||||||
}
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
const {contentEl} = this;
|
|
||||||
contentEl.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SampleSettingTab extends PluginSettingTab {
|
|
||||||
plugin: MyPlugin;
|
|
||||||
|
|
||||||
constructor(app: App, plugin: MyPlugin) {
|
|
||||||
super(app, plugin);
|
super(app, plugin);
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
display(): void {
|
display(): void {
|
||||||
const { containerEl } = this;
|
const { containerEl } = this;
|
||||||
|
|
||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
|
|
||||||
containerEl.createEl('h2', {text: 'Date Calc Plugin Syntax'});
|
containerEl.createEl("h2", { text: "Date Calc" });
|
||||||
containerEl.createEl('p', {text: 'This plugin allows you to embed dynamic date calculations in your notes using code blocks or inline code.'});
|
|
||||||
|
|
||||||
// Fenced code block examples
|
new Setting(containerEl)
|
||||||
const blockSection = containerEl.createDiv();
|
.setName("Debug logging")
|
||||||
blockSection.createEl('h3', {text: 'Fenced Code Block Examples'});
|
.setDesc("Logs how many `date-calc:` inline-code matches were seen and decorated.")
|
||||||
|
.addToggle((t) =>
|
||||||
|
t.setValue(this.plugin.settings.debug).onChange(async (v) => {
|
||||||
|
this.plugin.settings.debug = v;
|
||||||
|
await this.plugin.saveData(this.plugin.settings);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
blockSection.createEl('h4', {text: '1. Birthday Calculation'});
|
new Setting(containerEl)
|
||||||
blockSection.createEl('pre').createEl('code', {text:
|
.setName("Hide result while cursor inside inline code")
|
||||||
`\`\`\`date-calc
|
.setDesc("Prevents the widget from showing while you edit the inline backticks.")
|
||||||
type: birthday
|
.addToggle((t) =>
|
||||||
birthday: 1992-08-16
|
t.setValue(this.plugin.settings.hideResultWhileCursorInside).onChange(async (v) => {
|
||||||
\`\`\``});
|
this.plugin.settings.hideResultWhileCursorInside = v;
|
||||||
blockSection.createEl('p', {text: 'Shows age and time until next birthday. Can also use "birthdate" or "date" field.'});
|
await this.plugin.saveData(this.plugin.settings);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
blockSection.createEl('h4', {text: '2. Countdown to Event'});
|
new Setting(containerEl)
|
||||||
blockSection.createEl('pre').createEl('code', {text:
|
.setName("Verbose output")
|
||||||
`\`\`\`date-calc
|
.setDesc("When enabled, show the longer/wordier messages (your current output).")
|
||||||
type: countdown
|
.addToggle(t =>
|
||||||
label: New Year
|
t.setValue(this.plugin.settings.verbose).onChange(async (v) => {
|
||||||
to: 2025-12-31 23:59
|
this.plugin.settings.verbose = v;
|
||||||
\`\`\``});
|
await this.plugin.saveData(this.plugin.settings);
|
||||||
blockSection.createEl('p', {text: 'Shows time remaining until the target date. Can also use "until" type.'});
|
})
|
||||||
|
);
|
||||||
|
|
||||||
blockSection.createEl('h4', {text: '3. Time Since Event'});
|
|
||||||
blockSection.createEl('pre').createEl('code', {text:
|
|
||||||
`\`\`\`date-calc
|
|
||||||
type: since
|
|
||||||
since: 2024-01-01
|
|
||||||
\`\`\``});
|
|
||||||
blockSection.createEl('p', {text: 'Shows how much time has passed since an event.'});
|
|
||||||
|
|
||||||
blockSection.createEl('h4', {text: '4. Date Difference'});
|
containerEl.createEl("p", { text: 'Example: `date-calc: birthday=1992-08-16`' });
|
||||||
blockSection.createEl('pre').createEl('code', {text:
|
|
||||||
`\`\`\`date-calc
|
|
||||||
type: diff
|
|
||||||
from: 2024-01-01
|
|
||||||
to: 2024-12-31
|
|
||||||
\`\`\``});
|
|
||||||
blockSection.createEl('p', {text: 'Shows the difference between two specific dates.'});
|
|
||||||
|
|
||||||
// Inline examples
|
|
||||||
const inlineSection = containerEl.createDiv();
|
|
||||||
inlineSection.createEl('h3', {text: 'Inline Code Examples'});
|
|
||||||
inlineSection.createEl('p', {text: 'You can also use inline code for quick calculations:'});
|
|
||||||
|
|
||||||
const inlineExamples = inlineSection.createEl('ul');
|
|
||||||
inlineExamples.createEl('li').innerHTML = '<code>`date-calc: birthday=1992-08-16`</code> → Birthday summary';
|
|
||||||
inlineExamples.createEl('li').innerHTML = '<code>`date-calc: to=2025-12-31 label="New Year"`</code> → Countdown';
|
|
||||||
inlineExamples.createEl('li').innerHTML = '<code>`date-calc: since=2024-01-01`</code> → Time since';
|
|
||||||
inlineExamples.createEl('li').innerHTML = '<code>`date-calc: {type: diff, from: 2024-01-01, to: 2024-12-31}`</code> → YAML format';
|
|
||||||
|
|
||||||
// Notes section
|
|
||||||
const notesSection = containerEl.createDiv();
|
|
||||||
notesSection.createEl('h3', {text: 'Important Notes'});
|
|
||||||
|
|
||||||
const notesList = notesSection.createEl('ul');
|
|
||||||
notesList.createEl('li', {text: 'Date formats: Use YYYY-MM-DD or YYYY-MM-DD HH:mm format'});
|
|
||||||
notesList.createEl('li', {text: 'For birthdays: Can use frontmatter properties (birthday/birthdate) if not specified in the block'});
|
|
||||||
notesList.createEl('li', {text: 'Inline code only renders in Reading view or Live Preview mode'});
|
|
||||||
notesList.createEl('li', {text: 'All calculations use your local timezone'});
|
|
||||||
notesList.createEl('li', {text: 'YAML syntax must be valid - the plugin will show errors for invalid syntax'});
|
|
||||||
|
|
||||||
// Troubleshooting
|
|
||||||
const troubleSection = containerEl.createDiv();
|
|
||||||
troubleSection.createEl('h3', {text: 'Troubleshooting'});
|
|
||||||
|
|
||||||
const troubleList = troubleSection.createEl('ul');
|
|
||||||
troubleList.createEl('li', {text: 'If inline code isn\'t rendering, try switching to Reading view'});
|
|
||||||
troubleList.createEl('li', {text: 'Use single backticks for inline code (not triple backticks)'});
|
|
||||||
troubleList.createEl('li', {text: 'If calculations seem wrong, check your date format and timezone'});
|
|
||||||
troubleList.createEl('li', {text: 'Error messages will appear in place of the calculation if something is wrong'});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
58
package-lock.json
generated
58
package-lock.json
generated
@@ -9,6 +9,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@codemirror/language": "^6.12.1",
|
||||||
|
"@lezer/common": "^1.5.0",
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "^16.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||||
"@typescript-eslint/parser": "5.29.0",
|
"@typescript-eslint/parser": "5.29.0",
|
||||||
@@ -19,13 +21,27 @@
|
|||||||
"typescript": "4.7.4"
|
"typescript": "4.7.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@codemirror/language": {
|
||||||
|
"version": "6.12.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz",
|
||||||
|
"integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/state": "^6.0.0",
|
||||||
|
"@codemirror/view": "^6.23.0",
|
||||||
|
"@lezer/common": "^1.5.0",
|
||||||
|
"@lezer/highlight": "^1.0.0",
|
||||||
|
"@lezer/lr": "^1.0.0",
|
||||||
|
"style-mod": "^4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@codemirror/state": {
|
"node_modules/@codemirror/state": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz",
|
||||||
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
"integrity": "sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@marijn/find-cluster-break": "^1.0.0"
|
"@marijn/find-cluster-break": "^1.0.0"
|
||||||
}
|
}
|
||||||
@@ -36,7 +52,6 @@
|
|||||||
"integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==",
|
"integrity": "sha512-bTWAJxL6EOFLPzTx+O5P5xAO3gTqpatQ2b/ARQ8itfU/v2LlpS3pH2fkL0A3E/Fx8Y2St2KES7ZEV0sHTsSW/A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/state": "^6.5.0",
|
"@codemirror/state": "^6.5.0",
|
||||||
"crelt": "^1.0.6",
|
"crelt": "^1.0.6",
|
||||||
@@ -526,13 +541,39 @@
|
|||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"peer": true
|
"peer": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@lezer/common": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/highlight": {
|
||||||
|
"version": "1.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz",
|
||||||
|
"integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@lezer/lr": {
|
||||||
|
"version": "1.4.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.5.tgz",
|
||||||
|
"integrity": "sha512-/YTRKP5yPPSo1xImYQk7AZZMAgap0kegzqCSYHjAL9x1AZ0ZQW+IpcEzMKagCsbTsLnVeWkxYrCNeXG8xEPrjg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@lezer/common": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@marijn/find-cluster-break": {
|
"node_modules/@marijn/find-cluster-break": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz",
|
||||||
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
"integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
@@ -1012,8 +1053,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
|
||||||
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
"integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
@@ -2223,8 +2263,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz",
|
||||||
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
"integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/supports-color": {
|
"node_modules/supports-color": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
@@ -2349,8 +2388,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz",
|
||||||
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
"integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT"
|
||||||
"peer": true
|
|
||||||
},
|
},
|
||||||
"node_modules/which": {
|
"node_modules/which": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@codemirror/language": "^6.12.1",
|
||||||
|
"@lezer/common": "^1.5.0",
|
||||||
"@types/node": "^16.11.6",
|
"@types/node": "^16.11.6",
|
||||||
"@typescript-eslint/eslint-plugin": "5.29.0",
|
"@typescript-eslint/eslint-plugin": "5.29.0",
|
||||||
"@typescript-eslint/parser": "5.29.0",
|
"@typescript-eslint/parser": "5.29.0",
|
||||||
|
|||||||
39
styles.css
39
styles.css
@@ -2,45 +2,6 @@
|
|||||||
Date Calc Plugin Styles
|
Date Calc Plugin Styles
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* Inline date-calc styling for reading view and live preview */
|
|
||||||
.date-calc-inline {
|
.date-calc-inline {
|
||||||
background-color: var(--background-modifier-accent);
|
|
||||||
border-radius: 3px;
|
|
||||||
padding: 2px 6px;
|
|
||||||
color: var(--text-accent);
|
color: var(--text-accent);
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.9em;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 2px;
|
|
||||||
font-family: var(--font-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Specific styles for CodeMirror Live Preview widgets */
|
|
||||||
.cm-line .date-calc-inline {
|
|
||||||
background-color: var(--background-modifier-accent);
|
|
||||||
color: var(--text-accent);
|
|
||||||
padding: 2px 6px;
|
|
||||||
margin-left: 4px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 0.9em;
|
|
||||||
display: inline;
|
|
||||||
user-select: none;
|
|
||||||
cursor: default;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Additional styling for fenced code blocks */
|
|
||||||
.date-calc {
|
|
||||||
background-color: var(--background-modifier-accent);
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border-left: 3px solid var(--text-accent);
|
|
||||||
margin: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hover effect to make the result more prominent */
|
|
||||||
.date-calc-inline:hover {
|
|
||||||
background-color: var(--background-modifier-hover);
|
|
||||||
color: var(--text-accent-hover);
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user