@real1ty-obsidian-plugins/utils 2.12.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.
Files changed (50) hide show
  1. package/dist/components/frontmatter-propagation-modal.d.ts +16 -0
  2. package/dist/components/frontmatter-propagation-modal.d.ts.map +1 -0
  3. package/dist/components/frontmatter-propagation-modal.js +81 -0
  4. package/dist/components/frontmatter-propagation-modal.js.map +1 -0
  5. package/dist/components/index.d.ts +3 -0
  6. package/dist/components/index.d.ts.map +1 -0
  7. package/dist/components/index.js +3 -0
  8. package/dist/components/index.js.map +1 -0
  9. package/dist/components/input-managers/base.d.ts +30 -0
  10. package/dist/components/input-managers/base.d.ts.map +1 -0
  11. package/dist/components/input-managers/base.js +115 -0
  12. package/dist/components/input-managers/base.js.map +1 -0
  13. package/dist/components/input-managers/expression.d.ts +12 -0
  14. package/dist/components/input-managers/expression.d.ts.map +1 -0
  15. package/dist/components/input-managers/expression.js +56 -0
  16. package/dist/components/input-managers/expression.js.map +1 -0
  17. package/dist/components/input-managers/index.d.ts +4 -0
  18. package/dist/components/input-managers/index.d.ts.map +1 -0
  19. package/dist/components/input-managers/index.js +4 -0
  20. package/dist/components/input-managers/index.js.map +1 -0
  21. package/dist/components/input-managers/search.d.ts +6 -0
  22. package/dist/components/input-managers/search.d.ts.map +1 -0
  23. package/dist/components/input-managers/search.js +16 -0
  24. package/dist/components/input-managers/search.js.map +1 -0
  25. package/dist/core/evaluator/base.d.ts.map +1 -1
  26. package/dist/core/evaluator/base.js +12 -3
  27. package/dist/core/evaluator/base.js.map +1 -1
  28. package/dist/file/frontmatter-diff.d.ts +38 -0
  29. package/dist/file/frontmatter-diff.d.ts.map +1 -0
  30. package/dist/file/frontmatter-diff.js +162 -0
  31. package/dist/file/frontmatter-diff.js.map +1 -0
  32. package/dist/file/index.d.ts +1 -0
  33. package/dist/file/index.d.ts.map +1 -1
  34. package/dist/file/index.js +1 -0
  35. package/dist/file/index.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -0
  39. package/dist/index.js.map +1 -1
  40. package/package.json +1 -1
  41. package/src/components/frontmatter-propagation-modal.ts +111 -0
  42. package/src/components/index.ts +2 -0
  43. package/src/components/input-managers/base.ts +150 -0
  44. package/src/components/input-managers/expression.ts +92 -0
  45. package/src/components/input-managers/index.ts +3 -0
  46. package/src/components/input-managers/search.ts +25 -0
  47. package/src/core/evaluator/base.ts +15 -3
  48. package/src/file/frontmatter-diff.ts +198 -0
  49. package/src/file/index.ts +1 -0
  50. package/src/index.ts +2 -0
@@ -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/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./async";
2
+ export * from "./components";
2
3
  export * from "./core";
3
4
  export * from "./date";
4
5
  export * from "./file";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AAExB,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAE3B,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AAExB,cAAc,cAAc,CAAC;AAE7B,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAE3B,cAAc,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // Settings
2
2
  // Async utilities
3
3
  export * from "./async";
4
+ // Components
5
+ export * from "./components";
4
6
  // Core utilities
5
7
  export * from "./core";
