|
| 1 | +// Licensed to Elasticsearch B.V under one or more agreements. |
| 2 | +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. |
| 3 | +// See the LICENSE file in the project root for more information |
| 4 | + |
| 5 | +using System.Collections.Frozen; |
| 6 | +using System.ComponentModel.DataAnnotations; |
| 7 | +using NetEscapades.EnumGenerators; |
| 8 | +using System.Text; |
| 9 | +using System.Web; |
| 10 | + |
| 11 | +namespace Elastic.Markdown.Myst.Roles.Kbd; |
| 12 | + |
| 13 | +public class KeyboardShortcut(IReadOnlyList<IKeyNode> keys) |
| 14 | +{ |
| 15 | + private IReadOnlyList<IKeyNode> Keys { get; } = keys; |
| 16 | + |
| 17 | + public static KeyboardShortcut Parse(string input) |
| 18 | + { |
| 19 | + if (string.IsNullOrWhiteSpace(input)) |
| 20 | + return new KeyboardShortcut([]); |
| 21 | + |
| 22 | + var parts = input.Split('+', StringSplitOptions.RemoveEmptyEntries); |
| 23 | + var keys = new List<IKeyNode>(); |
| 24 | + |
| 25 | + foreach (var part in parts) |
| 26 | + { |
| 27 | + var trimmedPart = part.Trim().ToLowerInvariant(); |
| 28 | + if (NamedKeyboardKeyExtensions.TryParse(trimmedPart, out var specialKey, true, true)) |
| 29 | + keys.Add(new NamedKeyNode { Key = specialKey }); |
| 30 | + else |
| 31 | + { |
| 32 | + switch (trimmedPart.Length) |
| 33 | + { |
| 34 | + case 1: |
| 35 | + keys.Add(new CharacterKeyNode { Key = trimmedPart[0] }); |
| 36 | + break; |
| 37 | + default: |
| 38 | + throw new ArgumentException($"Invalid keyboard shortcut: {input}", nameof(input)); |
| 39 | + } |
| 40 | + } |
| 41 | + } |
| 42 | + return new KeyboardShortcut(keys); |
| 43 | + } |
| 44 | + |
| 45 | + public static string Render(KeyboardShortcut shortcut) |
| 46 | + { |
| 47 | + var viewModels = shortcut.Keys.Select(keyNode => |
| 48 | + { |
| 49 | + return keyNode switch |
| 50 | + { |
| 51 | + NamedKeyNode s => ViewModelMapping[s.Key], |
| 52 | + CharacterKeyNode c => new KeyboardKeyViewModel { DisplayText = c.Key.ToString(), UnicodeIcon = null }, |
| 53 | + _ => throw new ArgumentException($"Unknown key: {keyNode}") |
| 54 | + }; |
| 55 | + }); |
| 56 | + |
| 57 | + var kbdElements = viewModels.Select(viewModel => |
| 58 | + { |
| 59 | + var sb = new StringBuilder(); |
| 60 | + _ = sb.Append("<kbd class=\"kbd\">"); |
| 61 | + if (viewModel.UnicodeIcon is not null) |
| 62 | + _ = sb.Append($"<span class=\"kbd-icon\">{viewModel.UnicodeIcon}</span>"); |
| 63 | + _ = sb.Append(viewModel.DisplayText); |
| 64 | + _ = sb.Append("</kbd>"); |
| 65 | + return sb.ToString(); |
| 66 | + }); |
| 67 | + |
| 68 | + return string.Join(" + ", kbdElements); |
| 69 | + } |
| 70 | + |
| 71 | + private static FrozenDictionary<NamedKeyboardKey, KeyboardKeyViewModel> ViewModelMapping { get; } = |
| 72 | + Enum.GetValues<NamedKeyboardKey>().ToFrozenDictionary(k => k, GetDisplayModel); |
| 73 | + |
| 74 | + private static KeyboardKeyViewModel GetDisplayModel(NamedKeyboardKey key) => |
| 75 | + key switch |
| 76 | + { |
| 77 | + // Modifier keys with special symbols |
| 78 | + NamedKeyboardKey.Command => new KeyboardKeyViewModel { DisplayText = "Cmd", UnicodeIcon = "⌘" }, |
| 79 | + NamedKeyboardKey.Shift => new KeyboardKeyViewModel { DisplayText = "Shift", UnicodeIcon = "⇧" }, |
| 80 | + NamedKeyboardKey.Ctrl => new KeyboardKeyViewModel { DisplayText = "Ctrl", UnicodeIcon = "⌃" }, |
| 81 | + NamedKeyboardKey.Alt => new KeyboardKeyViewModel { DisplayText = "Alt", UnicodeIcon = "⌥" }, |
| 82 | + NamedKeyboardKey.Option => new KeyboardKeyViewModel { DisplayText = "Option", UnicodeIcon = "⌥" }, |
| 83 | + NamedKeyboardKey.Win => new KeyboardKeyViewModel { DisplayText = "Win", UnicodeIcon = "⊞" }, |
| 84 | + // Directional keys |
| 85 | + NamedKeyboardKey.Up => new KeyboardKeyViewModel { DisplayText = "Up", UnicodeIcon = "↑" }, |
| 86 | + NamedKeyboardKey.Down => new KeyboardKeyViewModel { DisplayText = "Down", UnicodeIcon = "↓" }, |
| 87 | + NamedKeyboardKey.Left => new KeyboardKeyViewModel { DisplayText = "Left", UnicodeIcon = "←" }, |
| 88 | + NamedKeyboardKey.Right => new KeyboardKeyViewModel { DisplayText = "Right", UnicodeIcon = "→" }, |
| 89 | + // Other special keys with symbols |
| 90 | + NamedKeyboardKey.Enter => new KeyboardKeyViewModel { DisplayText = "Enter", UnicodeIcon = "↵" }, |
| 91 | + NamedKeyboardKey.Escape => new KeyboardKeyViewModel { DisplayText = "Esc", UnicodeIcon = "⎋" }, |
| 92 | + NamedKeyboardKey.Tab => new KeyboardKeyViewModel { DisplayText = "Tab", UnicodeIcon = "↹" }, |
| 93 | + NamedKeyboardKey.Backspace => new KeyboardKeyViewModel { DisplayText = "Backspace", UnicodeIcon = "⌫" }, |
| 94 | + NamedKeyboardKey.Delete => new KeyboardKeyViewModel { DisplayText = "Del", UnicodeIcon = null }, |
| 95 | + NamedKeyboardKey.Home => new KeyboardKeyViewModel { DisplayText = "Home", UnicodeIcon = "⇱" }, |
| 96 | + NamedKeyboardKey.End => new KeyboardKeyViewModel { DisplayText = "End", UnicodeIcon = "⇲" }, |
| 97 | + NamedKeyboardKey.PageUp => new KeyboardKeyViewModel { DisplayText = "PageUp", UnicodeIcon = "⇞" }, |
| 98 | + NamedKeyboardKey.PageDown => new KeyboardKeyViewModel { DisplayText = "PageDown", UnicodeIcon = "⇟" }, |
| 99 | + NamedKeyboardKey.Space => new KeyboardKeyViewModel { DisplayText = "Space", UnicodeIcon = "␣" }, |
| 100 | + NamedKeyboardKey.Insert => new KeyboardKeyViewModel { DisplayText = "Ins", UnicodeIcon = null }, |
| 101 | + NamedKeyboardKey.Plus => new KeyboardKeyViewModel { DisplayText = "+", UnicodeIcon = null }, |
| 102 | + NamedKeyboardKey.Fn => new KeyboardKeyViewModel { DisplayText = "Fn", UnicodeIcon = null }, |
| 103 | + NamedKeyboardKey.F1 => new KeyboardKeyViewModel { DisplayText = "F1", UnicodeIcon = null }, |
| 104 | + NamedKeyboardKey.F2 => new KeyboardKeyViewModel { DisplayText = "F2", UnicodeIcon = null }, |
| 105 | + NamedKeyboardKey.F3 => new KeyboardKeyViewModel { DisplayText = "F3", UnicodeIcon = null }, |
| 106 | + NamedKeyboardKey.F4 => new KeyboardKeyViewModel { DisplayText = "F4", UnicodeIcon = null }, |
| 107 | + NamedKeyboardKey.F5 => new KeyboardKeyViewModel { DisplayText = "F5", UnicodeIcon = null }, |
| 108 | + NamedKeyboardKey.F6 => new KeyboardKeyViewModel { DisplayText = "F6", UnicodeIcon = null }, |
| 109 | + NamedKeyboardKey.F7 => new KeyboardKeyViewModel { DisplayText = "F7", UnicodeIcon = null }, |
| 110 | + NamedKeyboardKey.F8 => new KeyboardKeyViewModel { DisplayText = "F8", UnicodeIcon = null }, |
| 111 | + NamedKeyboardKey.F9 => new KeyboardKeyViewModel { DisplayText = "F9", UnicodeIcon = null }, |
| 112 | + NamedKeyboardKey.F10 => new KeyboardKeyViewModel { DisplayText = "F10", UnicodeIcon = null }, |
| 113 | + NamedKeyboardKey.F11 => new KeyboardKeyViewModel { DisplayText = "F11", UnicodeIcon = null }, |
| 114 | + NamedKeyboardKey.F12 => new KeyboardKeyViewModel { DisplayText = "F12", UnicodeIcon = null }, |
| 115 | + // Function keys |
| 116 | + _ => throw new ArgumentOutOfRangeException(nameof(key), key, null) |
| 117 | + }; |
| 118 | +} |
| 119 | + |
| 120 | +[EnumExtensions] |
| 121 | +public enum NamedKeyboardKey |
| 122 | +{ |
| 123 | + // Modifier Keys |
| 124 | + [Display(Name = "shift")] Shift, |
| 125 | + [Display(Name = "ctrl")] Ctrl, |
| 126 | + [Display(Name = "alt")] Alt, |
| 127 | + [Display(Name = "option")] Option, |
| 128 | + [Display(Name = "cmd")] Command, |
| 129 | + [Display(Name = "win")] Win, |
| 130 | + |
| 131 | + // Directional Keys |
| 132 | + [Display(Name = "up")] Up, |
| 133 | + [Display(Name = "down")] Down, |
| 134 | + [Display(Name = "left")] Left, |
| 135 | + [Display(Name = "right")] Right, |
| 136 | + |
| 137 | + // Control Keys |
| 138 | + [Display(Name = "space")] Space, |
| 139 | + [Display(Name = "tab")] Tab, |
| 140 | + [Display(Name = "enter")] Enter, |
| 141 | + [Display(Name = "esc")] Escape, |
| 142 | + [Display(Name = "backspace")] Backspace, |
| 143 | + [Display(Name = "del")] Delete, |
| 144 | + [Display(Name = "ins")] Insert, |
| 145 | + |
| 146 | + // Navigation Keys |
| 147 | + [Display(Name = "pageup")] PageUp, |
| 148 | + [Display(Name = "pagedown")] PageDown, |
| 149 | + [Display(Name = "home")] Home, |
| 150 | + [Display(Name = "end")] End, |
| 151 | + |
| 152 | + // Function Keys |
| 153 | + [Display(Name = "f1")] F1, |
| 154 | + [Display(Name = "f2")] F2, |
| 155 | + [Display(Name = "f3")] F3, |
| 156 | + [Display(Name = "f4")] F4, |
| 157 | + [Display(Name = "f5")] F5, |
| 158 | + [Display(Name = "f6")] F6, |
| 159 | + [Display(Name = "f7")] F7, |
| 160 | + [Display(Name = "f8")] F8, |
| 161 | + [Display(Name = "f9")] F9, |
| 162 | + [Display(Name = "f10")] F10, |
| 163 | + [Display(Name = "f11")] F11, |
| 164 | + [Display(Name = "f12")] F12, |
| 165 | + |
| 166 | + // Other Keys |
| 167 | + [Display(Name = "plus")] Plus, |
| 168 | + [Display(Name = "fn")] Fn |
| 169 | +} |
| 170 | + |
| 171 | +public class IKeyNode; |
| 172 | + |
| 173 | +public class NamedKeyNode : IKeyNode |
| 174 | +{ |
| 175 | + public required NamedKeyboardKey Key { get; init; } |
| 176 | +} |
| 177 | + |
| 178 | +public class CharacterKeyNode : IKeyNode |
| 179 | +{ |
| 180 | + public required char Key { get; init; } |
| 181 | +} |
| 182 | + |
| 183 | +public record KeyboardKeyViewModel |
| 184 | +{ |
| 185 | + public required string? UnicodeIcon { get; init; } |
| 186 | + public required string DisplayText { get; init; } |
| 187 | +} |
0 commit comments