@real1ty-obsidian-plugins/utils 2.29.0 → 2.30.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 (237) hide show
  1. package/package.json +1 -1
  2. package/dist/async/async.d.ts +0 -69
  3. package/dist/async/async.d.ts.map +0 -1
  4. package/dist/async/async.js +0 -108
  5. package/dist/async/async.js.map +0 -1
  6. package/dist/async/batch-operations.d.ts +0 -21
  7. package/dist/async/batch-operations.d.ts.map +0 -1
  8. package/dist/async/batch-operations.js +0 -60
  9. package/dist/async/batch-operations.js.map +0 -1
  10. package/dist/async/change-notifier.d.ts +0 -13
  11. package/dist/async/change-notifier.d.ts.map +0 -1
  12. package/dist/async/change-notifier.js +0 -26
  13. package/dist/async/change-notifier.js.map +0 -1
  14. package/dist/async/debounced-notifier.d.ts +0 -26
  15. package/dist/async/debounced-notifier.d.ts.map +0 -1
  16. package/dist/async/debounced-notifier.js +0 -50
  17. package/dist/async/debounced-notifier.js.map +0 -1
  18. package/dist/async/index.d.ts +0 -5
  19. package/dist/async/index.d.ts.map +0 -1
  20. package/dist/async/index.js +0 -5
  21. package/dist/async/index.js.map +0 -1
  22. package/dist/components/frontmatter-propagation-modal.d.ts +0 -17
  23. package/dist/components/frontmatter-propagation-modal.d.ts.map +0 -1
  24. package/dist/components/frontmatter-propagation-modal.js +0 -85
  25. package/dist/components/frontmatter-propagation-modal.js.map +0 -1
  26. package/dist/components/index.d.ts +0 -5
  27. package/dist/components/index.d.ts.map +0 -1
  28. package/dist/components/index.js +0 -5
  29. package/dist/components/index.js.map +0 -1
  30. package/dist/components/input-managers/base.d.ts +0 -30
  31. package/dist/components/input-managers/base.d.ts.map +0 -1
  32. package/dist/components/input-managers/base.js +0 -115
  33. package/dist/components/input-managers/base.js.map +0 -1
  34. package/dist/components/input-managers/expression.d.ts +0 -12
  35. package/dist/components/input-managers/expression.d.ts.map +0 -1
  36. package/dist/components/input-managers/expression.js +0 -56
  37. package/dist/components/input-managers/expression.js.map +0 -1
  38. package/dist/components/input-managers/index.d.ts +0 -4
  39. package/dist/components/input-managers/index.d.ts.map +0 -1
  40. package/dist/components/input-managers/index.js +0 -4
  41. package/dist/components/input-managers/index.js.map +0 -1
  42. package/dist/components/input-managers/search.d.ts +0 -6
  43. package/dist/components/input-managers/search.d.ts.map +0 -1
  44. package/dist/components/input-managers/search.js +0 -16
  45. package/dist/components/input-managers/search.js.map +0 -1
  46. package/dist/components/registered-events-component.d.ts +0 -30
  47. package/dist/components/registered-events-component.d.ts.map +0 -1
  48. package/dist/components/registered-events-component.js +0 -43
  49. package/dist/components/registered-events-component.js.map +0 -1
  50. package/dist/components/whats-new-modal.d.ts +0 -64
  51. package/dist/components/whats-new-modal.d.ts.map +0 -1
  52. package/dist/components/whats-new-modal.js +0 -139
  53. package/dist/components/whats-new-modal.js.map +0 -1
  54. package/dist/core/color-utils.d.ts +0 -17
  55. package/dist/core/color-utils.d.ts.map +0 -1
  56. package/dist/core/color-utils.js +0 -29
  57. package/dist/core/color-utils.js.map +0 -1
  58. package/dist/core/css-utils.d.ts +0 -39
  59. package/dist/core/css-utils.d.ts.map +0 -1
  60. package/dist/core/css-utils.js +0 -60
  61. package/dist/core/css-utils.js.map +0 -1
  62. package/dist/core/evaluator/base.d.ts +0 -22
  63. package/dist/core/evaluator/base.d.ts.map +0 -1
  64. package/dist/core/evaluator/base.js +0 -67
  65. package/dist/core/evaluator/base.js.map +0 -1
  66. package/dist/core/evaluator/color.d.ts +0 -19
  67. package/dist/core/evaluator/color.d.ts.map +0 -1
  68. package/dist/core/evaluator/color.js +0 -25
  69. package/dist/core/evaluator/color.js.map +0 -1
  70. package/dist/core/evaluator/excluded.d.ts +0 -32
  71. package/dist/core/evaluator/excluded.d.ts.map +0 -1
  72. package/dist/core/evaluator/excluded.js +0 -41
  73. package/dist/core/evaluator/excluded.js.map +0 -1
  74. package/dist/core/evaluator/filter.d.ts +0 -15
  75. package/dist/core/evaluator/filter.d.ts.map +0 -1
  76. package/dist/core/evaluator/filter.js +0 -27
  77. package/dist/core/evaluator/filter.js.map +0 -1
  78. package/dist/core/evaluator/included.d.ts +0 -36
  79. package/dist/core/evaluator/included.d.ts.map +0 -1
  80. package/dist/core/evaluator/included.js +0 -51
  81. package/dist/core/evaluator/included.js.map +0 -1
  82. package/dist/core/evaluator/index.d.ts +0 -6
  83. package/dist/core/evaluator/index.d.ts.map +0 -1
  84. package/dist/core/evaluator/index.js +0 -6
  85. package/dist/core/evaluator/index.js.map +0 -1
  86. package/dist/core/expression-utils.d.ts +0 -17
  87. package/dist/core/expression-utils.d.ts.map +0 -1
  88. package/dist/core/expression-utils.js +0 -40
  89. package/dist/core/expression-utils.js.map +0 -1
  90. package/dist/core/frontmatter-value.d.ts +0 -157
  91. package/dist/core/frontmatter-value.d.ts.map +0 -1
  92. package/dist/core/frontmatter-value.js +0 -417
  93. package/dist/core/frontmatter-value.js.map +0 -1
  94. package/dist/core/generate.d.ts +0 -7
  95. package/dist/core/generate.d.ts.map +0 -1
  96. package/dist/core/generate.js +0 -13
  97. package/dist/core/generate.js.map +0 -1
  98. package/dist/core/history.d.ts +0 -12
  99. package/dist/core/history.d.ts.map +0 -1
  100. package/dist/core/history.js +0 -39
  101. package/dist/core/history.js.map +0 -1
  102. package/dist/core/index.d.ts +0 -11
  103. package/dist/core/index.d.ts.map +0 -1
  104. package/dist/core/index.js +0 -11
  105. package/dist/core/index.js.map +0 -1
  106. package/dist/core/indexer.d.ts +0 -109
  107. package/dist/core/indexer.d.ts.map +0 -1
  108. package/dist/core/indexer.js +0 -185
  109. package/dist/core/indexer.js.map +0 -1
  110. package/dist/core/property-renderer.d.ts +0 -9
  111. package/dist/core/property-renderer.d.ts.map +0 -1
  112. package/dist/core/property-renderer.js +0 -42
  113. package/dist/core/property-renderer.js.map +0 -1
  114. package/dist/core/validation.d.ts +0 -13
  115. package/dist/core/validation.d.ts.map +0 -1
  116. package/dist/core/validation.js +0 -27
  117. package/dist/core/validation.js.map +0 -1
  118. package/dist/date/date-recurrence.d.ts +0 -30
  119. package/dist/date/date-recurrence.d.ts.map +0 -1
  120. package/dist/date/date-recurrence.js +0 -188
  121. package/dist/date/date-recurrence.js.map +0 -1
  122. package/dist/date/date.d.ts +0 -21
  123. package/dist/date/date.d.ts.map +0 -1
  124. package/dist/date/date.js +0 -105
  125. package/dist/date/date.js.map +0 -1
  126. package/dist/date/index.d.ts +0 -3
  127. package/dist/date/index.d.ts.map +0 -1
  128. package/dist/date/index.js +0 -3
  129. package/dist/date/index.js.map +0 -1
  130. package/dist/file/child-reference.d.ts +0 -9
  131. package/dist/file/child-reference.d.ts.map +0 -1
  132. package/dist/file/child-reference.js +0 -57
  133. package/dist/file/child-reference.js.map +0 -1
  134. package/dist/file/file-operations.d.ts +0 -31
  135. package/dist/file/file-operations.d.ts.map +0 -1
  136. package/dist/file/file-operations.js +0 -160
  137. package/dist/file/file-operations.js.map +0 -1
  138. package/dist/file/file-utils.d.ts +0 -33
  139. package/dist/file/file-utils.d.ts.map +0 -1
  140. package/dist/file/file-utils.js +0 -76
  141. package/dist/file/file-utils.js.map +0 -1
  142. package/dist/file/file.d.ts +0 -332
  143. package/dist/file/file.d.ts.map +0 -1
  144. package/dist/file/file.js +0 -559
  145. package/dist/file/file.js.map +0 -1
  146. package/dist/file/frontmatter-diff.d.ts +0 -38
  147. package/dist/file/frontmatter-diff.d.ts.map +0 -1
  148. package/dist/file/frontmatter-diff.js +0 -162
  149. package/dist/file/frontmatter-diff.js.map +0 -1
  150. package/dist/file/frontmatter-propagation.d.ts +0 -4
  151. package/dist/file/frontmatter-propagation.d.ts.map +0 -1
  152. package/dist/file/frontmatter-propagation.js +0 -28
  153. package/dist/file/frontmatter-propagation.js.map +0 -1
  154. package/dist/file/frontmatter-serialization.d.ts +0 -21
  155. package/dist/file/frontmatter-serialization.d.ts.map +0 -1
  156. package/dist/file/frontmatter-serialization.js +0 -57
  157. package/dist/file/frontmatter-serialization.js.map +0 -1
  158. package/dist/file/frontmatter.d.ts +0 -15
  159. package/dist/file/frontmatter.d.ts.map +0 -1
  160. package/dist/file/frontmatter.js +0 -68
  161. package/dist/file/frontmatter.js.map +0 -1
  162. package/dist/file/index.d.ts +0 -13
  163. package/dist/file/index.d.ts.map +0 -1
  164. package/dist/file/index.js +0 -13
  165. package/dist/file/index.js.map +0 -1
  166. package/dist/file/link-parser.d.ts +0 -63
  167. package/dist/file/link-parser.d.ts.map +0 -1
  168. package/dist/file/link-parser.js +0 -137
  169. package/dist/file/link-parser.js.map +0 -1
  170. package/dist/file/property-utils.d.ts +0 -55
  171. package/dist/file/property-utils.d.ts.map +0 -1
  172. package/dist/file/property-utils.js +0 -90
  173. package/dist/file/property-utils.js.map +0 -1
  174. package/dist/file/templater-service.d.ts +0 -16
  175. package/dist/file/templater-service.d.ts.map +0 -1
  176. package/dist/file/templater-service.js +0 -37
  177. package/dist/file/templater-service.js.map +0 -1
  178. package/dist/file/templater.d.ts +0 -28
  179. package/dist/file/templater.d.ts.map +0 -1
  180. package/dist/file/templater.js +0 -126
  181. package/dist/file/templater.js.map +0 -1
  182. package/dist/index.d.ts +0 -9
  183. package/dist/index.d.ts.map +0 -1
  184. package/dist/index.js +0 -17
  185. package/dist/index.js.map +0 -1
  186. package/dist/inputs/index.d.ts +0 -2
  187. package/dist/inputs/index.d.ts.map +0 -1
  188. package/dist/inputs/index.js +0 -2
  189. package/dist/inputs/index.js.map +0 -1
  190. package/dist/inputs/input-filter-manager.d.ts +0 -72
  191. package/dist/inputs/input-filter-manager.d.ts.map +0 -1
  192. package/dist/inputs/input-filter-manager.js +0 -140
  193. package/dist/inputs/input-filter-manager.js.map +0 -1
  194. package/dist/settings/index.d.ts +0 -3
  195. package/dist/settings/index.d.ts.map +0 -1
  196. package/dist/settings/index.js +0 -3
  197. package/dist/settings/index.js.map +0 -1
  198. package/dist/settings/settings-store.d.ts +0 -20
  199. package/dist/settings/settings-store.d.ts.map +0 -1
  200. package/dist/settings/settings-store.js +0 -82
  201. package/dist/settings/settings-store.js.map +0 -1
  202. package/dist/settings/settings-ui-builder.d.ts +0 -74
  203. package/dist/settings/settings-ui-builder.d.ts.map +0 -1
  204. package/dist/settings/settings-ui-builder.js +0 -436
  205. package/dist/settings/settings-ui-builder.js.map +0 -1
  206. package/dist/string/changelog-parser.d.ts +0 -17
  207. package/dist/string/changelog-parser.d.ts.map +0 -1
  208. package/dist/string/changelog-parser.js +0 -77
  209. package/dist/string/changelog-parser.js.map +0 -1
  210. package/dist/string/filename-utils.d.ts +0 -46
  211. package/dist/string/filename-utils.d.ts.map +0 -1
  212. package/dist/string/filename-utils.js +0 -65
  213. package/dist/string/filename-utils.js.map +0 -1
  214. package/dist/string/index.d.ts +0 -4
  215. package/dist/string/index.d.ts.map +0 -1
  216. package/dist/string/index.js +0 -4
  217. package/dist/string/index.js.map +0 -1
  218. package/dist/string/string.d.ts +0 -5
  219. package/dist/string/string.d.ts.map +0 -1
  220. package/dist/string/string.js +0 -25
  221. package/dist/string/string.js.map +0 -1
  222. package/dist/testing/index.d.ts +0 -5
  223. package/dist/testing/index.d.ts.map +0 -1
  224. package/dist/testing/index.js +0 -6
  225. package/dist/testing/index.js.map +0 -1
  226. package/dist/testing/mocks/obsidian.d.ts +0 -150
  227. package/dist/testing/mocks/obsidian.d.ts.map +0 -1
  228. package/dist/testing/mocks/obsidian.js +0 -226
  229. package/dist/testing/mocks/obsidian.js.map +0 -1
  230. package/dist/testing/mocks/utils.d.ts +0 -14
  231. package/dist/testing/mocks/utils.d.ts.map +0 -1
  232. package/dist/testing/mocks/utils.js +0 -85
  233. package/dist/testing/mocks/utils.js.map +0 -1
  234. package/dist/testing/setup.d.ts +0 -2
  235. package/dist/testing/setup.d.ts.map +0 -1
  236. package/dist/testing/setup.js +0 -18
  237. package/dist/testing/setup.js.map +0 -1
