2026-05-14 12:31:35 +02:00
@page "/editor"
@page "/editor/{action}"
@page "/editor/{action}/{projectId:int}"
@inject IOllamaService OllamaService
@inject IProjectService ProjectService
2026-05-20 06:12:54 +02:00
@inject IRagService RagService
@inject ISettingsService SettingsService
@inject IWikipediaService WikipediaService
2026-05-14 12:31:35 +02:00
@inject NavigationManager Navigation
2026-05-20 06:12:54 +02:00
@implements IDisposable
@rendermode InteractiveServer
2026-05-21 11:04:42 +02:00
@attribute [Authorize]
2026-05-20 06:12:54 +02:00
2026-05-20 07:27:41 +02:00
<PageTitle>@T("Editor - Author Buddy", "Editor - Author Buddy")</PageTitle>
2026-05-14 12:31:35 +02:00
2026-05-20 06:12:54 +02:00
<div class="editor-page">
2026-05-20 07:27:41 +02:00
<textarea class="editor-area" @bind="editorContent" placeholder="@T("Schreibe hier deinen Text...", "Write your text here...")"></textarea>
2026-05-20 06:12:54 +02:00
2026-05-14 12:31:35 +02:00
<div class="editor-toolbar">
<span style="flex: 1; font-size: 12px; color: var(--border-color);">
@statusText
</span>
@if (isChecking)
{
<progress value="@checkProgress" max="100"></progress>
}
2026-05-20 07:27:41 +02:00
<button @onclick="CheckStyle" disabled="@isChecking">@T("Stil pr\u00fcfen", "Check style")</button>
<button @onclick="SpellCheck" disabled="@isChecking">@T("Rechtschreibung pr\u00fcfen", "Spell check")</button>
<button @onclick="SaveCurrentFile" disabled="@(string.IsNullOrEmpty(currentFile))">@T("Speichern", "Save")</button>
<button @onclick="ShowVersionDialog" disabled="@(string.IsNullOrEmpty(currentFile))">@T("Version", "Version")</button>
<button @onclick="OpenAssistant">@T("Assistent", "Assistant")</button>
<button @onclick="IndexProject" disabled="@isChecking">@T("Projekt indexieren", "Index project")</button>
2026-05-14 12:31:35 +02:00
</div>
2026-05-20 06:12:54 +02:00
@if (showVersionDialog)
{
<div class="modal-overlay">
<div class="modal-dialog">
2026-05-20 07:27:41 +02:00
<div class="modal-title">@T("Version speichern", "Save version")</div>
2026-05-20 06:12:54 +02:00
<div class="modal-body">
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label>@T("Kommentar (optional)", "Comment (optional)")</label>
<input @bind="versionComment" placeholder="@T("Was hat sich ge\u00e4ndert?", "What changed?")" />
2026-05-20 06:12:54 +02:00
</div>
</div>
<div class="modal-actions">
2026-05-20 07:27:41 +02:00
<button @onclick="() => showVersionDialog = false">@T("Abbrechen", "Cancel")</button>
<button @onclick="SaveVersioned" class="primary">@T("Version speichern", "Save version")</button>
2026-05-20 06:12:54 +02:00
</div>
</div>
</div>
}
2026-05-14 12:31:35 +02:00
2026-05-20 06:12:54 +02:00
<div class="search-section">
2026-05-20 07:27:41 +02:00
<input value="@searchQuery" @oninput="HandleSearchInput" placeholder="@T("Suchbegriff f\u00fcr Fakten-/Wikipedia-Recherche...", "Search term for fact/Wikipedia research...")"
2026-05-20 06:12:54 +02:00
disabled="@isChecking" />
2026-05-20 07:27:41 +02:00
<button @onclick="RagSearch" disabled=@buttonDisabled>@T("Fakten suchen (RAG)", "Search facts (RAG)")</button>
<button @onclick="SearchWikipedia" disabled=@buttonDisabled>@T("Wikipedia-Recherche", "Wikipedia research")</button>
2026-05-17 07:27:18 +02:00
</div>
2026-05-20 06:12:54 +02:00
<div style="display:none">@searchQuery</div>
@if (showSpellDiff && !string.IsNullOrEmpty(correctedText))
{
<div class="results-area">
<div class="diff-panel">
2026-05-20 07:27:41 +02:00
<h4>@T("Rechtschreibpr\u00fcfung - \u00c4nderungen:", "Spell check - Changes:")</h4>
2026-05-20 06:12:54 +02:00
<DiffView Original="@originalText" Corrected="@correctedText" />
</div>
</div>
}
else if (ragResults != null && ragResults.Count > 0)
{
<div class="results-area">
2026-05-20 07:27:41 +02:00
<h4>@T("Gefundene Fakten (RAG):", "Found facts (RAG):")</h4>
2026-05-20 06:12:54 +02:00
@foreach (var r in ragResults)
{
<div class="fact-card">
2026-05-20 07:27:41 +02:00
<div class="fact-similarity">@T("\u00c4hnlichkeit:", "Similarity:") @r.Similarity.ToString("P1")</div>
2026-05-20 06:12:54 +02:00
<div class="fact-path">@r.FilePath</div>
<div class="fact-text">@r.Text</div>
</div>
}
</div>
}
else if (wikipediaResults != null && wikipediaResults.Count > 0)
{
<div class="results-area">
2026-05-20 07:27:41 +02:00
<h4>@T("Wikipedia-Recherche:", "Wikipedia research:")</h4>
2026-05-20 06:12:54 +02:00
@foreach (var r in wikipediaResults)
{
<div class="fact-card">
<div class="fact-similarity">@(r.Language == "de" ? "🇩🇪" : "🇬🇧") @r.Language.ToUpper()</div>
<div class="fact-path">
<a href="@r.Url" target="_blank">@r.Title</a>
</div>
<div class="fact-text">@r.Snippet</div>
@if (!string.IsNullOrEmpty(r.Extract))
{
<div class="fact-extract">@r.Extract</div>
}
<div class="fact-actions">
<button @onclick="() => LoadFullArticle(r)" disabled="@(loadedArticles.ContainsKey(r.Url))">
2026-05-20 07:27:41 +02:00
@(loadedArticles.ContainsKey(r.Url)
? T("✓ Geladen", "✓ Loaded")
: T("Vollst\u00e4ndigen Artikel laden", "Load full article"))
2026-05-20 06:12:54 +02:00
</button>
<button @onclick="() => SaveAsFact(r)" disabled="@(string.IsNullOrWhiteSpace(r.Extract) || currentProjectId == 0)">
2026-05-20 07:27:41 +02:00
@T("Als Fakt speichern", "Save as fact")
2026-05-20 06:12:54 +02:00
</button>
</div>
</div>
}
</div>
}
else if (!string.IsNullOrEmpty(analysisResult))
{
<div class="results-area">
2026-05-20 07:27:41 +02:00
<h4>@T("Ergebnis:", "Result:")</h4>
2026-05-20 06:12:54 +02:00
<div class="result-text">@analysisResult</div>
</div>
}
</div>
@if (showCreateDialog)
2026-05-17 07:27:18 +02:00
{
2026-05-20 06:12:54 +02:00
<div class="modal-overlay">
<div class="modal-dialog">
2026-05-20 07:27:41 +02:00
<div class="modal-title">@T("Neues Projekt erstellen", "Create new project")</div>
2026-05-20 06:12:54 +02:00
<div class="modal-body">
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label>@T("Projektname", "Project name")</label>
<input @bind="newProjectName" placeholder="@T("z.B. Mein Roman", "e.g. My novel")" />
2026-05-20 06:12:54 +02:00
</div>
<div class="form-group">
2026-05-20 07:27:41 +02:00
<label>@T("Projektpfad", "Project path")</label>
<input @bind="newProjectPath" placeholder="@T("z.B. C:\\Projekte\\MeinRoman", "e.g. C:\\Projects\\MyNovel")" />
2026-05-20 06:12:54 +02:00
</div>
</div>
<div class="modal-actions">
2026-05-20 07:27:41 +02:00
<button @onclick="CancelCreateProject">@T("Abbrechen", "Cancel")</button>
<button @onclick="CreateProject" disabled="@(() => string.IsNullOrWhiteSpace(newProjectName) || string.IsNullOrWhiteSpace(newProjectPath))" class="primary">@T("Erstellen", "Create")</button>
2026-05-20 06:12:54 +02:00
</div>
</div>
2026-05-14 12:31:35 +02:00
</div>
}
@code {
2026-05-20 07:27:41 +02:00
private string _language = "de";
private string T(string de, string en) => _language == "en" ? en : de;
2026-05-14 12:31:35 +02:00
[Parameter]
public string? Action { get; set; }
[Parameter]
public int? ProjectId { get; set; }
private string editorContent = string.Empty;
2026-05-20 06:12:54 +02:00
private string searchQuery = string.Empty;
2026-05-20 07:27:41 +02:00
private string statusText = string.Empty;
2026-05-14 12:31:35 +02:00
private bool isChecking;
2026-05-20 06:12:54 +02:00
private bool buttonDisabled;
2026-05-14 12:31:35 +02:00
private double checkProgress;
private string analysisResult = string.Empty;
2026-05-17 07:27:18 +02:00
private string originalText = string.Empty;
private string correctedText = string.Empty;
private bool showSpellDiff;
2026-05-20 06:12:54 +02:00
private List<RagSearchResult>? ragResults;
private List<WikipediaResult>? wikipediaResults;
private int currentProjectId;
private string? currentFile;
private bool showCreateDialog;
private string newProjectName = string.Empty;
private string newProjectPath = string.Empty;
private Dictionary<string, bool> loadedArticles = new();
private bool showVersionDialog;
private string versionComment = string.Empty;
private VersionEntry? latestVersion;
private record VersionEntry(string FilePath, DateTime Timestamp, string? Comment);
2026-05-14 12:31:35 +02:00
protected override async Task OnInitializedAsync()
{
2026-05-20 07:27:41 +02:00
_language = await SettingsService.GetLanguageAsync() ?? "de";
statusText = T("Bereit", "Ready");
2026-05-20 06:12:54 +02:00
Navigation.LocationChanged += OnLocationChanged;
2026-05-20 07:27:41 +02:00
2026-05-20 06:12:54 +02:00
await LoadProjectContext();
2026-05-20 07:27:41 +02:00
var pendingOutput = SettingsService.PendingAssistantOutput;
if (!string.IsNullOrEmpty(pendingOutput))
{
SettingsService.PendingAssistantOutput = null;
editorContent = pendingOutput;
statusText = T("Text aus Assistent \u00fcbernommen.", "Text imported from assistant.");
}
2026-05-20 06:12:54 +02:00
if (string.Equals(Action, "newproject", StringComparison.OrdinalIgnoreCase))
{
showCreateDialog = true;
}
}
private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
{
await LoadProjectContext();
StateHasChanged();
}
public void Dispose()
{
Navigation.LocationChanged -= OnLocationChanged;
}
2026-05-20 07:27:41 +02:00
private void OpenAssistant()
{
SettingsService.PendingAssistantInput = editorContent;
Navigation.NavigateTo("/assistant");
}
2026-05-20 06:12:54 +02:00
private void HandleSearchInput(ChangeEventArgs e)
{
searchQuery = e.Value?.ToString() ?? string.Empty;
buttonDisabled = isChecking || string.IsNullOrWhiteSpace(searchQuery);
StateHasChanged();
}
private async Task LoadProjectContext()
{
if (ProjectId.HasValue)
{
currentProjectId = ProjectId.Value;
SettingsService.CurrentProjectId = currentProjectId;
}
else if (SettingsService.CurrentProjectId.HasValue)
2026-05-14 12:31:35 +02:00
{
2026-05-20 06:12:54 +02:00
currentProjectId = SettingsService.CurrentProjectId.Value;
}
if (currentProjectId > 0)
await SettingsService.LoadProjectSettingsAsync();
var filePath = SettingsService.CurrentFilePath;
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
{
currentFile = filePath;
editorContent = await File.ReadAllTextAsync(filePath);
2026-05-20 07:27:41 +02:00
statusText = $"{T("Geladen:", "Loaded:")} {Path.GetFileName(filePath)}";
2026-05-20 06:12:54 +02:00
RefreshLatestVersion();
2026-05-14 12:31:35 +02:00
}
}
private async Task CheckStyle()
{
if (string.IsNullOrWhiteSpace(editorContent)) return;
isChecking = true;
2026-05-20 07:27:41 +02:00
statusText = T("Ollama analysiert Stil...", "Ollama analyzing style...");
2026-05-14 12:31:35 +02:00
checkProgress = 50;
2026-05-17 07:27:18 +02:00
showSpellDiff = false;
2026-05-20 06:12:54 +02:00
ragResults = null;
wikipediaResults = null;
2026-05-14 12:31:35 +02:00
try
{
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
analysisResult = $"{T("Fehler:", "Error:")} {ex.Message}";
2026-05-14 12:31:35 +02:00
}
finally
{
2026-05-20 07:27:41 +02:00
statusText = T("Bereit", "Ready");
2026-05-14 12:31:35 +02:00
isChecking = false;
checkProgress = 0;
}
}
private async Task SpellCheck()
{
if (string.IsNullOrWhiteSpace(editorContent)) return;
isChecking = true;
2026-05-20 07:27:41 +02:00
statusText = T("Ollama pr\u00fcft Rechtschreibung...", "Ollama checking spelling...");
2026-05-14 12:31:35 +02:00
checkProgress = 50;
2026-05-17 07:27:18 +02:00
showSpellDiff = false;
2026-05-20 06:12:54 +02:00
ragResults = null;
wikipediaResults = null;
2026-05-14 12:31:35 +02:00
try
{
2026-05-17 07:27:18 +02:00
originalText = editorContent;
correctedText = await OllamaService.QuickSpellCheckAsync(editorContent);
showSpellDiff = true;
2026-05-14 12:31:35 +02:00
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
analysisResult = $"{T("Fehler:", "Error:")} {ex.Message}";
2026-05-14 12:31:35 +02:00
}
finally
{
2026-05-20 07:27:41 +02:00
statusText = T("Bereit", "Ready");
2026-05-14 12:31:35 +02:00
isChecking = false;
checkProgress = 0;
}
}
private async Task RagSearch()
{
2026-05-20 06:12:54 +02:00
if (string.IsNullOrWhiteSpace(searchQuery)) return;
2026-05-14 12:31:35 +02:00
isChecking = true;
2026-05-20 07:27:41 +02:00
statusText = T("Suche verwandte Fakten...", "Searching related facts...");
2026-05-17 07:27:18 +02:00
showSpellDiff = false;
2026-05-20 06:12:54 +02:00
analysisResult = string.Empty;
ragResults = null;
wikipediaResults = null;
try
{
if (currentProjectId == 0)
{
2026-05-20 07:27:41 +02:00
analysisResult = T("Kein Projekt geladen. \u00d6ffne zuerst ein Projekt \u00fcber die Sidebar.", "No project loaded. Open a project via the sidebar first.");
2026-05-20 06:12:54 +02:00
return;
}
ragResults = await RagService.SearchAsync(currentProjectId, searchQuery, 5);
if (ragResults.Count == 0)
{
2026-05-20 07:27:41 +02:00
analysisResult = T("Keine relevanten Fakten gefunden. Indexiere das Projekt ggf. \u00fcber 'Projekt indexieren'.", "No relevant facts found. Index the project if needed via 'Index project'.");
2026-05-20 06:12:54 +02:00
}
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
analysisResult = $"{T("RAG-Fehler:", "RAG error:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
finally
{
2026-05-20 07:27:41 +02:00
statusText = T("Bereit", "Ready");
2026-05-20 06:12:54 +02:00
isChecking = false;
}
}
private async Task SearchWikipedia()
{
if (string.IsNullOrWhiteSpace(searchQuery)) return;
isChecking = true;
2026-05-20 07:27:41 +02:00
statusText = T("Suche auf Wikipedia...", "Searching Wikipedia...");
2026-05-20 06:12:54 +02:00
showSpellDiff = false;
analysisResult = string.Empty;
ragResults = null;
wikipediaResults = null;
loadedArticles.Clear();
try
{
var request = new WikipediaSearchRequest
{
Query = searchQuery,
SearchGerman = true,
SearchEnglish = true,
MaxResults = 3
};
wikipediaResults = await WikipediaService.SearchAsync(request);
foreach (var result in wikipediaResults)
{
var extract = await WikipediaService.GetExtractAsync(result.Title, result.Language);
if (extract != null)
result.Extract = extract.Length > 800 ? extract[..800] + "..." : extract;
}
if (wikipediaResults.Count == 0)
2026-05-20 07:27:41 +02:00
analysisResult = T("Keine Wikipedia-Artikel gefunden.", "No Wikipedia articles found.");
2026-05-20 06:12:54 +02:00
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
analysisResult = $"{T("Wikipedia-Fehler:", "Wikipedia error:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
finally
{
2026-05-20 07:27:41 +02:00
statusText = T("Bereit", "Ready");
2026-05-20 06:12:54 +02:00
isChecking = false;
}
}
private async Task LoadFullArticle(WikipediaResult result)
{
2026-05-20 07:27:41 +02:00
statusText = $"{T("Lade Artikel:", "Loading article:")} {result.Title}...";
2026-05-20 06:12:54 +02:00
try
{
var fullText = await WikipediaService.GetFullArticleAsync(result.Title, result.Language);
if (fullText != null)
{
result.Extract = fullText;
loadedArticles[result.Url] = true;
2026-05-20 07:27:41 +02:00
statusText = T("Artikel geladen.", "Article loaded.");
2026-05-20 06:12:54 +02:00
}
else
{
2026-05-20 07:27:41 +02:00
statusText = T("Konnte Artikel nicht laden.", "Could not load article.");
2026-05-20 06:12:54 +02:00
}
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
statusText = $"{T("Fehler:", "Error:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
}
2026-05-14 12:31:35 +02:00
2026-05-20 06:12:54 +02:00
private async Task SaveAsFact(WikipediaResult result)
{
if (string.IsNullOrWhiteSpace(result.Extract) || currentProjectId == 0) return;
2026-05-20 07:27:41 +02:00
statusText = T("Speichere als Fakt...", "Saving as fact...");
2026-05-20 06:12:54 +02:00
try
{
var projects = await ProjectService.GetAllProjectsAsync();
var project = projects.FirstOrDefault(p => p.Id == currentProjectId);
if (project == null)
{
2026-05-20 07:27:41 +02:00
statusText = T("Projekt nicht gefunden.", "Project not found.");
2026-05-20 06:12:54 +02:00
return;
}
var factsDir = Path.Combine(project.RootPath, "01_Fakten");
Directory.CreateDirectory(factsDir);
var safeName = SanitizeFilename(searchQuery);
var filePath = Path.Combine(factsDir, $"{safeName}.md");
var content = $"# {result.Title}\n" +
$"- Quelle: [{result.Title}]({result.Url})\n" +
$"- Sprache: {result.Language.ToUpper()}\n" +
$"- Abgerufen: {DateTime.Now:dd.MM.yyyy HH:mm}\n\n" +
result.Extract;
await File.WriteAllTextAsync(filePath, content);
2026-05-20 07:27:41 +02:00
statusText = $"{T("Gespeichert als:", "Saved as:")} {safeName}.md";
2026-05-20 06:12:54 +02:00
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
statusText = $"{T("Fehler beim Speichern:", "Error saving:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
}
private static string SanitizeFilename(string name)
{
var invalid = Path.GetInvalidFileNameChars();
var sanitized = new string(name.Where(c => !invalid.Contains(c)).ToArray());
return string.IsNullOrWhiteSpace(sanitized) ? "fakt" : sanitized.Trim();
}
private void CancelCreateProject()
{
showCreateDialog = false;
Navigation.NavigateTo("/editor");
}
private async Task CreateProject()
{
if (string.IsNullOrWhiteSpace(newProjectName) || string.IsNullOrWhiteSpace(newProjectPath)) return;
isChecking = true;
2026-05-20 07:27:41 +02:00
statusText = T("Erstelle Projekt...", "Creating project...");
2026-05-20 06:12:54 +02:00
try
{
Directory.CreateDirectory(newProjectPath);
var id = await ProjectService.CreateNewProjectAsync(newProjectName, newProjectPath);
SettingsService.CurrentProjectId = id;
await SettingsService.LoadProjectSettingsAsync();
showCreateDialog = false;
Navigation.NavigateTo($"/editor/index/{id}");
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
analysisResult = $"{T("Fehler beim Erstellen:", "Error creating:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
finally
{
isChecking = false;
2026-05-20 07:27:41 +02:00
statusText = T("Bereit", "Ready");
2026-05-20 06:12:54 +02:00
}
}
private async Task IndexProject()
{
if (currentProjectId == 0)
{
2026-05-20 07:27:41 +02:00
analysisResult = T("Kein Projekt geladen. \u00d6ffne zuerst ein Projekt \u00fcber die Sidebar.", "No project loaded. Open a project via the sidebar first.");
2026-05-20 06:12:54 +02:00
return;
}
isChecking = true;
2026-05-20 07:27:41 +02:00
statusText = T("Indexiere Projekt...", "Indexing project...");
2026-05-20 06:12:54 +02:00
analysisResult = string.Empty;
ragResults = null;
try
{
var projects = await ProjectService.GetAllProjectsAsync();
var project = projects.FirstOrDefault(p => p.Id == currentProjectId);
if (project == null)
{
2026-05-20 07:27:41 +02:00
analysisResult = T("Projekt nicht gefunden.", "Project not found.");
2026-05-20 06:12:54 +02:00
return;
}
var p = new Progress<string>(msg =>
{
statusText = msg;
InvokeAsync(StateHasChanged);
});
await RagService.IndexProjectAsync(currentProjectId, project.RootPath, p);
2026-05-20 07:27:41 +02:00
analysisResult = T("Projekt erfolgreich indexiert.", "Project indexed successfully.");
2026-05-20 06:12:54 +02:00
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
analysisResult = $"{T("Indexierungsfehler:", "Indexing error:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
finally
{
2026-05-20 07:27:41 +02:00
statusText = T("Bereit", "Ready");
2026-05-20 06:12:54 +02:00
isChecking = false;
}
}
private async Task SaveCurrentFile()
{
if (string.IsNullOrEmpty(currentFile)) return;
await File.WriteAllTextAsync(currentFile, editorContent);
2026-05-20 07:27:41 +02:00
statusText = $"{T("Gespeichert:", "Saved:")} {Path.GetFileName(currentFile)}";
2026-05-20 06:12:54 +02:00
}
private void ShowVersionDialog()
{
showVersionDialog = true;
versionComment = string.Empty;
}
private async Task SaveVersioned()
{
if (string.IsNullOrEmpty(currentFile)) return;
var dir = Path.GetDirectoryName(currentFile)!;
var name = Path.GetFileNameWithoutExtension(currentFile);
var ext = Path.GetExtension(currentFile);
var versionsDir = Path.Combine(dir, "_versions");
Directory.CreateDirectory(versionsDir);
var stamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
var versionPath = Path.Combine(versionsDir, $"{name}_{stamp}{ext}");
await File.WriteAllTextAsync(currentFile, editorContent);
await File.WriteAllTextAsync(versionPath, editorContent);
var meta = $"Kommentar: {versionComment}";
await File.WriteAllTextAsync(versionPath + ".meta", meta);
2026-05-20 07:27:41 +02:00
statusText = $"{T("Version gespeichert:", "Version saved:")} {name}_{stamp}{ext}";
2026-05-20 06:12:54 +02:00
showVersionDialog = false;
versionComment = string.Empty;
RefreshLatestVersion();
}
private void RefreshLatestVersion()
{
latestVersion = null;
if (string.IsNullOrEmpty(currentFile)) return;
var dir = Path.GetDirectoryName(currentFile)!;
var name = Path.GetFileNameWithoutExtension(currentFile);
var ext = Path.GetExtension(currentFile);
var versionsDir = Path.Combine(dir, "_versions");
if (!Directory.Exists(versionsDir)) return;
var pattern = $"{name}_*{ext}";
var files = Directory.GetFiles(versionsDir, pattern).OrderByDescending(f => f).ToList();
if (files.Count == 0) return;
var latest = files.First();
var metaFile = latest + ".meta";
string? comment = null;
if (File.Exists(metaFile))
{
var lines = File.ReadAllLines(metaFile);
if (lines.Length > 0 && lines[0].StartsWith("Kommentar: "))
comment = lines[0]["Kommentar: ".Length..];
}
var fileName = Path.GetFileNameWithoutExtension(latest);
var stamp = fileName[(name.Length + 1)..];
var parsed = DateTime.TryParseExact(stamp, "yyyyMMdd_HHmmss", null,
System.Globalization.DateTimeStyles.None, out var dt);
latestVersion = new VersionEntry(
latest,
parsed ? dt : File.GetLastWriteTime(latest),
comment
);
}
private async Task LoadLatestVersion()
{
if (latestVersion == null) return;
try
{
editorContent = await File.ReadAllTextAsync(latestVersion.FilePath);
2026-05-20 07:27:41 +02:00
statusText = $"{T("Version vom", "Version from")} {latestVersion.Timestamp:dd.MM.yyyy HH:mm} {T("geladen.", "loaded.")}";
2026-05-20 06:12:54 +02:00
}
catch (Exception ex)
{
2026-05-20 07:27:41 +02:00
statusText = $"{T("Fehler:", "Error:")} {ex.Message}";
2026-05-20 06:12:54 +02:00
}
2026-05-14 12:31:35 +02:00
}
}