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; /// One cheat sheet: title from the file's leading # heading, body is the rest. public sealed record CheatSheet(string Key, string Title, string Body); /// /// Backs the non-modal cheat-sheet window (Help → Cheat Sheets). Content lives as markdown /// under Assets/CheatSheets/*.md (embedded resources) and is rendered by the existing /// MarkdownView, /// 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. /// public partial class CheatSheetsViewModel : ViewModelBase { private static readonly string[] Keys = ["git", "conventional-commits", "claude-cli", "claude-config", "dotnet-cli", "powershell"]; public ObservableCollection Sheets { get; } = []; [ObservableProperty] private CheatSheet? _selectedSheet; /// Keeps the window above the shell so it can sit docked at a screen edge. [ObservableProperty] private bool _isPinned; public Action? CloseAction { get; set; } public CheatSheetsViewModel() { foreach (var key in Keys) Sheets.Add(Load(key)); SelectedSheet = Sheets.FirstOrDefault(); } /// Preselects a sheet by key; unknown keys keep the current selection. 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(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()); } }