@real1ty-obsidian-plugins/utils 2.3.0 → 2.5.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.
- package/dist/core/evaluator/base.d.ts +22 -0
- package/dist/core/evaluator/base.d.ts.map +1 -0
- package/dist/core/evaluator/base.js +52 -0
- package/dist/core/evaluator/base.js.map +1 -0
- package/dist/core/evaluator/color.d.ts +19 -0
- package/dist/core/evaluator/color.d.ts.map +1 -0
- package/dist/core/evaluator/color.js +25 -0
- package/dist/core/evaluator/color.js.map +1 -0
- package/dist/core/evaluator/excluded.d.ts +32 -0
- package/dist/core/evaluator/excluded.d.ts.map +1 -0
- package/dist/core/evaluator/excluded.js +41 -0
- package/dist/core/evaluator/excluded.js.map +1 -0
- package/dist/core/evaluator/filter.d.ts +15 -0
- package/dist/core/evaluator/filter.d.ts.map +1 -0
- package/dist/core/evaluator/filter.js +27 -0
- package/dist/core/evaluator/filter.js.map +1 -0
- package/dist/core/evaluator/included.d.ts +36 -0
- package/dist/core/evaluator/included.d.ts.map +1 -0
- package/dist/core/evaluator/included.js +51 -0
- package/dist/core/evaluator/included.js.map +1 -0
- package/dist/core/evaluator/index.d.ts +6 -0
- package/dist/core/evaluator/index.d.ts.map +1 -0
- package/dist/core/evaluator/index.js +6 -0
- package/dist/core/evaluator/index.js.map +1 -0
- package/dist/core/expression-utils.d.ts +17 -0
- package/dist/core/expression-utils.d.ts.map +1 -0
- package/dist/core/expression-utils.js +40 -0
- package/dist/core/expression-utils.js.map +1 -0
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +2 -1
- package/dist/core/index.js.map +1 -1
- package/package.json +3 -5
- package/src/async/async.ts +117 -0
- package/src/async/batch-operations.ts +53 -0
- package/src/async/index.ts +2 -0
- package/src/core/evaluator/base.ts +71 -0
- package/src/core/evaluator/color.ts +37 -0
- package/src/core/evaluator/excluded.ts +63 -0
- package/src/core/evaluator/filter.ts +35 -0
- package/src/core/evaluator/included.ts +74 -0
- package/src/core/evaluator/index.ts +5 -0
- package/src/core/expression-utils.ts +53 -0
- package/src/core/generate.ts +22 -0
- package/src/core/index.ts +3 -0
- package/src/date/date-recurrence.ts +244 -0
- package/src/date/date.ts +111 -0
- package/src/date/index.ts +2 -0
- package/src/file/child-reference.ts +76 -0
- package/src/file/file-operations.ts +197 -0
- package/src/file/file.ts +570 -0
- package/src/file/frontmatter.ts +80 -0
- package/src/file/index.ts +6 -0
- package/src/file/link-parser.ts +18 -0
- package/src/file/templater.ts +75 -0
- package/src/index.ts +14 -0
- package/src/settings/index.ts +2 -0
- package/src/settings/settings-store.ts +88 -0
- package/src/settings/settings-ui-builder.ts +507 -0
- package/src/string/index.ts +1 -0
- package/src/string/string.ts +26 -0
- package/src/testing/index.ts +23 -0
- package/src/testing/mocks/obsidian.ts +331 -0
- package/src/testing/mocks/utils.ts +113 -0
- package/src/testing/setup.ts +19 -0
- package/dist/core/evaluator-base.d.ts +0 -52
- package/dist/core/evaluator-base.d.ts.map +0 -1
- package/dist/core/evaluator-base.js +0 -84
- package/dist/core/evaluator-base.js.map +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a function that ensures an async operation runs only once,
|
|
3
|
+
* returning the same promise for concurrent calls.
|
|
4
|
+
*
|
|
5
|
+
* Useful for initialization patterns where you want to ensure
|
|
6
|
+
* expensive async operations (like indexing, API calls, etc.)
|
|
7
|
+
* only happen once even if called multiple times.
|
|
8
|
+
*
|
|
9
|
+
* @param fn The async function to memoize
|
|
10
|
+
* @returns A function that returns the same promise on subsequent calls
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const initializeOnce = onceAsync(async () => {
|
|
15
|
+
* await heavyInitialization();
|
|
16
|
+
* console.log("Initialized!");
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // All these calls will share the same promise
|
|
20
|
+
* await initializeOnce();
|
|
21
|
+
* await initializeOnce(); // Won't run again
|
|
22
|
+
* await initializeOnce(); // Won't run again
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export function onceAsync<T>(fn: () => Promise<T>): () => Promise<T> {
|
|
26
|
+
let promise: Promise<T> | null = null;
|
|
27
|
+
|
|
28
|
+
return () => {
|
|
29
|
+
if (!promise) {
|
|
30
|
+
try {
|
|
31
|
+
promise = fn();
|
|
32
|
+
} catch (error) {
|
|
33
|
+
// Convert synchronous errors to rejected promises
|
|
34
|
+
promise = Promise.reject(error);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return promise;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates a function that ensures an async operation runs only once per key,
|
|
43
|
+
* useful for caching expensive operations with different parameters.
|
|
44
|
+
*
|
|
45
|
+
* @param fn The async function to memoize
|
|
46
|
+
* @returns A function that memoizes results by key
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```typescript
|
|
50
|
+
* const fetchUserOnce = onceAsyncKeyed(async (userId: string) => {
|
|
51
|
+
* return await api.getUser(userId);
|
|
52
|
+
* });
|
|
53
|
+
*
|
|
54
|
+
* // Each unique userId will only be fetched once
|
|
55
|
+
* await fetchUserOnce("user1");
|
|
56
|
+
* await fetchUserOnce("user1"); // Returns cached promise
|
|
57
|
+
* await fetchUserOnce("user2"); // New fetch for different key
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export function onceAsyncKeyed<TArgs extends readonly unknown[], TReturn>(
|
|
61
|
+
fn: (...args: TArgs) => Promise<TReturn>
|
|
62
|
+
): (...args: TArgs) => Promise<TReturn> {
|
|
63
|
+
const cache = new Map<string, Promise<TReturn>>();
|
|
64
|
+
|
|
65
|
+
return (...args: TArgs) => {
|
|
66
|
+
const key = JSON.stringify(args);
|
|
67
|
+
|
|
68
|
+
if (!cache.has(key)) {
|
|
69
|
+
cache.set(key, fn(...args));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return cache.get(key)!;
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates a resettable version of onceAsync that can be cleared and re-run.
|
|
78
|
+
*
|
|
79
|
+
* @param fn The async function to memoize
|
|
80
|
+
* @returns Object with execute and reset methods
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* const { execute: initialize, reset } = onceAsyncResettable(async () => {
|
|
85
|
+
* await heavyInitialization();
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* await initialize(); // Runs
|
|
89
|
+
* await initialize(); // Cached
|
|
90
|
+
*
|
|
91
|
+
* reset(); // Clear cache
|
|
92
|
+
* await initialize(); // Runs again
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
export function onceAsyncResettable<T>(fn: () => Promise<T>): {
|
|
96
|
+
execute: () => Promise<T>;
|
|
97
|
+
reset: () => void;
|
|
98
|
+
} {
|
|
99
|
+
let promise: Promise<T> | null = null;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
execute: () => {
|
|
103
|
+
if (!promise) {
|
|
104
|
+
try {
|
|
105
|
+
promise = fn();
|
|
106
|
+
} catch (error) {
|
|
107
|
+
// Convert synchronous errors to rejected promises
|
|
108
|
+
promise = Promise.reject(error);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return promise;
|
|
112
|
+
},
|
|
113
|
+
reset: () => {
|
|
114
|
+
promise = null;
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Notice } from "obsidian";
|
|
2
|
+
|
|
3
|
+
export interface BatchOperationOptions {
|
|
4
|
+
closeAfter?: boolean;
|
|
5
|
+
callOnComplete?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface BatchOperationResult {
|
|
9
|
+
successCount: number;
|
|
10
|
+
errorCount: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function runBatchOperation<T>(
|
|
14
|
+
items: T[],
|
|
15
|
+
operationLabel: string,
|
|
16
|
+
handler: (item: T) => Promise<void>,
|
|
17
|
+
showResult: boolean = true
|
|
18
|
+
): Promise<BatchOperationResult> {
|
|
19
|
+
let successCount = 0;
|
|
20
|
+
let errorCount = 0;
|
|
21
|
+
|
|
22
|
+
for (const item of items) {
|
|
23
|
+
try {
|
|
24
|
+
await handler(item);
|
|
25
|
+
successCount++;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error(`${operationLabel}: error processing item:`, error);
|
|
28
|
+
errorCount++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (showResult) {
|
|
33
|
+
showBatchOperationResult(operationLabel, successCount, errorCount);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { successCount, errorCount };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function showBatchOperationResult(
|
|
40
|
+
operation: string,
|
|
41
|
+
successCount: number,
|
|
42
|
+
errorCount: number
|
|
43
|
+
): void {
|
|
44
|
+
if (errorCount === 0) {
|
|
45
|
+
new Notice(
|
|
46
|
+
`${operation}: ${successCount} item${successCount === 1 ? "" : "s"} processed successfully`
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
new Notice(
|
|
50
|
+
`${operation}: ${successCount} succeeded, ${errorCount} failed. Check console for details.`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { BehaviorSubject, Subscription } from "rxjs";
|
|
2
|
+
|
|
3
|
+
import { buildPropertyMapping, sanitizeExpression } from "../expression-utils";
|
|
4
|
+
|
|
5
|
+
export interface BaseRule {
|
|
6
|
+
id: string;
|
|
7
|
+
expression: string;
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generic base class for evaluating JavaScript expressions against frontmatter objects.
|
|
13
|
+
* Provides reactive compilation of rules via RxJS subscription and safe evaluation.
|
|
14
|
+
*/
|
|
15
|
+
export abstract class BaseEvaluator<TRule extends BaseRule, TSettings> {
|
|
16
|
+
protected rules: TRule[] = [];
|
|
17
|
+
private compiledFunctions = new Map<string, ((...args: any[]) => boolean) | null>();
|
|
18
|
+
private propertyMapping = new Map<string, string>();
|
|
19
|
+
private subscription: Subscription | null = null;
|
|
20
|
+
|
|
21
|
+
constructor(settingsStore: BehaviorSubject<TSettings>) {
|
|
22
|
+
this.subscription = settingsStore.subscribe((settings) => {
|
|
23
|
+
this.rules = this.extractRules(settings);
|
|
24
|
+
this.compiledFunctions.clear();
|
|
25
|
+
this.propertyMapping.clear();
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected abstract extractRules(settings: TSettings): TRule[];
|
|
30
|
+
|
|
31
|
+
destroy(): void {
|
|
32
|
+
this.subscription?.unsubscribe();
|
|
33
|
+
this.compiledFunctions.clear();
|
|
34
|
+
this.propertyMapping.clear();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
protected evaluateRule(rule: TRule, frontmatter: Record<string, unknown>): boolean {
|
|
38
|
+
if (!rule.enabled || !rule.expression) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
if (this.propertyMapping.size === 0) {
|
|
44
|
+
this.propertyMapping = buildPropertyMapping(Object.keys(frontmatter));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let compiledFunc = this.compiledFunctions.get(rule.id);
|
|
48
|
+
|
|
49
|
+
if (!compiledFunc) {
|
|
50
|
+
const sanitized = sanitizeExpression(rule.expression, this.propertyMapping);
|
|
51
|
+
const params = Array.from(this.propertyMapping.values());
|
|
52
|
+
compiledFunc = new Function(...params, `"use strict"; return ${sanitized};`) as (
|
|
53
|
+
...args: any[]
|
|
54
|
+
) => boolean;
|
|
55
|
+
this.compiledFunctions.set(rule.id, compiledFunc);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const values = Array.from(this.propertyMapping.keys()).map((key) => frontmatter[key]);
|
|
59
|
+
const result = compiledFunc(...values);
|
|
60
|
+
|
|
61
|
+
return result === true;
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn(`Invalid expression (${rule.id}):`, rule.expression, error);
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
protected isTruthy(value: any): boolean {
|
|
69
|
+
return value === true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BehaviorSubject } from "rxjs";
|
|
2
|
+
|
|
3
|
+
import { BaseEvaluator, type BaseRule } from "./base";
|
|
4
|
+
|
|
5
|
+
export interface ColorRule extends BaseRule {
|
|
6
|
+
color: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Generic evaluator for determining colors based on frontmatter rules.
|
|
11
|
+
* Extends BaseEvaluator to evaluate color rules against frontmatter.
|
|
12
|
+
*/
|
|
13
|
+
export class ColorEvaluator<
|
|
14
|
+
TSettings extends { defaultNodeColor: string; colorRules: ColorRule[] },
|
|
15
|
+
> extends BaseEvaluator<ColorRule, TSettings> {
|
|
16
|
+
private defaultColor: string;
|
|
17
|
+
|
|
18
|
+
constructor(settingsStore: BehaviorSubject<TSettings>) {
|
|
19
|
+
super(settingsStore);
|
|
20
|
+
this.defaultColor = settingsStore.value.defaultNodeColor;
|
|
21
|
+
|
|
22
|
+
settingsStore.subscribe((settings) => {
|
|
23
|
+
if (settings.defaultNodeColor) {
|
|
24
|
+
this.defaultColor = settings.defaultNodeColor;
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
protected extractRules(settings: TSettings): ColorRule[] {
|
|
30
|
+
return settings.colorRules;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
evaluateColor(frontmatter: Record<string, unknown>): string {
|
|
34
|
+
const match = this.rules.find((rule) => this.isTruthy(this.evaluateRule(rule, frontmatter)));
|
|
35
|
+
return match?.color ?? this.defaultColor;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { BehaviorSubject } from "rxjs";
|
|
2
|
+
|
|
3
|
+
export interface PathExcludedProperties {
|
|
4
|
+
id: string;
|
|
5
|
+
path: string;
|
|
6
|
+
excludedProperties: string[];
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generic evaluator for determining which frontmatter properties to exclude when creating new nodes.
|
|
12
|
+
*
|
|
13
|
+
* Logic:
|
|
14
|
+
* 1. ALWAYS includes the default excluded properties (e.g., Parent, Child, Related, _ZettelID)
|
|
15
|
+
* 2. Checks if the source file's path matches any path-based exclusion rules
|
|
16
|
+
* 3. First matching path rule's properties are ADDED to the default exclusion list
|
|
17
|
+
* 4. Returns the combined set of excluded properties
|
|
18
|
+
*/
|
|
19
|
+
export class ExcludedPropertiesEvaluator<
|
|
20
|
+
TSettings extends {
|
|
21
|
+
defaultExcludedProperties: string[];
|
|
22
|
+
pathExcludedProperties: PathExcludedProperties[];
|
|
23
|
+
},
|
|
24
|
+
> {
|
|
25
|
+
private defaultExcludedProperties: string[];
|
|
26
|
+
|
|
27
|
+
private pathRules: PathExcludedProperties[];
|
|
28
|
+
|
|
29
|
+
constructor(settingsObservable: BehaviorSubject<TSettings>) {
|
|
30
|
+
const assignSettings = (settings: TSettings) => {
|
|
31
|
+
this.defaultExcludedProperties = settings.defaultExcludedProperties;
|
|
32
|
+
this.pathRules = settings.pathExcludedProperties.filter((rule) => rule.enabled);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
assignSettings(settingsObservable.value);
|
|
36
|
+
settingsObservable.subscribe(assignSettings);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Evaluate which properties should be excluded for a given file path.
|
|
41
|
+
*
|
|
42
|
+
* @param filePath - The file path to match against path rules
|
|
43
|
+
* @returns Array of property names to exclude (always includes defaults + path rule matches)
|
|
44
|
+
*/
|
|
45
|
+
evaluateExcludedProperties(filePath: string): string[] {
|
|
46
|
+
// Always start with default excluded properties
|
|
47
|
+
const excludedProperties = [...this.defaultExcludedProperties];
|
|
48
|
+
|
|
49
|
+
// Find first matching path rule and add its excluded properties
|
|
50
|
+
const match = this.pathRules.find((rule) => filePath.startsWith(rule.path));
|
|
51
|
+
|
|
52
|
+
if (match) {
|
|
53
|
+
// Add path-specific excluded properties to the defaults
|
|
54
|
+
for (const prop of match.excludedProperties) {
|
|
55
|
+
if (!excludedProperties.includes(prop)) {
|
|
56
|
+
excludedProperties.push(prop);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return excludedProperties;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BehaviorSubject } from "rxjs";
|
|
2
|
+
|
|
3
|
+
import { BaseEvaluator, type BaseRule } from "./base";
|
|
4
|
+
|
|
5
|
+
export interface FilterRule extends BaseRule {}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generic evaluator for filtering based on frontmatter expressions.
|
|
9
|
+
* Extends BaseEvaluator to evaluate filter rules against frontmatter.
|
|
10
|
+
* Returns true only if ALL rules evaluate to true.
|
|
11
|
+
*/
|
|
12
|
+
export class FilterEvaluator<
|
|
13
|
+
TSettings extends { filterExpressions: string[] },
|
|
14
|
+
> extends BaseEvaluator<FilterRule, TSettings> {
|
|
15
|
+
protected extractRules(settings: TSettings): FilterRule[] {
|
|
16
|
+
return settings.filterExpressions.map((expression, index) => ({
|
|
17
|
+
id: `filter-${index}`,
|
|
18
|
+
expression: expression.trim(),
|
|
19
|
+
enabled: true,
|
|
20
|
+
}));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
evaluateFilters(frontmatter: Record<string, unknown>): boolean {
|
|
24
|
+
if (this.rules.length === 0) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return this.rules.every((rule) => {
|
|
29
|
+
if (!rule.enabled || !rule.expression) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return this.evaluateRule(rule, frontmatter);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { BehaviorSubject } from "rxjs";
|
|
2
|
+
|
|
3
|
+
export interface PathIncludedProperties {
|
|
4
|
+
id: string;
|
|
5
|
+
path: string;
|
|
6
|
+
includedProperties: string[];
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Generic evaluator for determining which properties to include in Bases view columns.
|
|
12
|
+
*
|
|
13
|
+
* Logic:
|
|
14
|
+
* 1. ALWAYS includes the default included properties
|
|
15
|
+
* 2. Checks if the file's path matches any path-based inclusion rules
|
|
16
|
+
* 3. First matching path rule's properties are ADDED to the default inclusion list
|
|
17
|
+
* 4. Returns the combined set of included properties in order:
|
|
18
|
+
* - file.name (always first)
|
|
19
|
+
* - default included properties (in specified order)
|
|
20
|
+
* - path-specific included properties (in specified order)
|
|
21
|
+
*/
|
|
22
|
+
export class IncludedPropertiesEvaluator<
|
|
23
|
+
TSettings extends {
|
|
24
|
+
defaultBasesIncludedProperties: string[];
|
|
25
|
+
pathBasesIncludedProperties: PathIncludedProperties[];
|
|
26
|
+
},
|
|
27
|
+
> {
|
|
28
|
+
private defaultIncludedProperties: string[];
|
|
29
|
+
|
|
30
|
+
private pathRules: PathIncludedProperties[];
|
|
31
|
+
|
|
32
|
+
constructor(settingsObservable: BehaviorSubject<TSettings>) {
|
|
33
|
+
const assignSettings = (settings: TSettings) => {
|
|
34
|
+
this.defaultIncludedProperties = settings.defaultBasesIncludedProperties;
|
|
35
|
+
this.pathRules = settings.pathBasesIncludedProperties.filter((rule) => rule.enabled);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
assignSettings(settingsObservable.value);
|
|
39
|
+
settingsObservable.subscribe(assignSettings);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Evaluate which properties should be included in the order array for a given file path.
|
|
44
|
+
* Returns an array with "file.name" as the first element, followed by default and path-specific properties.
|
|
45
|
+
*
|
|
46
|
+
* @param filePath - The file path to match against path rules
|
|
47
|
+
* @returns Array of property names to include in the order (file.name + defaults + path rule matches)
|
|
48
|
+
*/
|
|
49
|
+
evaluateIncludedProperties(filePath: string): string[] {
|
|
50
|
+
// Always start with file.name
|
|
51
|
+
const includedProperties = ["file.name"];
|
|
52
|
+
|
|
53
|
+
// Add default included properties
|
|
54
|
+
for (const prop of this.defaultIncludedProperties) {
|
|
55
|
+
if (!includedProperties.includes(prop)) {
|
|
56
|
+
includedProperties.push(prop);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Find first matching path rule and add its included properties
|
|
61
|
+
const match = this.pathRules.find((rule) => filePath.startsWith(rule.path));
|
|
62
|
+
|
|
63
|
+
if (match) {
|
|
64
|
+
// Add path-specific included properties to the defaults
|
|
65
|
+
for (const prop of match.includedProperties) {
|
|
66
|
+
if (!includedProperties.includes(prop)) {
|
|
67
|
+
includedProperties.push(prop);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return includedProperties;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sanitizes a property name for use as a JavaScript function parameter
|
|
3
|
+
* by replacing spaces and special characters with underscores.
|
|
4
|
+
* Adds a prefix to avoid conflicts with JavaScript reserved words.
|
|
5
|
+
*/
|
|
6
|
+
export function sanitizePropertyName(name: string): string {
|
|
7
|
+
const sanitized = name.replace(/[^a-zA-Z0-9_]/g, "_");
|
|
8
|
+
return `prop_${sanitized}`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds a mapping of original property names to sanitized versions
|
|
13
|
+
* suitable for use as JavaScript function parameters.
|
|
14
|
+
*/
|
|
15
|
+
export function buildPropertyMapping(properties: string[]): Map<string, string> {
|
|
16
|
+
const mapping = new Map<string, string>();
|
|
17
|
+
|
|
18
|
+
for (const prop of properties) {
|
|
19
|
+
mapping.set(prop, sanitizePropertyName(prop));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return mapping;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Replaces property names in an expression with their sanitized versions.
|
|
27
|
+
* Sorts by length descending to replace longer property names first and avoid partial matches.
|
|
28
|
+
*/
|
|
29
|
+
export function sanitizeExpression(
|
|
30
|
+
expression: string,
|
|
31
|
+
propertyMapping: Map<string, string>
|
|
32
|
+
): string {
|
|
33
|
+
let sanitized = expression;
|
|
34
|
+
|
|
35
|
+
// Sort by length descending to replace longer property names first
|
|
36
|
+
const sortedEntries = Array.from(propertyMapping.entries()).sort(
|
|
37
|
+
([a], [b]) => b.length - a.length
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
for (const [original, sanitizedName] of sortedEntries) {
|
|
41
|
+
if (original !== sanitizedName) {
|
|
42
|
+
const escaped = original.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
43
|
+
|
|
44
|
+
// Use a regex that matches the property name not preceded or followed by word characters
|
|
45
|
+
// This allows matching properties with special characters like "My-Property"
|
|
46
|
+
const regex = new RegExp(`(?<!\\w)${escaped}(?!\\w)`, "g");
|
|
47
|
+
|
|
48
|
+
sanitized = sanitized.replace(regex, sanitizedName);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return sanitized;
|
|
53
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface TimestampData {
|
|
2
|
+
startDate: string;
|
|
3
|
+
zettelId: number;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export const generateZettelId = (): number => {
|
|
7
|
+
const currentTimestamp = new Date();
|
|
8
|
+
const padWithZero = (number: number) => String(number).padStart(2, "0");
|
|
9
|
+
return Number(
|
|
10
|
+
`${currentTimestamp.getFullYear()}${padWithZero(currentTimestamp.getMonth() + 1)}${padWithZero(currentTimestamp.getDate())}${padWithZero(currentTimestamp.getHours())}${padWithZero(currentTimestamp.getMinutes())}${padWithZero(currentTimestamp.getSeconds())}`
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export const generateTimestamps = (): TimestampData => {
|
|
15
|
+
const padWithZero = (number: number): string => String(number).padStart(2, "0");
|
|
16
|
+
const currentDate = new Date();
|
|
17
|
+
|
|
18
|
+
const formattedStartDate: string = `${currentDate.getFullYear()}-${padWithZero(currentDate.getMonth() + 1)}-${padWithZero(currentDate.getDate())}`;
|
|
19
|
+
const uniqueZettelId: number = generateZettelId();
|
|
20
|
+
|
|
21
|
+
return { startDate: formattedStartDate, zettelId: uniqueZettelId };
|
|
22
|
+
};
|