@real1ty-obsidian-plugins/utils 2.21.0 → 2.22.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.
@@ -1,5 +1,10 @@
1
1
  import type { App } from "obsidian";
2
2
  import { TFile } from "obsidian";
3
+ /**
4
+ * Waits for a file to be accessible and readable by Obsidian's cache.
5
+ * This is necessary because Templater creates files asynchronously.
6
+ */
7
+ export declare function waitForFileReady(app: App, filePath: string, timeoutMs?: number): Promise<TFile | null>;
3
8
  /**
4
9
  * Gets a TFile by path or throws an error if not found.
5
10
  * Useful when you need to ensure a file exists before proceeding.
@@ -1 +1 @@
1
- {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/file/file-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,GAAG,EAAE,MAAM,MAAM,KAAG,KAIxD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC3B,KAAK,GAAG,EACR,MAAM,KAAK,EACX,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,kBACO,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAU,KAAK,GAAG,EAAE,MAAM,KAAK,qCAM5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,KAAK,GAAG,EACR,MAAM,KAAK,EACX,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,kBAO/B,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,8BAA8B,GAAI,aAAa,MAAM,KAAG,MAWpE,CAAC"}
1
+ {"version":3,"file":"file-utils.d.ts","sourceRoot":"","sources":["../../src/file/file-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC;;;GAGG;AACH,wBAAsB,gBAAgB,CACrC,GAAG,EAAE,GAAG,EACR,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAO,GACd,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAgBvB;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,GAAI,KAAK,GAAG,EAAE,MAAM,MAAM,KAAG,KAIxD,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,eAAe,GAC3B,KAAK,GAAG,EACR,MAAM,KAAK,EACX,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,kBACO,CAAC;AAEtD;;;GAGG;AACH,eAAO,MAAM,iBAAiB,GAAU,KAAK,GAAG,EAAE,MAAM,KAAK,qCAM5D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,GAC9B,KAAK,GAAG,EACR,MAAM,KAAK,EACX,UAAU,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,kBAO/B,CAAC;AAEJ;;;GAGG;AACH,eAAO,MAAM,8BAA8B,GAAI,aAAa,MAAM,KAAG,MAWpE,CAAC"}
@@ -1,5 +1,26 @@
1
1
  import { __awaiter } from "tslib";
2
2
  import { TFile } from "obsidian";
3
+ /**
4
+ * Waits for a file to be accessible and readable by Obsidian's cache.
5
+ * This is necessary because Templater creates files asynchronously.
6
+ */
7
+ export function waitForFileReady(app_1, filePath_1) {
8
+ return __awaiter(this, arguments, void 0, function* (app, filePath, timeoutMs = 5000) {
9
+ const started = Date.now();
10
+ while (Date.now() - started < timeoutMs) {
11
+ const file = app.vault.getAbstractFileByPath(filePath);
12
+ if (file instanceof TFile) {
13
+ // Check if file is readable by trying to get its metadata
14
+ const metadata = app.metadataCache.getFileCache(file);
15
+ if (metadata !== null && metadata !== undefined) {
16
+ return file;
17
+ }
18
+ }
19
+ yield new Promise((r) => setTimeout(r, 50));
20
+ }
21
+ return null;
22
+ });
23
+ }
3
24
  /**
4
25
  * Gets a TFile by path or throws an error if not found.
5
26
  * Useful when you need to ensure a file exists before proceeding.
@@ -1 +1 @@
1
- {"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file/file-utils.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEjC;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,GAAQ,EAAE,IAAY,EAAS,EAAE;IAChE,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC,IAAI,CAAC,CAAC;IAChD,IAAI,CAAC,CAAC,CAAC,YAAY,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,CAAC;AACV,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAC9B,GAAQ,EACR,IAAW,EACX,MAA6C,EAC5C,EAAE,kDAAC,OAAA,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA,GAAA,CAAC;AAEtD;;;GAGG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAO,GAAQ,EAAE,IAAW,EAAE,EAAE;IAChE,IAAI,IAAI,GAA4B,EAAE,CAAC;IACvC,MAAM,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;QACvC,IAAI,qBAAQ,EAAE,CAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC,CAAA,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CACjC,GAAQ,EACR,IAAW,EACX,QAAiC,EAChC,EAAE;IACH,OAAA,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;QACjC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACjC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;QACd,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAA;EAAA,CAAC;AAEJ;;;GAGG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,WAAmB,EAAU,EAAE;IAC7E,MAAM,gBAAgB,GAAG,+BAA+B,CAAC;IACzD,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAElD,IAAI,KAAK,EAAE,CAAC;QACX,mCAAmC;QACnC,OAAO,WAAW,CAAC,SAAS,CAAC,KAAK,CAAC,KAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAC9D,CAAC;IAED,qDAAqD;IACrD,OAAO,WAAW,CAAC;AACpB,CAAC,CAAC","sourcesContent":["import type { App } from \"obsidian\";\nimport { TFile } from \"obsidian\";\n\n/**\n * Gets a TFile by path or throws an error if not found.\n * Useful when you need to ensure a file exists before proceeding.\n */\nexport const getTFileOrThrow = (app: App, path: string): TFile => {\n\tconst f = app.vault.getAbstractFileByPath(path);\n\tif (!(f instanceof TFile)) throw new Error(`File not found: ${path}`);\n\treturn f;\n};\n\n/**\n * Executes an operation on a file's frontmatter.\n * Wrapper around Obsidian's processFrontMatter for more concise usage.\n */\nexport const withFrontmatter = async (\n\tapp: App,\n\tfile: TFile,\n\tupdate: (fm: Record<string, unknown>) => void\n) => app.fileManager.processFrontMatter(file, update);\n\n/**\n * Creates a backup copy of a file's frontmatter.\n * Useful for undo/redo operations or temporary modifications.\n */\nexport const backupFrontmatter = async (app: App, file: TFile) => {\n\tlet copy: Record<string, unknown> = {};\n\tawait withFrontmatter(app, file, (fm) => {\n\t\tcopy = { ...fm };\n\t});\n\treturn copy;\n};\n\n/**\n * Restores a file's frontmatter from a backup.\n * Clears existing frontmatter and replaces with the backup.\n */\nexport const restoreFrontmatter = async (\n\tapp: App,\n\tfile: TFile,\n\toriginal: Record<string, unknown>\n) =>\n\twithFrontmatter(app, file, (fm) => {\n\t\tfor (const k of Object.keys(fm)) {\n\t\t\tdelete fm[k];\n\t\t}\n\t\tObject.assign(fm, original);\n\t});\n\n/**\n * Extracts the content that appears after the frontmatter section.\n * Returns the entire content if no frontmatter is found.\n */\nexport const extractContentAfterFrontmatter = (fullContent: string): string => {\n\tconst frontmatterRegex = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n\tconst match = fullContent.match(frontmatterRegex);\n\n\tif (match) {\n\t\t// Return content after frontmatter\n\t\treturn fullContent.substring(match.index! + match[0].length);\n\t}\n\n\t// If no frontmatter found, return the entire content\n\treturn fullContent;\n};\n"]}
1
+ {"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/file/file-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import type { App } from \"obsidian\";\nimport { TFile } from \"obsidian\";\n\n/**\n * Waits for a file to be accessible and readable by Obsidian's cache.\n * This is necessary because Templater creates files asynchronously.\n */\nexport async function waitForFileReady(\n\tapp: App,\n\tfilePath: string,\n\ttimeoutMs = 5000\n): Promise<TFile | null> {\n\tconst started = Date.now();\n\n\twhile (Date.now() - started < timeoutMs) {\n\t\tconst file = app.vault.getAbstractFileByPath(filePath);\n\t\tif (file instanceof TFile) {\n\t\t\t// Check if file is readable by trying to get its metadata\n\t\t\tconst metadata = app.metadataCache.getFileCache(file);\n\t\t\tif (metadata !== null && metadata !== undefined) {\n\t\t\t\treturn file;\n\t\t\t}\n\t\t}\n\t\tawait new Promise((r) => setTimeout(r, 50));\n\t}\n\n\treturn null;\n}\n\n/**\n * Gets a TFile by path or throws an error if not found.\n * Useful when you need to ensure a file exists before proceeding.\n */\nexport const getTFileOrThrow = (app: App, path: string): TFile => {\n\tconst f = app.vault.getAbstractFileByPath(path);\n\tif (!(f instanceof TFile)) throw new Error(`File not found: ${path}`);\n\treturn f;\n};\n\n/**\n * Executes an operation on a file's frontmatter.\n * Wrapper around Obsidian's processFrontMatter for more concise usage.\n */\nexport const withFrontmatter = async (\n\tapp: App,\n\tfile: TFile,\n\tupdate: (fm: Record<string, unknown>) => void\n) => app.fileManager.processFrontMatter(file, update);\n\n/**\n * Creates a backup copy of a file's frontmatter.\n * Useful for undo/redo operations or temporary modifications.\n */\nexport const backupFrontmatter = async (app: App, file: TFile) => {\n\tlet copy: Record<string, unknown> = {};\n\tawait withFrontmatter(app, file, (fm) => {\n\t\tcopy = { ...fm };\n\t});\n\treturn copy;\n};\n\n/**\n * Restores a file's frontmatter from a backup.\n * Clears existing frontmatter and replaces with the backup.\n */\nexport const restoreFrontmatter = async (\n\tapp: App,\n\tfile: TFile,\n\toriginal: Record<string, unknown>\n) =>\n\twithFrontmatter(app, file, (fm) => {\n\t\tfor (const k of Object.keys(fm)) {\n\t\t\tdelete fm[k];\n\t\t}\n\t\tObject.assign(fm, original);\n\t});\n\n/**\n * Extracts the content that appears after the frontmatter section.\n * Returns the entire content if no frontmatter is found.\n */\nexport const extractContentAfterFrontmatter = (fullContent: string): string => {\n\tconst frontmatterRegex = /^---\\s*\\n([\\s\\S]*?)\\n---\\s*\\n/;\n\tconst match = fullContent.match(frontmatterRegex);\n\n\tif (match) {\n\t\t// Return content after frontmatter\n\t\treturn fullContent.substring(match.index! + match[0].length);\n\t}\n\n\t// If no frontmatter found, return the entire content\n\treturn fullContent;\n};\n"]}
@@ -116,6 +116,25 @@ export declare function withFileContext<T>(app: App, path: string, callback: (co
116
116
  * ```
117
117
  */
118
118
  export declare function getUniqueFilePath(app: App, folder: string, baseName: string): string;
119
+ /**
120
+ * Generates a unique file path by appending a counter if the file already exists.
121
+ * Accepts a complete file path and iterates with a counter suffix.
122
+ *
123
+ * @param app - The Obsidian App instance
124
+ * @param filePath - Complete file path including folder, name, and extension
125
+ * @returns Unique file path that doesn't exist in the vault
126
+ *
127
+ * @example
128
+ * ```ts
129
+ * // If "folder/note.md" exists, returns "folder/note 1.md"
130
+ * const path = getUniqueFilePathFromFull(app, "folder/note.md");
131
+ *
132
+ * // Works with any extension
133
+ * const path = getUniqueFilePathFromFull(app, "assets/image.png");
134
+ * // -> "assets/image 1.png" if image.png exists
135
+ * ```
136
+ */
137
+ export declare const getUniqueFilePathFromFull: (app: App, filePath: string) => string;
119
138
  /**
120
139
  * Generates a unique file path by appending a counter if the file already exists.
121
140
  * Supports custom file extensions.
@@ -1 +1 @@
1
- {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/file/file.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../src/file/file.ts"],"names":[],"mappings":"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"}
package/dist/file/file.js CHANGED
@@ -208,6 +208,44 @@ export function getUniqueFilePath(app, folder, baseName) {
208
208
  }
209
209
  return fullPath;
210
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
+ };
211
249
  /**
212
250
  * Generates a unique file path by appending a counter if the file already exists.
213
251
  * Supports custom file extensions.
@@ -220,12 +258,8 @@ export function getUniqueFilePath(app, folder, baseName) {
220
258
  */
221
259
  export const generateUniqueFilePath = (app, folder, baseName, extension = "md") => {
222
260
  const folderPath = folder ? `${folder}/` : "";
223
- let filePath = `${folderPath}${baseName}.${extension}`;
224
- let counter = 1;
225
- while (app.vault.getAbstractFileByPath(filePath)) {
226
- filePath = `${folderPath}${baseName} ${counter++}.${extension}`;
227
- }
228
- return filePath;
261
+ const fullPath = `${folderPath}${baseName}.${extension}`;
262
+ return getUniqueFilePathFromFull(app, fullPath);
229
263
  };
230
264
  // ============================================================================
231
265
  // Folder Note Operations
@@ -1 +1 @@
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 * 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\tlet filePath = `${folderPath}${baseName}.${extension}`;\n\tlet counter = 1;\n\n\twhile (app.vault.getAbstractFileByPath(filePath)) {\n\t\tfilePath = `${folderPath}${baseName} ${counter++}.${extension}`;\n\t}\n\n\treturn filePath;\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
+ {"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"]}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Creates file content with YAML frontmatter and body content.
3
+ * This is the atomic file creation format that prevents race conditions.
4
+ *
5
+ * @param frontmatter - Frontmatter as JSON object
6
+ * @param content - Body content (markdown text after frontmatter)
7
+ * @returns Complete file content with YAML frontmatter
8
+ */
9
+ export declare function createFileContentWithFrontmatter(frontmatter: Record<string, unknown>, content?: string): string;
10
+ /**
11
+ * Parses file content to extract frontmatter and body separately.
12
+ * Returns both the frontmatter object and the body content.
13
+ *
14
+ * @param fileContent - Complete file content including frontmatter
15
+ * @returns Object with frontmatter and body
16
+ */
17
+ export declare function parseFileContent(fileContent: string): {
18
+ frontmatter: Record<string, unknown>;
19
+ body: string;
20
+ };
21
+ //# sourceMappingURL=frontmatter-serialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-serialization.d.ts","sourceRoot":"","sources":["../../src/file/frontmatter-serialization.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,gCAAgC,CAC/C,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,OAAO,SAAK,GACV,MAAM,CAgCR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,MAAM,GAAG;IACtD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACb,CAcA"}
@@ -0,0 +1,57 @@
1
+ import { stringify as stringifyYAML } from "yaml";
2
+ /**
3
+ * Creates file content with YAML frontmatter and body content.
4
+ * This is the atomic file creation format that prevents race conditions.
5
+ *
6
+ * @param frontmatter - Frontmatter as JSON object
7
+ * @param content - Body content (markdown text after frontmatter)
8
+ * @returns Complete file content with YAML frontmatter
9
+ */
10
+ export function createFileContentWithFrontmatter(frontmatter, content = "") {
11
+ if (!frontmatter || Object.keys(frontmatter).length === 0) {
12
+ // No frontmatter, just return content
13
+ return content;
14
+ }
15
+ // Use the yaml library to stringify frontmatter
16
+ // This handles all edge cases: special characters, multiline strings, arrays, nested objects, etc.
17
+ const yaml = stringifyYAML(frontmatter, {
18
+ // Use 2-space indentation (Obsidian standard)
19
+ indent: 2,
20
+ // Don't add document markers (--- at the end)
21
+ lineWidth: 0,
22
+ // Minimize unnecessary quotes
23
+ defaultStringType: "PLAIN",
24
+ // Handle null values explicitly
25
+ nullStr: "",
26
+ }).trim();
27
+ if (!yaml) {
28
+ // Empty frontmatter after stringification
29
+ return content;
30
+ }
31
+ // Ensure content doesn't start with extra newlines
32
+ const trimmedContent = content.replace(/^\n+/, "");
33
+ if (trimmedContent) {
34
+ return `---\n${yaml}\n---\n\n${trimmedContent}`;
35
+ }
36
+ return `---\n${yaml}\n---\n`;
37
+ }
38
+ /**
39
+ * Parses file content to extract frontmatter and body separately.
40
+ * Returns both the frontmatter object and the body content.
41
+ *
42
+ * @param fileContent - Complete file content including frontmatter
43
+ * @returns Object with frontmatter and body
44
+ */
45
+ export function parseFileContent(fileContent) {
46
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
47
+ const match = fileContent.match(frontmatterRegex);
48
+ if (!match) {
49
+ // No frontmatter found
50
+ return { frontmatter: {}, body: fileContent };
51
+ }
52
+ const [, , body] = match;
53
+ // For this use case, we just need to split the file
54
+ // The frontmatter will be properly parsed when we read it back with Obsidian's APIs
55
+ return { frontmatter: {}, body: body.trim() };
56
+ }
57
+ //# sourceMappingURL=frontmatter-serialization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frontmatter-serialization.js","sourceRoot":"","sources":["../../src/file/frontmatter-serialization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,IAAI,aAAa,EAAE,MAAM,MAAM,CAAC;AAElD;;;;;;;GAOG;AACH,MAAM,UAAU,gCAAgC,CAC/C,WAAoC,EACpC,OAAO,GAAG,EAAE;IAEZ,IAAI,CAAC,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3D,sCAAsC;QACtC,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,gDAAgD;IAChD,mGAAmG;IACnG,MAAM,IAAI,GAAG,aAAa,CAAC,WAAW,EAAE;QACvC,8CAA8C;QAC9C,MAAM,EAAE,CAAC;QACT,8CAA8C;QAC9C,SAAS,EAAE,CAAC;QACZ,8BAA8B;QAC9B,iBAAiB,EAAE,OAAO;QAC1B,gCAAgC;QAChC,OAAO,EAAE,EAAE;KACX,CAAC,CAAC,IAAI,EAAE,CAAC;IAEV,IAAI,CAAC,IAAI,EAAE,CAAC;QACX,0CAA0C;QAC1C,OAAO,OAAO,CAAC;IAChB,CAAC;IAED,mDAAmD;IACnD,MAAM,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEnD,IAAI,cAAc,EAAE,CAAC;QACpB,OAAO,QAAQ,IAAI,YAAY,cAAc,EAAE,CAAC;IACjD,CAAC;IAED,OAAO,QAAQ,IAAI,SAAS,CAAC;AAC9B,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,WAAmB;IAInD,MAAM,gBAAgB,GAAG,oCAAoC,CAAC;IAC9D,MAAM,KAAK,GAAG,WAAW,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;IAElD,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,uBAAuB;QACvB,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC/C,CAAC;IAED,MAAM,CAAC,EAAE,AAAD,EAAG,IAAI,CAAC,GAAG,KAAK,CAAC;IAEzB,oDAAoD;IACpD,oFAAoF;IACpF,OAAO,EAAE,WAAW,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;AAC/C,CAAC","sourcesContent":["import { stringify as stringifyYAML } from \"yaml\";\n\n/**\n * Creates file content with YAML frontmatter and body content.\n * This is the atomic file creation format that prevents race conditions.\n *\n * @param frontmatter - Frontmatter as JSON object\n * @param content - Body content (markdown text after frontmatter)\n * @returns Complete file content with YAML frontmatter\n */\nexport function createFileContentWithFrontmatter(\n\tfrontmatter: Record<string, unknown>,\n\tcontent = \"\"\n): string {\n\tif (!frontmatter || Object.keys(frontmatter).length === 0) {\n\t\t// No frontmatter, just return content\n\t\treturn content;\n\t}\n\n\t// Use the yaml library to stringify frontmatter\n\t// This handles all edge cases: special characters, multiline strings, arrays, nested objects, etc.\n\tconst yaml = stringifyYAML(frontmatter, {\n\t\t// Use 2-space indentation (Obsidian standard)\n\t\tindent: 2,\n\t\t// Don't add document markers (--- at the end)\n\t\tlineWidth: 0,\n\t\t// Minimize unnecessary quotes\n\t\tdefaultStringType: \"PLAIN\",\n\t\t// Handle null values explicitly\n\t\tnullStr: \"\",\n\t}).trim();\n\n\tif (!yaml) {\n\t\t// Empty frontmatter after stringification\n\t\treturn content;\n\t}\n\n\t// Ensure content doesn't start with extra newlines\n\tconst trimmedContent = content.replace(/^\\n+/, \"\");\n\n\tif (trimmedContent) {\n\t\treturn `---\\n${yaml}\\n---\\n\\n${trimmedContent}`;\n\t}\n\n\treturn `---\\n${yaml}\\n---\\n`;\n}\n\n/**\n * Parses file content to extract frontmatter and body separately.\n * Returns both the frontmatter object and the body content.\n *\n * @param fileContent - Complete file content including frontmatter\n * @returns Object with frontmatter and body\n */\nexport function parseFileContent(fileContent: string): {\n\tfrontmatter: Record<string, unknown>;\n\tbody: string;\n} {\n\tconst frontmatterRegex = /^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/;\n\tconst match = fileContent.match(frontmatterRegex);\n\n\tif (!match) {\n\t\t// No frontmatter found\n\t\treturn { frontmatter: {}, body: fileContent };\n\t}\n\n\tconst [, , body] = match;\n\n\t// For this use case, we just need to split the file\n\t// The frontmatter will be properly parsed when we read it back with Obsidian's APIs\n\treturn { frontmatter: {}, body: body.trim() };\n}\n"]}
@@ -5,6 +5,7 @@ export * from "./file-utils";
5
5
  export * from "./frontmatter";
6
6
  export * from "./frontmatter-diff";
7
7
  export * from "./frontmatter-propagation";
8
+ export * from "./frontmatter-serialization";
8
9
  export * from "./link-parser";
9
10
  export * from "./property-utils";
10
11
  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,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,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,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,CAAC"}
@@ -5,6 +5,7 @@ export * from "./file-utils";
5
5
  export * from "./frontmatter";
6
6
  export * from "./frontmatter-diff";
7
7
  export * from "./frontmatter-propagation";
8
+ export * from "./frontmatter-serialization";
8
9
  export * from "./link-parser";
9
10
  export * from "./property-utils";
10
11
  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,oBAAoB,CAAC;AACnC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,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 \"./frontmatter-propagation\";\nexport * from \"./link-parser\";\nexport * from \"./property-utils\";\nexport * from \"./templater\";\nexport * from \"./templater-service\";\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,2BAA2B,CAAC;AAC1C,cAAc,6BAA6B,CAAC;AAC5C,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,cAAc,qBAAqB,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 \"./frontmatter-propagation\";\nexport * from \"./frontmatter-serialization\";\nexport * from \"./link-parser\";\nexport * from \"./property-utils\";\nexport * from \"./templater\";\nexport * from \"./templater-service\";\n"]}
@@ -1,4 +1,4 @@
1
- import { type App, TFile } from "obsidian";
1
+ import type { App, TFile } from "obsidian";
2
2
  import type { FileCreationOptions } from "./templater";
3
3
  export type { FileCreationOptions };
4
4
  export declare class TemplaterService {
@@ -12,7 +12,5 @@ export declare class TemplaterService {
12
12
  * Creates a file using Templater or falls back to manual creation.
13
13
  */
14
14
  createFile(options: FileCreationOptions): Promise<TFile>;
15
- private shouldUseTemplate;
16
- private createManually;
17
15
  }
18
16
  //# sourceMappingURL=templater-service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"templater-service.d.ts","sourceRoot":"","sources":["../../src/file/templater-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAGvD,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAMpC,qBAAa,gBAAgB;IAChB,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,GAAG;IAE5B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;IAmC9D,OAAO,CAAC,iBAAiB;YAIX,cAAc;CA8B5B"}
1
+ {"version":3,"file":"templater-service.d.ts","sourceRoot":"","sources":["../../src/file/templater-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAQvD,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAMpC,qBAAa,gBAAgB;IAChB,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,GAAG;IAE5B;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,KAAK,CAAC;CA2B9D"}
@@ -1,6 +1,5 @@
1
1
  import { __awaiter } from "tslib";
2
- import { TFile } from "obsidian";
3
- import { createFromTemplate, isTemplaterAvailable } from "./templater";
2
+ import { createFileManually, createFromTemplate, isTemplaterAvailable, shouldUseTemplate, } from "./templater";
4
3
  // ============================================================================
5
4
  // Templater Service (Class-based wrapper)
6
5
  // ============================================================================
@@ -21,49 +20,17 @@ export class TemplaterService {
21
20
  return __awaiter(this, void 0, void 0, function* () {
22
21
  const { title, targetDirectory, filename, content, frontmatter, templatePath, useTemplater } = options;
23
22
  const finalFilename = filename || title;
24
- // If content is provided, use manual creation to preserve the content
25
23
  if (content) {
26
- return this.createManually(title, targetDirectory, finalFilename, content, frontmatter);
24
+ return createFileManually(this.app, targetDirectory, finalFilename, content, frontmatter);
27
25
  }
28
- // Try to use Templater if requested and available
29
- if (useTemplater && templatePath && this.shouldUseTemplate(templatePath)) {
30
- const templateFile = yield createFromTemplate(this.app, templatePath, targetDirectory, finalFilename);
26
+ if (useTemplater && shouldUseTemplate(this.app, templatePath)) {
27
+ const templateFile = yield createFromTemplate(this.app, templatePath, targetDirectory, finalFilename, false, frontmatter);
31
28
  if (templateFile) {
32
- // Apply frontmatter if provided
33
- if (frontmatter && Object.keys(frontmatter).length > 0) {
34
- yield this.app.fileManager.processFrontMatter(templateFile, (fm) => {
35
- Object.assign(fm, frontmatter);
36
- });
37
- }
38
29
  return templateFile;
39
30
  }
40
31
  }
41
32
  // Fallback to manual creation
42
- return this.createManually(title, targetDirectory, finalFilename, content, frontmatter);
43
- });
44
- }
45
- shouldUseTemplate(templatePath) {
46
- return !!(templatePath && this.isAvailable() && this.app.vault.getFileByPath(templatePath));
47
- }
48
- createManually(title, targetDirectory, filename, customContent, frontmatter) {
49
- return __awaiter(this, void 0, void 0, function* () {
50
- const baseName = filename.replace(/\.md$/, "");
51
- const filePath = `${targetDirectory}/${baseName}.md`;
52
- // Check if file already exists
53
- const existingFile = this.app.vault.getAbstractFileByPath(filePath);
54
- if (existingFile instanceof TFile) {
55
- return existingFile;
56
- }
57
- // Use custom content or default
58
- const content = customContent || `# ${title}\n\n`;
59
- const file = yield this.app.vault.create(filePath, content);
60
- // Apply frontmatter if provided
61
- if (frontmatter && Object.keys(frontmatter).length > 0) {
62
- yield this.app.fileManager.processFrontMatter(file, (fm) => {
63
- Object.assign(fm, frontmatter);
64
- });
65
- }
66
- return file;
33
+ return createFileManually(this.app, targetDirectory, finalFilename, content, frontmatter);
67
34
  });
68
35
  }
