Sechs Markdown-Sheets (git, Conventional Commits, Claude CLI, Claude-Config, dotnet CLI, PowerShell) als EmbeddedResource, gerendert im modelosen, per Topmost anpinnbaren CheatSheetsWindow. MarkdownView bekommt dafuer das Opt-in CommandRows: `befehl` - beschreibung wird zu Mono-Chip + Copy-Button, jede ##-Sektion landet in einem Expander. Enthaelt ausserdem: Mission Control wird beim Oeffnen aus dem Minimiert-Zustand geholt, nicht nur aktiviert.
81 lines
3.2 KiB
C#
81 lines
3.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using ClaudeDo.Ui.Localization;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
using CommunityToolkit.Mvvm.Input;
|
|
|
|
namespace ClaudeDo.Ui.ViewModels.Modals;
|
|
|
|
/// <summary>One cheat sheet: title from the file's leading <c># </c> heading, body is the rest.</summary>
|
|
public sealed record CheatSheet(string Key, string Title, string Body);
|
|
|
|
/// <summary>
|
|
/// Backs the non-modal cheat-sheet window (Help → Cheat Sheets). Content lives as markdown
|
|
/// under <c>Assets/CheatSheets/*.md</c> (embedded resources) and is rendered by the existing
|
|
/// <c>MarkdownView</c>,
|
|
/// so a new sheet is a new file — but the key list here stays explicit to keep the order
|
|
/// deterministic and a missing resource visible instead of silently absent.
|
|
/// </summary>
|
|
public partial class CheatSheetsViewModel : ViewModelBase
|
|
{
|
|
private static readonly string[] Keys =
|
|
["git", "conventional-commits", "claude-cli", "claude-config", "dotnet-cli", "powershell"];
|
|
|
|
public ObservableCollection<CheatSheet> Sheets { get; } = [];
|
|
|
|
[ObservableProperty]
|
|
private CheatSheet? _selectedSheet;
|
|
|
|
/// <summary>Keeps the window above the shell so it can sit docked at a screen edge.</summary>
|
|
[ObservableProperty]
|
|
private bool _isPinned;
|
|
|
|
public Action? CloseAction { get; set; }
|
|
|
|
public CheatSheetsViewModel()
|
|
{
|
|
foreach (var key in Keys) Sheets.Add(Load(key));
|
|
SelectedSheet = Sheets.FirstOrDefault();
|
|
}
|
|
|
|
/// <summary>Preselects a sheet by key; unknown keys keep the current selection.</summary>
|
|
public void Select(string key)
|
|
{
|
|
var sheet = Sheets.FirstOrDefault(s => s.Key == key);
|
|
if (sheet is not null) SelectedSheet = sheet;
|
|
}
|
|
|
|
[RelayCommand]
|
|
private void Close() => CloseAction?.Invoke();
|
|
|
|
private static CheatSheet Load(string key)
|
|
{
|
|
var assembly = typeof(CheatSheetsViewModel).Assembly;
|
|
// Match by suffix rather than composing the logical name from RootNamespace — the
|
|
// prefix is a build detail, and a rename shouldn't silently blank the sheet.
|
|
var name = assembly.GetManifestResourceNames()
|
|
.FirstOrDefault(n => n.EndsWith($".CheatSheets.{key}.md", StringComparison.Ordinal));
|
|
var stream = name is null ? null : assembly.GetManifestResourceStream(name);
|
|
if (stream is null)
|
|
return new CheatSheet(key, key, Loc.T("modals.cheatSheets.missing", $"{key}.md"));
|
|
|
|
using var reader = new StreamReader(stream);
|
|
var text = reader.ReadToEnd().Replace("\r\n", "\n");
|
|
|
|
// The window shows the title in its own selector, so strip the leading heading
|
|
// rather than rendering it a second time inside the body.
|
|
var lines = new List<string>(text.Split('\n'));
|
|
var title = key;
|
|
var first = lines.FindIndex(l => !string.IsNullOrWhiteSpace(l));
|
|
if (first >= 0 && lines[first].StartsWith("# ", StringComparison.Ordinal))
|
|
{
|
|
title = lines[first][2..].Trim();
|
|
lines.RemoveRange(0, first + 1);
|
|
}
|
|
return new CheatSheet(key, title, string.Join('\n', lines).Trim());
|
|
}
|
|
}
|