( root: string, rel: string, nextTitle: string )
| 3105 | } |
| 3106 | |
| 3107 | export async function renameNote( |
| 3108 | root: string, |
| 3109 | rel: string, |
| 3110 | nextTitle: string |
| 3111 | ): Promise<NoteMeta> { |
| 3112 | const abs = resolveSafe(root, rel) |
| 3113 | const folder = folderOf(root, abs) |
| 3114 | if (!folder) throw new Error(`Note not in a known folder: ${rel}`) |
| 3115 | const dir = path.dirname(abs) |
| 3116 | const trimmed = sanitizeNoteTitle(nextTitle) |
| 3117 | // Preserve the file's type on rename — a `.excalidraw` drawing must stay a |
| 3118 | // drawing, not get turned into a `.md` note (which would render its JSON). |
| 3119 | const ext = isExcalidrawPath(abs) ? '.excalidraw' : '.md' |
| 3120 | const target = path.join(dir, `${trimmed}${ext}`) |
| 3121 | const willRename = target !== abs |
| 3122 | // Snapshot the vault before the rename so inbound [[wikilinks]] still |
| 3123 | // resolve to this note under its current name; we rewrite them afterwards. |
| 3124 | const notesBefore = willRename ? await listNotes(root) : [] |
| 3125 | if (willRename) { |
| 3126 | // Check for conflicts, but allow case-only renames on case-insensitive FS |
| 3127 | try { |
| 3128 | await fs.access(target) |
| 3129 | const [srcStat, dstStat] = await Promise.all([fs.stat(abs), fs.stat(target)]) |
| 3130 | if (srcStat.ino !== dstStat.ino) { |
| 3131 | throw new Error(`A note named "${trimmed}" already exists in ${folder}`) |
| 3132 | } |
| 3133 | } catch (e) { |
| 3134 | if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e |
| 3135 | } |
| 3136 | // Two-step rename for case-only changes on case-insensitive filesystems |
| 3137 | if (abs.toLowerCase() === target.toLowerCase() && abs !== target) { |
| 3138 | const tmp = abs + '_rename_tmp_' + Date.now() |
| 3139 | await fs.rename(abs, tmp) |
| 3140 | await fs.rename(tmp, target) |
| 3141 | } else { |
| 3142 | await fs.rename(abs, target) |
| 3143 | } |
| 3144 | } |
| 3145 | const meta = await readMeta(root, target, folder) |
| 3146 | await moveNoteComments(root, rel, meta.path) |
| 3147 | invalidateNoteMetaCache(root, rel) |
| 3148 | invalidateNoteMetaCache(root, meta.path) |
| 3149 | invalidateVaultTextSearchCache(root) |
| 3150 | if (willRename) { |
| 3151 | await updateInboundWikilinks(root, notesBefore, rel, meta.title) |
| 3152 | } |
| 3153 | return meta |
| 3154 | } |
| 3155 | |
| 3156 | /** |
| 3157 | * Rewrite every `[[wikilink]]` across the vault that pointed to a note's old |
no test coverage detected