69
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"templater-service.js","sourceRoot":"","sources":["../../src/file/templater-service.ts"],"names":[],"mappings":"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","sourcesContent":["import { type App, TFile } from \"obsidian\";\nimport type { FileCreationOptions } from \"./templater\";\nimport { createFromTemplate, isTemplaterAvailable } from \"./templater\";\n\nexport type { FileCreationOptions };\n\n// ============================================================================\n// Templater Service (Class-based wrapper)\n// ============================================================================\n\nexport class TemplaterService {\n\tconstructor(private app: App) {}\n\n\t/**\n\t * Checks if Templater plugin is installed and enabled.\n\t */\n\tisAvailable(): boolean {\n\t\treturn isTemplaterAvailable(this.app);\n\t}\n\n\t/**\n\t * Creates a file using Templater or falls back to manual creation.\n\t */\n\tasync createFile(options: FileCreationOptions): Promise<TFile> {\n\t\tconst { title, targetDirectory, filename, content, frontmatter, templatePath, useTemplater } =\n\t\t\toptions;\n\n\t\tconst finalFilename = filename || title;\n\n\t\t// If content is provided, use manual creation to preserve the content\n\t\tif (content) {\n\t\t\treturn this.createManually(title, targetDirectory, finalFilename, content, frontmatter);\n\t\t}\n\n\t\t// Try to use Templater if requested and available\n\t\tif (useTemplater && templatePath && this.shouldUseTemplate(templatePath)) {\n\t\t\tconst templateFile = await createFromTemplate(\n\t\t\t\tthis.app,\n\t\t\t\ttemplatePath,\n\t\t\t\ttargetDirectory,\n\t\t\t\tfinalFilename\n\t\t\t);\n\n\t\t\tif (templateFile) {\n\t\t\t\t// Apply frontmatter if provided\n\t\t\t\tif (frontmatter && Object.keys(frontmatter).length > 0) {\n\t\t\t\t\tawait this.app.fileManager.processFrontMatter(templateFile, (fm) => {\n\t\t\t\t\t\tObject.assign(fm, frontmatter);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn templateFile;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to manual creation\n\t\treturn this.createManually(title, targetDirectory, finalFilename, content, frontmatter);\n\t}\n\n\tprivate shouldUseTemplate(templatePath: string): boolean {\n\t\treturn !!(templatePath && this.isAvailable() && this.app.vault.getFileByPath(templatePath));\n\t}\n\n\tprivate async createManually(\n\t\ttitle: string,\n\t\ttargetDirectory: string,\n\t\tfilename: string,\n\t\tcustomContent?: string,\n\t\tfrontmatter?: Record<string, unknown>\n\t): Promise<TFile> {\n\t\tconst baseName = filename.replace(/\\.md$/, \"\");\n\t\tconst filePath = `${targetDirectory}/${baseName}.md`;\n\n\t\t// Check if file already exists\n\t\tconst existingFile = this.app.vault.getAbstractFileByPath(filePath);\n\t\tif (existingFile instanceof TFile) {\n\t\t\treturn existingFile;\n\t\t}\n\n\t\t// Use custom content or default\n\t\tconst content = customContent || `# ${title}\\n\\n`;\n\n\t\tconst file = await this.app.vault.create(filePath, content);\n\n\t\t// Apply frontmatter if provided\n\t\tif (frontmatter && Object.keys(frontmatter).length > 0) {\n\t\t\tawait this.app.fileManager.processFrontMatter(file, (fm) => {\n\t\t\t\tObject.assign(fm, frontmatter);\n\t\t\t});\n\t\t}\n\n\t\treturn file;\n\t}\n}\n"]}
1
+ {"version":3,"file":"templater-service.js","sourceRoot":"","sources":["../../src/file/templater-service.ts"],"names":[],"mappings":";AAEA,OAAO,EACN,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,iBAAiB,GACjB,MAAM,aAAa,CAAC;AAIrB,+EAA+E;AAC/E,0CAA0C;AAC1C,+EAA+E;AAE/E,MAAM,OAAO,gBAAgB;IAC5B,YAAoB,GAAQ;QAAR,QAAG,GAAH,GAAG,CAAK;IAAG,CAAC;IAEhC;;OAEG;IACH,WAAW;QACV,OAAO,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACvC,CAAC;IAED;;OAEG;IACG,UAAU,CAAC,OAA4B;;YAC5C,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,GAC3F,OAAO,CAAC;YAET,MAAM,aAAa,GAAG,QAAQ,IAAI,KAAK,CAAC;YAExC,IAAI,OAAO,EAAE,CAAC;gBACb,OAAO,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;YAC3F,CAAC;YAED,IAAI,YAAY,IAAI,iBAAiB,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,CAAC;gBAC/D,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAC5C,IAAI,CAAC,GAAG,EACR,YAAa,EACb,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,CACX,CAAC;gBAEF,IAAI,YAAY,EAAE,CAAC;oBAClB,OAAO,YAAY,CAAC;gBACrB,CAAC;YACF,CAAC;YACD,8BAA8B;YAC9B,OAAO,kBAAkB,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QAC3F,CAAC;KAAA;CACD","sourcesContent":["import type { App, TFile } from \"obsidian\";\nimport type { FileCreationOptions } from \"./templater\";\nimport {\n\tcreateFileManually,\n\tcreateFromTemplate,\n\tisTemplaterAvailable,\n\tshouldUseTemplate,\n} from \"./templater\";\n\nexport type { FileCreationOptions };\n\n// ============================================================================\n// Templater Service (Class-based wrapper)\n// ============================================================================\n\nexport class TemplaterService {\n\tconstructor(private app: App) {}\n\n\t/**\n\t * Checks if Templater plugin is installed and enabled.\n\t */\n\tisAvailable(): boolean {\n\t\treturn isTemplaterAvailable(this.app);\n\t}\n\n\t/**\n\t * Creates a file using Templater or falls back to manual creation.\n\t */\n\tasync createFile(options: FileCreationOptions): Promise<TFile> {\n\t\tconst { title, targetDirectory, filename, content, frontmatter, templatePath, useTemplater } =\n\t\t\toptions;\n\n\t\tconst finalFilename = filename || title;\n\n\t\tif (content) {\n\t\t\treturn createFileManually(this.app, targetDirectory, finalFilename, content, frontmatter);\n\t\t}\n\n\t\tif (useTemplater && shouldUseTemplate(this.app, templatePath)) {\n\t\t\tconst templateFile = await createFromTemplate(\n\t\t\t\tthis.app,\n\t\t\t\ttemplatePath!,\n\t\t\t\ttargetDirectory,\n\t\t\t\tfinalFilename,\n\t\t\t\tfalse,\n\t\t\t\tfrontmatter\n\t\t\t);\n\n\t\t\tif (templateFile) {\n\t\t\t\treturn templateFile;\n\t\t\t}\n\t\t}\n\t\t// Fallback to manual creation\n\t\treturn createFileManually(this.app, targetDirectory, finalFilename, content, frontmatter);\n\t}\n}\n"]}
@@ -9,6 +9,15 @@ export interface FileCreationOptions {
9
9
  useTemplater?: boolean;
10
10
  }