package/dist/file/file.js DELETED
@@ -1,559 +0,0 @@
1
- import { __awaiter } from "tslib";
2
- import { normalizePath, TFile } from "obsidian";
3
- // ============================================================================
4
- // File Path Operations
5
- // ============================================================================
6
- /**
7
- * Retrieves a TFile object from the vault by its path.
8
- * Handles path normalization using Obsidian's normalizePath utility.
9
- *
10
- * **Important**: Obsidian file paths ALWAYS include the `.md` extension.
11
- * The TFile.path property returns paths like "folder/file.md", not "folder/file".
12
- *
13
- * @param app - The Obsidian App instance
14
- * @param filePath - Path to the file (will be normalized, should include .md extension)
15
- * @returns TFile if found, null otherwise
16
- *
17
- * @example
18
- * ```ts
19
- * // Correct: Include .md extension
20
- * const file = getFileByPath(app, "folder/note.md");
21
- *
22
- * // For wikilinks without extension, add .md
23
- * const linkPath = "MyNote";
24
- * const file = getFileByPath(app, `${linkPath}.md`);
25
- * ```
26
- */
27
- export function getFileByPath(app, filePath) {
28
- // Normalize the path using Obsidian's utility
29
- // This handles slashes, spaces, and platform-specific path issues
30
- const normalizedPath = normalizePath(filePath);
31
- // Use Vault's direct lookup method (most efficient)
32
- // Prefer getFileByPath if available, otherwise use getAbstractFileByPath
33
- if (typeof app.vault.getFileByPath === "function") {
34
- return app.vault.getFileByPath(normalizedPath);
35
- }
36
- const abstractFile = app.vault.getAbstractFileByPath(normalizedPath);
37
- return abstractFile instanceof TFile ? abstractFile : null;
38
- }
39
- /**
40
- * Ensures a file path includes the .md extension.
41
- * Use this when working with wikilinks or user input that may omit extensions.
42
- *
43
- * @param path - File path that may or may not include .md extension
44
- * @returns Path guaranteed to end with .md
45
- *
46
- * @example
47
- * ```ts
48
- * ensureMarkdownExtension("MyNote") // "MyNote.md"
49
- * ensureMarkdownExtension("MyNote.md") // "MyNote.md"
50
- * ensureMarkdownExtension("folder/note") // "folder/note.md"
51
- * ```
52
- */
53
- export function ensureMarkdownExtension(path) {
54
- return path.endsWith(".md") ? path : `${path}.md`;
55
- }
56
- /**
57
- * Removes the .md extension from a file path if present.
58
- * Useful for displaying file names or creating wikilinks.
59
- *
60
- * @param path - File path that may include .md extension
61
- * @returns Path without .md extension
62
- *
63
- * @example
64
- * ```ts
65
- * removeMarkdownExtension("folder/note.md") // "folder/note"
66
- * removeMarkdownExtension("folder/note") // "folder/note"
67
- * ```
68
- */
69
- export function removeMarkdownExtension(path) {
70
- return path.endsWith(".md") ? path.slice(0, -3) : path;
71
- }
72
- // ============================================================================
73
- // File Name Extraction
74
- // ============================================================================
75
- /**
76
- * Extracts the display name from a file path or wiki link.
77
- *
78
- * Handles various formats:
79
- * - `[[path/to/file|Alias]]` -> returns "Alias"
80
- * - `[[path/to/file]]` -> returns "file"
81
- * - `path/to/file.md` -> returns "file"
82
- * - `file.md` -> returns "file"
83
- *
84
- * @param input - File path or wiki link string
85
- * @returns The display name to show in the UI
86
- */
87
- export function extractDisplayName(input) {
88
- if (!input)
89
- return "";
90
- // Remove any surrounding whitespace
91
- const trimmed = input.trim();
92
- // Check if it's a wiki link format [[path|alias]] or [[path]]
93
- const wikiLinkMatch = trimmed.match(/^\[\[([^\]]+)\]\]$/);
94
- if (wikiLinkMatch) {
95
- const innerContent = wikiLinkMatch[1];
96
- // Check if there's an alias (pipe character)
97
- const pipeIndex = innerContent.indexOf("|");
98
- if (pipeIndex !== -1) {
99
- // Return the alias (everything after the pipe)
100
- return innerContent.substring(pipeIndex + 1).trim();
101
- }
102
- // No alias, extract filename from path
103
- const path = innerContent.trim();
104
- const lastSlashIndex = path.lastIndexOf("/");
105
- const filename = lastSlashIndex !== -1 ? path.substring(lastSlashIndex + 1) : path;
106
- return filename.replace(/\.md$/i, "");
107
- }
108
- // Not a wiki link, treat as regular path
109
- const lastSlashIndex = trimmed.lastIndexOf("/");
110
- const filename = lastSlashIndex !== -1 ? trimmed.substring(lastSlashIndex + 1) : trimmed;
111
- return filename.replace(/\.md$/i, "");
112
- }
113
- /**
114
- * Extracts the actual file path from a wiki link or returns the path as-is.
115
- *
116
- * Handles:
117
- * - `[[path/to/file|Alias]]` -> returns "path/to/file.md"
118
- * - `[[path/to/file]]` -> returns "path/to/file.md"
119
- * - `path/to/file.md` -> returns "path/to/file.md"
120
- *
121
- * @param input - File path or wiki link string
122
- * @returns The actual file path (with .md extension)
123
- */
124
- export function extractFilePath(input) {
125
- if (!input)
126
- return "";
127
- const trimmed = input.trim();
128
- // Check if it's a wiki link format [[path|alias]] or [[path]]
129
- const wikiLinkMatch = trimmed.match(/^\[\[([^\]]+)\]\]$/);
130
- if (wikiLinkMatch) {
131
- const innerContent = wikiLinkMatch[1];
132
- // Check if there's an alias (pipe character)
133
- const pipeIndex = innerContent.indexOf("|");
134
- const path = pipeIndex !== -1 ? innerContent.substring(0, pipeIndex).trim() : innerContent.trim();
135
- // Ensure .md extension
136
- return path.endsWith(".md") ? path : `${path}.md`;
137
- }
138
- // Not a wiki link, ensure .md extension
139
- return trimmed.endsWith(".md") ? trimmed : `${trimmed}.md`;
140
- }
141
- /**
142
- * Creates a comprehensive file context object containing all relevant file information.
143
- * Handles path normalization, file lookup, and metadata caching.
144
- */
145
- export function getFileContext(app, path) {
146
- const pathWithExt = ensureMarkdownExtension(path);
147
- const baseName = removeMarkdownExtension(path);
148
- const file = getFileByPath(app, pathWithExt);
149
- const cache = file ? app.metadataCache.getFileCache(file) : null;
150
- const frontmatter = cache === null || cache === void 0 ? void 0 : cache.frontmatter;
151
- return {
152
- path,
153
- pathWithExt,
154
- baseName,
155
- file,
156
- frontmatter,
157
- cache,
158
- };
159
- }
160
- /**
161
- * Helper function to work with file context that automatically handles file not found cases.
162
- * Returns null if the file doesn't exist, otherwise executes the callback with the context.
163
- */
164
- export function withFileContext(app, path, callback) {
165
- return __awaiter(this, void 0, void 0, function* () {
166
- const context = getFileContext(app, path);
167
- if (!context.file) {
168
- console.warn(`File not found: ${context.pathWithExt}`);
169
- return null;
170
- }
171
- return yield callback(context);
172
- });
173
- }
174
- // ============================================================================
175
- // File Path Generation
176
- // ============================================================================
177
- /**
178
- * Generates a unique file path by appending a counter if the file already exists.
179
- * Automatically adds .md extension if not present.
180
- *
181
- * @param app - The Obsidian App instance
182
- * @param folder - Folder path (empty string for root, no trailing slash needed)
183
- * @param baseName - Base file name without extension
184
- * @returns Unique file path that doesn't exist in the vault
185
- *
186
- * @example
187
- * ```ts
188
- * // If "MyNote.md" exists, returns "MyNote 1.md"
189
- * const path = getUniqueFilePath(app, "", "MyNote");
190
- *
191
- * // With folder: "Projects/Task.md" -> "Projects/Task 1.md"
192
- * const path = getUniqueFilePath(app, "Projects", "Task");
193
- *
194
- * // Root folder handling
195
- * const path = getUniqueFilePath(app, "/", "Note"); // -> "Note.md"
196
- * ```
197
- */
198
- export function getUniqueFilePath(app, folder, baseName) {
199
- const normalizedFolder = folder && folder !== "/" ? folder : "";
200
- const folderPath = normalizedFolder ? `${normalizedFolder}/` : "";
201
- let fileName = `${baseName}.md`;
202
- let fullPath = `${folderPath}${fileName}`;
203
- let counter = 1;
204
- while (app.vault.getAbstractFileByPath(fullPath)) {
205
- fileName = `${baseName} ${counter}.md`;
206
- fullPath = `${folderPath}${fileName}`;
207
- counter++;
208
- }
209
- return fullPath;
210
- }
211
- /**
212
- * Generates a unique file path by appending a counter if the file already exists.
213
- * Accepts a complete file path and iterates with a counter suffix.
214
- *
215
- * @param app - The Obsidian App instance
216
- * @param filePath - Complete file path including folder, name, and extension
217
- * @returns Unique file path that doesn't exist in the vault
218
- *
219
- * @example
220
- * ```ts
221
- * // If "folder/note.md" exists, returns "folder/note 1.md"
222
- * const path = getUniqueFilePathFromFull(app, "folder/note.md");
223
- *
224
- * // Works with any extension
225
- * const path = getUniqueFilePathFromFull(app, "assets/image.png");
226
- * // -> "assets/image 1.png" if image.png exists
227
- * ```
228
- */
229
- export const getUniqueFilePathFromFull = (app, filePath) => {
230
- // If file doesn't exist, return as-is
231
- if (!app.vault.getAbstractFileByPath(filePath)) {
232
- return filePath;
233
- }
234
- // Extract folder, base name, and extension
235
- const lastSlashIndex = filePath.lastIndexOf("/");
236
- const folderPath = lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex + 1) : "";
237
- const fileName = lastSlashIndex !== -1 ? filePath.substring(lastSlashIndex + 1) : filePath;
238
- const lastDotIndex = fileName.lastIndexOf(".");
239
- const baseName = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
240
- const extension = lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : "";
241
- let counter = 1;
242
- let uniquePath = `${folderPath}${baseName} ${counter}${extension}`;
243
- while (app.vault.getAbstractFileByPath(uniquePath)) {
244
- counter++;
245
- uniquePath = `${folderPath}${baseName} ${counter}${extension}`;
246
- }
247
- return uniquePath;
248
- };
249
- /**
250
- * Generates a unique file path by appending a counter if the file already exists.
251
- * Supports custom file extensions.
252
- *
253
- * @param app - The Obsidian App instance
254
- * @param folder - Folder path (empty string for root)
255
- * @param baseName - Base file name without extension
256
- * @param extension - File extension (defaults to "md")
257
- * @returns Unique file path that doesn't exist in the vault
258
- */
259
- export const generateUniqueFilePath = (app, folder, baseName, extension = "md") => {
260
- const folderPath = folder ? `${folder}/` : "";
261
- const fullPath = `${folderPath}${baseName}.${extension}`;
262
- return getUniqueFilePathFromFull(app, fullPath);
263
- };
264
- // ============================================================================
265
- // Folder Note Operations
266
- // ============================================================================
267
- /**
268
- * Checks if a file is a folder note.
269
- * A folder note is a file whose name matches its parent folder name.
270
- *
271
- * @param filePath - Path to the file (e.g., "tasks/tasks.md")
272
- * @returns true if the file is a folder note, false otherwise
273
- *
274
- * @example
275
- * ```ts
276
- * isFolderNote("tasks/tasks.md") // true
277
- * isFolderNote("tasks/subtask.md") // false
278
- * isFolderNote("note.md") // false (no parent folder)
279
- * isFolderNote("projects/docs/docs.md") // true
280
- * ```
281
- */
282
- export function isFolderNote(filePath) {
283
- if (!filePath)
284
- return false;
285
- // Remove .md extension for comparison
286
- const pathWithoutExt = removeMarkdownExtension(filePath);
287
- // Split path into segments
288
- const segments = pathWithoutExt.split("/");
289
- // Need at least 2 segments (folder/file)
290
- if (segments.length < 2)
291
- return false;
292
- // Get the file name (last segment) and parent folder name (second to last)
293
- const fileName = segments[segments.length - 1];
294
- const parentFolderName = segments[segments.length - 2];
295
- // File is a folder note if its name matches the parent folder
296
- return fileName === parentFolderName;
297
- }
298
- /**
299
- * Gets the folder path for a file.
300
- *
301
- * @param filePath - Path to the file (e.g., "tasks/subtask.md")
302
- * @returns Folder path without trailing slash, or empty string if file is in root
303
- *
304
- * @example
305
- * ```ts
306
- * getFolderPath("tasks/subtask.md") // "tasks"
307
- * getFolderPath("projects/docs/notes.md") // "projects/docs"
308
- * getFolderPath("note.md") // ""
309
- * ```
310
- */
311
- export function getFolderPath(filePath) {
312
- if (!filePath)
313
- return "";
314
- const lastSlashIndex = filePath.lastIndexOf("/");
315
- if (lastSlashIndex === -1)
316
- return "";
317
- return filePath.substring(0, lastSlashIndex);
318
- }
319
- /**
320
- * Gets all markdown files in a specific folder (non-recursive).
321
- *
322
- * @param app - The Obsidian App instance
323
- * @param folderPath - Path to the folder (e.g., "tasks")
324
- * @returns Array of TFile objects in the folder
325
- *
326
- * @example
327
- * ```ts
328
- * const files = getFilesInFolder(app, "tasks");
329
- * // Returns [task1.md, task2.md, tasks.md] but not tasks/subtasks/file.md
330
- * ```
331
- */
332
- export function getFilesInFolder(app, folderPath) {
333
- const allFiles = app.vault.getMarkdownFiles();
334
- return allFiles.filter((file) => {
335
- const fileFolder = getFolderPath(file.path);
336
- return fileFolder === folderPath;
337
- });
338
- }
339
- /**
340
- * Gets all markdown files in a folder and its subfolders recursively.
341
- *
342
- * @param app - The Obsidian App instance
343
- * @param folderPath - Path to the folder (e.g., "tasks")
344
- * @returns Array of TFile objects in the folder tree
345
- *
346
- * @example
347
- * ```ts
348
- * const files = getAllFilesInFolderTree(app, "tasks");
349
- * // Returns all .md files in tasks/ and all its subdirectories
350
- * ```
351
- */
352
- export function getAllFilesInFolderTree(app, folderPath) {
353
- const allFiles = app.vault.getMarkdownFiles();
354
- const normalizedFolder = folderPath ? `${folderPath}/` : "";
355
- return allFiles.filter((file) => {
356
- if (!normalizedFolder)
357
- return true; // Root folder includes all files
358
- return file.path.startsWith(normalizedFolder);
359
- });
360
- }
361
- /**
362
- * Gets the parent file path based on folder structure.
363
- * For a file in a folder, the parent is the folder note if it exists.
364
- *
365
- * @param app - The Obsidian App instance
366
- * @param filePath - Path to the file
367
- * @returns Path to parent file, or null if no parent exists
368
- *
369
- * @example
370
- * ```ts
371
- * // If tasks/tasks.md exists
372
- * getParentByFolder(app, "tasks/subtask.md") // "tasks/tasks.md"
373
- *
374
- * // If parent folder note doesn't exist
375
- * getParentByFolder(app, "tasks/subtask.md") // null
376
- *
377
- * // Root level file
378
- * getParentByFolder(app, "note.md") // null
379
- * ```
380
- */
381
- export function getParentByFolder(app, filePath) {
382
- const folderPath = getFolderPath(filePath);
383
- if (!folderPath)
384
- return null; // File is at root level
385
- // Check if folder note exists
386
- const folderSegments = folderPath.split("/");
387
- const parentFolderName = folderSegments[folderSegments.length - 1];
388
- const potentialParentPath = `${folderPath}/${parentFolderName}.md`;
389
- const parentFile = getFileByPath(app, potentialParentPath);
390
- return parentFile ? potentialParentPath : null;
391
- }
392
- /**
393
- * Gets all child file paths based on folder structure.
394
- * Works for both folder notes and regular files.
395
- *
396
- * For folder notes (e.g., "tasks/tasks.md"):
397
- * - Returns all files directly in the folder (excluding the folder note)
398
- * - Includes subfolder notes one level down
399
- *
400
- * For regular files (e.g., "tasks/task1.md"):
401
- * - Returns the folder note from matching subfolder if it exists (e.g., "tasks/task1/task1.md")
402
- *
403
- * @param app - The Obsidian App instance
404
- * @param filePath - Path to the file
405
- * @returns Array of child file paths
406
- *
407
- * @example
408
- * ```ts
409
- * // For tasks/tasks.md (folder note)
410
- * getChildrenByFolder(app, "tasks/tasks.md")
411
- * // Returns ["tasks/task1.md", "tasks/task2.md", "tasks/subtasks/subtasks.md"]
412
- *
413
- * // For tasks/task1.md (regular file with matching subfolder)
414
- * getChildrenByFolder(app, "tasks/task1.md")
415
- * // Returns ["tasks/task1/task1.md"] if it exists
416
- * ```
417
- */
418
- export function getChildrenByFolder(app, filePath) {
419
- const allFiles = app.vault.getMarkdownFiles();
420
- // Case 1: Folder note - get all files in the folder
421
- if (isFolderNote(filePath)) {
422
- const folderPath = getFolderPath(filePath);
423
- const children = [];
424
- allFiles.forEach((file) => {
425
- // Skip the folder note itself
426
- if (file.path === filePath)
427
- return;
428
- const fileFolder = getFolderPath(file.path);
429
- // Direct child: file is in the same folder as the folder note
430
- if (fileFolder === folderPath) {
431
- children.push(file.path);
432
- return;
433
- }
434
- // Subfolder note: file is a folder note one level deeper
435
- // e.g., for "tasks/tasks.md", include "tasks/subtasks/subtasks.md"
436
- if (fileFolder.startsWith(`${folderPath}/`)) {
437
- // Check if it's exactly one level deeper and is a folder note
438
- const relativePath = fileFolder.substring(folderPath.length + 1);
439
- const isOneLevel = !relativePath.includes("/");
440
- if (isOneLevel && isFolderNote(file.path)) {
441
- children.push(file.path);
442
- }
443
- }
444
- });
445
- return children;
446
- }
447
- // Case 2: Regular file - check for matching subfolder with folder note
448
- const pathWithoutExt = removeMarkdownExtension(filePath);
449
- const fileName = pathWithoutExt.split("/").pop() || "";
450
- const potentialChildFolder = `${pathWithoutExt}`;
451
- const potentialChildPath = `${potentialChildFolder}/${fileName}.md`;
452
- // Check if the child folder note exists
453
- const childFile = getFileByPath(app, potentialChildPath);
454
- return childFile ? [potentialChildPath] : [];
455
- }
456
- /**
457
- * Finds all root nodes in a folder tree.
458
- * Root nodes are files at the top level of the folder (directly in the folder, not in subfolders).
459
- *
460
- * @param app - The Obsidian App instance
461
- * @param folderPath - Path to the folder
462
- * @returns Array of root file paths
463
- *
464
- * @example
465
- * ```ts
466
- * // For folder structure:
467
- * // tasks/
468
- * // tasks.md (folder note)
469
- * // task1.md
470
- * // subtasks/
471
- * // subtasks.md
472
- * // subtask1.md
473
- *
474
- * findRootNodesInFolder(app, "tasks")
475
- * // Returns ["tasks/tasks.md", "tasks/task1.md"]
476
- * // Excludes subtasks/subtasks.md and subtasks/subtask1.md (they're in subfolder)
477
- * ```
478
- */
479
- export function findRootNodesInFolder(app, folderPath) {
480
- return getFilesInFolder(app, folderPath).map((file) => file.path);
481
- }
482
- /**
483
- * Sanitizes a string for use as a filename.
484
- * Defaults to kebab-case style for backwards compatibility.
485
- *
486
- * @param input - String to sanitize
487
- * @param options - Sanitization options
488
- * @returns Sanitized filename string
489
- *
490
- * @example
491
- * // Default kebab-case style (backwards compatible)
492
- * sanitizeForFilename("My File Name") // "my-file-name"
493
- *
494
- * // Preserve spaces and case
495
- * sanitizeForFilename("My File Name", { style: "preserve" }) // "My File Name"
496
- */
497
- export const sanitizeForFilename = (input, options = {}) => {
498
- const { style = "kebab" } = options;
499
- if (style === "preserve") {
500
- return sanitizeFilenamePreserveSpaces(input);
501
- }
502
- // Default: kebab-case style (legacy behavior)
503
- return sanitizeFilenameKebabCase(input);
504
- };
505
- /**
506
- * Sanitizes filename using kebab-case style.
507
- * - Removes invalid characters
508
- * - Converts to lowercase
509
- * - Replaces spaces with hyphens
510
- *
511
- * Best for: CLI tools, URLs, slugs, technical files
512
- *
513
- * @example
514
- * sanitizeFilenameKebabCase("My File Name") // "my-file-name"
515
- * sanitizeFilenameKebabCase("Travel Around The World") // "travel-around-the-world"
516
- */
517
- export const sanitizeFilenameKebabCase = (input) => {
518
- return (input
519
- // Remove invalid filename characters
520
- .replace(/[<>:"/\\|?*]/g, "")
521
- // Replace spaces with hyphens
522
- .replace(/\s+/g, "-")
523
- // Replace multiple hyphens with single
524
- .replace(/-+/g, "-")
525
- // Remove leading/trailing hyphens
526
- .replace(/^-|-$/g, "")
527
- // Convert to lowercase
528
- .toLowerCase());
529
- };
530
- /**
531
- * Sanitizes filename while preserving spaces and case.
532
- * - Removes invalid characters only
533
- * - Preserves spaces and original casing
534
- * - Removes trailing dots (Windows compatibility)
535
- *
536
- * Best for: Note titles, human-readable filenames, Obsidian notes
537
- *
538
- * @example
539
- * sanitizeFilenamePreserveSpaces("My File Name") // "My File Name"
540
- * sanitizeFilenamePreserveSpaces("Travel Around The World") // "Travel Around The World"
541
- * sanitizeFilenamePreserveSpaces("File<Invalid>Chars") // "FileInvalidChars"
542
- */
543
- export const sanitizeFilenamePreserveSpaces = (input) => {
544
- return (input
545
- // Remove invalid filename characters (cross-platform compatibility)
546
- .replace(/[<>:"/\\|?*]/g, "")
547
- // Remove trailing dots (invalid on Windows)
548
- .replace(/\.+$/g, "")
549
- // Remove leading/trailing whitespace
550
- .trim());
551
- };
552
- export const getFilenameFromPath = (filePath) => {
553
- return filePath.split("/").pop() || "Unknown";
554
- };
555
- export const isFileInConfiguredDirectory = (filePath, directory) => {
556
- const normalizedDir = directory.endsWith("/") ? directory.slice(0, -1) : directory;
557
- return filePath.startsWith(`${normalizedDir}/`) || filePath === normalizedDir;
558
- };
559
- //# sourceMappingURL=file.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"file.js","sourceRoot":"","sources":["../../src/file/file.ts"],"names":[],"mappings":"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","sourcesContent":["import type { App, CachedMetadata } from \"obsidian\";\nimport { normalizePath, TFile } from \"obsidian\";\n\n// ============================================================================\n// File Path Operations\n// ============================================================================\n\n/**\n * Retrieves a TFile object from the vault by its path.\n * Handles path normalization using Obsidian's normalizePath utility.\n *\n * **Important**: Obsidian file paths ALWAYS include the `.md` extension.\n * The TFile.path property returns paths like \"folder/file.md\", not \"folder/file\".\n *\n * @param app - The Obsidian App instance\n * @param filePath - Path to the file (will be normalized, should include .md extension)\n * @returns TFile if found, null otherwise\n *\n * @example\n * ```ts\n * // Correct: Include .md extension\n * const file = getFileByPath(app, \"folder/note.md\");\n *\n * // For wikilinks without extension, add .md\n * const linkPath = \"MyNote\";\n * const file = getFileByPath(app, `${linkPath}.md`);\n * ```\n */\nexport function getFileByPath(app: App, filePath: string): TFile | null {\n\t// Normalize the path using Obsidian's utility\n\t// This handles slashes, spaces, and platform-specific path issues\n\tconst normalizedPath = normalizePath(filePath);\n\n\t// Use Vault's direct lookup method (most efficient)\n\t// Prefer getFileByPath if available, otherwise use getAbstractFileByPath\n\tif (typeof app.vault.getFileByPath === \"function\") {\n\t\treturn app.vault.getFileByPath(normalizedPath);\n\t}\n\n\tconst abstractFile = app.vault.getAbstractFileByPath(normalizedPath);\n\n\treturn abstractFile instanceof TFile ? abstractFile : null;\n}\n\n/**\n * Ensures a file path includes the .md extension.\n * Use this when working with wikilinks or user input that may omit extensions.\n *\n * @param path - File path that may or may not include .md extension\n * @returns Path guaranteed to end with .md\n *\n * @example\n * ```ts\n * ensureMarkdownExtension(\"MyNote\") // \"MyNote.md\"\n * ensureMarkdownExtension(\"MyNote.md\") // \"MyNote.md\"\n * ensureMarkdownExtension(\"folder/note\") // \"folder/note.md\"\n * ```\n */\nexport function ensureMarkdownExtension(path: string): string {\n\treturn path.endsWith(\".md\") ? path : `${path}.md`;\n}\n\n/**\n * Removes the .md extension from a file path if present.\n * Useful for displaying file names or creating wikilinks.\n *\n * @param path - File path that may include .md extension\n * @returns Path without .md extension\n *\n * @example\n * ```ts\n * removeMarkdownExtension(\"folder/note.md\") // \"folder/note\"\n * removeMarkdownExtension(\"folder/note\") // \"folder/note\"\n * ```\n */\nexport function removeMarkdownExtension(path: string): string {\n\treturn path.endsWith(\".md\") ? path.slice(0, -3) : path;\n}\n\n// ============================================================================\n// File Name Extraction\n// ============================================================================\n\n/**\n * Extracts the display name from a file path or wiki link.\n *\n * Handles various formats:\n * - `[[path/to/file|Alias]]` -> returns \"Alias\"\n * - `[[path/to/file]]` -> returns \"file\"\n * - `path/to/file.md` -> returns \"file\"\n * - `file.md` -> returns \"file\"\n *\n * @param input - File path or wiki link string\n * @returns The display name to show in the UI\n */\nexport function extractDisplayName(input: string): string {\n\tif (!input) return \"\";\n\n\t// Remove any surrounding whitespace\n\tconst trimmed = input.trim();\n\n\t// Check if it's a wiki link format [[path|alias]] or [[path]]\n\tconst wikiLinkMatch = trimmed.match(/^\\[\\[([^\\]]+)\\]\\]$/);\n\n\tif (wikiLinkMatch) {\n\t\tconst innerContent = wikiLinkMatch[1];\n\n\t\t// Check if there's an alias (pipe character)\n\t\tconst pipeIndex = innerContent.indexOf(\"|\");\n\n\t\tif (pipeIndex !== -1) {\n\t\t\t// Return the alias (everything after the pipe)\n\t\t\treturn innerContent.substring(pipeIndex + 1).trim();\n\t\t}\n\n\t\t// No alias, extract filename from path\n\t\tconst path = innerContent.trim();\n\n\t\tconst lastSlashIndex = path.lastIndexOf(\"/\");\n\n\t\tconst filename = lastSlashIndex !== -1 ? path.substring(lastSlashIndex + 1) : path;\n\n\t\treturn filename.replace(/\\.md$/i, \"\");\n\t}\n\n\t// Not a wiki link, treat as regular path\n\tconst lastSlashIndex = trimmed.lastIndexOf(\"/\");\n\n\tconst filename = lastSlashIndex !== -1 ? trimmed.substring(lastSlashIndex + 1) : trimmed;\n\n\treturn filename.replace(/\\.md$/i, \"\");\n}\n\n/**\n * Extracts the actual file path from a wiki link or returns the path as-is.\n *\n * Handles:\n * - `[[path/to/file|Alias]]` -> returns \"path/to/file.md\"\n * - `[[path/to/file]]` -> returns \"path/to/file.md\"\n * - `path/to/file.md` -> returns \"path/to/file.md\"\n *\n * @param input - File path or wiki link string\n * @returns The actual file path (with .md extension)\n */\nexport function extractFilePath(input: string): string {\n\tif (!input) return \"\";\n\n\tconst trimmed = input.trim();\n\n\t// Check if it's a wiki link format [[path|alias]] or [[path]]\n\tconst wikiLinkMatch = trimmed.match(/^\\[\\[([^\\]]+)\\]\\]$/);\n\n\tif (wikiLinkMatch) {\n\t\tconst innerContent = wikiLinkMatch[1];\n\n\t\t// Check if there's an alias (pipe character)\n\t\tconst pipeIndex = innerContent.indexOf(\"|\");\n\n\t\tconst path =\n\t\t\tpipeIndex !== -1 ? innerContent.substring(0, pipeIndex).trim() : innerContent.trim();\n\n\t\t// Ensure .md extension\n\t\treturn path.endsWith(\".md\") ? path : `${path}.md`;\n\t}\n\n\t// Not a wiki link, ensure .md extension\n\treturn trimmed.endsWith(\".md\") ? trimmed : `${trimmed}.md`;\n}\n\n// ============================================================================\n// File Context\n// ============================================================================\n\nexport interface FileContext {\n\tpath: string;\n\tpathWithExt: string;\n\tbaseName: string;\n\tfile: TFile | null;\n\tfrontmatter: Record<string, any> | undefined;\n\tcache: CachedMetadata | null;\n}\n\n/**\n * Creates a comprehensive file context object containing all relevant file information.\n * Handles path normalization, file lookup, and metadata caching.\n */\nexport function getFileContext(app: App, path: string): FileContext {\n\tconst pathWithExt = ensureMarkdownExtension(path);\n\n\tconst baseName = removeMarkdownExtension(path);\n\n\tconst file = getFileByPath(app, pathWithExt);\n\n\tconst cache = file ? app.metadataCache.getFileCache(file) : null;\n\n\tconst frontmatter = cache?.frontmatter;\n\n\treturn {\n\t\tpath,\n\t\tpathWithExt,\n\t\tbaseName,\n\t\tfile,\n\t\tfrontmatter,\n\t\tcache,\n\t};\n}\n\n/**\n * Helper function to work with file context that automatically handles file not found cases.\n * Returns null if the file doesn't exist, otherwise executes the callback with the context.\n */\nexport async function withFileContext<T>(\n\tapp: App,\n\tpath: string,\n\tcallback: (context: FileContext) => Promise<T> | T\n): Promise<T | null> {\n\tconst context = getFileContext(app, path);\n\n\tif (!context.file) {\n\t\tconsole.warn(`File not found: ${context.pathWithExt}`);\n\t\treturn null;\n\t}\n\n\treturn await callback(context);\n}\n\n// ============================================================================\n// File Path Generation\n// ============================================================================\n\n/**\n * Generates a unique file path by appending a counter if the file already exists.\n * Automatically adds .md extension if not present.\n *\n * @param app - The Obsidian App instance\n * @param folder - Folder path (empty string for root, no trailing slash needed)\n * @param baseName - Base file name without extension\n * @returns Unique file path that doesn't exist in the vault\n *\n * @example\n * ```ts\n * // If \"MyNote.md\" exists, returns \"MyNote 1.md\"\n * const path = getUniqueFilePath(app, \"\", \"MyNote\");\n *\n * // With folder: \"Projects/Task.md\" -> \"Projects/Task 1.md\"\n * const path = getUniqueFilePath(app, \"Projects\", \"Task\");\n *\n * // Root folder handling\n * const path = getUniqueFilePath(app, \"/\", \"Note\"); // -> \"Note.md\"\n * ```\n */\nexport function getUniqueFilePath(app: App, folder: string, baseName: string): string {\n\tconst normalizedFolder = folder && folder !== \"/\" ? folder : \"\";\n\tconst folderPath = normalizedFolder ? `${normalizedFolder}/` : \"\";\n\n\tlet fileName = `${baseName}.md`;\n\tlet fullPath = `${folderPath}${fileName}`;\n\tlet counter = 1;\n\n\twhile (app.vault.getAbstractFileByPath(fullPath)) {\n\t\tfileName = `${baseName} ${counter}.md`;\n\t\tfullPath = `${folderPath}${fileName}`;\n\t\tcounter++;\n\t}\n\n\treturn fullPath;\n}\n\n/**\n * Generates a unique file path by appending a counter if the file already exists.\n * Accepts a complete file path and iterates with a counter suffix.\n *\n * @param app - The Obsidian App instance\n * @param filePath - Complete file path including folder, name, and extension\n * @returns Unique file path that doesn't exist in the vault\n *\n * @example\n * ```ts\n * // If \"folder/note.md\" exists, returns \"folder/note 1.md\"\n * const path = getUniqueFilePathFromFull(app, \"folder/note.md\");\n *\n * // Works with any extension\n * const path = getUniqueFilePathFromFull(app, \"assets/image.png\");\n * // -> \"assets/image 1.png\" if image.png exists\n * ```\n */\nexport const getUniqueFilePathFromFull = (app: App, filePath: string): string => {\n\t// If file doesn't exist, return as-is\n\tif (!app.vault.getAbstractFileByPath(filePath)) {\n\t\treturn filePath;\n\t}\n\n\t// Extract folder, base name, and extension\n\tconst lastSlashIndex = filePath.lastIndexOf(\"/\");\n\tconst folderPath = lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex + 1) : \"\";\n\tconst fileName = lastSlashIndex !== -1 ? filePath.substring(lastSlashIndex + 1) : filePath;\n\n\tconst lastDotIndex = fileName.lastIndexOf(\".\");\n\tconst baseName = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;\n\tconst extension = lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : \"\";\n\n\tlet counter = 1;\n\tlet uniquePath = `${folderPath}${baseName} ${counter}${extension}`;\n\n\twhile (app.vault.getAbstractFileByPath(uniquePath)) {\n\t\tcounter++;\n\t\tuniquePath = `${folderPath}${baseName} ${counter}${extension}`;\n\t}\n\n\treturn uniquePath;\n};\n\n/**\n * Generates a unique file path by appending a counter if the file already exists.\n * Supports custom file extensions.\n *\n * @param app - The Obsidian App instance\n * @param folder - Folder path (empty string for root)\n * @param baseName - Base file name without extension\n * @param extension - File extension (defaults to \"md\")\n * @returns Unique file path that doesn't exist in the vault\n */\nexport const generateUniqueFilePath = (\n\tapp: App,\n\tfolder: string,\n\tbaseName: string,\n\textension: string = \"md\"\n): string => {\n\tconst folderPath = folder ? `${folder}/` : \"\";\n\tconst fullPath = `${folderPath}${baseName}.${extension}`;\n\treturn getUniqueFilePathFromFull(app, fullPath);\n};\n\n// ============================================================================\n// Folder Note Operations\n// ============================================================================\n\n/**\n * Checks if a file is a folder note.\n * A folder note is a file whose name matches its parent folder name.\n *\n * @param filePath - Path to the file (e.g., \"tasks/tasks.md\")\n * @returns true if the file is a folder note, false otherwise\n *\n * @example\n * ```ts\n * isFolderNote(\"tasks/tasks.md\") // true\n * isFolderNote(\"tasks/subtask.md\") // false\n * isFolderNote(\"note.md\") // false (no parent folder)\n * isFolderNote(\"projects/docs/docs.md\") // true\n * ```\n */\nexport function isFolderNote(filePath: string): boolean {\n\tif (!filePath) return false;\n\n\t// Remove .md extension for comparison\n\tconst pathWithoutExt = removeMarkdownExtension(filePath);\n\n\t// Split path into segments\n\tconst segments = pathWithoutExt.split(\"/\");\n\n\t// Need at least 2 segments (folder/file)\n\tif (segments.length < 2) return false;\n\n\t// Get the file name (last segment) and parent folder name (second to last)\n\tconst fileName = segments[segments.length - 1];\n\tconst parentFolderName = segments[segments.length - 2];\n\n\t// File is a folder note if its name matches the parent folder\n\treturn fileName === parentFolderName;\n}\n\n/**\n * Gets the folder path for a file.\n *\n * @param filePath - Path to the file (e.g., \"tasks/subtask.md\")\n * @returns Folder path without trailing slash, or empty string if file is in root\n *\n * @example\n * ```ts\n * getFolderPath(\"tasks/subtask.md\") // \"tasks\"\n * getFolderPath(\"projects/docs/notes.md\") // \"projects/docs\"\n * getFolderPath(\"note.md\") // \"\"\n * ```\n */\nexport function getFolderPath(filePath: string): string {\n\tif (!filePath) return \"\";\n\n\tconst lastSlashIndex = filePath.lastIndexOf(\"/\");\n\n\tif (lastSlashIndex === -1) return \"\";\n\n\treturn filePath.substring(0, lastSlashIndex);\n}\n\n/**\n * Gets all markdown files in a specific folder (non-recursive).\n *\n * @param app - The Obsidian App instance\n * @param folderPath - Path to the folder (e.g., \"tasks\")\n * @returns Array of TFile objects in the folder\n *\n * @example\n * ```ts\n * const files = getFilesInFolder(app, \"tasks\");\n * // Returns [task1.md, task2.md, tasks.md] but not tasks/subtasks/file.md\n * ```\n */\nexport function getFilesInFolder(app: App, folderPath: string): TFile[] {\n\tconst allFiles = app.vault.getMarkdownFiles();\n\n\treturn allFiles.filter((file) => {\n\t\tconst fileFolder = getFolderPath(file.path);\n\n\t\treturn fileFolder === folderPath;\n\t});\n}\n\n/**\n * Gets all markdown files in a folder and its subfolders recursively.\n *\n * @param app - The Obsidian App instance\n * @param folderPath - Path to the folder (e.g., \"tasks\")\n * @returns Array of TFile objects in the folder tree\n *\n * @example\n * ```ts\n * const files = getAllFilesInFolderTree(app, \"tasks\");\n * // Returns all .md files in tasks/ and all its subdirectories\n * ```\n */\nexport function getAllFilesInFolderTree(app: App, folderPath: string): TFile[] {\n\tconst allFiles = app.vault.getMarkdownFiles();\n\n\tconst normalizedFolder = folderPath ? `${folderPath}/` : \"\";\n\n\treturn allFiles.filter((file) => {\n\t\tif (!normalizedFolder) return true; // Root folder includes all files\n\n\t\treturn file.path.startsWith(normalizedFolder);\n\t});\n}\n\n/**\n * Gets the parent file path based on folder structure.\n * For a file in a folder, the parent is the folder note if it exists.\n *\n * @param app - The Obsidian App instance\n * @param filePath - Path to the file\n * @returns Path to parent file, or null if no parent exists\n *\n * @example\n * ```ts\n * // If tasks/tasks.md exists\n * getParentByFolder(app, \"tasks/subtask.md\") // \"tasks/tasks.md\"\n *\n * // If parent folder note doesn't exist\n * getParentByFolder(app, \"tasks/subtask.md\") // null\n *\n * // Root level file\n * getParentByFolder(app, \"note.md\") // null\n * ```\n */\nexport function getParentByFolder(app: App, filePath: string): string | null {\n\tconst folderPath = getFolderPath(filePath);\n\n\tif (!folderPath) return null; // File is at root level\n\n\t// Check if folder note exists\n\tconst folderSegments = folderPath.split(\"/\");\n\n\tconst parentFolderName = folderSegments[folderSegments.length - 1];\n\n\tconst potentialParentPath = `${folderPath}/${parentFolderName}.md`;\n\n\tconst parentFile = getFileByPath(app, potentialParentPath);\n\n\treturn parentFile ? potentialParentPath : null;\n}\n\n/**\n * Gets all child file paths based on folder structure.\n * Works for both folder notes and regular files.\n *\n * For folder notes (e.g., \"tasks/tasks.md\"):\n * - Returns all files directly in the folder (excluding the folder note)\n * - Includes subfolder notes one level down\n *\n * For regular files (e.g., \"tasks/task1.md\"):\n * - Returns the folder note from matching subfolder if it exists (e.g., \"tasks/task1/task1.md\")\n *\n * @param app - The Obsidian App instance\n * @param filePath - Path to the file\n * @returns Array of child file paths\n *\n * @example\n * ```ts\n * // For tasks/tasks.md (folder note)\n * getChildrenByFolder(app, \"tasks/tasks.md\")\n * // Returns [\"tasks/task1.md\", \"tasks/task2.md\", \"tasks/subtasks/subtasks.md\"]\n *\n * // For tasks/task1.md (regular file with matching subfolder)\n * getChildrenByFolder(app, \"tasks/task1.md\")\n * // Returns [\"tasks/task1/task1.md\"] if it exists\n * ```\n */\nexport function getChildrenByFolder(app: App, filePath: string): string[] {\n\tconst allFiles = app.vault.getMarkdownFiles();\n\n\t// Case 1: Folder note - get all files in the folder\n\tif (isFolderNote(filePath)) {\n\t\tconst folderPath = getFolderPath(filePath);\n\n\t\tconst children: string[] = [];\n\n\t\tallFiles.forEach((file) => {\n\t\t\t// Skip the folder note itself\n\t\t\tif (file.path === filePath) return;\n\n\t\t\tconst fileFolder = getFolderPath(file.path);\n\n\t\t\t// Direct child: file is in the same folder as the folder note\n\t\t\tif (fileFolder === folderPath) {\n\t\t\t\tchildren.push(file.path);\n\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Subfolder note: file is a folder note one level deeper\n\t\t\t// e.g., for \"tasks/tasks.md\", include \"tasks/subtasks/subtasks.md\"\n\t\t\tif (fileFolder.startsWith(`${folderPath}/`)) {\n\t\t\t\t// Check if it's exactly one level deeper and is a folder note\n\t\t\t\tconst relativePath = fileFolder.substring(folderPath.length + 1);\n\n\t\t\t\tconst isOneLevel = !relativePath.includes(\"/\");\n\n\t\t\t\tif (isOneLevel && isFolderNote(file.path)) {\n\t\t\t\t\tchildren.push(file.path);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\treturn children;\n\t}\n\n\t// Case 2: Regular file - check for matching subfolder with folder note\n\tconst pathWithoutExt = removeMarkdownExtension(filePath);\n\n\tconst fileName = pathWithoutExt.split(\"/\").pop() || \"\";\n\n\tconst potentialChildFolder = `${pathWithoutExt}`;\n\n\tconst potentialChildPath = `${potentialChildFolder}/${fileName}.md`;\n\n\t// Check if the child folder note exists\n\tconst childFile = getFileByPath(app, potentialChildPath);\n\n\treturn childFile ? [potentialChildPath] : [];\n}\n\n/**\n * Finds all root nodes in a folder tree.\n * Root nodes are files at the top level of the folder (directly in the folder, not in subfolders).\n *\n * @param app - The Obsidian App instance\n * @param folderPath - Path to the folder\n * @returns Array of root file paths\n *\n * @example\n * ```ts\n * // For folder structure:\n * // tasks/\n * // tasks.md (folder note)\n * // task1.md\n * // subtasks/\n * // subtasks.md\n * // subtask1.md\n *\n * findRootNodesInFolder(app, \"tasks\")\n * // Returns [\"tasks/tasks.md\", \"tasks/task1.md\"]\n * // Excludes subtasks/subtasks.md and subtasks/subtask1.md (they're in subfolder)\n * ```\n */\nexport function findRootNodesInFolder(app: App, folderPath: string): string[] {\n\treturn getFilesInFolder(app, folderPath).map((file) => file.path);\n}\n\n// ============================================================================\n// Filename Sanitization\n// ============================================================================\n\nexport interface SanitizeFilenameOptions {\n\t/**\n\t * Style of sanitization to apply.\n\t * - \"kebab\": Convert to lowercase, replace spaces with hyphens (default, backwards compatible)\n\t * - \"preserve\": Preserve spaces and case, only remove invalid characters\n\t */\n\tstyle?: \"kebab\" | \"preserve\";\n}\n\n/**\n * Sanitizes a string for use as a filename.\n * Defaults to kebab-case style for backwards compatibility.\n *\n * @param input - String to sanitize\n * @param options - Sanitization options\n * @returns Sanitized filename string\n *\n * @example\n * // Default kebab-case style (backwards compatible)\n * sanitizeForFilename(\"My File Name\") // \"my-file-name\"\n *\n * // Preserve spaces and case\n * sanitizeForFilename(\"My File Name\", { style: \"preserve\" }) // \"My File Name\"\n */\nexport const sanitizeForFilename = (\n\tinput: string,\n\toptions: SanitizeFilenameOptions = {}\n): string => {\n\tconst { style = \"kebab\" } = options;\n\n\tif (style === \"preserve\") {\n\t\treturn sanitizeFilenamePreserveSpaces(input);\n\t}\n\n\t// Default: kebab-case style (legacy behavior)\n\treturn sanitizeFilenameKebabCase(input);\n};\n\n/**\n * Sanitizes filename using kebab-case style.\n * - Removes invalid characters\n * - Converts to lowercase\n * - Replaces spaces with hyphens\n *\n * Best for: CLI tools, URLs, slugs, technical files\n *\n * @example\n * sanitizeFilenameKebabCase(\"My File Name\") // \"my-file-name\"\n * sanitizeFilenameKebabCase(\"Travel Around The World\") // \"travel-around-the-world\"\n */\nexport const sanitizeFilenameKebabCase = (input: string): string => {\n\treturn (\n\t\tinput\n\t\t\t// Remove invalid filename characters\n\t\t\t.replace(/[<>:\"/\\\\|?*]/g, \"\")\n\t\t\t// Replace spaces with hyphens\n\t\t\t.replace(/\\s+/g, \"-\")\n\t\t\t// Replace multiple hyphens with single\n\t\t\t.replace(/-+/g, \"-\")\n\t\t\t// Remove leading/trailing hyphens\n\t\t\t.replace(/^-|-$/g, \"\")\n\t\t\t// Convert to lowercase\n\t\t\t.toLowerCase()\n\t);\n};\n\n/**\n * Sanitizes filename while preserving spaces and case.\n * - Removes invalid characters only\n * - Preserves spaces and original casing\n * - Removes trailing dots (Windows compatibility)\n *\n * Best for: Note titles, human-readable filenames, Obsidian notes\n *\n * @example\n * sanitizeFilenamePreserveSpaces(\"My File Name\") // \"My File Name\"\n * sanitizeFilenamePreserveSpaces(\"Travel Around The World\") // \"Travel Around The World\"\n * sanitizeFilenamePreserveSpaces(\"File<Invalid>Chars\") // \"FileInvalidChars\"\n */\nexport const sanitizeFilenamePreserveSpaces = (input: string): string => {\n\treturn (\n\t\tinput\n\t\t\t// Remove invalid filename characters (cross-platform compatibility)\n\t\t\t.replace(/[<>:\"/\\\\|?*]/g, \"\")\n\t\t\t// Remove trailing dots (invalid on Windows)\n\t\t\t.replace(/\\.+$/g, \"\")\n\t\t\t// Remove leading/trailing whitespace\n\t\t\t.trim()\n\t);\n};\n\nexport const getFilenameFromPath = (filePath: string): string => {\n\treturn filePath.split(\"/\").pop() || \"Unknown\";\n};\n\nexport const isFileInConfiguredDirectory = (filePath: string, directory: string): boolean => {\n\tconst normalizedDir = directory.endsWith(\"/\") ? directory.slice(0, -1) : directory;\n\treturn filePath.startsWith(`${normalizedDir}/`) || filePath === normalizedDir;\n};\n"]}
@@ -1,38 +0,0 @@
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