6
8
  // Date operations
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AAEX,kBAAkB;AAClB,cAAc,SAAS,CAAC;AACxB,iBAAiB;AACjB,cAAc,QAAQ,CAAC;AAEvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,mBAAmB;AACnB,cAAc,UAAU,CAAC","sourcesContent":["// Settings\n\n// Async utilities\nexport * from \"./async\";\n// Core utilities\nexport * from \"./core\";\n\n// Date operations\nexport * from \"./date\";\n// File operations\nexport * from \"./file\";\n// Input utilities\nexport * from \"./inputs\";\nexport * from \"./settings\";\n// String utilities\nexport * from \"./string\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AAEX,kBAAkB;AAClB,cAAc,SAAS,CAAC;AACxB,aAAa;AACb,cAAc,cAAc,CAAC;AAC7B,iBAAiB;AACjB,cAAc,QAAQ,CAAC;AAEvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,mBAAmB;AACnB,cAAc,UAAU,CAAC","sourcesContent":["// Settings\n\n// Async utilities\nexport * from \"./async\";\n// Components\nexport * from \"./components\";\n// Core utilities\nexport * from \"./core\";\n\n// Date operations\nexport * from \"./date\";\n// File operations\nexport * from \"./file\";\n// Input utilities\nexport * from \"./inputs\";\nexport * from \"./settings\";\n// String utilities\nexport * from \"./string\";\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real1ty-obsidian-plugins/utils",
3
- "version": "2.12.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
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./frontmatter-propagation-modal";
2
+ export * from "./input-managers";
@@ -0,0 +1,150 @@
1
+ export type InputManagerFilterChangeCallback = () => void;
2
+
3
+ const DEFAULT_DEBOUNCE_MS = 150;
4
+
5
+ export abstract class InputManager {
6
+ protected containerEl: HTMLElement;
7
+ protected inputEl: HTMLInputElement | null = null;
8
+ protected debounceTimer: number | null = null;
9
+ protected currentValue = "";
10
+ protected persistentlyVisible = false;
11
+ protected onHide?: () => void;
12
+ protected hiddenClass: string;
13
+ protected debounceMs: number;
14
+ protected cssClass: string;
15
+
16
+ constructor(
17
+ protected parentEl: HTMLElement,
18
+ protected placeholder: string,
19
+ protected cssPrefix: string,
20
+ protected onFilterChange: InputManagerFilterChangeCallback,
21
+ initiallyVisible: boolean,
22
+ onHide?: () => void,
23
+ debounceMs: number = DEFAULT_DEBOUNCE_MS
24
+ ) {
25
+ this.hiddenClass = `${cssPrefix}-hidden`;
26
+ this.debounceMs = debounceMs;
27
+ this.cssClass = `${cssPrefix}-input`;
28
+
29
+ const classes = initiallyVisible
30
+ ? `${cssPrefix}-container`
31
+ : `${cssPrefix}-container ${this.hiddenClass}`;
32
+
33
+ this.containerEl = this.parentEl.createEl("div", {
34
+ cls: classes,
35
+ });
36
+
37
+ this.onHide = onHide;
38
+
39
+ this.render();
40
+ }
41
+
42
+ private render(): void {
43
+ this.inputEl = this.containerEl.createEl("input", {
44
+ type: "text",
45
+ cls: this.cssClass,
46
+ placeholder: this.placeholder,
47
+ });
48
+
49
+ this.inputEl.addEventListener("input", () => {
50
+ this.handleInputChange();
51
+ });
52
+
53
+ this.inputEl.addEventListener("keydown", (evt) => {
54
+ if (evt.key === "Escape") {
55
+ // Only allow hiding if not persistently visible
56
+ if (!this.persistentlyVisible) {
57
+ this.hide();
58
+ } else {
59
+ // Just blur the input if persistently visible
60
+ this.inputEl?.blur();
61
+ }
62
+ } else if (evt.key === "Enter") {
63
+ this.applyFilterImmediately();
64
+ }
65
+ });
66
+ }
67
+
68
+ private handleInputChange(): void {
69
+ if (this.debounceTimer !== null) {
70
+ window.clearTimeout(this.debounceTimer);
71
+ }
72
+
73
+ this.debounceTimer = window.setTimeout(() => {
74
+ this.applyFilterImmediately();
75
+ }, this.debounceMs);
76
+ }
77
+
78
+ protected applyFilterImmediately(): void {
79
+ const newValue = this.inputEl?.value.trim() ?? "";
80
+
81
+ if (newValue !== this.currentValue) {
82
+ this.updateFilterValue(newValue);
83
+ }
84
+ }
85
+
86
+ protected updateFilterValue(value: string): void {
87
+ this.currentValue = value;
88
+
89
+ this.onFilterChange();
90
+ }
91
+
92
+ getCurrentValue(): string {
93
+ return this.currentValue;
94
+ }
95
+
96
+ show(): void {
97
+ this.containerEl.removeClass(this.hiddenClass);
98
+
99
+ this.inputEl?.focus();
100
+ }
101
+
102
+ hide(): void {
103
+ // Don't allow hiding if persistently visible
104
+ if (this.persistentlyVisible) {
105
+ return;
106
+ }
107
+
108
+ this.containerEl.addClass(this.hiddenClass);
109
+
110
+ if (this.inputEl) {
111
+ this.inputEl.value = "";
112
+ }
113
+
114
+ this.updateFilterValue("");
115
+
116
+ this.onHide?.();
117
+ }
118
+
119
+ focus(): void {
120
+ this.inputEl?.focus();
121
+ }
122
+
123
+ isVisible(): boolean {
124
+ return !this.containerEl.hasClass(this.hiddenClass);
125
+ }
126
+
127
+ setPersistentlyVisible(value: boolean): void {
128
+ this.persistentlyVisible = value;
129
+
130
+ if (value) {
131
+ this.show();
132
+ } else {
133
+ this.hide();
134
+ }
135
+ }
136
+
137
+ destroy(): void {
138
+ if (this.debounceTimer !== null) {
139
+ window.clearTimeout(this.debounceTimer);
140
+
141
+ this.debounceTimer = null;
142
+ }
143
+
144
+ this.containerEl.remove();
145
+
146
+ this.inputEl = null;
147
+ }
148
+
149
+ abstract shouldInclude(data: unknown): boolean;
150
+ }
@@ -0,0 +1,92 @@
1
+ import { buildPropertyMapping, sanitizeExpression } from "../../core/expression-utils";
2
+
3
+ import { InputManager } from "./base";
4
+
5
+ export class ExpressionFilterInputManager extends InputManager {
6
+ private compiledFunc: ((...args: unknown[]) => boolean) | null = null;
7
+
8
+ private propertyMapping = new Map<string, string>();
9
+
10
+ private lastWarnedExpression: string | null = null;
11
+
12
+ constructor(
13
+ parentEl: HTMLElement,
14
+ cssPrefix: string,
15
+ onFilterChange: () => void,
16
+ initiallyVisible: boolean = false,
17
+ placeholder: string = "Status === 'Done'",
18
+ onHide?: () => void,
19
+ debounceMs?: number
20
+ ) {
21
+ super(parentEl, placeholder, cssPrefix, onFilterChange, initiallyVisible, onHide, debounceMs);
22
+ this.cssClass = `${cssPrefix}-expression-input`;
23
+ if (this.inputEl) {
24
+ this.inputEl.className = this.cssClass;
25
+ }
26
+ }
27
+
28
+ protected updateFilterValue(filterValue: string): void {
29
+ super.updateFilterValue(filterValue);
30
+
31
+ this.compiledFunc = null;
32
+
33
+ this.propertyMapping.clear();
34
+
35
+ this.lastWarnedExpression = null;
36
+ }
37
+
38
+ shouldInclude(event: { meta?: Record<string, unknown> }): boolean {
39
+ if (!this.currentValue) return true;
40
+
41
+ const frontmatter = event.meta || {};
42
+
43
+ try {
44
+ const currentKeys = new Set(Object.keys(frontmatter));
45
+
46
+ const existingKeys = new Set(this.propertyMapping.keys());
47
+
48
+ const newKeys = [...currentKeys].filter((key) => !existingKeys.has(key));
49
+
50
+ if (newKeys.length > 0) {
51
+ const allKeys = new Set([...existingKeys, ...currentKeys]);
52
+
53
+ this.propertyMapping = buildPropertyMapping(Array.from(allKeys));
54
+
55
+ this.compiledFunc = null;
56
+ }
57
+
58
+ if (!this.compiledFunc) {
59
+ const sanitized = sanitizeExpression(this.currentValue, this.propertyMapping);
60
+
61
+ const params = Array.from(this.propertyMapping.values());
62
+
63
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval -- Dynamic function creation for expression evaluation with sanitized input
64
+ this.compiledFunc = new Function(...params, `"use strict"; return ${sanitized};`) as (
65
+ ...args: unknown[]
66
+ ) => boolean;
67
+ }
68
+
69
+ const values = Array.from(this.propertyMapping.keys()).map(
70
+ (key) => frontmatter[key] ?? undefined
71
+ );
72
+
73
+ const result = this.compiledFunc(...values);
74
+
75
+ return result;
76
+ } catch (error) {
77
+ if (error instanceof ReferenceError) {
78
+ const hasInequality = this.currentValue.includes("!==") || this.currentValue.includes("!=");
79
+
80
+ return hasInequality;
81
+ }
82
+
83
+ if (this.lastWarnedExpression !== this.currentValue) {
84
+ console.warn("Invalid filter expression:", this.currentValue, error);
85
+
86
+ this.lastWarnedExpression = this.currentValue;
87
+ }
88
+
89
+ return false;
90
+ }
91
+ }
92
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./base";
2
+ export * from "./expression";
3
+ export * from "./search";
@@ -0,0 +1,25 @@
1
+ import { InputManager } from "./base";
2
+
3
+ export class SearchFilterInputManager extends InputManager {
4
+ constructor(
5
+ parentEl: HTMLElement,
6
+ cssPrefix: string,
7
+ onFilterChange: () => void,
8
+ initiallyVisible: boolean = false,
9
+ placeholder: string = "Search ...",
10
+ onHide?: () => void,
11
+ debounceMs?: number
12
+ ) {
13
+ super(parentEl, placeholder, cssPrefix, onFilterChange, initiallyVisible, onHide, debounceMs);
14
+ this.cssClass = `${cssPrefix}-search-input`;
15
+ if (this.inputEl) {
16
+ this.inputEl.className = this.cssClass;
17
+ }
18
+ }
19
+
20
+ shouldInclude(value: string): boolean {
21
+ if (!this.currentValue) return true;
22
+
23
+ return value.toLowerCase().includes(this.currentValue.toLowerCase());
24
+ }
25
+ }
@@ -40,8 +40,17 @@ export abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
40
40
  }
