reveal.js: open help overlay with '?' on non-US keyboard layouts

Upstream matches only the US keyCodes (63/191)+shift, so Shift+ß on a
German layout never opened the help screen in Chrome. Match event.key
instead; F1 unchanged. tools/patch-revealjs.py re-applies this (and any
future vendored tweaks) after reveal.js updates via update-reveal.R.

Change-Id: Ie83bf56916db94b7fd6ad8e7d5c6998c264dd72b
diff --git a/tools/patch-revealjs.py b/tools/patch-revealjs.py
new file mode 100755
index 0000000..92c9247
--- /dev/null
+++ b/tools/patch-revealjs.py
@@ -0,0 +1,69 @@
+#!/usr/bin/env python3
+"""Re-apply IDS-local patches to the vendored reveal.js bundle.
+
+Run after every reveal.js update (see tools/update-reveal.R):
+
+    python3 tools/patch-revealjs.py
+
+Every patch is an exact-string replacement keyed to the minified upstream
+code. If a newer reveal.js refactors the surroundings, the script fails
+loudly instead of skipping silently -- port the patch by hand, then update
+the pattern here. Safe to run repeatedly (already-applied patches are
+detected and skipped).
+"""
+import sys
+from pathlib import Path
+
+REPO = Path(__file__).resolve().parent.parent
+
+# Patch name -> [(upstream snippet, patched snippet), ...]
+PATCHES = {
+    # Upstream toggles the help overlay only on the US-layout keyCodes for '?'
+    # (63/191) + shift, and an earlier modifier guard swallows unknown keyCodes
+    # held with shift outright. On e.g. a German layout ('?' = Shift+ss) Chrome
+    # reports neither keyCode, so '?' never opened the help screen there.
+    # Match the produced character (event.key) instead; F1 keeps working.
+    "help overlay on non-US keyboard layouts ('?' via event.key)": [
+        (
+            'l=!(-1!==[32,37,38,39,40,63,78,80,191].indexOf(e.keyCode)&&e.shiftKey||e.altKey)&&(e.shiftKey||e.altKey||e.ctrlKey||e.metaKey)',
+            'l=!(-1!==[32,37,38,39,40,63,78,80,191].indexOf(e.keyCode)&&e.shiftKey||e.altKey)&&(e.shiftKey||e.altKey||e.ctrlKey||e.metaKey)&&"?"!==e.key',
+        ),
+        (
+            ':63!==i&&191!==i||!e.shiftKey?112===i?this.Reveal.toggleHelp():u=!1:this.Reveal.toggleHelp())',
+            ':"?"!==e.key&&(!e.shiftKey||63!==i&&191!==i)?112===i?this.Reveal.toggleHelp():u=!1:this.Reveal.toggleHelp())',
+        ),
+    ],
+}
+
+
+def main():
+    bundles = sorted((REPO / "inst").glob("reveal.js-*/dist/reveal.js"))
+    if len(bundles) != 1:
+        sys.exit(f"expected exactly one vendored reveal.js, found: {bundles}")
+    path = bundles[0]
+    original = src = path.read_text()
+
+    for name, pairs in PATCHES.items():
+        for old, new in pairs:
+            if new in src:
+                print(f"already applied: {name}")
+            elif old in src:
+                src = src.replace(old, new, 1)
+                print(f"applied:         {name}")
+            else:
+                sys.exit(
+                    f"UPSTREAM CHANGED -- cannot locate pattern for patch:\n"
+                    f"  {name}\n"
+                    f"  missing snippet starts with: {old[:70]}...\n"
+                    f"Port the patch by hand, then update tools/patch-revealjs.py."
+                )
+
+    if src != original:
+        path.write_text(src)
+        print(f"written: {path}")
+    else:
+        print("nothing to do")
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tools/update-reveal.R b/tools/update-reveal.R
index 3be6803..5d13db9 100644
--- a/tools/update-reveal.R
+++ b/tools/update-reveal.R
@@ -175,3 +175,13 @@
 })
 
 gert::git_add(plugin_folders)
+
+
+# Re-apply IDS-local patches ----------------------------------------------
+
+## The vendored dist/reveal.js carries local modifications on top of upstream
+## (currently: '?' opens the help overlay on non-US keyboard layouts). They
+## live in tools/patch-revealjs.py as exact-match replacements; re-apply them
+## after the update above. The script fails loudly if upstream changed the
+## patched code, so nothing is lost silently.
+system2("python3", "tools/patch-revealjs.py")