@real1ty-obsidian-plugins/utils 2.14.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ import { type App, Modal } from "obsidian";
2
+ import type { FrontmatterDiff } from "../file/frontmatter-diff";
3
+ export interface FrontmatterPropagationModalOptions {
4
+ eventTitle: string;
5
+ diff: FrontmatterDiff;
6
+ instanceCount: number;
7
+ onConfirm: () => void | Promise<void>;
8
+ onCancel?: () => void | Promise<void>;
9
+ }
10
+ export declare class FrontmatterPropagationModal extends Modal {
11
+ private options;
12
+ constructor(app: App, options: FrontmatterPropagationModalOptions);
13
+ onOpen(): void;
14
+ onClose(): void;
15
+ }
16
+ //# sourceMappingURL=frontmatter-propagation-modal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-propagation-modal.d.ts","sourceRoot":"","sources":["../../src/components/frontmatter-propagation-modal.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAGhE,MAAM,WAAW,kCAAkC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,eAAe,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,QAAQ,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,qBAAa,2BAA4B,SAAQ,KAAK;IACrD,OAAO,CAAC,OAAO,CAAqC;gBAExC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,kCAAkC;IAKjE,MAAM,IAAI,IAAI;IAqFd,OAAO,IAAI,IAAI;CAIf"}
@@ -0,0 +1,81 @@
1
+ import { Modal } from "obsidian";
2
+ import { formatChangeForDisplay } from "../file/frontmatter-diff";
3
+ export class FrontmatterPropagationModal extends Modal {
4
+ constructor(app, options) {
5
+ super(app);
6
+ this.options = options;
7
+ }
8
+ onOpen() {
9
+ const { contentEl } = this;
10
+ contentEl.empty();
11
+ contentEl.createEl("h2", { text: "Propagate frontmatter changes?" });
12
+ contentEl.createEl("p", {
13
+ text: `The recurring event "${this.options.eventTitle}" has frontmatter changes. Do you want to apply these changes to all ${this.options.instanceCount} physical instances?`,
14
+ });
15
+ const changesContainer = contentEl.createDiv({ cls: "prisma-frontmatter-changes" });
16
+ if (this.options.diff.added.length > 0) {
17
+ const addedSection = changesContainer.createDiv({ cls: "prisma-changes-section" });
18
+ addedSection.createEl("h4", { text: "Added properties:" });
19
+ const addedList = addedSection.createEl("ul");
20
+ for (const change of this.options.diff.added) {
21
+ addedList.createEl("li", {
22
+ text: formatChangeForDisplay(change),
23
+ cls: "prisma-change-added",
24
+ });
25
+ }
26
+ }
27
+ if (this.options.diff.modified.length > 0) {
28
+ const modifiedSection = changesContainer.createDiv({ cls: "prisma-changes-section" });
29
+ modifiedSection.createEl("h4", { text: "Modified properties:" });
30
+ const modifiedList = modifiedSection.createEl("ul");
31
+ for (const change of this.options.diff.modified) {
32
+ modifiedList.createEl("li", {
33
+ text: formatChangeForDisplay(change),
34
+ cls: "prisma-change-modified",
35
+ });
36
+ }
37
+ }
38
+ if (this.options.diff.deleted.length > 0) {
39
+ const deletedSection = changesContainer.createDiv({ cls: "prisma-changes-section" });
40
+ deletedSection.createEl("h4", { text: "Deleted properties:" });
41
+ const deletedList = deletedSection.createEl("ul");
42
+ for (const change of this.options.diff.deleted) {
43
+ deletedList.createEl("li", {
44
+ text: formatChangeForDisplay(change),
45
+ cls: "prisma-change-deleted",
46
+ });
47
+ }
48
+ }
49
+ const buttonContainer = contentEl.createDiv({ cls: "prisma-modal-buttons" });
50
+ const yesButton = buttonContainer.createEl("button", {
51
+ text: "Yes, propagate",
52
+ cls: "mod-cta",
53
+ });
54
+ yesButton.addEventListener("click", () => {
55
+ void Promise.resolve(this.options.onConfirm())
56
+ .then(() => {
57
+ this.close();
58
+ })
59
+ .catch((error) => {
60
+ console.error("Error in onConfirm callback:", error);
61
+ this.close();
62
+ });
63
+ });
64
+ const noButton = buttonContainer.createEl("button", { text: "No, skip" });
65
+ noButton.addEventListener("click", () => {
66
+ var _a, _b;
67
+ const result = (_b = (_a = this.options).onCancel) === null || _b === void 0 ? void 0 : _b.call(_a);
68
+ if (result instanceof Promise) {
69
+ void result.catch((error) => {
70
+ console.error("Error in onCancel callback:", error);
71
+ });
72
+ }
73
+ this.close();
74
+ });
75
+ }
76
+ onClose() {
77
+ const { contentEl } = this;
78
+ contentEl.empty();
79
+ }
80
+ }
81
+ //# sourceMappingURL=frontmatter-propagation-modal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-propagation-modal.js","sourceRoot":"","sources":["../../src/components/frontmatter-propagation-modal.ts"],"names":[],"mappings":"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","sourcesContent":["import { type App, Modal } from \"obsidian\";\n\nimport type { FrontmatterDiff } from \"../file/frontmatter-diff\";\nimport { formatChangeForDisplay } from \"../file/frontmatter-diff\";\n\nexport interface FrontmatterPropagationModalOptions {\n\teventTitle: string;\n\tdiff: FrontmatterDiff;\n\tinstanceCount: number;\n\tonConfirm: () => void | Promise<void>;\n\tonCancel?: () => void | Promise<void>;\n}\n\nexport class FrontmatterPropagationModal extends Modal {\n\tprivate options: FrontmatterPropagationModalOptions;\n\n\tconstructor(app: App, options: FrontmatterPropagationModalOptions) {\n\t\tsuper(app);\n\t\tthis.options = options;\n\t}\n\n\tonOpen(): void {\n\t\tconst { contentEl } = this;\n\n\t\tcontentEl.empty();\n\n\t\tcontentEl.createEl(\"h2\", { text: \"Propagate frontmatter changes?\" });\n\n\t\tcontentEl.createEl(\"p\", {\n\t\t\ttext: `The recurring event \"${this.options.eventTitle}\" has frontmatter changes. Do you want to apply these changes to all ${this.options.instanceCount} physical instances?`,\n\t\t});\n\n\t\tconst changesContainer = contentEl.createDiv({ cls: \"prisma-frontmatter-changes\" });\n\n\t\tif (this.options.diff.added.length > 0) {\n\t\t\tconst addedSection = changesContainer.createDiv({ cls: \"prisma-changes-section\" });\n\t\t\taddedSection.createEl(\"h4\", { text: \"Added properties:\" });\n\t\t\tconst addedList = addedSection.createEl(\"ul\");\n\n\t\t\tfor (const change of this.options.diff.added) {\n\t\t\t\taddedList.createEl(\"li\", {\n\t\t\t\t\ttext: formatChangeForDisplay(change),\n\t\t\t\t\tcls: \"prisma-change-added\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (this.options.diff.modified.length > 0) {\n\t\t\tconst modifiedSection = changesContainer.createDiv({ cls: \"prisma-changes-section\" });\n\t\t\tmodifiedSection.createEl(\"h4\", { text: \"Modified properties:\" });\n\t\t\tconst modifiedList = modifiedSection.createEl(\"ul\");\n\n\t\t\tfor (const change of this.options.diff.modified) {\n\t\t\t\tmodifiedList.createEl(\"li\", {\n\t\t\t\t\ttext: formatChangeForDisplay(change),\n\t\t\t\t\tcls: \"prisma-change-modified\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (this.options.diff.deleted.length > 0) {\n\t\t\tconst deletedSection = changesContainer.createDiv({ cls: \"prisma-changes-section\" });\n\t\t\tdeletedSection.createEl(\"h4\", { text: \"Deleted properties:\" });\n\t\t\tconst deletedList = deletedSection.createEl(\"ul\");\n\n\t\t\tfor (const change of this.options.diff.deleted) {\n\t\t\t\tdeletedList.createEl(\"li\", {\n\t\t\t\t\ttext: formatChangeForDisplay(change),\n\t\t\t\t\tcls: \"prisma-change-deleted\",\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst buttonContainer = contentEl.createDiv({ cls: \"prisma-modal-buttons\" });\n\n\t\tconst yesButton = buttonContainer.createEl(\"button\", {\n\t\t\ttext: \"Yes, propagate\",\n\t\t\tcls: \"mod-cta\",\n\t\t});\n\n\t\tyesButton.addEventListener(\"click\", () => {\n\t\t\tvoid Promise.resolve(this.options.onConfirm())\n\t\t\t\t.then(() => {\n\t\t\t\t\tthis.close();\n\t\t\t\t})\n\t\t\t\t.catch((error) => {\n\t\t\t\t\tconsole.error(\"Error in onConfirm callback:\", error);\n\t\t\t\t\tthis.close();\n\t\t\t\t});\n\t\t});\n\n\t\tconst noButton = buttonContainer.createEl(\"button\", { text: \"No, skip\" });\n\n\t\tnoButton.addEventListener(\"click\", () => {\n\t\t\tconst result = this.options.onCancel?.();\n\n\t\t\tif (result instanceof Promise) {\n\t\t\t\tvoid result.catch((error) => {\n\t\t\t\t\tconsole.error(\"Error in onCancel callback:\", error);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthis.close();\n\t\t});\n\t}\n\n\tonClose(): void {\n\t\tconst { contentEl } = this;\n\t\tcontentEl.empty();\n\t}\n}\n"]}
@@ -1,2 +1,3 @@
1
+ export * from "./frontmatter-propagation-modal";
1
2
  export * from "./input-managers";
