@real1ty-obsidian-plugins/utils 2.11.0 → 2.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/components/index.d.ts +2 -0
  2. package/dist/components/index.d.ts.map +1 -0
  3. package/dist/components/index.js +2 -0
  4. package/dist/components/index.js.map +1 -0
  5. package/dist/components/input-managers/base.d.ts +30 -0
  6. package/dist/components/input-managers/base.d.ts.map +1 -0
  7. package/dist/components/input-managers/base.js +115 -0
  8. package/dist/components/input-managers/base.js.map +1 -0
  9. package/dist/components/input-managers/expression.d.ts +12 -0
  10. package/dist/components/input-managers/expression.d.ts.map +1 -0
  11. package/dist/components/input-managers/expression.js +56 -0
  12. package/dist/components/input-managers/expression.js.map +1 -0
  13. package/dist/components/input-managers/index.d.ts +4 -0
  14. package/dist/components/input-managers/index.d.ts.map +1 -0
  15. package/dist/components/input-managers/index.js +4 -0
  16. package/dist/components/input-managers/index.js.map +1 -0
  17. package/dist/components/input-managers/search.d.ts +6 -0
  18. package/dist/components/input-managers/search.d.ts.map +1 -0
  19. package/dist/components/input-managers/search.js +16 -0
  20. package/dist/components/input-managers/search.js.map +1 -0
  21. package/dist/core/evaluator/base.d.ts.map +1 -1
  22. package/dist/core/evaluator/base.js +12 -3
  23. package/dist/core/evaluator/base.js.map +1 -1
  24. package/dist/core/index.d.ts +1 -0
  25. package/dist/core/index.d.ts.map +1 -1
  26. package/dist/core/index.js +1 -0
  27. package/dist/core/index.js.map +1 -1
  28. package/dist/core/property-renderer.d.ts +9 -0
  29. package/dist/core/property-renderer.d.ts.map +1 -0
  30. package/dist/core/property-renderer.js +42 -0
  31. package/dist/core/property-renderer.js.map +1 -0
  32. package/dist/file/file-utils.d.ts +28 -0
  33. package/dist/file/file-utils.d.ts.map +1 -0
  34. package/dist/file/file-utils.js +55 -0
  35. package/dist/file/file-utils.js.map +1 -0
  36. package/dist/file/file.d.ts +51 -1
  37. package/dist/file/file.d.ts.map +1 -1
  38. package/dist/file/file.js +69 -10
  39. package/dist/file/file.js.map +1 -1
  40. package/dist/file/index.d.ts +1 -0
  41. package/dist/file/index.d.ts.map +1 -1
  42. package/dist/file/index.js +1 -0
  43. package/dist/file/index.js.map +1 -1
  44. package/dist/file/link-parser.d.ts +28 -0
  45. package/dist/file/link-parser.d.ts.map +1 -1
  46. package/dist/file/link-parser.js +59 -0
  47. package/dist/file/link-parser.js.map +1 -1
  48. package/dist/index.d.ts +1 -0
  49. package/dist/index.d.ts.map +1 -1
  50. package/dist/index.js +2 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/string/filename-utils.d.ts +46 -0
  53. package/dist/string/filename-utils.d.ts.map +1 -0
  54. package/dist/string/filename-utils.js +65 -0
  55. package/dist/string/filename-utils.js.map +1 -0
  56. package/dist/string/index.d.ts +1 -0
  57. package/dist/string/index.d.ts.map +1 -1
  58. package/dist/string/index.js +1 -0
  59. package/dist/string/index.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/components/index.ts +1 -0
  62. package/src/components/input-managers/base.ts +150 -0
  63. package/src/components/input-managers/expression.ts +92 -0
  64. package/src/components/input-managers/index.ts +3 -0
  65. package/src/components/input-managers/search.ts +25 -0
  66. package/src/core/evaluator/base.ts +15 -3
  67. package/src/core/index.ts +1 -0
  68. package/src/core/property-renderer.ts +62 -0
  69. package/src/file/file-utils.ts +67 -0
  70. package/src/file/file.ts +90 -8
  71. package/src/file/index.ts +1 -0
  72. package/src/file/link-parser.ts +71 -0
  73. package/src/index.ts +2 -0
  74. package/src/string/filename-utils.ts +77 -0
  75. package/src/string/index.ts +1 -0