11
11
  export declare function isTemplaterAvailable(app: App): boolean;
12
- export declare function createFromTemplate(app: App, templatePath: string, targetFolder?: string, filename?: string, openNewNote?: boolean): Promise<TFile | null>;
12
+ /**
13
+ * Checks if a template should be used based on availability and file existence.
14
+ */
15
+ export declare function shouldUseTemplate(app: App, templatePath: string | undefined): boolean;
16
+ /**
17
+ * Creates a file manually with optional frontmatter and content.
18
+ * Returns existing file if it already exists.
19
+ */
20
+ export declare function createFileManually(app: App, targetDirectory: string, filename: string, content?: string, frontmatter?: Record<string, unknown>): Promise<TFile>;
21
+ export declare function createFromTemplate(app: App, templatePath: string, targetFolder?: string, filename?: string, openNewNote?: boolean, frontmatter?: Record<string, unknown>): Promise<TFile | null>;
13
22
  export declare function createFileWithTemplate(app: App, options: FileCreationOptions): Promise<TFile>;
14
23
  //# sourceMappingURL=templater.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"templater.d.ts","sourceRoot":"","sources":["../../src/file/templater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAyB,KAAK,EAAE,MAAM,UAAU,CAAC;AAelE,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAmBD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAGtD;AAED,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,UAAQ,GACjB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CA+BvB;AAED,wBAAsB,sBAAsB,CAC3C,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,mBAAmB,GAC1B,OAAO,CAAC,KAAK,CAAC,CAyChB"}