41
41
 
42
42
  try {
43
- if (this.propertyMapping.size === 0) {
44
- this.propertyMapping = buildPropertyMapping(Object.keys(frontmatter));
43
+ // Progressively build property mapping as we encounter new properties
44
+ const currentKeys = new Set(Object.keys(frontmatter));
45
+ const existingKeys = new Set(this.propertyMapping.keys());
46
+ const newKeys = [...currentKeys].filter((key) => !existingKeys.has(key));
47
+
48
+ // If new properties are found, rebuild the mapping and invalidate compiled functions
49
+ if (newKeys.length > 0) {
50
+ const allKeys = new Set([...existingKeys, ...currentKeys]);
51
+ this.propertyMapping = buildPropertyMapping(Array.from(allKeys));
52
+ // Clear compiled functions since property mapping changed
53
+ this.compiledFunctions.clear();
45
54
  }
46
55
 
47
56
  let compiledFunc = this.compiledFunctions.get(rule.id);
@@ -55,7 +64,10 @@ export abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
55
64
  this.compiledFunctions.set(rule.id, compiledFunc);
56
65
  }
57
66
 
58
- const values = Array.from(this.propertyMapping.keys()).map((key) => frontmatter[key]);
67
+ // Use undefined for missing properties instead of letting them be undefined implicitly
68
+ const values = Array.from(this.propertyMapping.keys()).map(
69
+ (key) => frontmatter[key] ?? undefined
70
+ );
59
71
  const result = compiledFunc(...values);
60
72
 
61
73
  return result === true;