2
3
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC"}
@@ -1,2 +1,3 @@
1
+ export * from "./frontmatter-propagation-modal";
1
2
  export * from "./input-managers";
2
3
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,kBAAkB,CAAC","sourcesContent":["export * from \"./input-managers\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,iCAAiC,CAAC;AAChD,cAAc,kBAAkB,CAAC","sourcesContent":["export * from \"./frontmatter-propagation-modal\";\nexport * from \"./input-managers\";\n"]}
@@ -0,0 +1,38 @@
1
+ export type Frontmatter = Record<string, unknown>;
2
+ export interface FrontmatterChange {
3
+ key: string;
4
+ oldValue: unknown;
5
+ newValue: unknown;
6
+ changeType: "added" | "modified" | "deleted";
7
+ }
8
+ export interface FrontmatterDiff {
9
+ hasChanges: boolean;
10
+ changes: FrontmatterChange[];
11
+ added: FrontmatterChange[];
12
+ modified: FrontmatterChange[];
13
+ deleted: FrontmatterChange[];
14
+ }
15
+ /**
16
+ * Compares two frontmatter objects and returns a detailed diff.
17
+ * Excludes specified properties from comparison (e.g., Prisma-managed properties).
18
+ *
19
+ * @param oldFrontmatter - The original frontmatter
20
+ * @param newFrontmatter - The updated frontmatter
21
+ * @param excludeProps - Set of property keys to exclude from comparison
22
+ * @returns Detailed diff with categorized changes
23
+ */
24
+ export declare function compareFrontmatter(oldFrontmatter: Frontmatter, newFrontmatter: Frontmatter, excludeProps?: Set<string>): FrontmatterDiff;
25
+ /**
26
+ * Merges multiple frontmatter diffs into a single accumulated diff.
27
+ * Later diffs override earlier ones for the same key.
28
+ *
29
+ * @param diffs - Array of diffs to merge (in chronological order)
30
+ * @returns A single merged diff containing all accumulated changes
31
+ */
32
+ export declare function mergeFrontmatterDiffs(diffs: FrontmatterDiff[]): FrontmatterDiff;
33
+ /**
34
+ * Formats a frontmatter change for display in a modal.
35
+ * Returns a human-readable string describing the change.
36
+ */
37
+ export declare function formatChangeForDisplay(change: FrontmatterChange): string;
38
+ //# sourceMappingURL=frontmatter-diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-diff.d.ts","sourceRoot":"","sources":["../../src/file/frontmatter-diff.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAElD,MAAM,WAAW,iBAAiB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CAC7C;AAED,MAAM,WAAW,eAAe;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC7B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,OAAO,EAAE,iBAAiB,EAAE,CAAC;CAC7B;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CACjC,cAAc,EAAE,WAAW,EAC3B,cAAc,EAAE,WAAW,EAC3B,YAAY,GAAE,GAAG,CAAC,MAAM,CAAa,GACnC,eAAe,CAwDjB;AAkCD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,eAAe,EAAE,GAAG,eAAe,CA8C/E;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,iBAAiB,GAAG,MAAM,CAkBxE"}
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Compares two frontmatter objects and returns a detailed diff.
3
+ * Excludes specified properties from comparison (e.g., Prisma-managed properties).
4
+ *
5
+ * @param oldFrontmatter - The original frontmatter
6
+ * @param newFrontmatter - The updated frontmatter
7
+ * @param excludeProps - Set of property keys to exclude from comparison
8
+ * @returns Detailed diff with categorized changes
9
+ */
10
+ export function compareFrontmatter(oldFrontmatter, newFrontmatter, excludeProps = new Set()) {
11
+ const changes = [];
12
+ const added = [];
13
+ const modified = [];
14
+ const deleted = [];
15
+ const allKeys = new Set([...Object.keys(oldFrontmatter), ...Object.keys(newFrontmatter)]);
16
+ for (const key of allKeys) {
17
+ if (excludeProps.has(key)) {
18
+ continue;
19
+ }
20
+ const oldValue = oldFrontmatter[key];
21
+ const newValue = newFrontmatter[key];
22
+ if (!(key in oldFrontmatter) && key in newFrontmatter) {
23
+ const change = {
24
+ key,
25
+ oldValue: undefined,
26
+ newValue,
27
+ changeType: "added",
28
+ };
29
+ changes.push(change);
30
+ added.push(change);
31
+ }
32
+ else if (key in oldFrontmatter && !(key in newFrontmatter)) {
33
+ const change = {
34
+ key,
35
+ oldValue,
36
+ newValue: undefined,
37
+ changeType: "deleted",
38
+ };
39
+ changes.push(change);
40
+ deleted.push(change);
41
+ }
42
+ else if (!deepEqual(oldValue, newValue)) {
43
+ const change = {
44
+ key,
45
+ oldValue,
46
+ newValue,
47
+ changeType: "modified",
48
+ };
49
+ changes.push(change);
50
+ modified.push(change);
51
+ }
52
+ }
53
+ return {
54
+ hasChanges: changes.length > 0,
55
+ changes,
56
+ added,
57
+ modified,
58
+ deleted,
59
+ };
60
+ }
61
+ /**
62
+ * Deep equality check for frontmatter values.
63
+ * Handles primitives, arrays, and objects.
64
+ */
65
+ function deepEqual(a, b) {
66
+ if (a === b)
67
+ return true;
68
+ if (a === null || b === null || a === undefined || b === undefined) {
69
+ return a === b;
70
+ }
71
+ if (typeof a !== typeof b)
72
+ return false;
73
+ if (Array.isArray(a) && Array.isArray(b)) {
74
+ if (a.length !== b.length)
75
+ return false;
76
+ return a.every((val, idx) => deepEqual(val, b[idx]));
77
+ }
78
+ if (typeof a === "object" && typeof b === "object") {
79
+ const keysA = Object.keys(a);
80
+ const keysB = Object.keys(b);
81
+ if (keysA.length !== keysB.length)
82
+ return false;
83
+ return keysA.every((key) => deepEqual(a[key], b[key]));
84
+ }
85
+ return false;
86
+ }
87
+ /**
88
+ * Merges multiple frontmatter diffs into a single accumulated diff.
89
+ * Later diffs override earlier ones for the same key.
90
+ *
91
+ * @param diffs - Array of diffs to merge (in chronological order)
92
+ * @returns A single merged diff containing all accumulated changes
93
+ */
94
+ export function mergeFrontmatterDiffs(diffs) {
95
+ if (diffs.length === 0) {
96
+ return {
97
+ hasChanges: false,
98
+ changes: [],
99
+ added: [],
100
+ modified: [],
101
+ deleted: [],
102
+ };
103
+ }
104
+ if (diffs.length === 1) {
105
+ return diffs[0];
106
+ }
107
+ const changesByKey = new Map();
108
+ for (const diff of diffs) {
109
+ for (const change of diff.changes) {
110
+ const existing = changesByKey.get(change.key);
111
+ if (!existing) {
112
+ changesByKey.set(change.key, Object.assign({}, change));
113
+ }
114
+ else {
115
+ existing.newValue = change.newValue;
116
+ existing.changeType = change.changeType;
117
+ if (existing.oldValue === change.newValue) {
118
+ changesByKey.delete(change.key);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ const allChanges = Array.from(changesByKey.values());
124
+ const added = allChanges.filter((c) => c.changeType === "added");
125
+ const modified = allChanges.filter((c) => c.changeType === "modified");
126
+ const deleted = allChanges.filter((c) => c.changeType === "deleted");
127
+ return {
128
+ hasChanges: allChanges.length > 0,
129
+ changes: allChanges,
130
+ added,
131
+ modified,
132
+ deleted,
133
+ };
134
+ }
135
+ /**
136
+ * Formats a frontmatter change for display in a modal.
137
+ * Returns a human-readable string describing the change.
138
+ */
139
+ export function formatChangeForDisplay(change) {
140
+ const formatValue = (value) => {
141
+ if (value === undefined)
142
+ return "(not set)";
143
+ if (value === null)
144
+ return "null";
145
+ if (typeof value === "string")
146
+ return `"${value}"`;
147
+ if (typeof value === "object")
148
+ return JSON.stringify(value);
149
+ if (typeof value === "number" || typeof value === "boolean")
150
+ return String(value);
151
+ return JSON.stringify(value);
152
+ };
153
+ switch (change.changeType) {
154
+ case "added":
155
+ return `+ ${change.key}: ${formatValue(change.newValue)}`;
156
+ case "deleted":
157
+ return `- ${change.key}: ${formatValue(change.oldValue)}`;
158
+ case "modified":
159
+ return `~ ${change.key}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`;
160
+ }
161
+ }
162
+ //# sourceMappingURL=frontmatter-diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-diff.js","sourceRoot":"","sources":["../../src/file/frontmatter-diff.ts"],"names":[],"mappings":"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","sourcesContent":["export type Frontmatter = Record<string, unknown>;\n\nexport interface FrontmatterChange {\n\tkey: string;\n\toldValue: unknown;\n\tnewValue: unknown;\n\tchangeType: \"added\" | \"modified\" | \"deleted\";\n}\n\nexport interface FrontmatterDiff {\n\thasChanges: boolean;\n\tchanges: FrontmatterChange[];\n\tadded: FrontmatterChange[];\n\tmodified: FrontmatterChange[];\n\tdeleted: FrontmatterChange[];\n}\n\n/**\n * Compares two frontmatter objects and returns a detailed diff.\n * Excludes specified properties from comparison (e.g., Prisma-managed properties).\n *\n * @param oldFrontmatter - The original frontmatter\n * @param newFrontmatter - The updated frontmatter\n * @param excludeProps - Set of property keys to exclude from comparison\n * @returns Detailed diff with categorized changes\n */\nexport function compareFrontmatter(\n\toldFrontmatter: Frontmatter,\n\tnewFrontmatter: Frontmatter,\n\texcludeProps: Set<string> = new Set()\n): FrontmatterDiff {\n\tconst changes: FrontmatterChange[] = [];\n\tconst added: FrontmatterChange[] = [];\n\tconst modified: FrontmatterChange[] = [];\n\tconst deleted: FrontmatterChange[] = [];\n\n\tconst allKeys = new Set([...Object.keys(oldFrontmatter), ...Object.keys(newFrontmatter)]);\n\n\tfor (const key of allKeys) {\n\t\tif (excludeProps.has(key)) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst oldValue = oldFrontmatter[key];\n\t\tconst newValue = newFrontmatter[key];\n\n\t\tif (!(key in oldFrontmatter) && key in newFrontmatter) {\n\t\t\tconst change: FrontmatterChange = {\n\t\t\t\tkey,\n\t\t\t\toldValue: undefined,\n\t\t\t\tnewValue,\n\t\t\t\tchangeType: \"added\",\n\t\t\t};\n\n\t\t\tchanges.push(change);\n\t\t\tadded.push(change);\n\t\t} else if (key in oldFrontmatter && !(key in newFrontmatter)) {\n\t\t\tconst change: FrontmatterChange = {\n\t\t\t\tkey,\n\t\t\t\toldValue,\n\t\t\t\tnewValue: undefined,\n\t\t\t\tchangeType: \"deleted\",\n\t\t\t};\n\n\t\t\tchanges.push(change);\n\t\t\tdeleted.push(change);\n\t\t} else if (!deepEqual(oldValue, newValue)) {\n\t\t\tconst change: FrontmatterChange = {\n\t\t\t\tkey,\n\t\t\t\toldValue,\n\t\t\t\tnewValue,\n\t\t\t\tchangeType: \"modified\",\n\t\t\t};\n\n\t\t\tchanges.push(change);\n\t\t\tmodified.push(change);\n\t\t}\n\t}\n\n\treturn {\n\t\thasChanges: changes.length > 0,\n\t\tchanges,\n\t\tadded,\n\t\tmodified,\n\t\tdeleted,\n\t};\n}\n\n/**\n * Deep equality check for frontmatter values.\n * Handles primitives, arrays, and objects.\n */\nfunction deepEqual(a: unknown, b: unknown): boolean {\n\tif (a === b) return true;\n\n\tif (a === null || b === null || a === undefined || b === undefined) {\n\t\treturn a === b;\n\t}\n\n\tif (typeof a !== typeof b) return false;\n\n\tif (Array.isArray(a) && Array.isArray(b)) {\n\t\tif (a.length !== b.length) return false;\n\t\treturn a.every((val, idx) => deepEqual(val, b[idx]));\n\t}\n\n\tif (typeof a === \"object\" && typeof b === \"object\") {\n\t\tconst keysA = Object.keys(a as Record<string, unknown>);\n\t\tconst keysB = Object.keys(b as Record<string, unknown>);\n\n\t\tif (keysA.length !== keysB.length) return false;\n\n\t\treturn keysA.every((key) =>\n\t\t\tdeepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])\n\t\t);\n\t}\n\n\treturn false;\n}\n\n/**\n * Merges multiple frontmatter diffs into a single accumulated diff.\n * Later diffs override earlier ones for the same key.\n *\n * @param diffs - Array of diffs to merge (in chronological order)\n * @returns A single merged diff containing all accumulated changes\n */\nexport function mergeFrontmatterDiffs(diffs: FrontmatterDiff[]): FrontmatterDiff {\n\tif (diffs.length === 0) {\n\t\treturn {\n\t\t\thasChanges: false,\n\t\t\tchanges: [],\n\t\t\tadded: [],\n\t\t\tmodified: [],\n\t\t\tdeleted: [],\n\t\t};\n\t}\n\n\tif (diffs.length === 1) {\n\t\treturn diffs[0];\n\t}\n\n\tconst changesByKey = new Map<string, FrontmatterChange>();\n\n\tfor (const diff of diffs) {\n\t\tfor (const change of diff.changes) {\n\t\t\tconst existing = changesByKey.get(change.key);\n\n\t\t\tif (!existing) {\n\t\t\t\tchangesByKey.set(change.key, { ...change });\n\t\t\t} else {\n\t\t\t\texisting.newValue = change.newValue;\n\t\t\t\texisting.changeType = change.changeType;\n\n\t\t\t\tif (existing.oldValue === change.newValue) {\n\t\t\t\t\tchangesByKey.delete(change.key);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tconst allChanges = Array.from(changesByKey.values());\n\tconst added = allChanges.filter((c) => c.changeType === \"added\");\n\tconst modified = allChanges.filter((c) => c.changeType === \"modified\");\n\tconst deleted = allChanges.filter((c) => c.changeType === \"deleted\");\n\n\treturn {\n\t\thasChanges: allChanges.length > 0,\n\t\tchanges: allChanges,\n\t\tadded,\n\t\tmodified,\n\t\tdeleted,\n\t};\n}\n\n/**\n * Formats a frontmatter change for display in a modal.\n * Returns a human-readable string describing the change.\n */\nexport function formatChangeForDisplay(change: FrontmatterChange): string {\n\tconst formatValue = (value: unknown): string => {\n\t\tif (value === undefined) return \"(not set)\";\n\t\tif (value === null) return \"null\";\n\t\tif (typeof value === \"string\") return `\"${value}\"`;\n\t\tif (typeof value === \"object\") return JSON.stringify(value);\n\t\tif (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n\t\treturn JSON.stringify(value);\n\t};\n\n\tswitch (change.changeType) {\n\t\tcase \"added\":\n\t\t\treturn `+ ${change.key}: ${formatValue(change.newValue)}`;\n\t\tcase \"deleted\":\n\t\t\treturn `- ${change.key}: ${formatValue(change.oldValue)}`;\n\t\tcase \"modified\":\n\t\t\treturn `~ ${change.key}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`;\n\t}\n}\n"]}
@@ -3,6 +3,7 @@ export * from "./file";
3
3
  export * from "./file-operations";
4
4
  export * from "./file-utils";
5
5
  export * from "./frontmatter";
6
+ export * from "./frontmatter-diff";
6
7
  export * from "./link-parser";
7
8
  export * from "./property-utils";
8
9
  export * from "./templater";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
@@ -3,6 +3,7 @@ export * from "./file";
3
3
  export * from "./file-operations";
4
4
  export * from "./file-utils";
5
5
  export * from "./frontmatter";
6
+ export * from "./frontmatter-diff";
6
7
  export * from "./link-parser";
7
8
  export * from "./property-utils";
8
9
  export * from "./templater";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./child-reference\";\nexport * from \"./file\";\nexport * from \"./file-operations\";\nexport * from \"./file-utils\";\nexport * from \"./frontmatter\";\nexport * from \"./link-parser\";\nexport * from \"./property-utils\";\nexport * from \"./templater\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./child-reference\";\nexport * from \"./file\";\nexport * from \"./file-operations\";\nexport * from \"./file-utils\";\nexport * from \"./frontmatter\";\nexport * from \"./frontmatter-diff\";\nexport * from \"./link-parser\";\nexport * from \"./property-utils\";\nexport * from \"./templater\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real1ty-obsidian-plugins/utils",
3
- "version": "2.14.0",
3
+ "version": "2.15.0",
4
4
  "description": "Shared utilities for Obsidian plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,111 @@
1
+ import { type App, Modal } from "obsidian";
2
+
3
+ import type { FrontmatterDiff } from "../file/frontmatter-diff";
4
+ import { formatChangeForDisplay } from "../file/frontmatter-diff";
5
+
6
+ export interface FrontmatterPropagationModalOptions {
7
+ eventTitle: string;
8
+ diff: FrontmatterDiff;
9
+ instanceCount: number;
10
+ onConfirm: () => void | Promise<void>;
11
+ onCancel?: () => void | Promise<void>;
12
+ }
13
+
14
+ export class FrontmatterPropagationModal extends Modal {
15
+ private options: FrontmatterPropagationModalOptions;
16
+
17
+ constructor(app: App, options: FrontmatterPropagationModalOptions) {
18
+ super(app);
19
+ this.options = options;
20
+ }
21
+
22
+ onOpen(): void {
23
+ const { contentEl } = this;
24
+
25
+ contentEl.empty();
26
+
27
+ contentEl.createEl("h2", { text: "Propagate frontmatter changes?" });
28
+
29
+ contentEl.createEl("p", {
30
+ text: `The recurring event "${this.options.eventTitle}" has frontmatter changes. Do you want to apply these changes to all ${this.options.instanceCount} physical instances?`,
31
+ });
32
+
33
+ const changesContainer = contentEl.createDiv({ cls: "prisma-frontmatter-changes" });
34
+
35
+ if (this.options.diff.added.length > 0) {
36
+ const addedSection = changesContainer.createDiv({ cls: "prisma-changes-section" });
37
+ addedSection.createEl("h4", { text: "Added properties:" });
38
+ const addedList = addedSection.createEl("ul");
39
+
40
+ for (const change of this.options.diff.added) {
41
+ addedList.createEl("li", {
42
+ text: formatChangeForDisplay(change),
43
+ cls: "prisma-change-added",
44
+ });
45
+ }
46
+ }
47
+
48
+ if (this.options.diff.modified.length > 0) {
49
+ const modifiedSection = changesContainer.createDiv({ cls: "prisma-changes-section" });
50
+ modifiedSection.createEl("h4", { text: "Modified properties:" });
51
+ const modifiedList = modifiedSection.createEl("ul");
52
+
53
+ for (const change of this.options.diff.modified) {
54
+ modifiedList.createEl("li", {
55
+ text: formatChangeForDisplay(change),
56
+ cls: "prisma-change-modified",
57
+ });
58
+ }
59
+ }
60
+
61
+ if (this.options.diff.deleted.length > 0) {
62
+ const deletedSection = changesContainer.createDiv({ cls: "prisma-changes-section" });
63
+ deletedSection.createEl("h4", { text: "Deleted properties:" });
64
+ const deletedList = deletedSection.createEl("ul");
65
+
66
+ for (const change of this.options.diff.deleted) {
67
+ deletedList.createEl("li", {
68
+ text: formatChangeForDisplay(change),
69
+ cls: "prisma-change-deleted",
70
+ });
71
+ }
72
+ }
73
+
74
+ const buttonContainer = contentEl.createDiv({ cls: "prisma-modal-buttons" });
75
+
76
+ const yesButton = buttonContainer.createEl("button", {
77
+ text: "Yes, propagate",
78
+ cls: "mod-cta",
79
+ });
80
+
81
+ yesButton.addEventListener("click", () => {
82
+ void Promise.resolve(this.options.onConfirm())
83
+ .then(() => {
84
+ this.close();
85
+ })
86
+ .catch((error) => {
87
+ console.error("Error in onConfirm callback:", error);
88
+ this.close();
89
+ });
90
+ });
91
+
92
+ const noButton = buttonContainer.createEl("button", { text: "No, skip" });
93
+
94
+ noButton.addEventListener("click", () => {
95
+ const result = this.options.onCancel?.();
96
+
97
+ if (result instanceof Promise) {
98
+ void result.catch((error) => {
99
+ console.error("Error in onCancel callback:", error);
100
+ });
101
+ }
102
+
103
+ this.close();
104
+ });
105
+ }
106
+
107
+ onClose(): void {
108
+ const { contentEl } = this;
109
+ contentEl.empty();
110
+ }
111
+ }
@@ -1 +1,2 @@
1
+ export * from "./frontmatter-propagation-modal";
1
2
  export * from "./input-managers";
@@ -0,0 +1,198 @@
1
+ export type Frontmatter = Record<string, unknown>;
2
+
3
+ export interface FrontmatterChange {
4
+ key: string;
5
+ oldValue: unknown;
6
+ newValue: unknown;
7
+ changeType: "added" | "modified" | "deleted";
8
+ }
9
+
10
+ export interface FrontmatterDiff {
11
+ hasChanges: boolean;
12
+ changes: FrontmatterChange[];
13
+ added: FrontmatterChange[];
14
+ modified: FrontmatterChange[];
15
+ deleted: FrontmatterChange[];
16
+ }
17
+
18
+ /**
19
+ * Compares two frontmatter objects and returns a detailed diff.
20
+ * Excludes specified properties from comparison (e.g., Prisma-managed properties).
21
+ *
22
+ * @param oldFrontmatter - The original frontmatter
23
+ * @param newFrontmatter - The updated frontmatter
24
+ * @param excludeProps - Set of property keys to exclude from comparison
25
+ * @returns Detailed diff with categorized changes
26
+ */
27
+ export function compareFrontmatter(
28
+ oldFrontmatter: Frontmatter,
29
+ newFrontmatter: Frontmatter,
30
+ excludeProps: Set<string> = new Set()
31
+ ): FrontmatterDiff {
32
+ const changes: FrontmatterChange[] = [];
33
+ const added: FrontmatterChange[] = [];
34
+ const modified: FrontmatterChange[] = [];
35
+ const deleted: FrontmatterChange[] = [];
36
+
37
+ const allKeys = new Set([...Object.keys(oldFrontmatter), ...Object.keys(newFrontmatter)]);
38
+
39
+ for (const key of allKeys) {
40
+ if (excludeProps.has(key)) {
41
+ continue;
42
+ }
43
+
44
+ const oldValue = oldFrontmatter[key];
45
+ const newValue = newFrontmatter[key];
46
+
47
+ if (!(key in oldFrontmatter) && key in newFrontmatter) {
48
+ const change: FrontmatterChange = {
49
+ key,
50
+ oldValue: undefined,
51
+ newValue,
52
+ changeType: "added",
53
+ };
54
+
55
+ changes.push(change);
56
+ added.push(change);
57
+ } else if (key in oldFrontmatter && !(key in newFrontmatter)) {
58
+ const change: FrontmatterChange = {
59
+ key,
60
+ oldValue,
61
+ newValue: undefined,
62
+ changeType: "deleted",
63
+ };
64
+
65
+ changes.push(change);
66
+ deleted.push(change);
67
+ } else if (!deepEqual(oldValue, newValue)) {
68
+ const change: FrontmatterChange = {
69
+ key,
70
+ oldValue,
71
+ newValue,
72
+ changeType: "modified",
73
+ };
74
+
75
+ changes.push(change);
76
+ modified.push(change);
77
+ }
78
+ }
79
+
80
+ return {
81
+ hasChanges: changes.length > 0,
82
+ changes,
83
+ added,
84
+ modified,
85
+ deleted,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Deep equality check for frontmatter values.
91
+ * Handles primitives, arrays, and objects.
92
+ */
93
+ function deepEqual(a: unknown, b: unknown): boolean {
94
+ if (a === b) return true;
95
+
96
+ if (a === null || b === null || a === undefined || b === undefined) {
97
+ return a === b;
98
+ }
99
+
100
+ if (typeof a !== typeof b) return false;
101
+
102
+ if (Array.isArray(a) && Array.isArray(b)) {
103
+ if (a.length !== b.length) return false;
104
+ return a.every((val, idx) => deepEqual(val, b[idx]));
105
+ }
106
+
107
+ if (typeof a === "object" && typeof b === "object") {
108
+ const keysA = Object.keys(a as Record<string, unknown>);
109
+ const keysB = Object.keys(b as Record<string, unknown>);
110
+
111
+ if (keysA.length !== keysB.length) return false;
112
+
113
+ return keysA.every((key) =>
114
+ deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key])
115
+ );
116
+ }
117
+
118
+ return false;
119
+ }
120
+
121
+ /**
122
+ * Merges multiple frontmatter diffs into a single accumulated diff.
123
+ * Later diffs override earlier ones for the same key.
124
+ *
125
+ * @param diffs - Array of diffs to merge (in chronological order)
126
+ * @returns A single merged diff containing all accumulated changes
127
+ */
128
+ export function mergeFrontmatterDiffs(diffs: FrontmatterDiff[]): FrontmatterDiff {
129
+ if (diffs.length === 0) {
130
+ return {
131
+ hasChanges: false,
132
+ changes: [],
133
+ added: [],
134
+ modified: [],
135
+ deleted: [],
136
+ };
137
+ }
138
+
139
+ if (diffs.length === 1) {
140
+ return diffs[0];
141
+ }
142
+
143
+ const changesByKey = new Map<string, FrontmatterChange>();
144
+
145
+ for (const diff of diffs) {
146
+ for (const change of diff.changes) {
147
+ const existing = changesByKey.get(change.key);
148
+
149
+ if (!existing) {
150
+ changesByKey.set(change.key, { ...change });
151
+ } else {
152
+ existing.newValue = change.newValue;
153
+ existing.changeType = change.changeType;
154
+
155
+ if (existing.oldValue === change.newValue) {
156
+ changesByKey.delete(change.key);
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ const allChanges = Array.from(changesByKey.values());
163
+ const added = allChanges.filter((c) => c.changeType === "added");
164
+ const modified = allChanges.filter((c) => c.changeType === "modified");
165
+ const deleted = allChanges.filter((c) => c.changeType === "deleted");
166
+
167
+ return {
168
+ hasChanges: allChanges.length > 0,
169
+ changes: allChanges,
170
+ added,
171
+ modified,
172
+ deleted,
173
+ };
174
+ }
175
+
176
+ /**
177
+ * Formats a frontmatter change for display in a modal.
178
+ * Returns a human-readable string describing the change.
179
+ */
180
+ export function formatChangeForDisplay(change: FrontmatterChange): string {
181
+ const formatValue = (value: unknown): string => {
182
+ if (value === undefined) return "(not set)";
183
+ if (value === null) return "null";
184
+ if (typeof value === "string") return `"${value}"`;
185
+ if (typeof value === "object") return JSON.stringify(value);
186
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
187
+ return JSON.stringify(value);
188
+ };
189
+
190
+ switch (change.changeType) {
191
+ case "added":
192
+ return `+ ${change.key}: ${formatValue(change.newValue)}`;
193
+ case "deleted":
194
+ return `- ${change.key}: ${formatValue(change.oldValue)}`;
195
+ case "modified":
196
+ return `~ ${change.key}: ${formatValue(change.oldValue)} → ${formatValue(change.newValue)}`;
197
+ }
198
+ }
package/src/file/index.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./file";
3
3
  export * from "./file-operations";
4
4
  export * from "./file-utils";
5
5
  export * from "./frontmatter";
6
+ export * from "./frontmatter-diff";
6
7
  export * from "./link-parser";
7
8
  export * from "./property-utils";
8
9
  export * from "./templater";