@@ -257,7 +257,57 @@ export declare function getChildrenByFolder(app: App, filePath: string): string[
257
257
  * ```
258
258
  */
259
259
  export declare function findRootNodesInFolder(app: App, folderPath: string): string[];
260
- export declare const sanitizeForFilename: (input: string) => string;
260
+ export interface SanitizeFilenameOptions {
261
+ /**
262
+ * Style of sanitization to apply.
263
+ * - "kebab": Convert to lowercase, replace spaces with hyphens (default, backwards compatible)
264
+ * - "preserve": Preserve spaces and case, only remove invalid characters
265
+ */
266
+ style?: "kebab" | "preserve";
267
+ }
268
+ /**
269
+ * Sanitizes a string for use as a filename.
270
+ * Defaults to kebab-case style for backwards compatibility.
271
+ *
272
+ * @param input - String to sanitize
273
+ * @param options - Sanitization options
274
+ * @returns Sanitized filename string
275
+ *
276
+ * @example
277
+ * // Default kebab-case style (backwards compatible)
278
+ * sanitizeForFilename("My File Name") // "my-file-name"
279
+ *
280
+ * // Preserve spaces and case
281
+ * sanitizeForFilename("My File Name", { style: "preserve" }) // "My File Name"
282
+ */
283
+ export declare const sanitizeForFilename: (input: string, options?: SanitizeFilenameOptions) => string;
284
+ /**
285
+ * Sanitizes filename using kebab-case style.
286
+ * - Removes invalid characters
287
+ * - Converts to lowercase
288
+ * - Replaces spaces with hyphens
289
+ *
290
+ * Best for: CLI tools, URLs, slugs, technical files
291
+ *
292
+ * @example
293
+ * sanitizeFilenameKebabCase("My File Name") // "my-file-name"
294
+ * sanitizeFilenameKebabCase("Travel Around The World") // "travel-around-the-world"
295
+ */
296
+ export declare const sanitizeFilenameKebabCase: (input: string) => string;
297
+ /**
298
+ * Sanitizes filename while preserving spaces and case.
299
+ * - Removes invalid characters only
300
+ * - Preserves spaces and original casing
301
+ * - Removes trailing dots (Windows compatibility)
302
+ *
303
+ * Best for: Note titles, human-readable filenames, Obsidian notes
304
+ *
305
+ * @example
306
+ * sanitizeFilenamePreserveSpaces("My File Name") // "My File Name"
307
+ * sanitizeFilenamePreserveSpaces("Travel Around The World") // "Travel Around The World"
308
+ * sanitizeFilenamePreserveSpaces("File<Invalid>Chars") // "FileInvalidChars"
309
+ */
310
+ export declare const sanitizeFilenamePreserveSpaces: (input: string) => string;
261
311
  export declare const getFilenameFromPath: (filePath: string) => string;
262
312
  export declare const isFileInConfiguredDirectory: (filePath: string, directory: string) => boolean;
263
313
  //# sourceMappingURL=file.d.ts.map
@@ -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
@@ -445,16 +445,75 @@ export function getChildrenByFolder(app, filePath) {
445
445
  export function findRootNodesInFolder(app, folderPath) {
446
446
  return getFilesInFolder(app, folderPath).map((file) => file.path);
447
447
  }
448
- // ============================================================================
449
- // Legacy Utility Functions (kept for backwards compatibility)
450
- // ============================================================================
451
- export const sanitizeForFilename = (input) => {
452
- return input
453
- .replace(/[<>:"/\\|?*]/g, "") // Remove invalid filename characters
454
- .replace(/\s+/g, "-") // Replace spaces with hyphens
455
- .replace(/-+/g, "-") // Replace multiple hyphens with single
456
- .replace(/^-|-$/g, "") // Remove leading/trailing hyphens
457
- .toLowerCase();
448
+ /**
449
+ * Sanitizes a string for use as a filename.
450
+ * Defaults to kebab-case style for backwards compatibility.
451
+ *
452
+ * @param input - String to sanitize
453
+ * @param options - Sanitization options
454
+ * @returns Sanitized filename string
455
+ *
456
+ * @example
457
+ * // Default kebab-case style (backwards compatible)
458
+ * sanitizeForFilename("My File Name") // "my-file-name"
459
+ *
460
+ * // Preserve spaces and case
461
+ * sanitizeForFilename("My File Name", { style: "preserve" }) // "My File Name"
462
+ */
463
+ export const sanitizeForFilename = (input, options = {}) => {
464
+ const { style = "kebab" } = options;
465
+ if (style === "preserve") {
466
+ return sanitizeFilenamePreserveSpaces(input);
467
+ }
468
+ // Default: kebab-case style (legacy behavior)
469
+ return sanitizeFilenameKebabCase(input);
470
+ };
471
+ /**
472
+ * Sanitizes filename using kebab-case style.
473
+ * - Removes invalid characters
474
+ * - Converts to lowercase
475
+ * - Replaces spaces with hyphens
476
+ *
477
+ * Best for: CLI tools, URLs, slugs, technical files
478
+ *
479
+ * @example
480
+ * sanitizeFilenameKebabCase("My File Name") // "my-file-name"
481
+ * sanitizeFilenameKebabCase("Travel Around The World") // "travel-around-the-world"
482
+ */
483
+ export const sanitizeFilenameKebabCase = (input) => {
484
+ return (input
485
+ // Remove invalid filename characters
486
+ .replace(/[<>:"/\\|?*]/g, "")
487
+ // Replace spaces with hyphens
488
+ .replace(/\s+/g, "-")
489
+ // Replace multiple hyphens with single
490
+ .replace(/-+/g, "-")
491
+ // Remove leading/trailing hyphens
492
+ .replace(/^-|-$/g, "")
493
+ // Convert to lowercase
494
+ .toLowerCase());
495
+ };
496
+ /**
497
+ * Sanitizes filename while preserving spaces and case.
498
+ * - Removes invalid characters only
499
+ * - Preserves spaces and original casing
500
+ * - Removes trailing dots (Windows compatibility)
501
+ *
502
+ * Best for: Note titles, human-readable filenames, Obsidian notes
503
+ *
504
+ * @example
505
+ * sanitizeFilenamePreserveSpaces("My File Name") // "My File Name"
506
+ * sanitizeFilenamePreserveSpaces("Travel Around The World") // "Travel Around The World"
507
+ * sanitizeFilenamePreserveSpaces("File<Invalid>Chars") // "FileInvalidChars"
508
+ */
509
+ export const sanitizeFilenamePreserveSpaces = (input) => {
510
+ return (input
511
+ // Remove invalid filename characters (cross-platform compatibility)
512
+ .replace(/[<>:"/\\|?*]/g, "")
513
+ // Remove trailing dots (invalid on Windows)
514
+ .replace(/\.+$/g, "")
515
+ // Remove leading/trailing whitespace
516
+ .trim());
458
517
  };
459
518
  export const getFilenameFromPath = (filePath) => {
460
519
  return filePath.split("/").pop() || "Unknown";
@@ -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// Legacy Utility Functions (kept for backwards compatibility)\n// ============================================================================\n\nexport const sanitizeForFilename = (input: string): string => {\n\treturn input\n\t\t.replace(/[<>:\"/\\\\|?*]/g, \"\") // Remove invalid filename characters\n\t\t.replace(/\\s+/g, \"-\") // Replace spaces with hyphens\n\t\t.replace(/-+/g, \"-\") // Replace multiple hyphens with single\n\t\t.replace(/^-|-$/g, \"\") // Remove leading/trailing hyphens\n\t\t.toLowerCase();\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 * 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,6 +1,7 @@
1
1
  export * from "./child-reference";
2
2
  export * from "./file";
3
3
  export * from "./file-operations";
4
+ export * from "./file-utils";
4
5
  export * from "./frontmatter";
5
6
  export * from "./link-parser";
6
7
  export * from "./property-utils";
@@ -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,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC"}
@@ -1,6 +1,7 @@
1
1
  export * from "./child-reference";
2
2
  export * from "./file";
3
3
  export * from "./file-operations";
4
+ export * from "./file-utils";
4
5
  export * from "./frontmatter";
5
6
  export * from "./link-parser";
6
7
  export * from "./property-utils";
@@ -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,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./child-reference\";\nexport * from \"./file\";\nexport * from \"./file-operations\";\nexport * from \"./frontmatter\";\nexport * from \"./link-parser\";\nexport * from \"./property-utils\";\nexport * from \"./templater\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/file/index.ts"],"names":[],"mappings":"AAAA,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC;AAC7B,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC","sourcesContent":["export * from \"./child-reference\";\nexport * from \"./file\";\nexport * from \"./file-operations\";\nexport * from \"./file-utils\";\nexport * from \"./frontmatter\";\nexport * from \"./link-parser\";\nexport * from \"./property-utils\";\nexport * from \"./templater\";\n"]}
@@ -32,4 +32,32 @@ export declare function parsePropertyLinks(value: string | string[] | undefined)
32
32
  * @returns The formatted wiki link with display name alias
33
33
  */
34
34
  export declare function formatWikiLink(filePath: string): string;
35
+ /**
36
+ * Represents a parsed Obsidian link with its components
37
+ */
38
+ export interface ObsidianLink {
39
+ raw: string;
40
+ path: string;
41
+ alias: string;
42
+ }
43
+ /**
44
+ * Checks if a value is an Obsidian internal link in the format [[...]]
45
+ */
46
+ export declare function isObsidianLink(value: unknown): boolean;
47
+ /**
48
+ * Parses an Obsidian internal link and extracts its components
49
+ *
50
+ * Supports both formats:
51
+ * - Simple: [[Page Name]]
52
+ * - With alias: [[Path/To/Page|Display Name]]
53
+ */
54
+ export declare function parseObsidianLink(linkString: string): ObsidianLink | null;
55
+ /**
56
+ * Gets the display alias from an Obsidian link
57
+ */
58
+ export declare function getObsidianLinkAlias(linkString: string): string;
59
+ /**
60
+ * Gets the file path from an Obsidian link
61
+ */
62
+ export declare function getObsidianLinkPath(linkString: string): string;
35
63
  //# sourceMappingURL=link-parser.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"link-parser.d.ts","sourceRoot":"","sources":["../../src/file/link-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAU/D,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAezD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAQjF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqBvD"}
1
+ {"version":3,"file":"link-parser.d.ts","sourceRoot":"","sources":["../../src/file/link-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,uBAAuB,GAAI,MAAM,MAAM,KAAG,MAAM,GAAG,IAU/D,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAezD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,GAAG,MAAM,EAAE,CAQjF;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAqBvD;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAItD;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CA4BzE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG/D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG9D"}
@@ -75,4 +75,63 @@ export function formatWikiLink(filePath) {
75
75
  }
76
76
  return `[[${trimmed}|${displayName}]]`;
77
77
  }
78
+ /**
79
+ * Checks if a value is an Obsidian internal link in the format [[...]]
80
+ */
81
+ export function isObsidianLink(value) {
82
+ if (typeof value !== "string")
83
+ return false;
84
+ const trimmed = value.trim();
85
+ return /^\[\[.+\]\]$/.test(trimmed);
86
+ }
87
+ /**
88
+ * Parses an Obsidian internal link and extracts its components
89
+ *
90
+ * Supports both formats:
91
+ * - Simple: [[Page Name]]
92
+ * - With alias: [[Path/To/Page|Display Name]]
93
+ */
94
+ export function parseObsidianLink(linkString) {
95
+ var _a;
96
+ if (!isObsidianLink(linkString))
97
+ return null;
98
+ const trimmed = linkString.trim();
99
+ const linkContent = (_a = trimmed.match(/^\[\[(.+?)\]\]$/)) === null || _a === void 0 ? void 0 : _a[1];
100
+ if (!linkContent)
101
+ return null;
102
+ // Handle pipe syntax: [[path|display]]
103
+ if (linkContent.includes("|")) {
104
+ const parts = linkContent.split("|");
105
+ const path = parts[0].trim();
106
+ const alias = parts.slice(1).join("|").trim(); // Handle multiple pipes
107
+ return {
108
+ raw: trimmed,
109
+ path,
110
+ alias,
111
+ };
112
+ }
113
+ // Simple format: [[path]]
114
+ const path = linkContent.trim();
115
+ return {
116
+ raw: trimmed,
117
+ path,
118
+ alias: path,
119
+ };
120
+ }
121
+ /**
122
+ * Gets the display alias from an Obsidian link
123
+ */
124
+ export function getObsidianLinkAlias(linkString) {
125
+ var _a;
126
+ const parsed = parseObsidianLink(linkString);
127
+ return (_a = parsed === null || parsed === void 0 ? void 0 : parsed.alias) !== null && _a !== void 0 ? _a : linkString;
128
+ }
129
+ /**
130
+ * Gets the file path from an Obsidian link
131
+ */
132
+ export function getObsidianLinkPath(linkString) {
133
+ var _a;
134
+ const parsed = parseObsidianLink(linkString);
135
+ return (_a = parsed === null || parsed === void 0 ? void 0 : parsed.path) !== null && _a !== void 0 ? _a : linkString;
136
+ }
78
137
  //# sourceMappingURL=link-parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"link-parser.js","sourceRoot":"","sources":["../../src/file/link-parser.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,IAAY,EAAiB,EAAE;IACtE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,8BAA8B,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,QAAQ,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAE3B,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAEpE,OAAO,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ,KAAK,CAAC;AAC/D,CAAC,CAAC;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,aAAa,CAAC,IAAY;IACzC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,IAAI,CAAC;IACb,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAE5B,0DAA0D;IAC1D,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAEhE,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,IAAI,CAAC;IACb,CAAC;IAED,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AACxB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAAoC;IACtE,IAAI,CAAC,KAAK,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAErD,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AACjG,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB;IAC9C,IAAI,CAAC,QAAQ,IAAI,OAAO,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC/C,OAAO,EAAE,CAAC;IACX,CAAC;IAED,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,EAAE,CAAC;IAEhC,IAAI,CAAC,OAAO,EAAE,CAAC;QACd,OAAO,EAAE,CAAC;IACX,CAAC;IAED,uDAAuD;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACpC,MAAM,WAAW,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAElD,wDAAwD;IACxD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,KAAK,OAAO,IAAI,CAAC;IACzB,CAAC;IAED,OAAO,KAAK,OAAO,IAAI,WAAW,IAAI,CAAC;AACxC,CAAC","sourcesContent":["/**\n * Handles different link formats and edge cases:\n * [[FileName]] -> FileName.md\n * [[Folder/FileName]] -> Folder/FileName.md\n * [[Folder/FileName|DisplayName]] -> Folder/FileName.md\n * Normalizes paths and handles malformed links gracefully.\n */\nexport const extractFilePathFromLink = (link: string): string | null => {\n\tconst match = link.match(/\\[\\[([^|\\]]+?)(?:\\|.*?)?\\]\\]/);\n\tif (!match) return null;\n\n\tconst filePath = match[1].trim();\n\tif (!filePath) return null;\n\n\tif (filePath.includes(\"[[\") || filePath.includes(\"]]\")) return null;\n\n\treturn filePath.endsWith(\".md\") ? filePath : `${filePath}.md`;\n};\n\n/**\n * Parses an Obsidian wiki link to extract the file path.\n * Handles formats like:\n * - [[path/to/file]]\n * - [[path/to/file|Display Name]]\n *\n * @param link - The wiki link string\n * @returns The file path without the [[ ]] brackets, or null if invalid\n */\nexport function parseWikiLink(link: string): string | null {\n\tif (!link || typeof link !== \"string\") {\n\t\treturn null;\n\t}\n\n\tconst trimmed = link.trim();\n\n\t// Match [[path/to/file]] or [[path/to/file|Display Name]]\n\tconst match = trimmed.match(/^\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]$/);\n\n\tif (!match) {\n\t\treturn null;\n\t}\n\n\treturn match[1].trim();\n}\n\n/**\n * Parses a property value that can be a single link or an array of links.\n * Extracts file paths from all valid wiki links.\n *\n * @param value - The property value (string, string[], or undefined)\n * @returns Array of file paths, empty if no valid links found\n */\nexport function parsePropertyLinks(value: string | string[] | undefined): string[] {\n\tif (!value) {\n\t\treturn [];\n\t}\n\n\tconst links = Array.isArray(value) ? value : [value];\n\n\treturn links.map((link) => parseWikiLink(link)).filter((path): path is string => path !== null);\n}\n\n/**\n * Formats a file path as an Obsidian wiki link with display name.\n * Example: \"Projects/MyProject\" -> \"[[Projects/MyProject|MyProject]]\"\n *\n * @param filePath - The file path to format\n * @returns The formatted wiki link with display name alias\n */\nexport function formatWikiLink(filePath: string): string {\n\tif (!filePath || typeof filePath !== \"string\") {\n\t\treturn \"\";\n\t}\n\n\tconst trimmed = filePath.trim();\n\n\tif (!trimmed) {\n\t\treturn \"\";\n\t}\n\n\t// Extract the filename (last segment after the last /)\n\tconst segments = trimmed.split(\"/\");\n\tconst displayName = segments[segments.length - 1];\n\n\t// If there's no path separator, just return simple link\n\tif (segments.length === 1) {\n\t\treturn `[[${trimmed}]]`;\n\t}\n\n\treturn `[[${trimmed}|${displayName}]]`;\n}\n"]}
1
+ {"version":3,"file":"link-parser.js","sourceRoot":"","sources":["../../src/file/link-parser.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Handles different link formats and edge cases:\n * [[FileName]] -> FileName.md\n * [[Folder/FileName]] -> Folder/FileName.md\n * [[Folder/FileName|DisplayName]] -> Folder/FileName.md\n * Normalizes paths and handles malformed links gracefully.\n */\nexport const extractFilePathFromLink = (link: string): string | null => {\n\tconst match = link.match(/\\[\\[([^|\\]]+?)(?:\\|.*?)?\\]\\]/);\n\tif (!match) return null;\n\n\tconst filePath = match[1].trim();\n\tif (!filePath) return null;\n\n\tif (filePath.includes(\"[[\") || filePath.includes(\"]]\")) return null;\n\n\treturn filePath.endsWith(\".md\") ? filePath : `${filePath}.md`;\n};\n\n/**\n * Parses an Obsidian wiki link to extract the file path.\n * Handles formats like:\n * - [[path/to/file]]\n * - [[path/to/file|Display Name]]\n *\n * @param link - The wiki link string\n * @returns The file path without the [[ ]] brackets, or null if invalid\n */\nexport function parseWikiLink(link: string): string | null {\n\tif (!link || typeof link !== \"string\") {\n\t\treturn null;\n\t}\n\n\tconst trimmed = link.trim();\n\n\t// Match [[path/to/file]] or [[path/to/file|Display Name]]\n\tconst match = trimmed.match(/^\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]$/);\n\n\tif (!match) {\n\t\treturn null;\n\t}\n\n\treturn match[1].trim();\n}\n\n/**\n * Parses a property value that can be a single link or an array of links.\n * Extracts file paths from all valid wiki links.\n *\n * @param value - The property value (string, string[], or undefined)\n * @returns Array of file paths, empty if no valid links found\n */\nexport function parsePropertyLinks(value: string | string[] | undefined): string[] {\n\tif (!value) {\n\t\treturn [];\n\t}\n\n\tconst links = Array.isArray(value) ? value : [value];\n\n\treturn links.map((link) => parseWikiLink(link)).filter((path): path is string => path !== null);\n}\n\n/**\n * Formats a file path as an Obsidian wiki link with display name.\n * Example: \"Projects/MyProject\" -> \"[[Projects/MyProject|MyProject]]\"\n *\n * @param filePath - The file path to format\n * @returns The formatted wiki link with display name alias\n */\nexport function formatWikiLink(filePath: string): string {\n\tif (!filePath || typeof filePath !== \"string\") {\n\t\treturn \"\";\n\t}\n\n\tconst trimmed = filePath.trim();\n\n\tif (!trimmed) {\n\t\treturn \"\";\n\t}\n\n\t// Extract the filename (last segment after the last /)\n\tconst segments = trimmed.split(\"/\");\n\tconst displayName = segments[segments.length - 1];\n\n\t// If there's no path separator, just return simple link\n\tif (segments.length === 1) {\n\t\treturn `[[${trimmed}]]`;\n\t}\n\n\treturn `[[${trimmed}|${displayName}]]`;\n}\n\n/**\n * Represents a parsed Obsidian link with its components\n */\nexport interface ObsidianLink {\n\traw: string;\n\tpath: string;\n\talias: string;\n}\n\n/**\n * Checks if a value is an Obsidian internal link in the format [[...]]\n */\nexport function isObsidianLink(value: unknown): boolean {\n\tif (typeof value !== \"string\") return false;\n\tconst trimmed = value.trim();\n\treturn /^\\[\\[.+\\]\\]$/.test(trimmed);\n}\n\n/**\n * Parses an Obsidian internal link and extracts its components\n *\n * Supports both formats:\n * - Simple: [[Page Name]]\n * - With alias: [[Path/To/Page|Display Name]]\n */\nexport function parseObsidianLink(linkString: string): ObsidianLink | null {\n\tif (!isObsidianLink(linkString)) return null;\n\n\tconst trimmed = linkString.trim();\n\tconst linkContent = trimmed.match(/^\\[\\[(.+?)\\]\\]$/)?.[1];\n\n\tif (!linkContent) return null;\n\n\t// Handle pipe syntax: [[path|display]]\n\tif (linkContent.includes(\"|\")) {\n\t\tconst parts = linkContent.split(\"|\");\n\t\tconst path = parts[0].trim();\n\t\tconst alias = parts.slice(1).join(\"|\").trim(); // Handle multiple pipes\n\n\t\treturn {\n\t\t\traw: trimmed,\n\t\t\tpath,\n\t\t\talias,\n\t\t};\n\t}\n\n\t// Simple format: [[path]]\n\tconst path = linkContent.trim();\n\treturn {\n\t\traw: trimmed,\n\t\tpath,\n\t\talias: path,\n\t};\n}\n\n/**\n * Gets the display alias from an Obsidian link\n */\nexport function getObsidianLinkAlias(linkString: string): string {\n\tconst parsed = parseObsidianLink(linkString);\n\treturn parsed?.alias ?? linkString;\n}\n\n/**\n * Gets the file path from an Obsidian link\n */\nexport function getObsidianLinkPath(linkString: string): string {\n\tconst parsed = parseObsidianLink(linkString);\n\treturn parsed?.path ?? linkString;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./async";
2
+ export * from "./components";
2
3
  export * from "./core";
3
4
  export * from "./date";
4
5
  export * from "./file";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AAExB,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAE3B,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,SAAS,CAAC;AAExB,cAAc,cAAc,CAAC;AAE7B,cAAc,QAAQ,CAAC;AAGvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,QAAQ,CAAC;AAEvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAE3B,cAAc,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // Settings
2
2
  // Async utilities
3
3
  export * from "./async";
4
+ // Components
5
+ export * from "./components";
4
6
  // Core utilities
5
7
  export * from "./core";
6
8
  // Date operations
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AAEX,kBAAkB;AAClB,cAAc,SAAS,CAAC;AACxB,iBAAiB;AACjB,cAAc,QAAQ,CAAC;AAEvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,mBAAmB;AACnB,cAAc,UAAU,CAAC","sourcesContent":["// Settings\n\n// Async utilities\nexport * from \"./async\";\n// Core utilities\nexport * from \"./core\";\n\n// Date operations\nexport * from \"./date\";\n// File operations\nexport * from \"./file\";\n// Input utilities\nexport * from \"./inputs\";\nexport * from \"./settings\";\n// String utilities\nexport * from \"./string\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AAEX,kBAAkB;AAClB,cAAc,SAAS,CAAC;AACxB,aAAa;AACb,cAAc,cAAc,CAAC;AAC7B,iBAAiB;AACjB,cAAc,QAAQ,CAAC;AAEvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,QAAQ,CAAC;AACvB,kBAAkB;AAClB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,mBAAmB;AACnB,cAAc,UAAU,CAAC","sourcesContent":["// Settings\n\n// Async utilities\nexport * from \"./async\";\n// Components\nexport * from \"./components\";\n// Core utilities\nexport * from \"./core\";\n\n// Date operations\nexport * from \"./date\";\n// File operations\nexport * from \"./file\";\n// Input utilities\nexport * from \"./inputs\";\nexport * from \"./settings\";\n// String utilities\nexport * from \"./string\";\n"]}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Normalizes a directory path for consistent comparison.
3
+ *
4
+ * - Trims whitespace
5
+ * - Removes leading and trailing slashes
6
+ * - Converts empty/whitespace-only strings to empty string
7
+ *
8
+ * Examples:
9
+ * - "tasks/" → "tasks"
10
+ * - "/tasks" → "tasks"
11
+ * - "/tasks/" → "tasks"
12
+ * - " tasks " → "tasks"
13
+ * - "" → ""
14
+ * - " " → ""
15
+ * - "tasks/homework" → "tasks/homework"
16
+ */
17
+ export declare const normalizeDirectoryPath: (directory: string) => string;
18
+ /**
19
+ * Extracts the date and suffix (everything after the date) from a physical instance filename.
20
+ * Physical instance format: "[title] [date]-[ZETTELID]"
21
+ *
22
+ * @param basename - The filename without extension
23
+ * @returns Object with dateStr and suffix, or null if no date found
24
+ *
25
+ * @example
26
+ * extractDateAndSuffix("My Event 2025-01-15-ABC123") // { dateStr: "2025-01-15", suffix: "-ABC123" }
27
+ * extractDateAndSuffix("Invalid filename") // null
28
+ */
29
+ export declare const extractDateAndSuffix: (basename: string) => {
30
+ dateStr: string;
31
+ suffix: string;
32
+ } | null;
33
+ /**
34
+ * Rebuilds a physical instance filename with a new title while preserving the date and zettel ID.
35
+ * Physical instance format: "[title] [date]-[ZETTELID]"
36
+ *
37
+ * @param currentBasename - Current filename without extension
38
+ * @param newTitle - New title (with or without zettel ID - will be stripped)
39
+ * @returns New filename, or null if current filename format is invalid
40
+ *
41
+ * @example
42
+ * rebuildPhysicalInstanceFilename("Old Title 2025-01-15-ABC123", "New Title-XYZ789")
43
+ * // Returns: "New Title 2025-01-15-ABC123"
44
+ */
45
+ export declare const rebuildPhysicalInstanceFilename: (currentBasename: string, newTitle: string) => string | null;
46
+ //# sourceMappingURL=filename-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"filename-utils.d.ts","sourceRoot":"","sources":["../../src/string/filename-utils.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,sBAAsB,GAAI,WAAW,MAAM,KAAG,MAE1D,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,oBAAoB,GAChC,UAAU,MAAM,KACd;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAWxC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,+BAA+B,GAC3C,iBAAiB,MAAM,EACvB,UAAU,MAAM,KACd,MAAM,GAAG,IAaX,CAAC"}
@@ -0,0 +1,65 @@
1
+ import { sanitizeFilenamePreserveSpaces } from "../file/file";
2
+ /**
3
+ * Normalizes a directory path for consistent comparison.
4
+ *
5
+ * - Trims whitespace
6
+ * - Removes leading and trailing slashes
7
+ * - Converts empty/whitespace-only strings to empty string
8
+ *
9
+ * Examples:
10
+ * - "tasks/" → "tasks"
11
+ * - "/tasks" → "tasks"
12
+ * - "/tasks/" → "tasks"
13
+ * - " tasks " → "tasks"
14
+ * - "" → ""
15
+ * - " " → ""
16
+ * - "tasks/homework" → "tasks/homework"
17
+ */
18
+ export const normalizeDirectoryPath = (directory) => {
19
+ return directory.trim().replace(/^\/+|\/+$/g, "");
20
+ };
21
+ /**
22
+ * Extracts the date and suffix (everything after the date) from a physical instance filename.
23
+ * Physical instance format: "[title] [date]-[ZETTELID]"
24
+ *
25
+ * @param basename - The filename without extension
26
+ * @returns Object with dateStr and suffix, or null if no date found
27
+ *
28
+ * @example
29
+ * extractDateAndSuffix("My Event 2025-01-15-ABC123") // { dateStr: "2025-01-15", suffix: "-ABC123" }
30
+ * extractDateAndSuffix("Invalid filename") // null
31
+ */
32
+ export const extractDateAndSuffix = (basename) => {
33
+ const dateMatch = basename.match(/(\d{4}-\d{2}-\d{2})/);
34
+ if (!dateMatch) {
35
+ return null;
36
+ }
37
+ const dateStr = dateMatch[1];
38
+ const dateIndex = basename.indexOf(dateStr);
39
+ const suffix = basename.substring(dateIndex + dateStr.length);
40
+ return { dateStr, suffix };
41
+ };
42
+ /**
43
+ * Rebuilds a physical instance filename with a new title while preserving the date and zettel ID.
44
+ * Physical instance format: "[title] [date]-[ZETTELID]"
45
+ *
46
+ * @param currentBasename - Current filename without extension
47
+ * @param newTitle - New title (with or without zettel ID - will be stripped)
48
+ * @returns New filename, or null if current filename format is invalid
49
+ *
50
+ * @example
51
+ * rebuildPhysicalInstanceFilename("Old Title 2025-01-15-ABC123", "New Title-XYZ789")
52
+ * // Returns: "New Title 2025-01-15-ABC123"
53
+ */
54
+ export const rebuildPhysicalInstanceFilename = (currentBasename, newTitle) => {
55
+ const dateAndSuffix = extractDateAndSuffix(currentBasename);
56
+ if (!dateAndSuffix) {
57
+ return null;
58
+ }
59
+ const { dateStr, suffix } = dateAndSuffix;
60
+ // Remove any zettel ID from the new title (just in case)
61
+ const newTitleClean = newTitle.replace(/-[A-Z0-9]{6}$/, "");
62
+ const newTitleSanitized = sanitizeFilenamePreserveSpaces(newTitleClean);
63
+ return `${newTitleSanitized} ${dateStr}${suffix}`;
64
+ };
65
+ //# sourceMappingURL=filename-utils.js.map