NoteRenamer fetches inbound graph edges, creates the note under the new slug (patching the topic frontmatter field if the prefix changes), rewrites all wikilinks and markdown links in linking notes, then deletes the old slug. Tests cover wikilink/markdown-link replacement and topic patching.
88 lines
2.7 KiB
C#
88 lines
2.7 KiB
C#
using NozCli.Core.Client;
|
|
using NozCli.Core.Security;
|
|
using NozCli.Repl;
|
|
using Spectre.Console;
|
|
|
|
namespace NozCli.Commands;
|
|
|
|
public class MvCommand : ICommand
|
|
{
|
|
public string Name => "mv";
|
|
public string Description => "Rename a note and update all inbound links";
|
|
public string Usage => "mv <old-name> <new-name>";
|
|
|
|
public async Task ExecuteAsync(string[] args, ReplContext ctx, CancellationToken ct = default)
|
|
{
|
|
if (args.Length < 2)
|
|
{
|
|
AnsiConsole.MarkupLine("[red]Usage:[/] mv <old-name> <new-name>");
|
|
return;
|
|
}
|
|
|
|
// Resolve short names to full slugs
|
|
var oldSlug = ctx.ResolveSlug(args[0]);
|
|
var newSlug = ctx.ResolveNewSlug(args[1]);
|
|
|
|
if (!SlugGuard.IsValid(oldSlug) || !SlugGuard.IsValid(newSlug))
|
|
{
|
|
AnsiConsole.MarkupLine("[red]Ungültiger Slug.[/] Nur Kleinbuchstaben, Ziffern und Bindestriche erlaubt.");
|
|
return;
|
|
}
|
|
|
|
if (oldSlug == newSlug)
|
|
{
|
|
AnsiConsole.MarkupLine("[yellow]Quelle und Ziel sind identisch — nichts zu tun.[/]");
|
|
return;
|
|
}
|
|
|
|
if (!ctx.NoteCache.Any(n => n.Slug == oldSlug))
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Notiz nicht gefunden:[/] {oldSlug}");
|
|
return;
|
|
}
|
|
|
|
if (ctx.NoteCache.Any(n => n.Slug == newSlug))
|
|
{
|
|
AnsiConsole.MarkupLine($"[red]Ziel existiert bereits:[/] {newSlug}");
|
|
return;
|
|
}
|
|
|
|
AnsiConsole.MarkupLine($"[dim]Umbenennen:[/] [cyan]{oldSlug}[/] → [green]{newSlug}[/]");
|
|
|
|
NoteRenamer.Result result = default!;
|
|
|
|
await AnsiConsole.Progress()
|
|
.AutoClear(false)
|
|
.HideCompleted(false)
|
|
.StartAsync(async prog =>
|
|
{
|
|
var task = prog.AddTask("Umbenennen…", maxValue: 100);
|
|
|
|
result = await NoteRenamer.ExecuteAsync(
|
|
ctx.Api, oldSlug, newSlug,
|
|
onProgress: msg =>
|
|
{
|
|
task.Description = msg;
|
|
task.Increment(20);
|
|
},
|
|
ct: ct);
|
|
|
|
task.Value = 100;
|
|
task.Description = "Fertig";
|
|
});
|
|
|
|
// Update local cache: swap old entry for a patched new one
|
|
var oldEntry = ctx.NoteCache.FirstOrDefault(n => n.Slug == oldSlug);
|
|
if (oldEntry is not null)
|
|
{
|
|
ctx.NoteCache.Remove(oldEntry);
|
|
ctx.NoteCache.Add(oldEntry with { Slug = newSlug });
|
|
}
|
|
|
|
var linkInfo = result.LinksUpdated > 0
|
|
? $" [dim]· {result.LinksUpdated} Link(s) aktualisiert[/]"
|
|
: "";
|
|
|
|
AnsiConsole.MarkupLine($"[green]✓[/] {oldSlug} → {newSlug}{linkInfo}");
|
|
}
|
|
}
|