(root: string, rel: string, nextTitle: string)
| 622 | } |
| 623 | |
| 624 | export async function renameNote(root: string, rel: string, nextTitle: string): Promise<NoteMeta> { |
| 625 | const abs = resolveSafe(root, rel) |
| 626 | const folder = folderOf(root, abs) |
| 627 | if (!folder) throw new Error(`Note not in a known folder: ${rel}`) |
| 628 | const dir = path.dirname(abs) |
| 629 | const trimmed = sanitizeTitle(nextTitle) |
| 630 | const target = path.join(dir, `${trimmed}.md`) |
| 631 | if (target !== abs) { |
| 632 | try { |
| 633 | await fs.access(target) |
| 634 | const [srcStat, dstStat] = await Promise.all([fs.stat(abs), fs.stat(target)]) |
| 635 | if (srcStat.ino !== dstStat.ino) { |
| 636 | throw new Error(`A note named "${trimmed}" already exists in ${folder}`) |
| 637 | } |
| 638 | } catch (e) { |
| 639 | if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e |
| 640 | } |
| 641 | if (abs.toLowerCase() === target.toLowerCase() && abs !== target) { |
| 642 | const tmp = abs + '_rename_tmp_' + Date.now() |
| 643 | await fs.rename(abs, tmp) |
| 644 | await fs.rename(tmp, target) |
| 645 | } else { |
| 646 | await fs.rename(abs, target) |
| 647 | } |
| 648 | } |
| 649 | return await readMeta(root, target, folder) |
| 650 | } |
| 651 | |
| 652 | /** |
| 653 | * The note's directory relative to its top-level folder root ('' when |
no test coverage detected