1
+ {"version":3,"file":"templater.d.ts","sourceRoot":"","sources":["../../src/file/templater.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,GAAG,EAAyB,KAAK,EAAE,MAAM,UAAU,CAAC;AAiBlE,MAAM,WAAW,mBAAmB;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;CACvB;AAmBD,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAGtD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,GAAG,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAOrF;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,GAAG,EACR,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,KAAK,CAAC,CAqBhB;AAED,wBAAsB,kBAAkB,CACvC,GAAG,EAAE,GAAG,EACR,YAAY,EAAE,MAAM,EACpB,YAAY,CAAC,EAAE,MAAM,EACrB,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,UAAQ,EACnB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CA8CvB;AAED,wBAAsB,sBAAsB,CAC3C,GAAG,EAAE,GAAG,EACR,OAAO,EAAE,mBAAmB,GAC1B,OAAO,CAAC,KAAK,CAAC,CA4BhB"}
@@ -1,5 +1,7 @@
1
1
  import { __awaiter } from "tslib";
2
2
  import { Notice, normalizePath, TFile } from "obsidian";
3
+ import { waitForFileReady } from "./file-utils";
4
+ import { createFileContentWithFrontmatter } from "./frontmatter-serialization";
3
5
  const TEMPLATER_ID = "templater-obsidian";
4
6
  function waitForTemplater(app_1) {
5
7
  return __awaiter(this, arguments, void 0, function* (app, timeoutMs = 8000) {
@@ -23,8 +25,42 @@ export function isTemplaterAvailable(app) {
23
25
  const instance = (_b = (_a = app.plugins) === null || _a === void 0 ? void 0 : _a.getPlugin) === null || _b === void 0 ? void 0 : _b.call(_a, TEMPLATER_ID);
24
26
  return !!instance;
25
27
  }
28
+ /**
29
+ * Checks if a template should be used based on availability and file existence.
30
+ */
31
+ export function shouldUseTemplate(app, templatePath) {
32
+ return !!(templatePath &&
33
+ templatePath.trim() !== "" &&
34
+ isTemplaterAvailable(app) &&
35
+ app.vault.getFileByPath(templatePath));
36
+ }
37
+ /**
38
+ * Creates a file manually with optional frontmatter and content.
39
+ * Returns existing file if it already exists.
40
+ */
41
+ export function createFileManually(app, targetDirectory, filename, content, frontmatter) {
42
+ return __awaiter(this, void 0, void 0, function* () {
43
+ const baseName = filename.replace(/\.md$/, "");
44
+ const filePath = `${targetDirectory}/${baseName}.md`;
45
+ // Check if file already exists
46
+ const existingFile = app.vault.getAbstractFileByPath(filePath);
47
+ if (existingFile instanceof TFile) {
48
+ return existingFile;
49
+ }
50
+ const bodyContent = content || "";
51
+ let fileContent;
52
+ if (frontmatter && Object.keys(frontmatter).length > 0) {
53
+ fileContent = createFileContentWithFrontmatter(frontmatter, bodyContent);
54
+ }
55
+ else {
56
+ fileContent = bodyContent;
57
+ }
58
+ const file = yield app.vault.create(filePath, fileContent);
59
+ return file;
60
+ });
61
+ }
26
62
  export function createFromTemplate(app_1, templatePath_1, targetFolder_1, filename_1) {
27
- return __awaiter(this, arguments, void 0, function* (app, templatePath, targetFolder, filename, openNewNote = false) {
63
+ return __awaiter(this, arguments, void 0, function* (app, templatePath, targetFolder, filename, openNewNote = false, frontmatter) {
28
64
  const templater = yield waitForTemplater(app);
29
65
  if (!templater) {
30
66
  console.warn("Templater isn't ready yet (or not installed/enabled).");
@@ -39,7 +75,19 @@ export function createFromTemplate(app_1, templatePath_1, targetFolder_1, filena
39
75
  }
40
76
  try {
41
77
  const newFile = yield templater.create_new_note_from_template(templateFile, targetFolder, filename, openNewNote);
42
- return newFile !== null && newFile !== void 0 ? newFile : null;
78
+ if (!newFile) {
79
+ return null;
80
+ }
81
+ if (frontmatter && Object.keys(frontmatter).length > 0) {
82
+ const readyFile = yield waitForFileReady(app, newFile.path);
83
+ if (readyFile) {
84
+ yield app.fileManager.processFrontMatter(readyFile, (fm) => {
85
+ Object.assign(fm, frontmatter);
86
+ });
87
+ return readyFile;
88
+ }
89
+ }
90
+ return newFile;
43
91
  }
44
92
  catch (error) {
45
93
  console.error("Error creating file from template:", error);
@@ -52,31 +100,18 @@ export function createFileWithTemplate(app, options) {
52
100
  return __awaiter(this, void 0, void 0, function* () {
53
101
  const { title, targetDirectory, filename, content, frontmatter, templatePath, useTemplater } = options;
54
102
  const finalFilename = filename || title;
55
- const baseName = finalFilename.replace(/\.md$/, "");
56
- const filePath = normalizePath(`${targetDirectory}/${baseName}.md`);
57
- const existingFile = app.vault.getAbstractFileByPath(filePath);
58
- if (existingFile instanceof TFile) {
59
- return existingFile;
103
+ // If content is provided, use manual creation to preserve the content
104
+ if (content) {
105
+ return createFileManually(app, targetDirectory, finalFilename, content, frontmatter);
60
106
  }
61
- if (useTemplater && templatePath && templatePath.trim() !== "" && isTemplaterAvailable(app)) {
62
- const templateFile = yield createFromTemplate(app, templatePath, targetDirectory, finalFilename);
107
+ // Try to use template if requested and available
108
+ if (useTemplater && shouldUseTemplate(app, templatePath)) {
109
+ const templateFile = yield createFromTemplate(app, templatePath, targetDirectory, finalFilename, false, frontmatter);
63
110
  if (templateFile) {
64
- if (frontmatter && Object.keys(frontmatter).length > 0) {
65
- yield app.fileManager.processFrontMatter(templateFile, (fm) => {
66
- Object.assign(fm, frontmatter);
67
- });
68
- }
69
111
  return templateFile;
70
112
  }
71
113
  }
72
- const fileContent = content || "";
73
- const file = yield app.vault.create(filePath, fileContent);
74
- if (frontmatter && Object.keys(frontmatter).length > 0) {
75
- yield app.fileManager.processFrontMatter(file, (fm) => {
76
- Object.assign(fm, frontmatter);
77
- });
78
- }
79
- return file;
114
+ return createFileManually(app, targetDirectory, finalFilename, content, frontmatter);
80
115
  });
81
116
  }
82
117
  //# sourceMappingURL=templater.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"templater.js","sourceRoot":"","sources":["../../src/file/templater.ts"],"names":[],"mappings":"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","sourcesContent":["import { type App, Notice, normalizePath, TFile } from \"obsidian\";\n\nconst TEMPLATER_ID = \"templater-obsidian\";\n\ntype CreateFn = (\n\ttemplateFile: TFile,\n\tfolder?: string,\n\tfilename?: string,\n\topenNewNote?: boolean\n) => Promise<TFile | undefined>;\n\ninterface TemplaterLike {\n\tcreate_new_note_from_template: CreateFn;\n}\n\nexport interface FileCreationOptions {\n\ttitle: string;\n\ttargetDirectory: string;\n\tfilename?: string;\n\tcontent?: string;\n\tfrontmatter?: Record<string, unknown>;\n\ttemplatePath?: string;\n\tuseTemplater?: boolean;\n}\n\nasync function waitForTemplater(app: App, timeoutMs = 8000): Promise<TemplaterLike | null> {\n\tawait new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n\n\tconst started = Date.now();\n\twhile (Date.now() - started < timeoutMs) {\n\t\tconst plug: any = (app as any).plugins?.getPlugin?.(TEMPLATER_ID);\n\t\tconst api = plug?.templater ?? null;\n\n\t\tconst createFn: CreateFn | undefined = api?.create_new_note_from_template?.bind(api);\n\t\tif (typeof createFn === \"function\") {\n\t\t\treturn { create_new_note_from_template: createFn };\n\t\t}\n\t\tawait new Promise((r) => setTimeout(r, 150));\n\t}\n\treturn null;\n}\n\nexport function isTemplaterAvailable(app: App): boolean {\n\tconst instance = (app as any).plugins?.getPlugin?.(TEMPLATER_ID);\n\treturn !!instance;\n}\n\nexport async function createFromTemplate(\n\tapp: App,\n\ttemplatePath: string,\n\ttargetFolder?: string,\n\tfilename?: string,\n\topenNewNote = false\n): Promise<TFile | null> {\n\tconst templater = await waitForTemplater(app);\n\tif (!templater) {\n\t\tconsole.warn(\"Templater isn't ready yet (or not installed/enabled).\");\n\t\tnew Notice(\n\t\t\t\"Templater plugin is not available or enabled. Please ensure it is installed and enabled.\"\n\t\t);\n\t\treturn null;\n\t}\n\n\tconst templateFile = app.vault.getFileByPath(normalizePath(templatePath));\n\tif (!templateFile) {\n\t\tconsole.error(`Template not found: ${templatePath}`);\n\t\tnew Notice(`Template file not found: ${templatePath}. Please ensure the template file exists.`);\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst newFile = await templater.create_new_note_from_template(\n\t\t\ttemplateFile,\n\t\t\ttargetFolder,\n\t\t\tfilename,\n\t\t\topenNewNote\n\t\t);\n\n\t\treturn newFile ?? null;\n\t} catch (error) {\n\t\tconsole.error(\"Error creating file from template:\", error);\n\t\tnew Notice(\"Error creating file from template. Please ensure the template file is valid.\");\n\t\treturn null;\n\t}\n}\n\nexport async function createFileWithTemplate(\n\tapp: App,\n\toptions: FileCreationOptions\n): Promise<TFile> {\n\tconst { title, targetDirectory, filename, content, frontmatter, templatePath, useTemplater } =\n\t\toptions;\n\n\tconst finalFilename = filename || title;\n\tconst baseName = finalFilename.replace(/\\.md$/, \"\");\n\tconst filePath = normalizePath(`${targetDirectory}/${baseName}.md`);\n\n\tconst existingFile = app.vault.getAbstractFileByPath(filePath);\n\tif (existingFile instanceof TFile) {\n\t\treturn existingFile;\n\t}\n\n\tif (useTemplater && templatePath && templatePath.trim() !== \"\" && isTemplaterAvailable(app)) {\n\t\tconst templateFile = await createFromTemplate(\n\t\t\tapp,\n\t\t\ttemplatePath,\n\t\t\ttargetDirectory,\n\t\t\tfinalFilename\n\t\t);\n\n\t\tif (templateFile) {\n\t\t\tif (frontmatter && Object.keys(frontmatter).length > 0) {\n\t\t\t\tawait app.fileManager.processFrontMatter(templateFile, (fm) => {\n\t\t\t\t\tObject.assign(fm, frontmatter);\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn templateFile;\n\t\t}\n\t}\n\n\tconst fileContent = content || \"\";\n\tconst file = await app.vault.create(filePath, fileContent);\n\n\tif (frontmatter && Object.keys(frontmatter).length > 0) {\n\t\tawait app.fileManager.processFrontMatter(file, (fm) => {\n\t\t\tObject.assign(fm, frontmatter);\n\t\t});\n\t}\n\n\treturn file;\n}\n"]}
1
+ {"version":3,"file":"templater.js","sourceRoot":"","sources":["../../src/file/templater.ts"],"names":[],"mappings":";AAAA,OAAO,EAAY,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAChD,OAAO,EAAE,gCAAgC,EAAE,MAAM,6BAA6B,CAAC;AAE/E,MAAM,YAAY,GAAG,oBAAoB,CAAC;AAuB1C,SAAe,gBAAgB;yDAAC,GAAQ,EAAE,SAAS,GAAG,IAAI;;QACzD,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAC;QAE3E,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAC3B,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,SAAS,EAAE,CAAC;YACzC,MAAM,IAAI,GAAQ,MAAA,MAAC,GAAW,CAAC,OAAO,0CAAE,SAAS,mDAAG,YAAY,CAAC,CAAC;YAClE,MAAM,GAAG,GAAG,MAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,SAAS,mCAAI,IAAI,CAAC;YAEpC,MAAM,QAAQ,GAAyB,MAAA,GAAG,aAAH,GAAG,uBAAH,GAAG,CAAE,6BAA6B,0CAAE,IAAI,CAAC,GAAG,CAAC,CAAC;YACrF,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;gBACpC,OAAO,EAAE,6BAA6B,EAAE,QAAQ,EAAE,CAAC;YACpD,CAAC;YACD,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;QAC9C,CAAC;QACD,OAAO,IAAI,CAAC;IACb,CAAC;CAAA;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAQ;;IAC5C,MAAM,QAAQ,GAAG,MAAA,MAAC,GAAW,CAAC,OAAO,0CAAE,SAAS,mDAAG,YAAY,CAAC,CAAC;IACjE,OAAO,CAAC,CAAC,QAAQ,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAQ,EAAE,YAAgC;IAC3E,OAAO,CAAC,CAAC,CACR,YAAY;QACZ,YAAY,CAAC,IAAI,EAAE,KAAK,EAAE;QAC1B,oBAAoB,CAAC,GAAG,CAAC;QACzB,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CACrC,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAgB,kBAAkB,CACvC,GAAQ,EACR,eAAuB,EACvB,QAAgB,EAChB,OAAgB,EAChB,WAAqC;;QAErC,MAAM,QAAQ,GAAG,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC/C,MAAM,QAAQ,GAAG,GAAG,eAAe,IAAI,QAAQ,KAAK,CAAC;QAErD,+BAA+B;QAC/B,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC/D,IAAI,YAAY,YAAY,KAAK,EAAE,CAAC;YACnC,OAAO,YAAY,CAAC;QACrB,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,IAAI,EAAE,CAAC;QAElC,IAAI,WAAmB,CAAC;QACxB,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACxD,WAAW,GAAG,gCAAgC,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;QAC1E,CAAC;aAAM,CAAC;YACP,WAAW,GAAG,WAAW,CAAC;QAC3B,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;QAC3D,OAAO,IAAI,CAAC;IACb,CAAC;CAAA;AAED,MAAM,UAAgB,kBAAkB;yDACvC,GAAQ,EACR,YAAoB,EACpB,YAAqB,EACrB,QAAiB,EACjB,WAAW,GAAG,KAAK,EACnB,WAAqC;QAErC,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,uDAAuD,CAAC,CAAC;YACtE,IAAI,MAAM,CACT,0FAA0F,CAC1F,CAAC;YACF,OAAO,IAAI,CAAC;QACb,CAAC;QAED,MAAM,YAAY,GAAG,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;QAC1E,IAAI,CAAC,YAAY,EAAE,CAAC;YACnB,OAAO,CAAC,KAAK,CAAC,uBAAuB,YAAY,EAAE,CAAC,CAAC;YACrD,IAAI,MAAM,CAAC,4BAA4B,YAAY,2CAA2C,CAAC,CAAC;YAChG,OAAO,IAAI,CAAC;QACb,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,OAAO,GAAG,MAAM,SAAS,CAAC,6BAA6B,CAC5D,YAAY,EACZ,YAAY,EACZ,QAAQ,EACR,WAAW,CACX,CAAC;YAEF,IAAI,CAAC,OAAO,EAAE,CAAC;gBACd,OAAO,IAAI,CAAC;YACb,CAAC;YAED,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxD,MAAM,SAAS,GAAG,MAAM,gBAAgB,CAAC,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;gBAE5D,IAAI,SAAS,EAAE,CAAC;oBACf,MAAM,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC,SAAS,EAAE,CAAC,EAAE,EAAE,EAAE;wBAC1D,MAAM,CAAC,MAAM,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;oBAChC,CAAC,CAAC,CAAC;oBACH,OAAO,SAAS,CAAC;gBAClB,CAAC;YACF,CAAC;YAED,OAAO,OAAO,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;YAC3D,IAAI,MAAM,CAAC,8EAA8E,CAAC,CAAC;YAC3F,OAAO,IAAI,CAAC;QACb,CAAC;IACF,CAAC;CAAA;AAED,MAAM,UAAgB,sBAAsB,CAC3C,GAAQ,EACR,OAA4B;;QAE5B,MAAM,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,YAAY,EAAE,GAC3F,OAAO,CAAC;QAET,MAAM,aAAa,GAAG,QAAQ,IAAI,KAAK,CAAC;QAExC,sEAAsE;QACtE,IAAI,OAAO,EAAE,CAAC;YACb,OAAO,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;QACtF,CAAC;QAED,iDAAiD;QACjD,IAAI,YAAY,IAAI,iBAAiB,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,CAAC;YAC1D,MAAM,YAAY,GAAG,MAAM,kBAAkB,CAC5C,GAAG,EACH,YAAa,EACb,eAAe,EACf,aAAa,EACb,KAAK,EACL,WAAW,CACX,CAAC;YAEF,IAAI,YAAY,EAAE,CAAC;gBAClB,OAAO,YAAY,CAAC;YACrB,CAAC;QACF,CAAC;QAED,OAAO,kBAAkB,CAAC,GAAG,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,EAAE,WAAW,CAAC,CAAC;IACtF,CAAC;CAAA","sourcesContent":["import { type App, Notice, normalizePath, TFile } from \"obsidian\";\nimport { waitForFileReady } from \"./file-utils\";\nimport { createFileContentWithFrontmatter } from \"./frontmatter-serialization\";\n\nconst TEMPLATER_ID = \"templater-obsidian\";\n\ntype CreateFn = (\n\ttemplateFile: TFile,\n\tfolder?: string,\n\tfilename?: string,\n\topenNewNote?: boolean\n) => Promise<TFile | undefined>;\n\ninterface TemplaterLike {\n\tcreate_new_note_from_template: CreateFn;\n}\n\nexport interface FileCreationOptions {\n\ttitle: string;\n\ttargetDirectory: string;\n\tfilename?: string;\n\tcontent?: string;\n\tfrontmatter?: Record<string, unknown>;\n\ttemplatePath?: string;\n\tuseTemplater?: boolean;\n}\n\nasync function waitForTemplater(app: App, timeoutMs = 8000): Promise<TemplaterLike | null> {\n\tawait new Promise<void>((resolve) => app.workspace.onLayoutReady(resolve));\n\n\tconst started = Date.now();\n\twhile (Date.now() - started < timeoutMs) {\n\t\tconst plug: any = (app as any).plugins?.getPlugin?.(TEMPLATER_ID);\n\t\tconst api = plug?.templater ?? null;\n\n\t\tconst createFn: CreateFn | undefined = api?.create_new_note_from_template?.bind(api);\n\t\tif (typeof createFn === \"function\") {\n\t\t\treturn { create_new_note_from_template: createFn };\n\t\t}\n\t\tawait new Promise((r) => setTimeout(r, 150));\n\t}\n\treturn null;\n}\n\nexport function isTemplaterAvailable(app: App): boolean {\n\tconst instance = (app as any).plugins?.getPlugin?.(TEMPLATER_ID);\n\treturn !!instance;\n}\n\n/**\n * Checks if a template should be used based on availability and file existence.\n */\nexport function shouldUseTemplate(app: App, templatePath: string | undefined): boolean {\n\treturn !!(\n\t\ttemplatePath &&\n\t\ttemplatePath.trim() !== \"\" &&\n\t\tisTemplaterAvailable(app) &&\n\t\tapp.vault.getFileByPath(templatePath)\n\t);\n}\n\n/**\n * Creates a file manually with optional frontmatter and content.\n * Returns existing file if it already exists.\n */\nexport async function createFileManually(\n\tapp: App,\n\ttargetDirectory: string,\n\tfilename: string,\n\tcontent?: string,\n\tfrontmatter?: Record<string, unknown>\n): Promise<TFile> {\n\tconst baseName = filename.replace(/\\.md$/, \"\");\n\tconst filePath = `${targetDirectory}/${baseName}.md`;\n\n\t// Check if file already exists\n\tconst existingFile = app.vault.getAbstractFileByPath(filePath);\n\tif (existingFile instanceof TFile) {\n\t\treturn existingFile;\n\t}\n\n\tconst bodyContent = content || \"\";\n\n\tlet fileContent: string;\n\tif (frontmatter && Object.keys(frontmatter).length > 0) {\n\t\tfileContent = createFileContentWithFrontmatter(frontmatter, bodyContent);\n\t} else {\n\t\tfileContent = bodyContent;\n\t}\n\n\tconst file = await app.vault.create(filePath, fileContent);\n\treturn file;\n}\n\nexport async function createFromTemplate(\n\tapp: App,\n\ttemplatePath: string,\n\ttargetFolder?: string,\n\tfilename?: string,\n\topenNewNote = false,\n\tfrontmatter?: Record<string, unknown>\n): Promise<TFile | null> {\n\tconst templater = await waitForTemplater(app);\n\tif (!templater) {\n\t\tconsole.warn(\"Templater isn't ready yet (or not installed/enabled).\");\n\t\tnew Notice(\n\t\t\t\"Templater plugin is not available or enabled. Please ensure it is installed and enabled.\"\n\t\t);\n\t\treturn null;\n\t}\n\n\tconst templateFile = app.vault.getFileByPath(normalizePath(templatePath));\n\tif (!templateFile) {\n\t\tconsole.error(`Template not found: ${templatePath}`);\n\t\tnew Notice(`Template file not found: ${templatePath}. Please ensure the template file exists.`);\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst newFile = await templater.create_new_note_from_template(\n\t\t\ttemplateFile,\n\t\t\ttargetFolder,\n\t\t\tfilename,\n\t\t\topenNewNote\n\t\t);\n\n\t\tif (!newFile) {\n\t\t\treturn null;\n\t\t}\n\n\t\tif (frontmatter && Object.keys(frontmatter).length > 0) {\n\t\t\tconst readyFile = await waitForFileReady(app, newFile.path);\n\n\t\t\tif (readyFile) {\n\t\t\t\tawait app.fileManager.processFrontMatter(readyFile, (fm) => {\n\t\t\t\t\tObject.assign(fm, frontmatter);\n\t\t\t\t});\n\t\t\t\treturn readyFile;\n\t\t\t}\n\t\t}\n\n\t\treturn newFile;\n\t} catch (error) {\n\t\tconsole.error(\"Error creating file from template:\", error);\n\t\tnew Notice(\"Error creating file from template. Please ensure the template file is valid.\");\n\t\treturn null;\n\t}\n}\n\nexport async function createFileWithTemplate(\n\tapp: App,\n\toptions: FileCreationOptions\n): Promise<TFile> {\n\tconst { title, targetDirectory, filename, content, frontmatter, templatePath, useTemplater } =\n\t\toptions;\n\n\tconst finalFilename = filename || title;\n\n\t// If content is provided, use manual creation to preserve the content\n\tif (content) {\n\t\treturn createFileManually(app, targetDirectory, finalFilename, content, frontmatter);\n\t}\n\n\t// Try to use template if requested and available\n\tif (useTemplater && shouldUseTemplate(app, templatePath)) {\n\t\tconst templateFile = await createFromTemplate(\n\t\t\tapp,\n\t\t\ttemplatePath!,\n\t\t\ttargetDirectory,\n\t\t\tfinalFilename,\n\t\t\tfalse,\n\t\t\tfrontmatter\n\t\t);\n\n\t\tif (templateFile) {\n\t\t\treturn templateFile;\n\t\t}\n\t}\n\n\treturn createFileManually(app, targetDirectory, finalFilename, content, frontmatter);\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@real1ty-obsidian-plugins/utils",
3
- "version": "2.21.0",
3
+ "version": "2.22.0",
4
4
  "description": "Shared utilities for Obsidian plugins",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -55,6 +55,7 @@
55
55
  "obsidian": "^1.8.7",
56
56
  "rxjs": "^7.8.2",
57
57
  "tslib": "2.8.1",
58
+ "yaml": "^2.8.2",
58
59
  "zod": "^4.1.5"
59
60
  },
60
61
  "nx": {
@@ -1,6 +1,32 @@
1
1
  import type { App } from "obsidian";
2
2
  import { TFile } from "obsidian";
3
3
 
4
+ /**
5
+ * Waits for a file to be accessible and readable by Obsidian's cache.
6
+ * This is necessary because Templater creates files asynchronously.
7
+ */
8
+ export async function waitForFileReady(
9
+ app: App,
10
+ filePath: string,
11
+ timeoutMs = 5000
12
+ ): Promise<TFile | null> {
13
+ const started = Date.now();
14
+
15
+ while (Date.now() - started < timeoutMs) {
16
+ const file = app.vault.getAbstractFileByPath(filePath);
17
+ if (file instanceof TFile) {
18
+ // Check if file is readable by trying to get its metadata
19
+ const metadata = app.metadataCache.getFileCache(file);
20
+ if (metadata !== null && metadata !== undefined) {
21
+ return file;
22
+ }
23
+ }
24
+ await new Promise((r) => setTimeout(r, 50));
25
+ }
26
+
27
+ return null;
28
+ }
29
+
4
30
  /**
5
31
  * Gets a TFile by path or throws an error if not found.
6
32
  * Useful when you need to ensure a file exists before proceeding.
package/src/file/file.ts CHANGED
@@ -266,6 +266,50 @@ export function getUniqueFilePath(app: App, folder: string, baseName: string): s
266
266
  return fullPath;
267
267
  }
268
268
 
269
+ /**
270
+ * Generates a unique file path by appending a counter if the file already exists.
271
+ * Accepts a complete file path and iterates with a counter suffix.
272
+ *
273
+ * @param app - The Obsidian App instance
274
+ * @param filePath - Complete file path including folder, name, and extension
275
+ * @returns Unique file path that doesn't exist in the vault
276
+ *
277
+ * @example
278
+ * ```ts
279
+ * // If "folder/note.md" exists, returns "folder/note 1.md"
280
+ * const path = getUniqueFilePathFromFull(app, "folder/note.md");
281
+ *
282
+ * // Works with any extension
283
+ * const path = getUniqueFilePathFromFull(app, "assets/image.png");
284
+ * // -> "assets/image 1.png" if image.png exists
285
+ * ```
286
+ */
287
+ export const getUniqueFilePathFromFull = (app: App, filePath: string): string => {
288
+ // If file doesn't exist, return as-is
289
+ if (!app.vault.getAbstractFileByPath(filePath)) {
290
+ return filePath;
291
+ }
292
+
293
+ // Extract folder, base name, and extension
294
+ const lastSlashIndex = filePath.lastIndexOf("/");
295
+ const folderPath = lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex + 1) : "";
296
+ const fileName = lastSlashIndex !== -1 ? filePath.substring(lastSlashIndex + 1) : filePath;
297
+
298
+ const lastDotIndex = fileName.lastIndexOf(".");
299
+ const baseName = lastDotIndex !== -1 ? fileName.substring(0, lastDotIndex) : fileName;
300
+ const extension = lastDotIndex !== -1 ? fileName.substring(lastDotIndex) : "";
301
+
302
+ let counter = 1;
303
+ let uniquePath = `${folderPath}${baseName} ${counter}${extension}`;
304
+
305
+ while (app.vault.getAbstractFileByPath(uniquePath)) {
306
+ counter++;
307
+ uniquePath = `${folderPath}${baseName} ${counter}${extension}`;
308
+ }
309
+
310
+ return uniquePath;
311
+ };
312
+
269
313
  /**
270
314
  * Generates a unique file path by appending a counter if the file already exists.
271
315
  * Supports custom file extensions.
@@ -283,14 +327,8 @@ export const generateUniqueFilePath = (
283
327
  extension: string = "md"
284
328
  ): string => {
285
329
  const folderPath = folder ? `${folder}/` : "";
286
- let filePath = `${folderPath}${baseName}.${extension}`;
287
- let counter = 1;
288
-
289
- while (app.vault.getAbstractFileByPath(filePath)) {
290
- filePath = `${folderPath}${baseName} ${counter++}.${extension}`;
291
- }
292
-
293
- return filePath;
330
+ const fullPath = `${folderPath}${baseName}.${extension}`;
331
+ return getUniqueFilePathFromFull(app, fullPath);
294
332
  };
295
333
 
296
334
  // ============================================================================
@@ -0,0 +1,72 @@
1
+ import { stringify as stringifyYAML } from "yaml";
2
+
3
+ /**
4
+ * Creates file content with YAML frontmatter and body content.
5
+ * This is the atomic file creation format that prevents race conditions.
6
+ *
7
+ * @param frontmatter - Frontmatter as JSON object
8
+ * @param content - Body content (markdown text after frontmatter)
9
+ * @returns Complete file content with YAML frontmatter
10
+ */
11
+ export function createFileContentWithFrontmatter(
12
+ frontmatter: Record<string, unknown>,
13
+ content = ""
14
+ ): string {
15
+ if (!frontmatter || Object.keys(frontmatter).length === 0) {
16
+ // No frontmatter, just return content
17
+ return content;
18
+ }
19
+
20
+ // Use the yaml library to stringify frontmatter
21
+ // This handles all edge cases: special characters, multiline strings, arrays, nested objects, etc.
22
+ const yaml = stringifyYAML(frontmatter, {
23
+ // Use 2-space indentation (Obsidian standard)
24
+ indent: 2,
25
+ // Don't add document markers (--- at the end)
26
+ lineWidth: 0,
27
+ // Minimize unnecessary quotes
28
+ defaultStringType: "PLAIN",
29
+ // Handle null values explicitly
30
+ nullStr: "",
31
+ }).trim();
32
+
33
+ if (!yaml) {
34
+ // Empty frontmatter after stringification
35
+ return content;
36
+ }
37
+
38
+ // Ensure content doesn't start with extra newlines
39
+ const trimmedContent = content.replace(/^\n+/, "");
40
+
41
+ if (trimmedContent) {
42
+ return `---\n${yaml}\n---\n\n${trimmedContent}`;
43
+ }
44
+
45
+ return `---\n${yaml}\n---\n`;
46
+ }
47
+
48
+ /**
49
+ * Parses file content to extract frontmatter and body separately.
50
+ * Returns both the frontmatter object and the body content.
51
+ *
52
+ * @param fileContent - Complete file content including frontmatter
53
+ * @returns Object with frontmatter and body
54
+ */
55
+ export function parseFileContent(fileContent: string): {
56
+ frontmatter: Record<string, unknown>;
57
+ body: string;
58
+ } {
59
+ const frontmatterRegex = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/;
60
+ const match = fileContent.match(frontmatterRegex);
61
+
62
+ if (!match) {
63
+ // No frontmatter found
64
+ return { frontmatter: {}, body: fileContent };
65
+ }
66
+
67
+ const [, , body] = match;
68
+
69
+ // For this use case, we just need to split the file
70
+ // The frontmatter will be properly parsed when we read it back with Obsidian's APIs
71
+ return { frontmatter: {}, body: body.trim() };
72
+ }
package/src/file/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from "./file-utils";
5
5
  export * from "./frontmatter";
6
6
  export * from "./frontmatter-diff";
7
7
  export * from "./frontmatter-propagation";
8
+ export * from "./frontmatter-serialization";
8
9
  export * from "./link-parser";
9
10
  export * from "./property-utils";
10
11
  export * from "./templater";
@@ -1,6 +1,11 @@
1
- import { type App, TFile } from "obsidian";
1
+ import type { App, TFile } from "obsidian";
2
2
  import type { FileCreationOptions } from "./templater";
3
- import { createFromTemplate, isTemplaterAvailable } from "./templater";
3
+ import {
4
+ createFileManually,
5
+ createFromTemplate,
6
+ isTemplaterAvailable,
7
+ shouldUseTemplate,
8
+ } from "./templater";
4
9
 
5
10
  export type { FileCreationOptions };
6
11
 
@@ -27,67 +32,25 @@ export class TemplaterService {
27
32
 
28
33
  const finalFilename = filename || title;
29
34
 
30
- // If content is provided, use manual creation to preserve the content
31
35
  if (content) {
32
- return this.createManually(title, targetDirectory, finalFilename, content, frontmatter);
36
+ return createFileManually(this.app, targetDirectory, finalFilename, content, frontmatter);
33
37
  }
34
38
 
35
- // Try to use Templater if requested and available
36
- if (useTemplater && templatePath && this.shouldUseTemplate(templatePath)) {
39
+ if (useTemplater && shouldUseTemplate(this.app, templatePath)) {
37
40
  const templateFile = await createFromTemplate(
38
41
  this.app,
39
- templatePath,
42
+ templatePath!,
40
43
  targetDirectory,
41
- finalFilename
44
+ finalFilename,
45
+ false,
46
+ frontmatter
42
47
  );
43
48
 
44
49
  if (templateFile) {
45
- // Apply frontmatter if provided
46
- if (frontmatter && Object.keys(frontmatter).length > 0) {
47
- await this.app.fileManager.processFrontMatter(templateFile, (fm) => {
48
- Object.assign(fm, frontmatter);
49
- });
50
- }
51
50
  return templateFile;
52
51
  }
53
52
  }
54
-
55
53
  // Fallback to manual creation
56
- return this.createManually(title, targetDirectory, finalFilename, content, frontmatter);
57
- }
58
-
59
- private shouldUseTemplate(templatePath: string): boolean {
60
- return !!(templatePath && this.isAvailable() && this.app.vault.getFileByPath(templatePath));
61
- }
62
-
63
- private async createManually(
64
- title: string,
65
- targetDirectory: string,
66
- filename: string,
67
- customContent?: string,
68
- frontmatter?: Record<string, unknown>
69
- ): Promise<TFile> {
70
- const baseName = filename.replace(/\.md$/, "");
71
- const filePath = `${targetDirectory}/${baseName}.md`;
72
-
73
- // Check if file already exists
74
- const existingFile = this.app.vault.getAbstractFileByPath(filePath);
75
- if (existingFile instanceof TFile) {
76
- return existingFile;
77
- }
78
-
79
- // Use custom content or default
80
- const content = customContent || `# ${title}\n\n`;
81
-
82
- const file = await this.app.vault.create(filePath, content);
83
-
84
- // Apply frontmatter if provided
85
- if (frontmatter && Object.keys(frontmatter).length > 0) {
86
- await this.app.fileManager.processFrontMatter(file, (fm) => {
87
- Object.assign(fm, frontmatter);
88
- });
89
- }
90
-
91
- return file;
54
+ return createFileManually(this.app, targetDirectory, finalFilename, content, frontmatter);
92
55
  }
93
56
  }
@@ -1,4 +1,6 @@
1
1
  import { type App, Notice, normalizePath, TFile } from "obsidian";
2
+ import { waitForFileReady } from "./file-utils";
3
+ import { createFileContentWithFrontmatter } from "./frontmatter-serialization";
2
4
 
3
5
  const TEMPLATER_ID = "templater-obsidian";
4
6
 
@@ -45,12 +47,58 @@ export function isTemplaterAvailable(app: App): boolean {
45
47
  return !!instance;
46
48
  }
47
49
 
50
+ /**
51
+ * Checks if a template should be used based on availability and file existence.
52
+ */
53
+ export function shouldUseTemplate(app: App, templatePath: string | undefined): boolean {
54
+ return !!(
55
+ templatePath &&
56
+ templatePath.trim() !== "" &&
57
+ isTemplaterAvailable(app) &&
58
+ app.vault.getFileByPath(templatePath)
59
+ );
60
+ }
61
+
62
+ /**
63
+ * Creates a file manually with optional frontmatter and content.
64
+ * Returns existing file if it already exists.
65
+ */
66
+ export async function createFileManually(
67
+ app: App,
68
+ targetDirectory: string,
69
+ filename: string,
70
+ content?: string,
71
+ frontmatter?: Record<string, unknown>
72
+ ): Promise<TFile> {
73
+ const baseName = filename.replace(/\.md$/, "");
74
+ const filePath = `${targetDirectory}/${baseName}.md`;
75
+
76
+ // Check if file already exists
77
+ const existingFile = app.vault.getAbstractFileByPath(filePath);
78
+ if (existingFile instanceof TFile) {
79
+ return existingFile;
80
+ }
81
+
82
+ const bodyContent = content || "";
83
+
84
+ let fileContent: string;
85
+ if (frontmatter && Object.keys(frontmatter).length > 0) {
86
+ fileContent = createFileContentWithFrontmatter(frontmatter, bodyContent);
87
+ } else {
88
+ fileContent = bodyContent;
89
+ }
90
+
91
+ const file = await app.vault.create(filePath, fileContent);
92
+ return file;
93
+ }
94
+
48
95
  export async function createFromTemplate(
49
96
  app: App,
50
97
  templatePath: string,
51
98
  targetFolder?: string,
52
99
  filename?: string,
53
- openNewNote = false
100
+ openNewNote = false,
101
+ frontmatter?: Record<string, unknown>
54
102
  ): Promise<TFile | null> {
55
103
  const templater = await waitForTemplater(app);
56
104
  if (!templater) {
@@ -76,7 +124,22 @@ export async function createFromTemplate(
76
124
  openNewNote
77
125
  );
78
126
 
79
- return newFile ?? null;
127
+ if (!newFile) {
128
+ return null;
129
+ }
130
+
131
+ if (frontmatter && Object.keys(frontmatter).length > 0) {
132
+ const readyFile = await waitForFileReady(app, newFile.path);
133
+
134
+ if (readyFile) {
135
+ await app.fileManager.processFrontMatter(readyFile, (fm) => {
136
+ Object.assign(fm, frontmatter);
137
+ });
138
+ return readyFile;
139
+ }
140
+ }
141
+
142
+ return newFile;
80
143
  } catch (error) {
81
144
  console.error("Error creating file from template:", error);
82
145
  new Notice("Error creating file from template. Please ensure the template file is valid.");
@@ -92,40 +155,27 @@ export async function createFileWithTemplate(
92
155
  options;
93
156
 
94
157
  const finalFilename = filename || title;
95
- const baseName = finalFilename.replace(/\.md$/, "");
96
- const filePath = normalizePath(`${targetDirectory}/${baseName}.md`);
97
158
 
98
- const existingFile = app.vault.getAbstractFileByPath(filePath);
99
- if (existingFile instanceof TFile) {
100
- return existingFile;
159
+ // If content is provided, use manual creation to preserve the content
160
+ if (content) {
161
+ return createFileManually(app, targetDirectory, finalFilename, content, frontmatter);
101
162
  }
102
163
 
103
- if (useTemplater && templatePath && templatePath.trim() !== "" && isTemplaterAvailable(app)) {
164
+ // Try to use template if requested and available
165
+ if (useTemplater && shouldUseTemplate(app, templatePath)) {
104
166
  const templateFile = await createFromTemplate(
105
167
  app,
106
- templatePath,
168
+ templatePath!,
107
169
  targetDirectory,
108
- finalFilename
170
+ finalFilename,
171
+ false,
172
+ frontmatter
109
173
  );
110
174
 
111
175
  if (templateFile) {
112
- if (frontmatter && Object.keys(frontmatter).length > 0) {
113
- await app.fileManager.processFrontMatter(templateFile, (fm) => {
114
- Object.assign(fm, frontmatter);
115
- });
116
- }
117
176
  return templateFile;
118
177
  }
119
178
  }
120
179
 
121
- const fileContent = content || "";
122
- const file = await app.vault.create(filePath, fileContent);
123
-
124
- if (frontmatter && Object.keys(frontmatter).length > 0) {
125
- await app.fileManager.processFrontMatter(file, (fm) => {
126
- Object.assign(fm, frontmatter);
127
- });
128
- }
129
-
130
- return file;
180
+ return createFileManually(app, targetDirectory, finalFilename, content, frontmatter);
131
181
  }