From 467bec32b7300cbceb7b1cd20fcbd10173c5612a Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:47:10 -0400 Subject: [PATCH 1/8] Add 'keys' Quarto shortcode extension --- .../assets/_extensions/keys/_extension.yml | 7 + great_docs/assets/_extensions/keys/keys.lua | 204 ++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 great_docs/assets/_extensions/keys/_extension.yml create mode 100644 great_docs/assets/_extensions/keys/keys.lua diff --git a/great_docs/assets/_extensions/keys/_extension.yml b/great_docs/assets/_extensions/keys/_extension.yml new file mode 100644 index 0000000..f504053 --- /dev/null +++ b/great_docs/assets/_extensions/keys/_extension.yml @@ -0,0 +1,7 @@ +title: Keyboard Keys +author: Great Docs +version: 1.0.0 +quarto-required: ">=1.3.0" +contributes: + shortcodes: + - keys.lua diff --git a/great_docs/assets/_extensions/keys/keys.lua b/great_docs/assets/_extensions/keys/keys.lua new file mode 100644 index 0000000..d99c3f6 --- /dev/null +++ b/great_docs/assets/_extensions/keys/keys.lua @@ -0,0 +1,204 @@ +-- keys.lua — Quarto shortcode for styled keyboard key caps +-- +-- Usage in .qmd files: +-- +-- {{< keys "Esc" >}} +-- {{< keys "Ctrl" >}}+{{< keys "Shift" >}}+{{< keys "P" >}} +-- {{< keys shortcut="Ctrl+Shift+P" >}} +-- {{< keys shortcut="Ctrl+Shift+P" platform="mac" >}} +-- {{< keys shortcut="Cmd+K" platform="win" >}} +-- +-- Pure Lua implementation — no Python helper needed. +-- All styling is handled via CSS classes in great-docs.scss. +-- +-- NOTE: Quarto ships a built-in {{< kbd >}} shortcode that renders plain-text +-- shortcuts with per-OS keyword args (mac=, win=, linux=). This extension is +-- intentionally named "keys" to avoid conflict. It provides *styled* key caps +-- with a 3D border effect and macOS symbol translation. + +local function kwarg_str(kwargs, key) + local raw = kwargs[key] + if raw == nil then return "" end + local s = pandoc.utils.stringify(raw) + return s or "" +end + +-- Map generic key names to macOS symbols +local MAC_KEYS = { + ctrl = "⌃", + control = "⌃", + alt = "⌥", + option = "⌥", + opt = "⌥", + shift = "⇧", + cmd = "⌘", + command = "⌘", + meta = "⌘", + super = "⌘", + enter = "⏎", + ["return"] = "⏎", + tab = "⇥", + delete = "⌫", + backspace = "⌫", + esc = "⎋", + escape = "⎋", + space = "␣", + up = "▲", + down = "▼", + left = "◀", + right = "▶", +} + +-- Map macOS-specific key names to Windows/generic equivalents +local WIN_KEYS = { + cmd = "Ctrl", + command = "Ctrl", + meta = "Win", + super = "Win", + option = "Alt", + opt = "Alt", +} + +-- Tooltip text for symbolic key labels (shown on hover) +local TOOLTIPS = { + ["⌃"] = "Control", + ["⌥"] = "Option", + ["⇧"] = "Shift", + ["⌘"] = "Command", + ["⏎"] = "Enter", + ["⇥"] = "Tab", + ["⌫"] = "Delete", + ["⎋"] = "Escape", + ["␣"] = "Space", + ["▲"] = "Up", + ["▼"] = "Down", + ["◀"] = "Left", + ["▶"] = "Right", +} + +--- Escape HTML special characters. +local function escape_html(s) + return s:gsub("&", "&"):gsub("<", "<"):gsub(">", ">"):gsub('"', """) +end + +--- Check whether a label is a function key (F1–F20). +local function is_fn_key(label) + return label:match("^[Ff]%d%d?$") ~= nil +end + +--- Render a single key label into an HTML element. +--- @param label string The display text for the key +--- @return string HTML string +local function render_key(label) + local cls = "gd-keys" + if is_fn_key(label) then + cls = "gd-keys gd-keys-fn" + end + local tooltip = TOOLTIPS[label] + if tooltip then + return '' .. escape_html(label) .. '' + end + return '' .. escape_html(label) .. '' +end + +--- Translate a single key name for a given platform. +--- @param key string Raw key name (e.g. "Ctrl", "Cmd") +--- @param platform string "mac" | "win" | "" +--- @return string Display label for the key +local function translate_key(key, platform) + local lower = key:lower() + + if platform == "mac" then + local sym = MAC_KEYS[lower] + if sym then return sym end + elseif platform == "win" then + local mapped = WIN_KEYS[lower] + if mapped then return mapped end + end + + -- For keys that aren't platform-special, title-case single-char keys + -- and preserve the original casing for multi-char keys + if #key == 1 then + return key:upper() + end + return key +end + +--- Split a shortcut string on "+" while respecting a literal "+" key. +--- E.g. "Ctrl+Shift++" => {"Ctrl", "Shift", "+"} +--- @param shortcut string +--- @return table List of key names +local function split_shortcut(shortcut) + local keys = {} + local i = 1 + local len = #shortcut + + while i <= len do + -- Find next "+" + local j = shortcut:find("+", i, true) + if j == nil then + -- Last segment + local seg = shortcut:sub(i) + if seg ~= "" then + table.insert(keys, seg) + end + break + end + + local seg = shortcut:sub(i, j - 1) + if seg == "" then + -- The "+" itself is the key (e.g. at start, or after another "+") + table.insert(keys, "+") + i = j + 1 + else + table.insert(keys, seg) + i = j + 1 + end + end + + return keys +end + +return { + ["keys"] = function(args, kwargs) + local shortcut = kwarg_str(kwargs, "shortcut") + local platform = kwarg_str(kwargs, "platform") + + -- Normalise platform + if platform ~= "" then + platform = platform:lower() + if platform ~= "mac" and platform ~= "win" then + platform = "" + end + end + + -- Single-key mode: positional arg or key="" kwarg + if shortcut == "" then + local key = kwarg_str(kwargs, "key") + if key == "" and #args > 0 then + key = pandoc.utils.stringify(args[1]) + end + + if key == "" then + return pandoc.RawInline( + "html", + "" + ) + end + + local label = translate_key(key, platform) + return pandoc.RawInline("html", render_key(label)) + end + + -- Shortcut combo mode: split on "+" and render each key + local keys = split_shortcut(shortcut) + local parts = {} + for _, k in ipairs(keys) do + local label = translate_key(k, platform) + table.insert(parts, render_key(label)) + end + + local separator = '+' + return pandoc.RawInline("html", table.concat(parts, separator)) + end +} From 072659b8305110fd2ef9871e5a43842e749e88be Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:47:32 -0400 Subject: [PATCH 2/8] Add keyboard keys shortcode styles --- great_docs/assets/great-docs.scss | 61 +++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/great_docs/assets/great-docs.scss b/great_docs/assets/great-docs.scss index 04dcfc7..25df6ea 100644 --- a/great_docs/assets/great-docs.scss +++ b/great_docs/assets/great-docs.scss @@ -6192,6 +6192,67 @@ td > p:has(> svg.gd-icon) { } +/* ── Keyboard Keys Shortcode ──────────────────────────────────── */ + +.gd-keys { + display: inline-block; + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, + "Liberation Mono", monospace; + font-size: 0.8em; + font-weight: 500; + line-height: 1; + padding: 0.15em 0.45em; + min-width: 1.6em; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + color: #1f2328; + background: linear-gradient(180deg, #f6f8fa 0%, #eaeef2 100%); + border: 1px solid #d0d7de; + border-bottom-width: 2px; + border-radius: 5px; + box-shadow: 0 1px 0 rgba(27, 31, 36, 0.04), + inset 0 1px 0 rgba(255, 255, 255, 0.25); +} + +.gd-keys-sep { + display: inline-block; + margin: 0 0.15em; + font-size: 0.85em; + color: #656d76; + vertical-align: baseline; + user-select: none; +} + +/* Function keys (F1–F20): smallcaps-height text on the baseline */ +.gd-keys-fn { + font-size: 0.7em; + font-weight: 600; + letter-spacing: 0.04em; + padding: 0.25em 0.45em; + text-transform: uppercase; + position: relative; + top: -1.1px; +} + +/* Dark mode */ +body.quarto-dark .gd-keys, +html.quarto-dark .gd-keys, +:root[data-bs-theme="dark"] .gd-keys { + color: #e6edf3; + background: linear-gradient(180deg, #2d333b 0%, #22272e 100%); + border-color: #444c56; + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.3), + inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + +body.quarto-dark .gd-keys-sep, +html.quarto-dark .gd-keys-sep, +:root[data-bs-theme="dark"] .gd-keys-sep { + color: #8b949e; +} + + /* ── Video embedding enhancements ────────────────────────────── */ /* Subtle border around video containers so the frame edge is visible */ From eaea0e4ed5a97f2efc8e8ea31a419a959c9a93be Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:47:55 -0400 Subject: [PATCH 3/8] Add gdtest_keys_shortcode spec and catalog entry --- test-packages/synthetic/catalog.py | 9 + .../synthetic/specs/gdtest_keys_shortcode.py | 280 ++++++++++++++++++ 2 files changed, 289 insertions(+) create mode 100644 test-packages/synthetic/specs/gdtest_keys_shortcode.py diff --git a/test-packages/synthetic/catalog.py b/test-packages/synthetic/catalog.py index 5b62014..558b41a 100644 --- a/test-packages/synthetic/catalog.py +++ b/test-packages/synthetic/catalog.py @@ -368,6 +368,8 @@ "gdtest_hr_shortcode", # 181 # 182: Site-wide accent_color config option "gdtest_accent_color", # 182 + # 183: Keyboard keys shortcode showcase + "gdtest_keys_shortcode", # 183 ] @@ -2065,6 +2067,13 @@ "and text dividers while palette colors (sky, peach, etc.) retain " "their own hues. Tests light and dark mode handling." ), + "gdtest_keys_shortcode": ( + "Keyboard keys shortcode showcase exercising the {{< keys >}} shortcode " + "in four user-guide pages: single keys (Esc, Enter, Tab, modifiers, " + "arrows), shortcut combos (Ctrl+Shift+P auto-split), platform-aware " + "rendering (macOS symbols ⌘⌥⇧⌃ vs Windows labels), and keys in " + "context (headings, callouts, lists, blockquotes, prose)." + ), } diff --git a/test-packages/synthetic/specs/gdtest_keys_shortcode.py b/test-packages/synthetic/specs/gdtest_keys_shortcode.py new file mode 100644 index 0000000..140250b --- /dev/null +++ b/test-packages/synthetic/specs/gdtest_keys_shortcode.py @@ -0,0 +1,280 @@ +""" +gdtest_keys_shortcode — Exercise the {{< keys >}} shortcode in many contexts. + +Dimensions: A1, B1, C4, D2, E6, F1, G1, H7 +Focus: The keyboard key-cap shortcode for single keys, shortcut combos, + platform-aware rendering (macOS symbols vs Windows labels), and use + in headings, prose, tables, callouts, lists, and code-adjacent docs. + Tests that keys render as styled elements with correct classes. +""" + +SPEC = { + "name": "gdtest_keys_shortcode", + "description": "Keyboard key shortcode with combos, platform-aware rendering", + "dimensions": ["A1", "B1", "C4", "D2", "E6", "F1", "G1", "H7"], + "pyproject_toml": { + "project": { + "name": "gdtest-keys-shortcode", + "version": "1.0.0", + "description": "A package demonstrating the keys shortcode extension", + }, + "build-system": { + "requires": ["setuptools"], + "build-backend": "setuptools.build_meta", + }, + }, + "files": { + # ── Python module (minimal) ────────────────────────────────────── + "gdtest_keys_shortcode/__init__.py": ( + '"""Keyboard shortcode demo package."""\n' + "\n" + '__version__ = "1.0.0"\n' + '__all__ = ["render", "transform"]\n' + "\n" + "\n" + "def render(template: str) -> str:\n" + ' """Render a template string.\n' + "\n" + " Parameters\n" + " ----------\n" + " template\n" + " The template to render.\n" + "\n" + " Returns\n" + " -------\n" + " str\n" + " Rendered output.\n" + ' """\n' + " return template\n" + "\n" + "\n" + "def transform(data: list) -> list:\n" + ' """Transform a data list.\n' + "\n" + " Parameters\n" + " ----------\n" + " data\n" + " Input data.\n" + "\n" + " Returns\n" + " -------\n" + " list\n" + " Transformed data.\n" + ' """\n' + " return data\n" + ), + # ── User guide page 1: Single keys ─────────────────────────────── + "user_guide/01-single-keys.qmd": ( + "---\n" + "title: Single Keys\n" + "---\n" + "\n" + "The `{{< keys >}}` shortcode renders individual keyboard keys\n" + "with styled key-cap appearance.\n" + "\n" + "## Basic Keys\n" + "\n" + 'Press {{< keys "Esc" >}} to cancel.\n' + "\n" + 'Press {{< keys "Enter" >}} to confirm.\n' + "\n" + 'Press {{< keys "Tab" >}} to move to the next field.\n' + "\n" + 'Press {{< keys "Space" >}} to toggle.\n' + "\n" + "## Modifier Keys\n" + "\n" + 'The {{< keys "Ctrl" >}} key is used for shortcuts.\n' + "\n" + 'Hold {{< keys "Shift" >}} to select a range.\n' + "\n" + 'The {{< keys "Alt" >}} key triggers alternate actions.\n' + "\n" + "## Letter and Number Keys\n" + "\n" + 'Press {{< keys "A" >}} to select all (with Ctrl).\n' + "\n" + 'Press {{< keys "F5" >}} to refresh.\n' + "\n" + 'Press {{< keys "F1" >}} for help.\n' + "\n" + "## Arrow Keys\n" + "\n" + "Use the arrow keys to navigate:\n" + '{{< keys "Up" >}} {{< keys "Down" >}}' + ' {{< keys "Left" >}} {{< keys "Right" >}}\n' + "\n" + "## Function Keys\n" + "\n" + "Function keys render with compact, x-height styling:\n" + "\n" + '{{< keys "F1" >}} {{< keys "F2" >}} {{< keys "F3" >}} ' + '{{< keys "F4" >}} {{< keys "F5" >}}\n' + '{{< keys "F6" >}} {{< keys "F7" >}} {{< keys "F8" >}} ' + '{{< keys "F9" >}} {{< keys "F10" >}}\n' + '{{< keys "F11" >}} {{< keys "F12" >}}\n' + ), + # ── User guide page 2: Shortcut combos ────────────────────────── + "user_guide/02-shortcut-combos.qmd": ( + "---\n" + "title: Shortcut Combos\n" + "---\n" + "\n" + "Use `shortcut=` to render multi-key combinations automatically.\n" + "\n" + "## Common Editor Shortcuts\n" + "\n" + "| Action | Shortcut |\n" + "|--------|----------|\n" + '| Copy | {{< keys shortcut="Ctrl+C" >}} |\n' + '| Paste | {{< keys shortcut="Ctrl+V" >}} |\n' + '| Undo | {{< keys shortcut="Ctrl+Z" >}} |\n' + '| Save | {{< keys shortcut="Ctrl+S" >}} |\n' + '| Find | {{< keys shortcut="Ctrl+F" >}} |\n' + "\n" + "## VS Code Shortcuts\n" + "\n" + "Open the command palette with\n" + '{{< keys shortcut="Ctrl+Shift+P" >}}.\n' + "\n" + "Toggle the terminal with\n" + '{{< keys shortcut="Ctrl+Shift+`" >}}.\n' + "\n" + "Quick open a file with\n" + '{{< keys shortcut="Ctrl+P" >}}.\n' + "\n" + "## Manual Combos\n" + "\n" + "You can also build combos manually:\n" + '{{< keys "Ctrl" >}}+{{< keys "Shift" >}}+{{< keys "P" >}}\n' + ), + # ── User guide page 3: Platform-aware rendering ────────────────── + "user_guide/03-platform-aware.qmd": ( + "---\n" + "title: Platform-Aware Rendering\n" + "---\n" + "\n" + "The `platform=` parameter translates keys for macOS or Windows.\n" + "\n" + "## macOS Rendering\n" + "\n" + "On macOS, modifier keys render as symbols:\n" + "\n" + "| Generic | macOS |\n" + "|---------|-------|\n" + '| Ctrl | {{< keys shortcut="Ctrl+C" platform="mac" >}} |\n' + '| Cmd | {{< keys shortcut="Cmd+S" platform="mac" >}} |\n' + '| Alt/Option | {{< keys shortcut="Alt+F" platform="mac" >}} |\n' + '| Shift | {{< keys shortcut="Shift+A" platform="mac" >}} |\n' + "\n" + "Command palette on macOS:\n" + '{{< keys shortcut="Cmd+Shift+P" platform="mac" >}}\n' + "\n" + "## Windows Rendering\n" + "\n" + "On Windows, macOS-specific keys are translated:\n" + "\n" + "| macOS | Windows |\n" + "|-------|--------|\n" + '| Cmd+S | {{< keys shortcut="Cmd+S" platform="win" >}} |\n' + '| Option+F | {{< keys shortcut="Option+F" platform="win" >}} |\n' + "\n" + "## Default (No Platform)\n" + "\n" + "Without `platform=`, keys are rendered as-is:\n" + "\n" + '{{< keys shortcut="Ctrl+Shift+P" >}}\n' + ), + # ── User guide page 4: Keys in context ────────────────────────── + "user_guide/04-keys-in-context.qmd": ( + "---\n" + "title: Keys in Context\n" + "---\n" + "\n" + "Keyboard shortcuts work in many documentation contexts.\n" + "\n" + '## {{< keys "F1" >}} Headings with Keys\n' + "\n" + "Keys can appear in section headings for quick reference.\n" + "\n" + "## In Callouts\n" + "\n" + ":::{.callout-tip}\n" + "## Keyboard Shortcut\n" + 'Press {{< keys shortcut="Ctrl+Shift+P" >}} to open the command\n' + "palette in VS Code.\n" + ":::\n" + "\n" + ":::{.callout-note}\n" + "## Navigation\n" + 'Use {{< keys "Tab" >}} and {{< keys shortcut="Shift+Tab" >}} to\n' + "move between form fields.\n" + ":::\n" + "\n" + "## In Lists\n" + "\n" + "Common shortcuts:\n" + "\n" + '- {{< keys shortcut="Ctrl+C" >}} — Copy\n' + '- {{< keys shortcut="Ctrl+V" >}} — Paste\n' + '- {{< keys shortcut="Ctrl+Z" >}} — Undo\n' + '- {{< keys shortcut="Ctrl+Y" >}} — Redo\n' + "\n" + "## In Blockquotes\n" + "\n" + '> Press {{< keys "Esc" >}} to close any dialog or cancel\n' + "> the current operation.\n" + "\n" + "## In Prose\n" + "\n" + 'To save your work, press {{< keys shortcut="Ctrl+S" >}}. ' + "If you need to undo a mistake, reach for\n" + '{{< keys shortcut="Ctrl+Z" >}}. For more advanced operations,\n' + 'open the command palette with {{< keys shortcut="Ctrl+Shift+P" >}}.\n' + ), + # ── README ─────────────────────────────────────────────────────── + "README.md": ( + "# gdtest-keys-shortcode\n" + "\n" + "A synthetic test package that exercises the `{{< keys >}}` Quarto\n" + "shortcode for rendering keyboard keys with styled key-caps.\n" + "Covers single keys, shortcut combos, platform-aware rendering,\n" + "and keys in various content contexts.\n" + ), + }, + "expected": { + "detected_name": "gdtest-keys-shortcode", + "detected_module": "gdtest_keys_shortcode", + "detected_parser": "numpy", + "export_names": ["render", "transform"], + "num_exports": 2, + "has_user_guide": True, + "user_guide_files": [ + "single-keys.qmd", + "shortcut-combos.qmd", + "platform-aware.qmd", + "keys-in-context.qmd", + ], + # Content assertions for dedicated tests + "files_contain": { + "great-docs/_site/user-guide/single-keys.html": [ + "gd-keys", # CSS class on kbd elements + "gd-keys-fn", # function key compact styling + "Single Keys", # page title + ], + "great-docs/_site/user-guide/shortcut-combos.html": [ + "gd-keys", + "gd-keys-sep", # separator between combo keys + "Shortcut Combos", + ], + "great-docs/_site/user-guide/platform-aware.html": [ + "gd-keys", + "Platform-Aware Rendering", + ], + "great-docs/_site/user-guide/keys-in-context.html": [ + "gd-keys", + "Keys in Context", + ], + }, + }, +} From 6166ebcbfc3896f01086059828119a49f7907c01 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:48:09 -0400 Subject: [PATCH 4/8] Add tests for Keys shortcode rendering --- tests/test_gdg_rendered.py | 107 +++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tests/test_gdg_rendered.py b/tests/test_gdg_rendered.py index 1e1fba7..7f49187 100644 --- a/tests/test_gdg_rendered.py +++ b/tests/test_gdg_rendered.py @@ -8516,3 +8516,110 @@ def test_DED_sec_blog_user_index_copies_post_image(): img = _site_dir(pkg) / "blog" / "first-post" / "post-banner.svg" assert img.exists(), "Post-level image first-post/post-banner.svg not copied" + + +# ═══════════════════════════════════════════════════════════════════════════════ +# Keys Shortcode — {{< keys >}} renders styled keyboard key caps +# ═══════════════════════════════════════════════════════════════════════════════ + +_KBD_PKG = "gdtest_keys_shortcode" + + +@requires_bs4 +def test_KBD_single_keys_page_exists(): + """The single-keys user guide page should be rendered.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + html_path = _site_dir(_KBD_PKG) / "user-guide" / "single-keys.html" + assert html_path.exists(), "single-keys.html not found" + + +@requires_bs4 +def test_KBD_shortcut_combos_page_exists(): + """The shortcut-combos user guide page should be rendered.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + html_path = _site_dir(_KBD_PKG) / "user-guide" / "shortcut-combos.html" + assert html_path.exists(), "shortcut-combos.html not found" + + +@requires_bs4 +def test_KBD_platform_aware_page_exists(): + """The platform-aware user guide page should be rendered.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + html_path = _site_dir(_KBD_PKG) / "user-guide" / "platform-aware.html" + assert html_path.exists(), "platform-aware.html not found" + + +@requires_bs4 +def test_KBD_keys_in_context_page_exists(): + """The keys-in-context user guide page should be rendered.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + html_path = _site_dir(_KBD_PKG) / "user-guide" / "keys-in-context.html" + assert html_path.exists(), "keys-in-context.html not found" + + +@requires_bs4 +def test_KBD_single_keys_contains_kbd_elements(): + """Single keys page should contain elements with gd-keys class.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + page = _site_dir(_KBD_PKG) / "user-guide" / "single-keys.html" + if not page.exists(): + pytest.skip("single-keys.html not found") + + soup = _load_html(page) + kbd_els = soup.find_all("kbd", class_="gd-keys") + assert len(kbd_els) >= 5, f"Expected ≥5 kbd elements, found {len(kbd_els)}" + + +@requires_bs4 +def test_KBD_shortcut_combos_has_separator(): + """Shortcut combos page should contain gd-keys-sep separator spans.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + page = _site_dir(_KBD_PKG) / "user-guide" / "shortcut-combos.html" + if not page.exists(): + pytest.skip("shortcut-combos.html not found") + + soup = _load_html(page) + seps = soup.find_all("span", class_="gd-keys-sep") + assert len(seps) >= 3, f"Expected ≥3 separator spans, found {len(seps)}" + + +@requires_bs4 +def test_KBD_platform_mac_has_symbols(): + """Platform-aware page with platform=mac should render macOS symbols.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + page = _site_dir(_KBD_PKG) / "user-guide" / "platform-aware.html" + if not page.exists(): + pytest.skip("platform-aware.html not found") + + content = page.read_text() + # macOS symbols for Cmd and Shift should appear + assert "⌘" in content, "Expected ⌘ (Command) symbol for mac platform" + + +@requires_bs4 +def test_KBD_no_shortcode_errors(): + """No keys shortcode should produce an error HTML comment.""" + if not _has_rendered_site(_KBD_PKG): + pytest.skip(f"{_KBD_PKG} not rendered") + + ug_dir = _site_dir(_KBD_PKG) / "user-guide" + if not ug_dir.exists(): + pytest.skip("user-guide directory not found") + + for html_file in ug_dir.glob("*.html"): + content = html_file.read_text() + assert "keys shortcode error" not in content, f"Shortcode error in {html_file.name}" From f6d677fd761c00f91ea26f64bcb81167e4f556b6 Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:49:08 -0400 Subject: [PATCH 5/8] Add tests for keys shortcode extension --- tests/test_keys_shortcode.py | 153 +++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) create mode 100644 tests/test_keys_shortcode.py diff --git a/tests/test_keys_shortcode.py b/tests/test_keys_shortcode.py new file mode 100644 index 0000000..b0f37c2 --- /dev/null +++ b/tests/test_keys_shortcode.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from pathlib import Path + +import pytest +import yaml + + +def _ext_dir() -> Path: + return Path(__file__).parent.parent / "great_docs" / "assets" / "_extensions" / "keys" + + +class TestKeysExtensionFiles: + """Verify that the keys extension ships the required files.""" + + def test_extension_yml_exists(self): + assert (_ext_dir() / "_extension.yml").exists() + + def test_lua_filter_exists(self): + assert (_ext_dir() / "keys.lua").exists() + + def test_extension_yml_declares_shortcode(self): + ext_yml = _ext_dir() / "_extension.yml" + data = yaml.safe_load(ext_yml.read_text()) + assert "contributes" in data + assert "shortcodes" in data["contributes"] + assert "keys.lua" in data["contributes"]["shortcodes"] + + def test_extension_yml_metadata(self): + ext_yml = _ext_dir() / "_extension.yml" + data = yaml.safe_load(ext_yml.read_text()) + assert data["title"] == "Keyboard Keys" + assert data["author"] == "Great Docs" + assert "version" in data + assert "quarto-required" in data + + def test_lua_defines_keys_function(self): + lua_src = (_ext_dir() / "keys.lua").read_text() + assert '["keys"]' in lua_src + assert "function(args, kwargs)" in lua_src + + +class TestKeysLuaContent: + """Verify key logic markers in the Lua source.""" + + @pytest.fixture() + def lua_src(self) -> str: + return (_ext_dir() / "keys.lua").read_text() + + def test_has_mac_key_table(self, lua_src): + assert "MAC_KEYS" in lua_src + + def test_has_win_key_table(self, lua_src): + assert "WIN_KEYS" in lua_src + + def test_mac_command_symbol(self, lua_src): + assert "⌘" in lua_src + + def test_mac_option_symbol(self, lua_src): + assert "⌥" in lua_src + + def test_mac_shift_symbol(self, lua_src): + assert "⇧" in lua_src + + def test_mac_control_symbol(self, lua_src): + assert "⌃" in lua_src + + def test_renders_gd_keys_class(self, lua_src): + assert '"gd-keys"' in lua_src + + def test_renders_gd_keys_sep_class(self, lua_src): + assert 'class="gd-keys-sep"' in lua_src + + def test_escape_html_function(self, lua_src): + assert "escape_html" in lua_src + + def test_handles_shortcut_kwarg(self, lua_src): + assert 'kwarg_str(kwargs, "shortcut")' in lua_src + + def test_handles_platform_kwarg(self, lua_src): + assert 'kwarg_str(kwargs, "platform")' in lua_src + + def test_split_shortcut_function(self, lua_src): + assert "split_shortcut" in lua_src + + def test_translate_key_function(self, lua_src): + assert "translate_key" in lua_src + + def test_outputs_raw_inline_html(self, lua_src): + assert 'pandoc.RawInline("html"' in lua_src + + def test_error_comment_on_missing_key(self, lua_src): + assert "keys shortcode error" in lua_src + + def test_has_tooltips_table(self, lua_src): + assert "TOOLTIPS" in lua_src + + def test_title_attribute_for_symbols(self, lua_src): + assert 'title="' in lua_src + + def test_fn_key_detection(self, lua_src): + assert "is_fn_key" in lua_src + + def test_fn_key_class(self, lua_src): + assert "gd-keys-fn" in lua_src + + +class TestKeysScssStyles: + """Verify keys styles are present in great-docs.scss.""" + + @pytest.fixture() + def scss_src(self) -> str: + scss_path = Path(__file__).parent.parent / "great_docs" / "assets" / "great-docs.scss" + return scss_path.read_text() + + def test_gd_keys_class(self, scss_src): + assert ".gd-keys" in scss_src + + def test_gd_keys_sep_class(self, scss_src): + assert ".gd-keys-sep" in scss_src + + def test_dark_mode_keys(self, scss_src): + assert "quarto-dark .gd-keys" in scss_src + + def test_dark_mode_sep(self, scss_src): + assert "quarto-dark .gd-keys-sep" in scss_src + + def test_3d_border_effect(self, scss_src): + """The keys should have a subtle 3D border-bottom effect.""" + assert "border-bottom-width: 2px" in scss_src + + def test_monospace_font(self, scss_src): + """Keyboard keys should use a monospace font.""" + idx = scss_src.index(".gd-keys {") + block = scss_src[idx : idx + 500] + assert "monospace" in block + + def test_fn_key_styles(self, scss_src): + """Function keys should have smallcaps-height styling.""" + assert ".gd-keys-fn" in scss_src + + +class TestKeysOutputDir: + """Verify that the extension was also copied to the output great-docs/ dir.""" + + def _output_ext_dir(self) -> Path: + return Path(__file__).parent.parent / "great-docs" / "_extensions" / "keys" + + def test_output_extension_yml_exists(self): + assert (self._output_ext_dir() / "_extension.yml").exists() + + def test_output_lua_exists(self): + assert (self._output_ext_dir() / "keys.lua").exists() From 8ed2f4936b581fdd5dba8ad7645b8873865e0ced Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:49:47 -0400 Subject: [PATCH 6/8] Add Keyboard Keys shortcode guide --- user_guide/34-keyboard-keys.qmd | 278 ++++++++++++++++++++++++++++++++ 1 file changed, 278 insertions(+) create mode 100644 user_guide/34-keyboard-keys.qmd diff --git a/user_guide/34-keyboard-keys.qmd b/user_guide/34-keyboard-keys.qmd new file mode 100644 index 0000000..dda7b36 --- /dev/null +++ b/user_guide/34-keyboard-keys.qmd @@ -0,0 +1,278 @@ +--- +title: "Keyboard Keys" +guide-section: "Site Content" +tags: [Content, Extensions] +status: experimental +upcoming: "0.10" +versions: ">=0.9" +--- + +# Keyboard Keys + +Documentation often needs to show keyboard shortcuts: "press {{< keys shortcut="Ctrl+S" >}} to +save" or "hit {{< keys "Esc" >}} to close the dialog". The `{{{< keys >}}}` shortcode renders these +as styled key caps with a subtle 3D border effect, monospace font, and automatic dark-mode support. + +It handles single keys, multi-key combos, and platform-aware rendering so that macOS readers see +`⌘` while Windows readers see `Ctrl`. + +## Quick Start + +Wrap a key name in the shortcode to render a styled key cap: + +````markdown +Press {{{< keys "Esc" >}}} to close. +```` + +Press {{< keys "Esc" >}} to close. + +For a multi-key shortcut, use the `shortcut` parameter. Keys are split on `+` automatically: + +````markdown +{{{< keys shortcut="Ctrl+Shift+P" >}}} +```` + +{{< keys shortcut="Ctrl+Shift+P" >}} + +## Single Keys + +The simplest usage is a single key name passed as a positional argument. This works for any key +you might find on a keyboard: letters, numbers, function keys, navigation keys, and modifiers. + +````markdown +{{{< keys "Enter" >}}} +{{{< keys "Tab" >}}} +{{{< keys "Space" >}}} +{{{< keys "F5" >}}} +{{{< keys "A" >}}} +```` + +{{< keys "Enter" >}} {{< keys "Tab" >}} {{< keys "Space" >}} {{< keys "F5" >}} {{< keys "A" >}} + +Modifier keys render just like regular keys: + +{{< keys "Ctrl" >}} {{< keys "Shift" >}} {{< keys "Alt" >}} + +All keys share the same visual style: a rounded border, subtle shadow, and monospace font that +mimics the appearance of physical key caps. + +## Shortcut Combos + +Most keyboard shortcuts involve pressing multiple keys together. The `shortcut` parameter accepts a +string like `"Ctrl+Shift+P"`, splits it on `+`, and renders each key with a `+` separator between +them: + +````markdown +{{{< keys shortcut="Ctrl+C" >}}} +{{{< keys shortcut="Ctrl+Shift+P" >}}} +{{{< keys shortcut="Alt+F4" >}}} +```` + +{{< keys shortcut="Ctrl+C" >}} + +{{< keys shortcut="Ctrl+Shift+P" >}} + +{{< keys shortcut="Alt+F4" >}} + +| Action | Shortcut | +|--------|----------| +| Copy | {{< keys shortcut="Ctrl+C" >}} | +| Paste | {{< keys shortcut="Ctrl+V" >}} | +| Undo | {{< keys shortcut="Ctrl+Z" >}} | +| Save | {{< keys shortcut="Ctrl+S" >}} | +| Find | {{< keys shortcut="Ctrl+F" >}} | +| Command palette | {{< keys shortcut="Ctrl+Shift+P" >}} | + +You can also build combos manually by placing individual keys next to each other: + +````markdown +{{{< keys "Ctrl" >}}}+{{{< keys "Shift" >}}}+{{{< keys "P" >}}} +```` + +{{< keys "Ctrl" >}}+{{< keys "Shift" >}}+{{< keys "P" >}} + +Both approaches are valid. Use `shortcut=` for convenience, or manual placement when you want +different separators or spacing between keys. + +## Platform-Aware Rendering + +Different operating systems use different key names. macOS has Command (⌘) and Option (⌥), while +Windows uses Ctrl and Alt. The `platform` parameter translates key names so you can write +platform-specific documentation without hard-coding symbols. + +### macOS + +With `platform="mac"`, standard modifier names are replaced with their macOS symbol equivalents. +This is useful when writing documentation targeted at macOS users: + +| Key name | macOS symbol | +|----------|-------------| +| Ctrl | {{< keys "Ctrl" platform="mac" >}} (⌃) | +| Cmd / Command | {{< keys "Cmd" platform="mac" >}} (⌘) | +| Alt / Option | {{< keys "Alt" platform="mac" >}} (⌥) | +| Shift | {{< keys "Shift" platform="mac" >}} (⇧) | +| Up / Down / Left / Right | {{< keys "Up" platform="mac" >}} {{< keys "Down" platform="mac" >}} {{< keys "Left" platform="mac" >}} {{< keys "Right" platform="mac" >}} (▲▼◀▶) | + +A full shortcut with macOS rendering: + +````markdown +{{{< keys shortcut="Cmd+Shift+P" platform="mac" >}}} +```` + +{{< keys shortcut="Cmd+Shift+P" platform="mac" >}} + +The symbols are compact and universally recognized by macOS users, making them ideal for +application-specific documentation. + +### Windows + +With `platform="win"`, macOS-specific key names are translated to their Windows equivalents. This +lets you write a single shortcode and render it appropriately for Windows readers: + +| macOS key | Windows key | +|-----------|------------| +| Cmd | {{< keys "Cmd" platform="win" >}} (Ctrl) | +| Option | {{< keys "Option" platform="win" >}} (Alt) | + +````markdown +{{{< keys shortcut="Cmd+S" platform="win" >}}} +```` + +{{< keys shortcut="Cmd+S" platform="win" >}} + +Keys that have no platform-specific equivalent (like letter keys and function keys) pass through +unchanged. + +### Default (No Platform) + +Without a `platform` parameter, key names are rendered exactly as written. This is the right choice +when your audience uses multiple platforms or when the shortcut is platform-neutral: + +{{< keys shortcut="Ctrl+Shift+P" >}} + +For most documentation, leaving `platform` unset is the simplest and most portable option. + +## In Context + +Keyboard keys work naturally in all common documentation contexts. The examples below show how +`{{{< keys >}}}` integrates with prose, lists, callouts, and blockquotes. + +### Inline Prose + +Keys blend seamlessly into running text without disrupting the reading flow: + +To save your work, press {{< keys shortcut="Ctrl+S" >}}. If you need to undo, reach for +{{< keys shortcut="Ctrl+Z" >}}. For more advanced operations, open the command palette with +{{< keys shortcut="Ctrl+Shift+P" >}}. + +This pattern works well in tutorials where you walk readers through a series of actions. + +### Lists + +Shortcuts pair naturally with list items. Place the key combo first, followed by a description: + +- {{< keys shortcut="Ctrl+C" >}} Copy +- {{< keys shortcut="Ctrl+V" >}} Paste +- {{< keys shortcut="Ctrl+Z" >}} Undo +- {{< keys shortcut="Ctrl+Y" >}} Redo +- {{< keys shortcut="Ctrl+F" >}} Find + +This layout makes it easy to scan a list of shortcuts at a glance. + +### Callouts + +Callouts are a great way to highlight important shortcuts that readers should remember: + +:::{.callout-tip} +## Quick Navigation +Press {{< keys shortcut="Ctrl+P" >}} to quickly open any file by name, or +{{< keys shortcut="Ctrl+Shift+P" >}} to access the full command palette. +::: + +The styled key caps remain legible inside colored callout backgrounds. + +### Blockquotes + +Keys also render correctly inside blockquotes: + +> Press {{< keys "Esc" >}} to close any dialog or cancel the current operation. + +This is useful for quoting interface instructions or reproducing help text from an application. + +## Parameter Reference + +The table below summarizes all available parameters for the `{{{< keys >}}}` shortcode. + +| Parameter | Values | Default | Description | +|-----------|--------|---------|-------------| +| _(positional)_ | Any key name | (none) | Single key to render | +| `shortcut` | Key combo string | (none) | Multi-key shortcut, split on `+` | +| `platform` | `mac`, `win` | (none) | Translate keys for a specific OS | + +Either the positional argument or `shortcut` is required. The `platform` parameter is always +optional. + +## Function Keys + +Function keys ({{< keys "F1" >}} through {{< keys "F20" >}}) receive a compact typographic +treatment inspired by macOS menu glyphs. The text is rendered at x-height scale with slightly +bolder weight, giving them a distinct look that sets them apart from regular keys: + +{{< keys "F1" >}} {{< keys "F2" >}} {{< keys "F3" >}} {{< keys "F4" >}} {{< keys "F5" >}} +{{< keys "F6" >}} {{< keys "F7" >}} {{< keys "F8" >}} {{< keys "F9" >}} {{< keys "F10" >}} +{{< keys "F11" >}} {{< keys "F12" >}} + +Function keys work in combos too: + +{{< keys shortcut="Ctrl+F5" >}} + +{{< keys shortcut="Alt+F4" >}} + +{{< keys shortcut="Cmd+Shift+F12" platform="mac" >}} + +The compact styling is applied automatically and is not platform-specific. + +## Dark Mode + +Key caps automatically adapt in dark mode. The background shifts to a dark gradient, borders become +more subtle, and text brightens for readability. No extra configuration is needed; the shortcode +works identically in both light and dark themes. + +## Copy/Paste Friendly + +Rendered shortcuts are plain text under the hood, so copying them from the page preserves the +readable key names and separators. For example, copying a macOS shortcut like +{{< keys shortcut="Cmd+Shift+P" platform="mac" >}} produces `⌘+⇧+P` in the clipboard. This +makes it easy for readers to paste shortcuts into notes, chat messages, or other documents without +losing meaning. + +## Symbol Tooltips + +When platform-aware rendering produces macOS symbols, each key cap includes a `title` attribute +with the full key name. Hovering over {{< keys "Cmd" platform="mac" >}} shows "Command", +{{< keys "Alt" platform="mac" >}} shows "Option", and so on. This helps readers who may not +recognize every symbol at a glance. + +## Comparison with Quarto's Built-In `kbd` + +Quarto ships with its own `{{{< kbd >}}}` shortcode +([documentation](https://quarto.org/docs/authoring/markdown-basics.html#keyboard-shortcuts)) that +renders keyboard shortcuts as plain text. It supports per-OS keyword arguments (`mac=`, `win=`, +`linux=`) and auto-detects the reader's operating system in HTML output: + +````markdown +{{{< kbd Shift-Ctrl-P >}}} +{{{< kbd mac=Shift-Command-O win=Shift-Control-O linux=Shift-Ctrl-L >}}} +```` + +The Great Docs `{{{< keys >}}}` shortcode differs in three ways: + +1. **Visual styling.** Keys render as styled caps with a 3D border, gradient background, and + monospace font rather than plain text. +2. **Symbol translation.** With `platform="mac"`, modifier names are replaced by their macOS + symbols (⌘, ⌥, ⇧, ⌃) rather than spelled out. +3. **Separator rendering.** Combo keys are joined by a styled `+` separator element. + +Choose `{{{< kbd >}}}` when you want minimal, format-agnostic output that works in PDF, DOCX, and +other non-HTML targets. Choose `{{{< keys >}}}` when you are authoring HTML documentation and want +visually distinct key-cap styling that matches the Great Docs design system. From 3f909c898a12018b3db7439c1a990dba6982b54c Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:49:51 -0400 Subject: [PATCH 7/8] Update great-docs.yml --- great-docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/great-docs.yml b/great-docs.yml index b899826..6f92b1d 100644 --- a/great-docs.yml +++ b/great-docs.yml @@ -265,6 +265,7 @@ nav_icons: Table Previews: table Table Explorer: telescope Horizontal Rules: minus + Keyboard Keys: keyboard # Author Information # ------------------ From d961fca54aa6a3bb4481f4d61e1f113b11ca3f7d Mon Sep 17 00:00:00 2001 From: Richard Iannone Date: Wed, 29 Apr 2026 11:53:28 -0400 Subject: [PATCH 8/8] Skip keys output tests if dir is missing --- tests/test_keys_shortcode.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/test_keys_shortcode.py b/tests/test_keys_shortcode.py index b0f37c2..7d768ed 100644 --- a/tests/test_keys_shortcode.py +++ b/tests/test_keys_shortcode.py @@ -147,7 +147,13 @@ def _output_ext_dir(self) -> Path: return Path(__file__).parent.parent / "great-docs" / "_extensions" / "keys" def test_output_extension_yml_exists(self): - assert (self._output_ext_dir() / "_extension.yml").exists() + d = self._output_ext_dir() + if not d.exists(): + pytest.skip("output dir not present (gitignored, needs local build)") + assert (d / "_extension.yml").exists() def test_output_lua_exists(self): - assert (self._output_ext_dir() / "keys.lua").exists() + d = self._output_ext_dir() + if not d.exists(): + pytest.skip("output dir not present (gitignored, needs local build)") + assert (d / "keys.lua").exists()