fix(web): accessibility of the command palette, live swaps and shortcuts (#59)
- commit
- ef38ad003e0ebee3afecd42d576ac4228405050e
- author
- brice · · 2026-10-18 06:39 UTC
- committer
- bnema · · 2026-10-18 06:39 UTC
- parent
- 79d6b04
- signature
- SSH Unverified
SHA256:VOCA5lYARHcbKDZ7GhTV45VmcjWnJ6ZQIBazj17LivAThe key is unknown - refs
- tag: v0.21.0
45 files · +1393 -451
M internal/adapters/web/diff.templ +1 -1
| @@ -54,7 +54,7 @@ templ Commit(l Layout, p CommitPage) { | |||
| 54 | 54 | } | |
| 55 | 55 | </dl> | |
| 56 | 56 | </header> | |
| 57 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 57 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 58 | 58 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | |
| 59 | 59 | <span class="grow"></span> | |
| 60 | 60 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
M internal/adapters/web/diff_templ.go +1 -1 generated file: collapsed
A internal/adapters/web/jstest/a11y.test.mjs +319 -0
| @@ -0,0 +1,319 @@ | |||
| 1 | + | // Tests of the keyboard accessibility of static/js/ermite.js and files.js on | |
| 2 | + | // the small DOM of dom.mjs: the "go to" palette as a combobox, Escape in the | |
| 3 | + | // file finder, the focus kept through a live swap, and the preference that | |
| 4 | + | // turns the single-key shortcuts off. Run with | |
| 5 | + | // node --test 'internal/adapters/web/jstest/*.test.mjs' | |
| 6 | + | import assert from 'node:assert/strict'; | |
| 7 | + | import { test } from 'node:test'; | |
| 8 | + | import { dom, El, el, flushFrames, installDom, settle } from './dom.mjs'; | |
| 9 | + | ||
| 10 | + | // The few things ermite.js uses that dom.mjs does not have. | |
| 11 | + | El.prototype.replaceWith = function (n) { | |
| 12 | + | const p = this.parent; | |
| 13 | + | const i = p.nodes.indexOf(this); | |
| 14 | + | const [added] = p.adopt([n]); | |
| 15 | + | p.nodes.splice(i, 1, added); | |
| 16 | + | this.parent = null; | |
| 17 | + | }; | |
| 18 | + | El.prototype.contains = function (e) { for (let n = e; n; n = n.parent) if (n === this) return true; return false; }; | |
| 19 | + | Object.defineProperty(El.prototype, 'isConnected', { get() { let n = this; while (n.parent) n = n.parent; return n === dom.body; } }); | |
| 20 | + | El.prototype.toggleAttribute = function (k, on) { if (on) this.setAttribute(k, ''); else this.removeAttribute(k); return on; }; | |
| 21 | + | El.prototype.dispatchEvent = function (ev) { (this.events ||= []).push(ev.type); }; | |
| 22 | + | // The focusable controls of a fragment, as the selector of ermiteLive says. | |
| 23 | + | const nativeAll = El.prototype.querySelectorAll; | |
| 24 | + | El.prototype.querySelectorAll = function (sel) { | |
| 25 | + | if (sel === '[data-rows] > [data-cursor]') return this.querySelectorAll('li[data-cursor]'); // no cursor in these tests | |
| 26 | + | if (sel === '[data-rows] > *') return []; | |
| 27 | + | if (!sel.startsWith('a[href], button')) return nativeAll.call(this, sel); | |
| 28 | + | const out = []; | |
| 29 | + | const walk = (e) => e.children.forEach((c) => { | |
| 30 | + | if ((c.tagName === 'A' && c.hasAttribute('href')) || ['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'SUMMARY'].includes(c.tagName) || c.hasAttribute('tabindex')) out.push(c); | |
| 31 | + | walk(c); | |
| 32 | + | }); | |
| 33 | + | walk(this); | |
| 34 | + | return out; | |
| 35 | + | }; | |
| 36 | + | ||
| 37 | + | let active = null; | |
| 38 | + | // As in a browser, an element inside a hidden one does not take the focus. | |
| 39 | + | El.prototype.focus = function (opts) { | |
| 40 | + | dom.focused.push({ el: this, opts }); | |
| 41 | + | if (!this.closest('[hidden]')) active = this; | |
| 42 | + | }; | |
| 43 | + | const doc = installDom({ parseHTML: (text) => parsed[text] }); | |
| 44 | + | const parsed = {}; | |
| 45 | + | Object.defineProperty(doc, 'activeElement', { get: () => active ?? dom.body }); | |
| 46 | + | doc.documentElement = { classList: { add() {} } }; | |
| 47 | + | const query = { all: doc.querySelectorAll, one: doc.querySelector }; | |
| 48 | + | doc.querySelectorAll = () => []; // the page-wide queries of the top of ermite.js | |
| 49 | + | doc.querySelector = () => null; | |
| 50 | + | ||
| 51 | + | const store = new Map(); | |
| 52 | + | globalThis.localStorage = { getItem: (k) => store.get(k) ?? null, setItem: (k, v) => store.set(k, String(v)) }; | |
| 53 | + | const listeners = {}; | |
| 54 | + | globalThis.addEventListener = (type, f) => (listeners[type] ||= []).push(f); | |
| 55 | + | globalThis.scrollTo = () => {}; | |
| 56 | + | globalThis.fetch = async () => ({ ok: true, json: async () => ({ paths: ['cmd/main.go', 'README.md'] }) }); | |
| 57 | + | ||
| 58 | + | await import('../static/js/files.js'); | |
| 59 | + | await import('../static/js/ermite.js'); | |
| 60 | + | doc.querySelectorAll = query.all; | |
| 61 | + | doc.querySelector = query.one; | |
| 62 | + | const F = window.ermiteFinder; | |
| 63 | + | const K = window.ermiteKeys; | |
| 64 | + | ||
| 65 | + | // keydown runs an element handler, then the window ones unless stopped, as a | |
| 66 | + | // browser does. | |
| 67 | + | function keydown(target, key, elementHandler, extra = {}) { | |
| 68 | + | const e = { | |
| 69 | + | key, target, isComposing: false, prevented: false, stopped: false, blurred: false, | |
| 70 | + | preventDefault() { this.prevented = true; }, | |
| 71 | + | stopPropagation() { this.stopped = true; }, | |
| 72 | + | ...extra, | |
| 73 | + | }; | |
| 74 | + | target.closest ||= () => null; | |
| 75 | + | elementHandler?.(e); | |
| 76 | + | if (!e.stopped) for (const f of listeners.keydown) f(e); | |
| 77 | + | return e; | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | // ---- the palette -------------------------------------------------------- | |
| 81 | + | ||
| 82 | + | function palette() { | |
| 83 | + | const li = (k, label) => el('li', { attrs: { 'data-k': k } }, el('a', { attrs: { href: '/' + label } })); | |
| 84 | + | const items = [li('go home', 'home'), li('section files', 'files'), li('branch main', 'main')]; | |
| 85 | + | items[0].setAttribute('data-cursor', ''); | |
| 86 | + | const input = el('input', { id: 'cmd-q', attrs: { role: 'combobox', 'aria-controls': 'cmd-list' } }); | |
| 87 | + | const list = el('ul', { id: 'cmd-list' }, ...items); | |
| 88 | + | dom.body.replaceChildren(input, list); | |
| 89 | + | return { input, list, items }; | |
| 90 | + | } | |
| 91 | + | ||
| 92 | + | test('the palette: options, selected option and aria-activedescendant follow the cursor', () => { | |
| 93 | + | const p = palette(); | |
| 94 | + | window.ermiteCmd.sync(); | |
| 95 | + | assert.deepEqual(p.items.map((i) => i.getAttribute('role')), ['option', 'option', 'option']); | |
| 96 | + | assert.equal(new Set(p.items.map((i) => i.id)).size, 3, 'unique ids'); | |
| 97 | + | assert.equal(p.input.getAttribute('aria-expanded'), 'true'); | |
| 98 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), p.items[0].id); | |
| 99 | + | assert.deepEqual(p.items.map((i) => i.getAttribute('aria-selected')), ['true', 'false', 'false']); | |
| 100 | + | ||
| 101 | + | window.ermite.cmdKey({ key: 'ArrowDown', preventDefault() {} }); | |
| 102 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), p.items[1].id); | |
| 103 | + | assert.deepEqual(p.items.map((i) => i.getAttribute('aria-selected')), ['false', 'true', 'false']); | |
| 104 | + | assert.ok(p.items[1].hasAttribute('data-cursor') && !p.items[0].hasAttribute('data-cursor')); | |
| 105 | + | ||
| 106 | + | window.ermite.cmdKey({ key: 'ArrowUp', preventDefault() {} }); | |
| 107 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), p.items[0].id); | |
| 108 | + | }); | |
| 109 | + | ||
| 110 | + | test('the palette: a filter moves the active option, no match empties it and collapses the popup', () => { | |
| 111 | + | const p = palette(); | |
| 112 | + | p.input.value = 'branch'; | |
| 113 | + | window.ermite.cmdFilter(p.input); | |
| 114 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), p.items[2].id); | |
| 115 | + | assert.equal(p.items[2].getAttribute('aria-selected'), 'true'); | |
| 116 | + | assert.equal(p.items[0].getAttribute('aria-selected'), 'false'); | |
| 117 | + | p.input.value = 'zzz'; | |
| 118 | + | window.ermite.cmdFilter(p.input); | |
| 119 | + | assert.equal(p.input.getAttribute('aria-expanded'), 'false'); | |
| 120 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), null); | |
| 121 | + | }); | |
| 122 | + | ||
| 123 | + | test('the palette: the file hits that arrive later get ids, roles and the active option', async () => { | |
| 124 | + | const p = palette(); | |
| 125 | + | dom.body.setAttribute('data-paths', '/paths.json'); | |
| 126 | + | dom.body.setAttribute('data-blob', '/blob/main'); | |
| 127 | + | p.input.value = 'main'; | |
| 128 | + | window.ermite.cmdFilter(p.input); // the branch "main" is the active option | |
| 129 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), p.items[2].id); | |
| 130 | + | F.palette(p.input); | |
| 131 | + | flushFrames(); | |
| 132 | + | await settle(); | |
| 133 | + | const hits = p.list.children.filter((li) => li.className === 'finder-hit'); | |
| 134 | + | assert.ok(hits.length > 0, 'a file hit came'); | |
| 135 | + | for (const h of hits) { | |
| 136 | + | assert.equal(h.getAttribute('role'), 'option'); | |
| 137 | + | assert.ok(h.id); | |
| 138 | + | } | |
| 139 | + | assert.equal(p.list.children.length, new Set(p.list.children.map((i) => i.id)).size, 'ids stay unique'); | |
| 140 | + | // down to the hit: the field points at it | |
| 141 | + | window.ermite.cmdKey({ key: 'ArrowDown', preventDefault() {} }); | |
| 142 | + | assert.equal(p.input.getAttribute('aria-activedescendant'), hits[0].id); | |
| 143 | + | assert.equal(hits[0].getAttribute('aria-selected'), 'true'); | |
| 144 | + | dom.body.removeAttribute('data-paths'); | |
| 145 | + | }); | |
| 146 | + | ||
| 147 | + | // ---- the file finder ---------------------------------------------------- | |
| 148 | + | ||
| 149 | + | function finder() { | |
| 150 | + | const q = el('input', { id: 'finder-q', attrs: { role: 'combobox' } }); | |
| 151 | + | const rows = ['a', 'b'].map((n) => el('tr', { attrs: { 'data-nm': n } })); | |
| 152 | + | rows[0].setAttribute('data-cursor', ''); | |
| 153 | + | const treeRows = el('tbody', { id: 'tree-rows', attrs: { 'data-rows': '' } }, ...rows); | |
| 154 | + | const list = el('ul', { id: 'finder-list' }); | |
| 155 | + | dom.body.replaceChildren(q, el('div', { id: 'tree-view' }, treeRows), el('div', { id: 'finder', hidden: true }, el('p', { id: 'finder-status' }), list)); | |
| 156 | + | dom.body.setAttribute('data-paths', '/paths.json'); | |
| 157 | + | dom.body.setAttribute('data-blob', '/blob/main'); | |
| 158 | + | return { q, list }; | |
| 159 | + | } | |
| 160 | + | ||
| 161 | + | test('Escape in the finder clears the query, keeps the focus in the field and does not reach the global handler', async () => { | |
| 162 | + | const f = finder(); | |
| 163 | + | f.q.value = 'main'; | |
| 164 | + | F.input(f.q); | |
| 165 | + | flushFrames(); | |
| 166 | + | await settle(); | |
| 167 | + | active = f.q; | |
| 168 | + | let blurred = false; | |
| 169 | + | f.q.blur = () => { blurred = true; }; | |
| 170 | + | f.q.closest = () => f.q; // the global handler sees a field being typed in | |
| 171 | + | const e = keydown(f.q, 'Escape', (ev) => F.key(f.q, ev)); | |
| 172 | + | assert.equal(f.q.value, ''); | |
| 173 | + | assert.equal(e.stopped, true); | |
| 174 | + | assert.equal(e.prevented, true); | |
| 175 | + | assert.equal(blurred, false, 'the field keeps the focus'); | |
| 176 | + | assert.equal(active, f.q); | |
| 177 | + | // Escape again, with nothing typed: the field is left (global handler) | |
| 178 | + | const e2 = keydown(f.q, 'Escape', (ev) => F.key(f.q, ev)); | |
| 179 | + | assert.equal(e2.stopped, false); | |
| 180 | + | assert.equal(blurred, true); | |
| 181 | + | }); | |
| 182 | + | ||
| 183 | + | test('the finder follows the cursor moved from outside (j / k)', async () => { | |
| 184 | + | const f = finder(); | |
| 185 | + | f.q.value = 'm'; | |
| 186 | + | F.input(f.q); | |
| 187 | + | flushFrames(); | |
| 188 | + | await settle(); | |
| 189 | + | const [first, second] = f.list.children; | |
| 190 | + | assert.equal(f.q.getAttribute('aria-activedescendant'), first.id); | |
| 191 | + | // ermite.move: the cursor goes to the next option | |
| 192 | + | first.removeAttribute('data-cursor'); | |
| 193 | + | second.setAttribute('data-cursor', ''); | |
| 194 | + | F.sync(); | |
| 195 | + | assert.equal(second.getAttribute('aria-selected'), 'true'); | |
| 196 | + | assert.equal(first.getAttribute('aria-selected'), 'false'); | |
| 197 | + | assert.equal(f.q.getAttribute('aria-activedescendant'), second.id); | |
| 198 | + | }); | |
| 199 | + | ||
| 200 | + | // ---- the focus through a live swap -------------------------------------- | |
| 201 | + | ||
| 202 | + | function rows() { | |
| 203 | + | const mk = (id, href) => el('li', { id }, el('a', { attrs: { href }, id: id + '-a' }), el('button', { id: id + '-b' })); | |
| 204 | + | const main = el('main', { id: 'main', attrs: { tabindex: '-1' } }); | |
| 205 | + | const list = el('ul', { id: 'repo-rows' }, mk('repo-a', '/a'), mk('repo-b', '/b'), mk('repo-c', '/c')); | |
| 206 | + | dom.body.replaceChildren(main, list); | |
| 207 | + | return { main, list, mk }; | |
| 208 | + | } | |
| 209 | + | const swapWith = (html, ...nodes) => { parsed[html] = nodes; window.ermiteLive.swap(html); }; | |
| 210 | + | ||
| 211 | + | test('a swap that replaces the focused row puts the focus on the same control of the new one', () => { | |
| 212 | + | const r = rows(); | |
| 213 | + | const old = r.list.children[1]; | |
| 214 | + | active = old.children[0]; | |
| 215 | + | dom.focused = []; | |
| 216 | + | const fresh = r.mk('repo-b', '/b'); | |
| 217 | + | swapWith('b1', fresh); | |
| 218 | + | assert.equal(active, fresh.children[0], 'the link with the same href'); | |
| 219 | + | assert.equal(dom.focused.at(-1).opts.preventScroll, true); | |
| 220 | + | // the second control | |
| 221 | + | active = fresh.children[1]; | |
| 222 | + | const fresh2 = r.mk('repo-b', '/b'); | |
| 223 | + | swapWith('b2', fresh2); | |
| 224 | + | assert.equal(active, fresh2.children[1], 'the button with the same id'); | |
| 225 | + | }); | |
| 226 | + | ||
| 227 | + | test('a swap with the focus elsewhere does not touch it', () => { | |
| 228 | + | const r = rows(); | |
| 229 | + | active = r.list.children[0].children[0]; | |
| 230 | + | dom.focused = []; | |
| 231 | + | swapWith('b3', r.mk('repo-b', '/b')); | |
| 232 | + | assert.equal(dom.focused.length, 0); | |
| 233 | + | assert.equal(active, r.list.children[0].children[0]); | |
| 234 | + | }); | |
| 235 | + | ||
| 236 | + | test('a removed row hands the focus to the next row, else the previous, else the content', () => { | |
| 237 | + | const r = rows(); | |
| 238 | + | active = r.list.children[1].children[0]; | |
| 239 | + | const gone = (id) => { const g = el('li', { id, attrs: { 'data-gone': '' } }); return g; }; | |
| 240 | + | swapWith('g1', gone('repo-b')); | |
| 241 | + | assert.equal(active, r.list.children[1].children[0], 'repo-c, which took its place'); | |
| 242 | + | assert.equal(r.list.children[1].id, 'repo-c'); | |
| 243 | + | active = r.list.children[1].children[0]; | |
| 244 | + | swapWith('g2', gone('repo-c')); | |
| 245 | + | assert.equal(active, r.list.children[0].children[0], 'repo-a, the previous row'); | |
| 246 | + | swapWith('g3', gone('repo-a')); | |
| 247 | + | // focus was on repo-a's link? it was handed there: now it is removed too | |
| 248 | + | assert.equal(active, r.main); | |
| 249 | + | }); | |
| 250 | + | ||
| 251 | + | test('a removed row skips the rows a filter hides', () => { | |
| 252 | + | const r = rows(); | |
| 253 | + | const [a, b, c] = r.list.children; | |
| 254 | + | b.hidden = true; // filtered out | |
| 255 | + | active = a.children[0]; | |
| 256 | + | swapWith('g4', el('li', { id: 'repo-a', attrs: { 'data-gone': '' } })); | |
| 257 | + | assert.equal(active, c.children[0], 'repo-c, the next shown row, not the hidden repo-b'); | |
| 258 | + | c.hidden = true; | |
| 259 | + | swapWith('g5', el('li', { id: 'repo-c', attrs: { 'data-gone': '' } })); | |
| 260 | + | assert.equal(active, r.main, 'no shown row left: the content'); | |
| 261 | + | }); | |
| 262 | + | ||
| 263 | + | // ---- single-character shortcuts ----------------------------------------- | |
| 264 | + | ||
| 265 | + | test('shortcuts off: single characters do nothing, ctrl k, escape and enter still work', () => { | |
| 266 | + | const events = () => dom.body.events.splice(0); | |
| 267 | + | dom.body.events = []; | |
| 268 | + | doc.querySelector = () => null; | |
| 269 | + | const press = (key, extra) => keydown({ closest: () => null }, key, null, extra); | |
| 270 | + | press('j'); | |
| 271 | + | assert.deepEqual(events(), ['k-j'], 'on by default'); | |
| 272 | + | ||
| 273 | + | K.set(true); | |
| 274 | + | assert.equal(store.get('keys-off'), '1', 'persisted'); | |
| 275 | + | for (const k of ['j', 'k', '?', ':', 'G', 's', 'y']) press(k); | |
| 276 | + | press('g'); press('h'); | |
| 277 | + | assert.deepEqual(events(), [], 'nothing fires'); | |
| 278 | + | press('k', { ctrlKey: true }); | |
| 279 | + | press('Escape'); | |
| 280 | + | assert.deepEqual(events(), ['k-cmd', 'k-esc']); | |
| 281 | + | ||
| 282 | + | K.set(false); | |
| 283 | + | assert.equal(store.get('keys-off'), '', 'persisted off'); | |
| 284 | + | press('j'); | |
| 285 | + | assert.deepEqual(events(), ['k-j']); | |
| 286 | + | }); | |
| 287 | + | ||
| 288 | + | test('shortcuts off: the checkbox, the note and the palette action show the state', () => { | |
| 289 | + | const box = el('input', { id: 'keys-off', attrs: { type: 'checkbox' } }); | |
| 290 | + | const note = el('p', { id: 'keys-note' }); | |
| 291 | + | const lbl = el('span', { cls: 'lbl' }); | |
| 292 | + | const list = el('ul', { id: 'cmd-list' }, el('li', {}, el('a', { attrs: { 'data-fire': 'keys' } }, lbl))); | |
| 293 | + | dom.body.replaceChildren(box, note, list); | |
| 294 | + | const find = doc.querySelector; | |
| 295 | + | doc.querySelector = (sel) => (sel === '#cmd-list [data-fire="keys"] .lbl' ? lbl : null); | |
| 296 | + | K.set(true); | |
| 297 | + | assert.equal(box.checked, true); | |
| 298 | + | assert.match(note.textContent, /are off/); | |
| 299 | + | assert.match(lbl.textContent, /on$/); | |
| 300 | + | K.set(false); | |
| 301 | + | assert.equal(box.checked, false); | |
| 302 | + | assert.match(note.textContent, /are on/); | |
| 303 | + | assert.match(lbl.textContent, /off$/); | |
| 304 | + | doc.querySelector = find; | |
| 305 | + | }); | |
| 306 | + | ||
| 307 | + | test('the preference is read from storage when the page loads', async () => { | |
| 308 | + | store.set('keys-off', '1'); | |
| 309 | + | listeners.keydown = []; | |
| 310 | + | const src = await import('node:fs').then((m) => m.readFileSync(new URL('../static/js/ermite.js', import.meta.url), 'utf8')); | |
| 311 | + | doc.querySelectorAll = () => []; | |
| 312 | + | doc.querySelector = () => null; | |
| 313 | + | new Function(src)(); // a second page load | |
| 314 | + | assert.equal(window.ermiteKeys.off(), true); | |
| 315 | + | dom.body.events = []; | |
| 316 | + | keydown({ closest: () => null }, 'j'); | |
| 317 | + | assert.deepEqual(dom.body.events, []); | |
| 318 | + | store.set('keys-off', ''); | |
| 319 | + | }); | |
M internal/adapters/web/jstest/finder.test.mjs +1 -1
| @@ -49,7 +49,7 @@ function page() { | |||
| 49 | 49 | } | |
| 50 | 50 | ||
| 51 | 51 | const key = (k, extra = {}) => { | |
| 52 | - | const e = { key: k, isComposing: false, prevented: false, preventDefault() { this.prevented = true; }, ...extra }; | |
| 52 | + | const e = { key: k, isComposing: false, prevented: false, preventDefault() { this.prevented = true; }, stopPropagation() {}, ...extra }; | |
| 53 | 53 | return e; | |
| 54 | 54 | }; | |
| 55 | 55 | const status = () => document.getElementById('finder-status').textContent; | |
M internal/adapters/web/jstest/tree.test.mjs +12 -0
| @@ -276,6 +276,18 @@ test('init: one tab stop, the row under the cursor; name links are out of the ta | |||
| 276 | 276 | assert.equal(body.children[1].getAttribute('tabindex'), '-1'); | |
| 277 | 277 | }); | |
| 278 | 278 | ||
| 279 | + | test('l or h before any focus in the tree brings the focus to the cursor row', async () => { | |
| 280 | + | pending.clear(); | |
| 281 | + | const body = table('P/', 'f'); | |
| 282 | + | assert.equal(dom.active, null, 'the page has the focus after a load'); | |
| 283 | + | T.key(true); // opens P/, the cursor stays on it | |
| 284 | + | assert.equal(dom.active, body.children[0]); | |
| 285 | + | await answer('/rows/P/', 'a'); | |
| 286 | + | dom.active = null; | |
| 287 | + | T.key(false); // closes P/ | |
| 288 | + | assert.equal(dom.active, body.children[0]); | |
| 289 | + | }); | |
| 290 | + | ||
| 279 | 291 | test('l and h move the focus with the cursor, and the tab stop with them', async () => { | |
| 280 | 292 | pending.clear(); | |
| 281 | 293 | const body = table('P/', 'f'); | |
M internal/adapters/web/keys.templ +2 -1
| @@ -36,6 +36,7 @@ templ Keys() { | |||
| 36 | 36 | k-permalink from:body -> ermite.permalink(); | |
| 37 | 37 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 38 | 38 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 39 | - | k-gf from:body -> window.ermiteMR?.focusFiles()" | |
| 39 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 40 | + | k-keys from:body -> ermite.toggleKeys()" | |
| 40 | 41 | ></div> | |
| 41 | 42 | } | |
M internal/adapters/web/keys_templ.go +1 -1 generated file: collapsed
M internal/adapters/web/mr.templ +2 -2
| @@ -358,7 +358,7 @@ templ mrRail(p MRPage) { | |||
| 358 | 358 | // request, the merge menu and the other actions. Each menu is a <details>: it | |
| 359 | 359 | // works without scripts; with them, M and the keys of the menu drive it. | |
| 360 | 360 | templ mrBar(p MRPage) { | |
| 361 | - | <div class="diff-tools mr-bar" role="toolbar" aria-label="Diff view and merge"> | |
| 361 | + | <div class="diff-tools mr-bar" role="group" aria-label="Diff view and merge"> | |
| 362 | 362 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | |
| 363 | 363 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 364 | 364 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| @@ -488,7 +488,7 @@ templ Compare(l Layout, p ComparePage) { | |||
| 488 | 488 | </form> | |
| 489 | 489 | } | |
| 490 | 490 | if p.HasDiff { | |
| 491 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 491 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 492 | 492 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | |
| 493 | 493 | <span class="grow"></span> | |
| 494 | 494 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
M internal/adapters/web/mr_templ.go +2 -2 generated file: collapsed
M internal/adapters/web/overlays.templ +15 -3
| @@ -2,13 +2,20 @@ | |||
| 2 | 2 | ||
| 3 | 3 | package web | |
| 4 | 4 | ||
| 5 | - | import "strings" | |
| 5 | + | import ( | |
| 6 | + | "strconv" | |
| 7 | + | "strings" | |
| 8 | + | ) | |
| 6 | 9 | ||
| 7 | 10 | // OverlayHelp is the keyboard map panel; the keys of the merge request page | |
| 8 | 11 | // show on that page only. | |
| 9 | 12 | templ OverlayHelp(mr bool) { | |
| 10 | 13 | <dialog id="panel-help" class="panel modal" aria-label="Keyboard shortcuts" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 11 | 14 | <div class="panel-head"><h2>Keyboard shortcuts</h2><button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd> close</button></div> | |
| 15 | + | <div class="keys-pref"> | |
| 16 | + | <label><input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"/> Turn off single-key shortcuts</label> | |
| 17 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 18 | + | </div> | |
| 12 | 19 | <dl class="keys"> | |
| 13 | 20 | <div><dt><kbd>ctrl k</kbd> <kbd>:</kbd></dt><dd>go to a section, branch, tag, file or repository</dd></div> | |
| 14 | 21 | <div><dt><kbd>?</kbd></dt><dd>this list</dd></div> | |
| @@ -104,16 +111,21 @@ templ OverlayCmd(items []CmdItem) { | |||
| 104 | 111 | placeholder="Go to a section, branch, tag, file or repository…" | |
| 105 | 112 | autocomplete="off" | |
| 106 | 113 | spellcheck="false" | |
| 114 | + | role="combobox" | |
| 115 | + | aria-autocomplete="list" | |
| 116 | + | aria-expanded="true" | |
| 107 | 117 | aria-controls="cmd-list" | |
| 118 | + | aria-activedescendant="cmd-o0" | |
| 108 | 119 | hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" | |
| 109 | 120 | hx-on:keydown="ermite.cmdKey(event)" | |
| 110 | 121 | /> | |
| 111 | 122 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button> | |
| 112 | 123 | </div> | |
| 113 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 124 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 114 | 125 | for i, c := range items { | |
| 115 | - | <li data-k={ strings.ToLower(c.Kind + " " + c.Label) } data-cursor?={ i == 0 }><a href={ templ.SafeURL(c.Href) } tabindex="-1"><span class="lbl">{ c.Label }</span><span class="k">{ c.Kind }</span></a></li> | |
| 126 | + | <li id={ "cmd-o" + itoa(i) } role="option" aria-selected={ strconv.FormatBool(i == 0) } data-k={ strings.ToLower(c.Kind + " " + c.Label) } data-cursor?={ i == 0 }><a href={ templ.SafeURL(c.Href) } tabindex="-1"><span class="lbl">{ c.Label }</span><span class="k">{ c.Kind }</span></a></li> | |
| 116 | 127 | } | |
| 128 | + | <li id={ "cmd-o" + itoa(len(items)) } role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"><a href="#" data-fire="keys" tabindex="-1"><span class="lbl">turn single-key shortcuts off</span><span class="k">action</span></a></li> | |
| 117 | 129 | </ul> | |
| 118 | 130 | <p class="panel-foot"><span><kbd>↑</kbd><kbd>↓</kbd> move</span><span><kbd>enter</kbd> open</span><span><kbd>esc</kbd> close</span></p> | |
| 119 | 131 | </dialog> | |
M internal/adapters/web/overlays_templ.go +72 -30 generated file: collapsed
M internal/adapters/web/static/css/25-panel.css +11 -4
| @@ -21,7 +21,6 @@ | |||
| 21 | 21 | & h2 { font-size: var(--fs); flex: 1; } | |
| 22 | 22 | & input { flex: 1; min-width: 0; height: 32px; font-size: 16px; } | |
| 23 | 23 | & input::placeholder { color: var(--ink-mute); opacity: 1; } | |
| 24 | - | & input:focus-visible { outline: none; } | |
| 25 | 24 | } | |
| 26 | 25 | .panel-x { color: var(--ink-mute); font-size: var(--fs-s); display: flex; gap: var(--s2); align-items: center; } | |
| 27 | 26 | .panel-x:hover { color: var(--ink-hi); } | |
| @@ -60,10 +59,18 @@ | |||
| 60 | 59 | } | |
| 61 | 60 | ||
| 62 | 61 | .keys { | |
| 63 | - | display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr)); column-gap: var(--s5); row-gap: var(--s1); | |
| 62 | + | display: grid; grid-template-columns: repeat(auto-fill, minmax(min(100%, 340px), 1fr)); column-gap: var(--s5); row-gap: var(--s1); | |
| 64 | 63 | padding: var(--s3) var(--s4) var(--s4) !important; | |
| 65 | 64 | & > div { display: grid; grid-template-columns: 130px 1fr; gap: var(--s3); align-items: baseline; padding: var(--s1) 0; border-bottom: 1px solid var(--border); } | |
| 66 | - | & dt { white-space: nowrap; display: flex; gap: var(--s1); } | |
| 67 | - | & dd { color: var(--ink-dim); font-size: var(--fs-s); } | |
| 65 | + | & dt { display: flex; flex-wrap: wrap; gap: var(--s1); min-width: 0; } | |
| 66 | + | & dd { color: var(--ink-dim); font-size: var(--fs-s); min-width: 0; overflow-wrap: anywhere; } | |
| 67 | + | @media (max-width: 480px) { & > div { grid-template-columns: minmax(0, 1fr); gap: var(--s1); } } | |
| 68 | + | } | |
| 69 | + | /* the preference "single-key shortcuts" of the help dialog */ | |
| 70 | + | .keys-pref { | |
| 71 | + | flex: none; padding: var(--s3) var(--s4) 0; | |
| 72 | + | & label { display: flex; align-items: center; gap: var(--s2); cursor: pointer; } | |
| 73 | + | & input[type=checkbox] { width: 16px; height: 16px; flex: none; accent-color: var(--teal); } | |
| 74 | + | & p { font-size: var(--fs-s); margin-top: var(--s1); } | |
| 68 | 75 | } | |
| 69 | 76 | } | |
M internal/adapters/web/static/js/ermite.js +184 -24
| @@ -64,6 +64,7 @@ | |||
| 64 | 64 | setCursor(rows[j], $$('[data-rows] > [data-cursor]')); | |
| 65 | 65 | rows[j].scrollIntoView({ block: 'nearest' }); | |
| 66 | 66 | window.ermiteTree?.sync(rows[j]); | |
| 67 | + | window.ermiteFinder?.sync(); // the rows may be the file finder's options | |
| 67 | 68 | const mark = j + 1 + '/' + rows.length; | |
| 68 | 69 | body().dataset.nav = mark; | |
| 69 | 70 | setTimeout(() => body().dataset.nav === mark && (body().dataset.nav = ''), 1500); | |
| @@ -76,6 +77,7 @@ | |||
| 76 | 77 | setCursor(row, $$('[data-rows] > [data-cursor]')); | |
| 77 | 78 | row.scrollIntoView({ block: 'nearest' }); | |
| 78 | 79 | window.ermiteTree?.sync(row); | |
| 80 | + | window.ermiteFinder?.sync(); | |
| 79 | 81 | }, | |
| 80 | 82 | open() { | |
| 81 | 83 | if ($('dialog[open]') || window.ermiteTree?.open()) return; | |
| @@ -108,6 +110,18 @@ | |||
| 108 | 110 | $(`.tabs [data-key=${key}]`)?.click(); | |
| 109 | 111 | }, | |
| 110 | 112 | ||
| 113 | + | // The preference "single-key shortcuts" (ermiteKeys): the checkbox of the | |
| 114 | + | // help dialog and the action of the palette. | |
| 115 | + | setKeysOff(off) { | |
| 116 | + | window.ermiteKeys.set(off); | |
| 117 | + | }, | |
| 118 | + | toggleKeys() { | |
| 119 | + | const off = !window.ermiteKeys.off(); | |
| 120 | + | window.ermiteKeys.set(off); | |
| 121 | + | ermite.closeDialogs(); | |
| 122 | + | flash(off ? 'single-key shortcuts off · ctrl k and esc still work' : 'single-key shortcuts on'); | |
| 123 | + | }, | |
| 124 | + | ||
| 111 | 125 | // Dialogs. | |
| 112 | 126 | closeDialogs() { | |
| 113 | 127 | for (const d of $$('dialog[open]')) d.close(); | |
| @@ -117,6 +131,7 @@ | |||
| 117 | 131 | if (!d) return; | |
| 118 | 132 | ermite.closeDialogs(); | |
| 119 | 133 | d.showModal(); | |
| 134 | + | if (id === 'panel-cmd') window.ermiteCmd?.sync(); | |
| 120 | 135 | if (focusId) document.getElementById(focusId)?.focus(); | |
| 121 | 136 | }, | |
| 122 | 137 | ||
| @@ -146,31 +161,12 @@ | |||
| 146 | 161 | } | |
| 147 | 162 | return rows; | |
| 148 | 163 | }, | |
| 149 | - | // The "go to" palette. | |
| 164 | + | // The "go to" palette (ermiteCmd below). | |
| 150 | 165 | cmdFilter(input) { | |
| 151 | - | const t = input.value.toLowerCase().trim(); | |
| 152 | - | // The file hits the finder adds (li.finder-hit, files.js) have no data-k: | |
| 153 | - | // the finder owns them. | |
| 154 | - | const items = $$('#cmd-list > li[data-k]'); | |
| 155 | - | for (const li of items) li.hidden = !li.dataset.k.includes(t); | |
| 156 | - | const first = items.find((li) => !li.hidden); | |
| 157 | - | if (first) setCursor(first, $$('#cmd-list > li')); | |
| 166 | + | window.ermiteCmd.filter(input); | |
| 158 | 167 | }, | |
| 159 | 168 | cmdKey(e) { | |
| 160 | - | const vis = $$('#cmd-list > li:not([hidden])'); | |
| 161 | - | const i = vis.findIndex((li) => li.hasAttribute('data-cursor')); | |
| 162 | - | const step = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0; | |
| 163 | - | if (step) { | |
| 164 | - | e.preventDefault(); | |
| 165 | - | const n = vis[Math.max(0, Math.min(vis.length - 1, i + step))]; | |
| 166 | - | if (n) { | |
| 167 | - | setCursor(n, $$('#cmd-list > li')); | |
| 168 | - | n.scrollIntoView({ block: 'nearest' }); | |
| 169 | - | } | |
| 170 | - | } else if (e.key === 'Enter') { | |
| 171 | - | e.preventDefault(); | |
| 172 | - | vis[Math.max(i, 0)]?.querySelector('a')?.click(); | |
| 173 | - | } | |
| 169 | + | window.ermiteCmd.key(e); | |
| 174 | 170 | }, | |
| 175 | 171 | ||
| 176 | 172 | // Diff. | |
| @@ -253,6 +249,69 @@ | |||
| 253 | 249 | }; | |
| 254 | 250 | window.ermite = ermite; | |
| 255 | 251 | ||
| 252 | + | // The "go to" palette is an editable combobox with a listbox popup: the | |
| 253 | + | // focus stays in #cmd-q, the options are the <li> of #cmd-list, and the one | |
| 254 | + | // with data-cursor is the active one (aria-selected on it, aria-activedescendant | |
| 255 | + | // on the field). sync keeps the two in step; it runs on every change of the | |
| 256 | + | // cursor or of the list, including the file hits files.js adds later. | |
| 257 | + | // Self-contained: jstest/palette.test.mjs evaluates this block alone. | |
| 258 | + | window.ermiteCmd = (() => { | |
| 259 | + | const list = () => document.getElementById('cmd-list'); | |
| 260 | + | const field = () => document.getElementById('cmd-q'); | |
| 261 | + | const options = () => [...list().querySelectorAll(':scope > li')]; | |
| 262 | + | let seq = 0; | |
| 263 | + | const moveCursor = (li) => { | |
| 264 | + | for (const o of options()) if (o !== li) o.removeAttribute('data-cursor'); | |
| 265 | + | li.setAttribute('data-cursor', ''); | |
| 266 | + | }; | |
| 267 | + | function sync() { | |
| 268 | + | const input = field(); | |
| 269 | + | if (!input || !list()) return; | |
| 270 | + | const all = options(); | |
| 271 | + | const shown = all.filter((li) => !li.hidden); | |
| 272 | + | const cur = shown.find((li) => li.hasAttribute('data-cursor')); | |
| 273 | + | for (const li of all) { | |
| 274 | + | li.id ||= 'cmd-h' + seq++; | |
| 275 | + | li.setAttribute('role', 'option'); | |
| 276 | + | li.setAttribute('aria-selected', li === cur ? 'true' : 'false'); | |
| 277 | + | } | |
| 278 | + | input.setAttribute('aria-expanded', shown.length ? 'true' : 'false'); | |
| 279 | + | if (cur) input.setAttribute('aria-activedescendant', cur.id); | |
| 280 | + | else input.removeAttribute('aria-activedescendant'); | |
| 281 | + | } | |
| 282 | + | return { | |
| 283 | + | sync, | |
| 284 | + | // filter hides the entries that do not contain the text of the field. | |
| 285 | + | // The file hits the finder adds (li.finder-hit, files.js) have no data-k: | |
| 286 | + | // the finder owns them. | |
| 287 | + | filter(input) { | |
| 288 | + | const t = input.value.toLowerCase().trim(); | |
| 289 | + | const items = options().filter((li) => li.hasAttribute('data-k')); | |
| 290 | + | for (const li of items) li.hidden = !li.getAttribute('data-k').includes(t); | |
| 291 | + | const first = items.find((li) => !li.hidden); | |
| 292 | + | if (first) moveCursor(first); | |
| 293 | + | sync(); | |
| 294 | + | }, | |
| 295 | + | key(e) { | |
| 296 | + | const vis = options().filter((li) => !li.hidden); | |
| 297 | + | const i = vis.findIndex((li) => li.hasAttribute('data-cursor')); | |
| 298 | + | const step = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : 0; | |
| 299 | + | if (step) { | |
| 300 | + | e.preventDefault(); | |
| 301 | + | const n = vis[Math.max(0, Math.min(vis.length - 1, i + step))]; | |
| 302 | + | if (n) { | |
| 303 | + | moveCursor(n); | |
| 304 | + | n.scrollIntoView({ block: 'nearest' }); | |
| 305 | + | } | |
| 306 | + | sync(); | |
| 307 | + | } else if (e.key === 'Enter') { | |
| 308 | + | e.preventDefault(); | |
| 309 | + | vis[Math.max(i, 0)]?.querySelector('a')?.click(); | |
| 310 | + | } | |
| 311 | + | }, | |
| 312 | + | }; | |
| 313 | + | })(); | |
| 314 | + | ||
| 256 | 315 | // The side panel (aside.rail) by keyboard: l enters it on its main action | |
| 257 | 316 | // (the primary button) or its first control, j / k step between its shown | |
| 258 | 317 | // controls (not hidden, not in a closed <details>), h leaves it for <main>. | |
| @@ -458,6 +517,50 @@ | |||
| 458 | 517 | return { load, file, current }; | |
| 459 | 518 | })(); | |
| 460 | 519 | ||
| 520 | + | // The preference "single-character shortcuts" (WCAG 2.1.4): off, the keys | |
| 521 | + | // that are one printable character (j, k, s, ?, :, G, 1-9 in the merge menu, | |
| 522 | + | // …) do nothing. Shortcuts with ctrl or cmd, escape, enter and the arrows | |
| 523 | + | // keep working. The choice lives in localStorage ("keys-off") and is made in | |
| 524 | + | // the help dialog (a checkbox) or the palette (an action). Self-contained: | |
| 525 | + | // jstest/keys.test.mjs evaluates this block alone. | |
| 526 | + | window.ermiteKeys = (() => { | |
| 527 | + | const KEY = 'keys-off'; | |
| 528 | + | let off = false; | |
| 529 | + | try { off = localStorage.getItem(KEY) === '1'; } catch { /* no storage: on, and the choice holds for this page */ } | |
| 530 | + | // apply shows the state: the checkbox and the note of the help dialog, | |
| 531 | + | // and the label of the palette action. | |
| 532 | + | function apply() { | |
| 533 | + | const box = document.getElementById('keys-off'); | |
| 534 | + | if (box) box.checked = off; | |
| 535 | + | const note = document.getElementById('keys-note'); | |
| 536 | + | if (note) { | |
| 537 | + | note.textContent = off | |
| 538 | + | ? 'Single-key shortcuts are off: only ctrl k and esc, enter and the arrows work. Turn them on again here.' | |
| 539 | + | : 'Single-key shortcuts are on. ctrl k and esc work whatever this says.'; | |
| 540 | + | } | |
| 541 | + | const act = document.querySelector('#cmd-list [data-fire="keys"] .lbl'); | |
| 542 | + | if (act) act.textContent = off ? 'turn single-key shortcuts on' : 'turn single-key shortcuts off'; | |
| 543 | + | } | |
| 544 | + | function set(v) { | |
| 545 | + | off = !!v; | |
| 546 | + | try { localStorage.setItem(KEY, off ? '1' : ''); } catch { /* storage refused: holds for this page */ } | |
| 547 | + | apply(); | |
| 548 | + | } | |
| 549 | + | addEventListener('storage', (e) => { | |
| 550 | + | if (e.key !== KEY) return; | |
| 551 | + | off = e.newValue === '1'; | |
| 552 | + | apply(); | |
| 553 | + | }); | |
| 554 | + | apply(); | |
| 555 | + | return { | |
| 556 | + | off: () => off, | |
| 557 | + | set, | |
| 558 | + | apply, | |
| 559 | + | // blocks says whether the preference turns this key off. | |
| 560 | + | blocks: (e) => off && e.key.length === 1, | |
| 561 | + | }; | |
| 562 | + | })(); | |
| 563 | + | ||
| 461 | 564 | // 1. Keys. | |
| 462 | 565 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | |
| 463 | 566 | s: 's', t: 't', c: 'c', r: 'r', m: 'm', n: 'next', h: 'left', l: 'right', d: 'split', u: 'unified', x: 'collapse', e: 'expand', E: 'expandAll', Enter: 'open', Escape: 'esc', | |
| @@ -480,6 +583,8 @@ | |||
| 480 | 583 | if (typing) e.target.blur(); | |
| 481 | 584 | return ermite.fire('esc'); | |
| 482 | 585 | } | |
| 586 | + | // Single-character shortcuts turned off: only the keys above work. | |
| 587 | + | if (window.ermiteKeys.blocks(e)) { pending = 0; return; } | |
| 483 | 588 | // An open merge menu takes its keys (1-9, D) before the field check: its | |
| 484 | 589 | // radios and its checkbox hold the focus. | |
| 485 | 590 | if (mr?.menuKey(e)) return; | |
| @@ -607,6 +712,50 @@ window.ermiteLive = (() => { | |||
| 607 | 712 | // server closing it, a network hiccup) is not worth a message. | |
| 608 | 713 | const LOST_AFTER = 5000; | |
| 609 | 714 | let lostTimer = 0; | |
| 715 | + | // The focus survives a swap. focusables are the controls of a fragment in | |
| 716 | + | // document order; where(root) says which one has the focus (its id, else its | |
| 717 | + | // tag and href or name, and its index among the controls) and refocus finds | |
| 718 | + | // it again in the element that replaced root. | |
| 719 | + | const FOCUSABLE = 'a[href], button:not([disabled]), input:not([type=hidden]):not([disabled]), select:not([disabled]), textarea:not([disabled]), summary, [tabindex]'; | |
| 720 | + | // shown: not inside a hidden element (a filtered row, a folded block); the | |
| 721 | + | // browser refuses the focus there. | |
| 722 | + | const shown = (e) => !e.closest('[hidden]') && (!e.checkVisibility || e.checkVisibility()); | |
| 723 | + | const focusables = (root) => [...root.querySelectorAll(FOCUSABLE)].filter(shown); | |
| 724 | + | // take focuses e and says whether the focus is there now. | |
| 725 | + | const take = (e) => { e.focus({ preventScroll: true }); return document.activeElement === e; }; | |
| 726 | + | const where = (root) => { | |
| 727 | + | const a = document.activeElement; | |
| 728 | + | if (!a || a === document.body || !root.contains(a)) return null; | |
| 729 | + | if (a === root) return { self: true }; | |
| 730 | + | const all = focusables(root); | |
| 731 | + | return { id: a.id, tag: a.tagName, href: a.getAttribute('href'), name: a.getAttribute('name'), index: all.indexOf(a) }; | |
| 732 | + | }; | |
| 733 | + | const refocus = (root, w) => { | |
| 734 | + | if (w.self) return root.focus({ preventScroll: true }); | |
| 735 | + | const all = focusables(root); | |
| 736 | + | const same = (e) => e.tagName === w.tag; | |
| 737 | + | const target = | |
| 738 | + | (w.id && all.find((e) => e.id === w.id)) || | |
| 739 | + | (w.href != null && all.find((e) => same(e) && e.getAttribute('href') === w.href)) || | |
| 740 | + | (w.name != null && all.find((e) => same(e) && e.getAttribute('name') === w.name)) || | |
| 741 | + | (all[w.index] && same(all[w.index]) && all[w.index]) || | |
| 742 | + | all[0]; | |
| 743 | + | if (target) target.focus({ preventScroll: true }); | |
| 744 | + | else if (root.hasAttribute('tabindex')) root.focus({ preventScroll: true }); | |
| 745 | + | }; | |
| 746 | + | // lostFocus: the element that had the focus inside is gone. The focus goes to | |
| 747 | + | // the first control of the next shown row, else of the nearest shown row | |
| 748 | + | // before, else to the content. | |
| 749 | + | const lostFocus = (after, before) => { | |
| 750 | + | const tryRow = (e) => { | |
| 751 | + | if (!shown(e)) return false; | |
| 752 | + | const c = focusables(e)[0] || (e.hasAttribute('tabindex') ? e : null); | |
| 753 | + | return !!c && take(c); | |
| 754 | + | }; | |
| 755 | + | for (let e = after; e; e = e.nextElementSibling) if (tryRow(e)) return; | |
| 756 | + | for (let e = before; e; e = e.previousElementSibling) if (tryRow(e)) return; | |
| 757 | + | document.getElementById('main')?.focus({ preventScroll: true }); | |
| 758 | + | }; | |
| 610 | 759 | const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; }; | |
| 611 | 760 | return { | |
| 612 | 761 | // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows | |
| @@ -628,16 +777,27 @@ window.ermiteLive = (() => { | |||
| 628 | 777 | const cursorKey = cursor?.id || cursor?.querySelector('a')?.getAttribute('href'); | |
| 629 | 778 | for (const el of [...t.content.children]) { | |
| 630 | 779 | const old = el.id && document.getElementById(el.id); | |
| 631 | - | if (el.hasAttribute('data-gone')) { old?.remove(); continue; } | |
| 780 | + | const had = old ? where(old) : null; // the focus is inside what is replaced | |
| 781 | + | if (el.hasAttribute('data-gone')) { | |
| 782 | + | const after = old?.nextElementSibling, before = old?.previousElementSibling; | |
| 783 | + | old?.remove(); | |
| 784 | + | if (had) lostFocus(after, before); | |
| 785 | + | continue; | |
| 786 | + | } | |
| 632 | 787 | if (!old) { | |
| 633 | 788 | const rows = document.getElementById('repo-rows'); | |
| 634 | 789 | if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); } | |
| 635 | 790 | continue; | |
| 636 | 791 | } | |
| 637 | - | if (old.hasAttribute('data-slot')) { old.replaceChildren(...el.childNodes); continue; } | |
| 792 | + | if (old.hasAttribute('data-slot')) { | |
| 793 | + | old.replaceChildren(...el.childNodes); | |
| 794 | + | if (had) refocus(old, had); | |
| 795 | + | continue; | |
| 796 | + | } | |
| 638 | 797 | if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', ''); | |
| 639 | 798 | old.replaceWith(el); | |
| 640 | 799 | window.htmx?.process(el); | |
| 800 | + | if (had) refocus(el, had); | |
| 641 | 801 | } | |
| 642 | 802 | // The rows may have changed under an active filter: apply it again and | |
| 643 | 803 | // keep the cursor on its row while that row is still shown. | |
M internal/adapters/web/static/js/files.js +18 -1
| @@ -232,6 +232,9 @@ window.ermiteTree = (() => { | |||
| 232 | 232 | if (!this.active()) return false; | |
| 233 | 233 | const li = cursorRow(); | |
| 234 | 234 | if (!li) return false; | |
| 235 | + | // The focus joins the cursor row first (it may still be on the page, as | |
| 236 | + | // after a load): Enter then acts on the row the key acted on. | |
| 237 | + | if (document.activeElement !== li && !li.contains(document.activeElement)) mark(li, true); | |
| 235 | 238 | if (into) { | |
| 236 | 239 | if (isDir(li)) { | |
| 237 | 240 | if (!isOpen(li)) return toggle(li), true; | |
| @@ -672,6 +675,13 @@ window.ermiteFinder = (() => { | |||
| 672 | 675 | } | |
| 673 | 676 | } | |
| 674 | 677 | ||
| 678 | + | // sync is syncActive for whoever moves the cursor of the results from | |
| 679 | + | // outside (j / k and gg / G, ermite.js): the options and the field follow it. | |
| 680 | + | function sync() { | |
| 681 | + | const el = $('finder-q'); | |
| 682 | + | if (el && $('finder') && !$('finder').hidden) syncActive(el); | |
| 683 | + | } | |
| 684 | + | ||
| 675 | 685 | // showTree: no query, the directory again, with j/k and Enter on its rows. | |
| 676 | 686 | function showTree(el) { | |
| 677 | 687 | rendered = null; | |
| @@ -755,6 +765,7 @@ window.ermiteFinder = (() => { | |||
| 755 | 765 | function palette(el) { | |
| 756 | 766 | if (!document.body.dataset.paths) return; | |
| 757 | 767 | clearHits(); | |
| 768 | + | window.ermiteCmd?.sync(); // the cursor may have been on a hit | |
| 758 | 769 | if (pFrame) cancelAnimationFrame(pFrame); | |
| 759 | 770 | pFrame = 0; | |
| 760 | 771 | if (!el.value.trim()) { | |
| @@ -802,6 +813,7 @@ window.ermiteFinder = (() => { | |||
| 802 | 813 | list.querySelectorAll(':scope > li[data-cursor]').forEach((li) => li.removeAttribute('data-cursor')); | |
| 803 | 814 | shown[0].setAttribute('data-cursor', ''); | |
| 804 | 815 | } | |
| 816 | + | window.ermiteCmd?.sync(); // roles, ids and the active option of the new hits | |
| 805 | 817 | } | |
| 806 | 818 | ||
| 807 | 819 | // input runs the search of the field, once per frame. | |
| @@ -816,12 +828,17 @@ window.ermiteFinder = (() => { | |||
| 816 | 828 | } | |
| 817 | 829 | ||
| 818 | 830 | return { | |
| 819 | - | index, search, positions, visible, label, load, palette, paletteClose, input, | |
| 831 | + | index, search, positions, visible, label, load, palette, paletteClose, input, sync, | |
| 820 | 832 | // key moves the cursor of the results, opens one, or clears the field. | |
| 821 | 833 | key(el, e) { | |
| 822 | 834 | if (e.isComposing) return; // the Enter or Escape of an input method is its own | |
| 823 | 835 | const results = !$('finder').hidden; | |
| 824 | 836 | if (e.key === 'Escape') { | |
| 837 | + | // With a query, Escape only clears it: the field keeps the focus (the | |
| 838 | + | // global handler of ermite.js would blur it). Without one, it leaves the field. | |
| 839 | + | if (!el.value) return; | |
| 840 | + | e.preventDefault(); | |
| 841 | + | e.stopPropagation(); | |
| 825 | 842 | el.value = ''; | |
| 826 | 843 | input(el); | |
| 827 | 844 | return; | |
M internal/app/testdata/web/blob-binary.html +25 -13
| @@ -153,6 +153,11 @@ | |||
| 153 | 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 154 | 154 | <kbd>esc</kbd> close</button> | |
| 155 | 155 | </div> | |
| 156 | + | <div class="keys-pref"> | |
| 157 | + | <label> | |
| 158 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 159 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 160 | + | </div> | |
| 156 | 161 | <dl class="keys"> | |
| 157 | 162 | <div> | |
| 158 | 163 | <dt> | |
| @@ -306,72 +311,78 @@ | |||
| 306 | 311 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 307 | 312 | <div class="panel-head"> | |
| 308 | 313 | <label for="cmd-q" class="sr">go to</label> | |
| 309 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 314 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 310 | 315 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 311 | 316 | <kbd>esc</kbd> | |
| 312 | 317 | </button> | |
| 313 | 318 | </div> | |
| 314 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 315 | - | <li data-k="go home" data-cursor> | |
| 319 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 320 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 316 | 321 | <a href="/" tabindex="-1"> | |
| 317 | 322 | <span class="lbl">home</span> | |
| 318 | 323 | <span class="k">go</span> | |
| 319 | 324 | </a> | |
| 320 | 325 | </li> | |
| 321 | - | <li data-k="section summary"> | |
| 326 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 322 | 327 | <a href="/files" tabindex="-1"> | |
| 323 | 328 | <span class="lbl">summary</span> | |
| 324 | 329 | <span class="k">section</span> | |
| 325 | 330 | </a> | |
| 326 | 331 | </li> | |
| 327 | - | <li data-k="section files"> | |
| 332 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 328 | 333 | <a href="/files/tree/main" tabindex="-1"> | |
| 329 | 334 | <span class="lbl">files</span> | |
| 330 | 335 | <span class="k">section</span> | |
| 331 | 336 | </a> | |
| 332 | 337 | </li> | |
| 333 | - | <li data-k="section commits"> | |
| 338 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 334 | 339 | <a href="/files/commits/main" tabindex="-1"> | |
| 335 | 340 | <span class="lbl">commits</span> | |
| 336 | 341 | <span class="k">section</span> | |
| 337 | 342 | </a> | |
| 338 | 343 | </li> | |
| 339 | - | <li data-k="section branches & tags"> | |
| 344 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 340 | 345 | <a href="/files/refs" tabindex="-1"> | |
| 341 | 346 | <span class="lbl">branches & tags</span> | |
| 342 | 347 | <span class="k">section</span> | |
| 343 | 348 | </a> | |
| 344 | 349 | </li> | |
| 345 | - | <li data-k="section merge requests"> | |
| 350 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 346 | 351 | <a href="/files/merge-requests" tabindex="-1"> | |
| 347 | 352 | <span class="lbl">merge requests</span> | |
| 348 | 353 | <span class="k">section</span> | |
| 349 | 354 | </a> | |
| 350 | 355 | </li> | |
| 351 | - | <li data-k="section actions"> | |
| 356 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 352 | 357 | <a href="/files/actions" tabindex="-1"> | |
| 353 | 358 | <span class="lbl">actions</span> | |
| 354 | 359 | <span class="k">section</span> | |
| 355 | 360 | </a> | |
| 356 | 361 | </li> | |
| 357 | - | <li data-k="branch feature/x"> | |
| 362 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 358 | 363 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 359 | 364 | <span class="lbl">feature/x</span> | |
| 360 | 365 | <span class="k">branch</span> | |
| 361 | 366 | </a> | |
| 362 | 367 | </li> | |
| 363 | - | <li data-k="branch main"> | |
| 368 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 364 | 369 | <a href="/files/tree/main" tabindex="-1"> | |
| 365 | 370 | <span class="lbl">main</span> | |
| 366 | 371 | <span class="k">branch</span> | |
| 367 | 372 | </a> | |
| 368 | 373 | </li> | |
| 369 | - | <li data-k="tag v1.0"> | |
| 374 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 370 | 375 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 371 | 376 | <span class="lbl">v1.0</span> | |
| 372 | 377 | <span class="k">tag</span> | |
| 373 | 378 | </a> | |
| 374 | 379 | </li> | |
| 380 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 381 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 382 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 383 | + | <span class="k">action</span> | |
| 384 | + | </a> | |
| 385 | + | </li> | |
| 375 | 386 | </ul> | |
| 376 | 387 | <p class="panel-foot"> | |
| 377 | 388 | <span> | |
| @@ -410,7 +421,8 @@ | |||
| 410 | 421 | k-permalink from:body -> ermite.permalink(); | |
| 411 | 422 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 412 | 423 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 413 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 424 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 425 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 414 | 426 | </div> | |
| 415 | 427 | </body> | |
| 416 | 428 | </html> | |
M internal/app/testdata/web/blob-code.html +25 -13
| @@ -223,6 +223,11 @@ | |||
| 223 | 223 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 224 | 224 | <kbd>esc</kbd> close</button> | |
| 225 | 225 | </div> | |
| 226 | + | <div class="keys-pref"> | |
| 227 | + | <label> | |
| 228 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 229 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 230 | + | </div> | |
| 226 | 231 | <dl class="keys"> | |
| 227 | 232 | <div> | |
| 228 | 233 | <dt> | |
| @@ -376,72 +381,78 @@ | |||
| 376 | 381 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 377 | 382 | <div class="panel-head"> | |
| 378 | 383 | <label for="cmd-q" class="sr">go to</label> | |
| 379 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 384 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 380 | 385 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 381 | 386 | <kbd>esc</kbd> | |
| 382 | 387 | </button> | |
| 383 | 388 | </div> | |
| 384 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 385 | - | <li data-k="go home" data-cursor> | |
| 389 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 390 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 386 | 391 | <a href="/" tabindex="-1"> | |
| 387 | 392 | <span class="lbl">home</span> | |
| 388 | 393 | <span class="k">go</span> | |
| 389 | 394 | </a> | |
| 390 | 395 | </li> | |
| 391 | - | <li data-k="section summary"> | |
| 396 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 392 | 397 | <a href="/files" tabindex="-1"> | |
| 393 | 398 | <span class="lbl">summary</span> | |
| 394 | 399 | <span class="k">section</span> | |
| 395 | 400 | </a> | |
| 396 | 401 | </li> | |
| 397 | - | <li data-k="section files"> | |
| 402 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 398 | 403 | <a href="/files/tree/main" tabindex="-1"> | |
| 399 | 404 | <span class="lbl">files</span> | |
| 400 | 405 | <span class="k">section</span> | |
| 401 | 406 | </a> | |
| 402 | 407 | </li> | |
| 403 | - | <li data-k="section commits"> | |
| 408 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 404 | 409 | <a href="/files/commits/main" tabindex="-1"> | |
| 405 | 410 | <span class="lbl">commits</span> | |
| 406 | 411 | <span class="k">section</span> | |
| 407 | 412 | </a> | |
| 408 | 413 | </li> | |
| 409 | - | <li data-k="section branches & tags"> | |
| 414 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 410 | 415 | <a href="/files/refs" tabindex="-1"> | |
| 411 | 416 | <span class="lbl">branches & tags</span> | |
| 412 | 417 | <span class="k">section</span> | |
| 413 | 418 | </a> | |
| 414 | 419 | </li> | |
| 415 | - | <li data-k="section merge requests"> | |
| 420 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 416 | 421 | <a href="/files/merge-requests" tabindex="-1"> | |
| 417 | 422 | <span class="lbl">merge requests</span> | |
| 418 | 423 | <span class="k">section</span> | |
| 419 | 424 | </a> | |
| 420 | 425 | </li> | |
| 421 | - | <li data-k="section actions"> | |
| 426 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 422 | 427 | <a href="/files/actions" tabindex="-1"> | |
| 423 | 428 | <span class="lbl">actions</span> | |
| 424 | 429 | <span class="k">section</span> | |
| 425 | 430 | </a> | |
| 426 | 431 | </li> | |
| 427 | - | <li data-k="branch feature/x"> | |
| 432 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 428 | 433 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 429 | 434 | <span class="lbl">feature/x</span> | |
| 430 | 435 | <span class="k">branch</span> | |
| 431 | 436 | </a> | |
| 432 | 437 | </li> | |
| 433 | - | <li data-k="branch main"> | |
| 438 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 434 | 439 | <a href="/files/tree/main" tabindex="-1"> | |
| 435 | 440 | <span class="lbl">main</span> | |
| 436 | 441 | <span class="k">branch</span> | |
| 437 | 442 | </a> | |
| 438 | 443 | </li> | |
| 439 | - | <li data-k="tag v1.0"> | |
| 444 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 440 | 445 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 441 | 446 | <span class="lbl">v1.0</span> | |
| 442 | 447 | <span class="k">tag</span> | |
| 443 | 448 | </a> | |
| 444 | 449 | </li> | |
| 450 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 451 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 452 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 453 | + | <span class="k">action</span> | |
| 454 | + | </a> | |
| 455 | + | </li> | |
| 445 | 456 | </ul> | |
| 446 | 457 | <p class="panel-foot"> | |
| 447 | 458 | <span> | |
| @@ -480,7 +491,8 @@ | |||
| 480 | 491 | k-permalink from:body -> ermite.permalink(); | |
| 481 | 492 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 482 | 493 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 483 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 494 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 495 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 484 | 496 | </div> | |
| 485 | 497 | </body> | |
| 486 | 498 | </html> | |
M internal/app/testdata/web/blob-empty.html +25 -13
| @@ -153,6 +153,11 @@ | |||
| 153 | 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 154 | 154 | <kbd>esc</kbd> close</button> | |
| 155 | 155 | </div> | |
| 156 | + | <div class="keys-pref"> | |
| 157 | + | <label> | |
| 158 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 159 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 160 | + | </div> | |
| 156 | 161 | <dl class="keys"> | |
| 157 | 162 | <div> | |
| 158 | 163 | <dt> | |
| @@ -306,72 +311,78 @@ | |||
| 306 | 311 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 307 | 312 | <div class="panel-head"> | |
| 308 | 313 | <label for="cmd-q" class="sr">go to</label> | |
| 309 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 314 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 310 | 315 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 311 | 316 | <kbd>esc</kbd> | |
| 312 | 317 | </button> | |
| 313 | 318 | </div> | |
| 314 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 315 | - | <li data-k="go home" data-cursor> | |
| 319 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 320 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 316 | 321 | <a href="/" tabindex="-1"> | |
| 317 | 322 | <span class="lbl">home</span> | |
| 318 | 323 | <span class="k">go</span> | |
| 319 | 324 | </a> | |
| 320 | 325 | </li> | |
| 321 | - | <li data-k="section summary"> | |
| 326 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 322 | 327 | <a href="/files" tabindex="-1"> | |
| 323 | 328 | <span class="lbl">summary</span> | |
| 324 | 329 | <span class="k">section</span> | |
| 325 | 330 | </a> | |
| 326 | 331 | </li> | |
| 327 | - | <li data-k="section files"> | |
| 332 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 328 | 333 | <a href="/files/tree/main" tabindex="-1"> | |
| 329 | 334 | <span class="lbl">files</span> | |
| 330 | 335 | <span class="k">section</span> | |
| 331 | 336 | </a> | |
| 332 | 337 | </li> | |
| 333 | - | <li data-k="section commits"> | |
| 338 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 334 | 339 | <a href="/files/commits/main" tabindex="-1"> | |
| 335 | 340 | <span class="lbl">commits</span> | |
| 336 | 341 | <span class="k">section</span> | |
| 337 | 342 | </a> | |
| 338 | 343 | </li> | |
| 339 | - | <li data-k="section branches & tags"> | |
| 344 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 340 | 345 | <a href="/files/refs" tabindex="-1"> | |
| 341 | 346 | <span class="lbl">branches & tags</span> | |
| 342 | 347 | <span class="k">section</span> | |
| 343 | 348 | </a> | |
| 344 | 349 | </li> | |
| 345 | - | <li data-k="section merge requests"> | |
| 350 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 346 | 351 | <a href="/files/merge-requests" tabindex="-1"> | |
| 347 | 352 | <span class="lbl">merge requests</span> | |
| 348 | 353 | <span class="k">section</span> | |
| 349 | 354 | </a> | |
| 350 | 355 | </li> | |
| 351 | - | <li data-k="section actions"> | |
| 356 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 352 | 357 | <a href="/files/actions" tabindex="-1"> | |
| 353 | 358 | <span class="lbl">actions</span> | |
| 354 | 359 | <span class="k">section</span> | |
| 355 | 360 | </a> | |
| 356 | 361 | </li> | |
| 357 | - | <li data-k="branch feature/x"> | |
| 362 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 358 | 363 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 359 | 364 | <span class="lbl">feature/x</span> | |
| 360 | 365 | <span class="k">branch</span> | |
| 361 | 366 | </a> | |
| 362 | 367 | </li> | |
| 363 | - | <li data-k="branch main"> | |
| 368 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 364 | 369 | <a href="/files/tree/main" tabindex="-1"> | |
| 365 | 370 | <span class="lbl">main</span> | |
| 366 | 371 | <span class="k">branch</span> | |
| 367 | 372 | </a> | |
| 368 | 373 | </li> | |
| 369 | - | <li data-k="tag v1.0"> | |
| 374 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 370 | 375 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 371 | 376 | <span class="lbl">v1.0</span> | |
| 372 | 377 | <span class="k">tag</span> | |
| 373 | 378 | </a> | |
| 374 | 379 | </li> | |
| 380 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 381 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 382 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 383 | + | <span class="k">action</span> | |
| 384 | + | </a> | |
| 385 | + | </li> | |
| 375 | 386 | </ul> | |
| 376 | 387 | <p class="panel-foot"> | |
| 377 | 388 | <span> | |
| @@ -410,7 +421,8 @@ | |||
| 410 | 421 | k-permalink from:body -> ermite.permalink(); | |
| 411 | 422 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 412 | 423 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 413 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 424 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 425 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 414 | 426 | </div> | |
| 415 | 427 | </body> | |
| 416 | 428 | </html> | |
M internal/app/testdata/web/blob-image.html +25 -13
| @@ -155,6 +155,11 @@ | |||
| 155 | 155 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 156 | 156 | <kbd>esc</kbd> close</button> | |
| 157 | 157 | </div> | |
| 158 | + | <div class="keys-pref"> | |
| 159 | + | <label> | |
| 160 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 161 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 162 | + | </div> | |
| 158 | 163 | <dl class="keys"> | |
| 159 | 164 | <div> | |
| 160 | 165 | <dt> | |
| @@ -308,72 +313,78 @@ | |||
| 308 | 313 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 309 | 314 | <div class="panel-head"> | |
| 310 | 315 | <label for="cmd-q" class="sr">go to</label> | |
| 311 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 316 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 312 | 317 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 313 | 318 | <kbd>esc</kbd> | |
| 314 | 319 | </button> | |
| 315 | 320 | </div> | |
| 316 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 317 | - | <li data-k="go home" data-cursor> | |
| 321 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 322 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 318 | 323 | <a href="/" tabindex="-1"> | |
| 319 | 324 | <span class="lbl">home</span> | |
| 320 | 325 | <span class="k">go</span> | |
| 321 | 326 | </a> | |
| 322 | 327 | </li> | |
| 323 | - | <li data-k="section summary"> | |
| 328 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 324 | 329 | <a href="/files" tabindex="-1"> | |
| 325 | 330 | <span class="lbl">summary</span> | |
| 326 | 331 | <span class="k">section</span> | |
| 327 | 332 | </a> | |
| 328 | 333 | </li> | |
| 329 | - | <li data-k="section files"> | |
| 334 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 330 | 335 | <a href="/files/tree/main" tabindex="-1"> | |
| 331 | 336 | <span class="lbl">files</span> | |
| 332 | 337 | <span class="k">section</span> | |
| 333 | 338 | </a> | |
| 334 | 339 | </li> | |
| 335 | - | <li data-k="section commits"> | |
| 340 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 336 | 341 | <a href="/files/commits/main" tabindex="-1"> | |
| 337 | 342 | <span class="lbl">commits</span> | |
| 338 | 343 | <span class="k">section</span> | |
| 339 | 344 | </a> | |
| 340 | 345 | </li> | |
| 341 | - | <li data-k="section branches & tags"> | |
| 346 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 342 | 347 | <a href="/files/refs" tabindex="-1"> | |
| 343 | 348 | <span class="lbl">branches & tags</span> | |
| 344 | 349 | <span class="k">section</span> | |
| 345 | 350 | </a> | |
| 346 | 351 | </li> | |
| 347 | - | <li data-k="section merge requests"> | |
| 352 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 348 | 353 | <a href="/files/merge-requests" tabindex="-1"> | |
| 349 | 354 | <span class="lbl">merge requests</span> | |
| 350 | 355 | <span class="k">section</span> | |
| 351 | 356 | </a> | |
| 352 | 357 | </li> | |
| 353 | - | <li data-k="section actions"> | |
| 358 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 354 | 359 | <a href="/files/actions" tabindex="-1"> | |
| 355 | 360 | <span class="lbl">actions</span> | |
| 356 | 361 | <span class="k">section</span> | |
| 357 | 362 | </a> | |
| 358 | 363 | </li> | |
| 359 | - | <li data-k="branch feature/x"> | |
| 364 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 360 | 365 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 361 | 366 | <span class="lbl">feature/x</span> | |
| 362 | 367 | <span class="k">branch</span> | |
| 363 | 368 | </a> | |
| 364 | 369 | </li> | |
| 365 | - | <li data-k="branch main"> | |
| 370 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 366 | 371 | <a href="/files/tree/main" tabindex="-1"> | |
| 367 | 372 | <span class="lbl">main</span> | |
| 368 | 373 | <span class="k">branch</span> | |
| 369 | 374 | </a> | |
| 370 | 375 | </li> | |
| 371 | - | <li data-k="tag v1.0"> | |
| 376 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 372 | 377 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 373 | 378 | <span class="lbl">v1.0</span> | |
| 374 | 379 | <span class="k">tag</span> | |
| 375 | 380 | </a> | |
| 376 | 381 | </li> | |
| 382 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 383 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 384 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 385 | + | <span class="k">action</span> | |
| 386 | + | </a> | |
| 387 | + | </li> | |
| 377 | 388 | </ul> | |
| 378 | 389 | <p class="panel-foot"> | |
| 379 | 390 | <span> | |
| @@ -412,7 +423,8 @@ | |||
| 412 | 423 | k-permalink from:body -> ermite.permalink(); | |
| 413 | 424 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 414 | 425 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 415 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 426 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 427 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 416 | 428 | </div> | |
| 417 | 429 | </body> | |
| 418 | 430 | </html> | |
M internal/app/testdata/web/blob-markdown.html +25 -13
| @@ -220,6 +220,11 @@ | |||
| 220 | 220 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 221 | 221 | <kbd>esc</kbd> close</button> | |
| 222 | 222 | </div> | |
| 223 | + | <div class="keys-pref"> | |
| 224 | + | <label> | |
| 225 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 226 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 227 | + | </div> | |
| 223 | 228 | <dl class="keys"> | |
| 224 | 229 | <div> | |
| 225 | 230 | <dt> | |
| @@ -373,72 +378,78 @@ | |||
| 373 | 378 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 374 | 379 | <div class="panel-head"> | |
| 375 | 380 | <label for="cmd-q" class="sr">go to</label> | |
| 376 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 381 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 377 | 382 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 378 | 383 | <kbd>esc</kbd> | |
| 379 | 384 | </button> | |
| 380 | 385 | </div> | |
| 381 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 382 | - | <li data-k="go home" data-cursor> | |
| 386 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 387 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 383 | 388 | <a href="/" tabindex="-1"> | |
| 384 | 389 | <span class="lbl">home</span> | |
| 385 | 390 | <span class="k">go</span> | |
| 386 | 391 | </a> | |
| 387 | 392 | </li> | |
| 388 | - | <li data-k="section summary"> | |
| 393 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 389 | 394 | <a href="/files" tabindex="-1"> | |
| 390 | 395 | <span class="lbl">summary</span> | |
| 391 | 396 | <span class="k">section</span> | |
| 392 | 397 | </a> | |
| 393 | 398 | </li> | |
| 394 | - | <li data-k="section files"> | |
| 399 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 395 | 400 | <a href="/files/tree/main" tabindex="-1"> | |
| 396 | 401 | <span class="lbl">files</span> | |
| 397 | 402 | <span class="k">section</span> | |
| 398 | 403 | </a> | |
| 399 | 404 | </li> | |
| 400 | - | <li data-k="section commits"> | |
| 405 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 401 | 406 | <a href="/files/commits/main" tabindex="-1"> | |
| 402 | 407 | <span class="lbl">commits</span> | |
| 403 | 408 | <span class="k">section</span> | |
| 404 | 409 | </a> | |
| 405 | 410 | </li> | |
| 406 | - | <li data-k="section branches & tags"> | |
| 411 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 407 | 412 | <a href="/files/refs" tabindex="-1"> | |
| 408 | 413 | <span class="lbl">branches & tags</span> | |
| 409 | 414 | <span class="k">section</span> | |
| 410 | 415 | </a> | |
| 411 | 416 | </li> | |
| 412 | - | <li data-k="section merge requests"> | |
| 417 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 413 | 418 | <a href="/files/merge-requests" tabindex="-1"> | |
| 414 | 419 | <span class="lbl">merge requests</span> | |
| 415 | 420 | <span class="k">section</span> | |
| 416 | 421 | </a> | |
| 417 | 422 | </li> | |
| 418 | - | <li data-k="section actions"> | |
| 423 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 419 | 424 | <a href="/files/actions" tabindex="-1"> | |
| 420 | 425 | <span class="lbl">actions</span> | |
| 421 | 426 | <span class="k">section</span> | |
| 422 | 427 | </a> | |
| 423 | 428 | </li> | |
| 424 | - | <li data-k="branch feature/x"> | |
| 429 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 425 | 430 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 426 | 431 | <span class="lbl">feature/x</span> | |
| 427 | 432 | <span class="k">branch</span> | |
| 428 | 433 | </a> | |
| 429 | 434 | </li> | |
| 430 | - | <li data-k="branch main"> | |
| 435 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 431 | 436 | <a href="/files/tree/main" tabindex="-1"> | |
| 432 | 437 | <span class="lbl">main</span> | |
| 433 | 438 | <span class="k">branch</span> | |
| 434 | 439 | </a> | |
| 435 | 440 | </li> | |
| 436 | - | <li data-k="tag v1.0"> | |
| 441 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 437 | 442 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 438 | 443 | <span class="lbl">v1.0</span> | |
| 439 | 444 | <span class="k">tag</span> | |
| 440 | 445 | </a> | |
| 441 | 446 | </li> | |
| 447 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 448 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 449 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 450 | + | <span class="k">action</span> | |
| 451 | + | </a> | |
| 452 | + | </li> | |
| 442 | 453 | </ul> | |
| 443 | 454 | <p class="panel-foot"> | |
| 444 | 455 | <span> | |
| @@ -477,7 +488,8 @@ | |||
| 477 | 488 | k-permalink from:body -> ermite.permalink(); | |
| 478 | 489 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 479 | 490 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 480 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 491 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 492 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 481 | 493 | </div> | |
| 482 | 494 | </body> | |
| 483 | 495 | </html> | |
M internal/app/testdata/web/blob-svg.html +25 -13
| @@ -178,6 +178,11 @@ | |||
| 178 | 178 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 179 | 179 | <kbd>esc</kbd> close</button> | |
| 180 | 180 | </div> | |
| 181 | + | <div class="keys-pref"> | |
| 182 | + | <label> | |
| 183 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 184 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 185 | + | </div> | |
| 181 | 186 | <dl class="keys"> | |
| 182 | 187 | <div> | |
| 183 | 188 | <dt> | |
| @@ -331,72 +336,78 @@ | |||
| 331 | 336 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 332 | 337 | <div class="panel-head"> | |
| 333 | 338 | <label for="cmd-q" class="sr">go to</label> | |
| 334 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 339 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 335 | 340 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 336 | 341 | <kbd>esc</kbd> | |
| 337 | 342 | </button> | |
| 338 | 343 | </div> | |
| 339 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 340 | - | <li data-k="go home" data-cursor> | |
| 344 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 345 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 341 | 346 | <a href="/" tabindex="-1"> | |
| 342 | 347 | <span class="lbl">home</span> | |
| 343 | 348 | <span class="k">go</span> | |
| 344 | 349 | </a> | |
| 345 | 350 | </li> | |
| 346 | - | <li data-k="section summary"> | |
| 351 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 347 | 352 | <a href="/files" tabindex="-1"> | |
| 348 | 353 | <span class="lbl">summary</span> | |
| 349 | 354 | <span class="k">section</span> | |
| 350 | 355 | </a> | |
| 351 | 356 | </li> | |
| 352 | - | <li data-k="section files"> | |
| 357 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 353 | 358 | <a href="/files/tree/main" tabindex="-1"> | |
| 354 | 359 | <span class="lbl">files</span> | |
| 355 | 360 | <span class="k">section</span> | |
| 356 | 361 | </a> | |
| 357 | 362 | </li> | |
| 358 | - | <li data-k="section commits"> | |
| 363 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 359 | 364 | <a href="/files/commits/main" tabindex="-1"> | |
| 360 | 365 | <span class="lbl">commits</span> | |
| 361 | 366 | <span class="k">section</span> | |
| 362 | 367 | </a> | |
| 363 | 368 | </li> | |
| 364 | - | <li data-k="section branches & tags"> | |
| 369 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 365 | 370 | <a href="/files/refs" tabindex="-1"> | |
| 366 | 371 | <span class="lbl">branches & tags</span> | |
| 367 | 372 | <span class="k">section</span> | |
| 368 | 373 | </a> | |
| 369 | 374 | </li> | |
| 370 | - | <li data-k="section merge requests"> | |
| 375 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 371 | 376 | <a href="/files/merge-requests" tabindex="-1"> | |
| 372 | 377 | <span class="lbl">merge requests</span> | |
| 373 | 378 | <span class="k">section</span> | |
| 374 | 379 | </a> | |
| 375 | 380 | </li> | |
| 376 | - | <li data-k="section actions"> | |
| 381 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 377 | 382 | <a href="/files/actions" tabindex="-1"> | |
| 378 | 383 | <span class="lbl">actions</span> | |
| 379 | 384 | <span class="k">section</span> | |
| 380 | 385 | </a> | |
| 381 | 386 | </li> | |
| 382 | - | <li data-k="branch feature/x"> | |
| 387 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 383 | 388 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 384 | 389 | <span class="lbl">feature/x</span> | |
| 385 | 390 | <span class="k">branch</span> | |
| 386 | 391 | </a> | |
| 387 | 392 | </li> | |
| 388 | - | <li data-k="branch main"> | |
| 393 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 389 | 394 | <a href="/files/tree/main" tabindex="-1"> | |
| 390 | 395 | <span class="lbl">main</span> | |
| 391 | 396 | <span class="k">branch</span> | |
| 392 | 397 | </a> | |
| 393 | 398 | </li> | |
| 394 | - | <li data-k="tag v1.0"> | |
| 399 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 395 | 400 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 396 | 401 | <span class="lbl">v1.0</span> | |
| 397 | 402 | <span class="k">tag</span> | |
| 398 | 403 | </a> | |
| 399 | 404 | </li> | |
| 405 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 406 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 407 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 408 | + | <span class="k">action</span> | |
| 409 | + | </a> | |
| 410 | + | </li> | |
| 400 | 411 | </ul> | |
| 401 | 412 | <p class="panel-foot"> | |
| 402 | 413 | <span> | |
| @@ -435,7 +446,8 @@ | |||
| 435 | 446 | k-permalink from:body -> ermite.permalink(); | |
| 436 | 447 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 437 | 448 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 438 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 449 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 450 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 439 | 451 | </div> | |
| 440 | 452 | </body> | |
| 441 | 453 | </html> | |
M internal/app/testdata/web/blob-text.html +25 -13
| @@ -173,6 +173,11 @@ | |||
| 173 | 173 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 174 | 174 | <kbd>esc</kbd> close</button> | |
| 175 | 175 | </div> | |
| 176 | + | <div class="keys-pref"> | |
| 177 | + | <label> | |
| 178 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 179 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 180 | + | </div> | |
| 176 | 181 | <dl class="keys"> | |
| 177 | 182 | <div> | |
| 178 | 183 | <dt> | |
| @@ -326,72 +331,78 @@ | |||
| 326 | 331 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 327 | 332 | <div class="panel-head"> | |
| 328 | 333 | <label for="cmd-q" class="sr">go to</label> | |
| 329 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 334 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 330 | 335 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 331 | 336 | <kbd>esc</kbd> | |
| 332 | 337 | </button> | |
| 333 | 338 | </div> | |
| 334 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 335 | - | <li data-k="go home" data-cursor> | |
| 339 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 340 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 336 | 341 | <a href="/" tabindex="-1"> | |
| 337 | 342 | <span class="lbl">home</span> | |
| 338 | 343 | <span class="k">go</span> | |
| 339 | 344 | </a> | |
| 340 | 345 | </li> | |
| 341 | - | <li data-k="section summary"> | |
| 346 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 342 | 347 | <a href="/files" tabindex="-1"> | |
| 343 | 348 | <span class="lbl">summary</span> | |
| 344 | 349 | <span class="k">section</span> | |
| 345 | 350 | </a> | |
| 346 | 351 | </li> | |
| 347 | - | <li data-k="section files"> | |
| 352 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 348 | 353 | <a href="/files/tree/main" tabindex="-1"> | |
| 349 | 354 | <span class="lbl">files</span> | |
| 350 | 355 | <span class="k">section</span> | |
| 351 | 356 | </a> | |
| 352 | 357 | </li> | |
| 353 | - | <li data-k="section commits"> | |
| 358 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 354 | 359 | <a href="/files/commits/main" tabindex="-1"> | |
| 355 | 360 | <span class="lbl">commits</span> | |
| 356 | 361 | <span class="k">section</span> | |
| 357 | 362 | </a> | |
| 358 | 363 | </li> | |
| 359 | - | <li data-k="section branches & tags"> | |
| 364 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 360 | 365 | <a href="/files/refs" tabindex="-1"> | |
| 361 | 366 | <span class="lbl">branches & tags</span> | |
| 362 | 367 | <span class="k">section</span> | |
| 363 | 368 | </a> | |
| 364 | 369 | </li> | |
| 365 | - | <li data-k="section merge requests"> | |
| 370 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 366 | 371 | <a href="/files/merge-requests" tabindex="-1"> | |
| 367 | 372 | <span class="lbl">merge requests</span> | |
| 368 | 373 | <span class="k">section</span> | |
| 369 | 374 | </a> | |
| 370 | 375 | </li> | |
| 371 | - | <li data-k="section actions"> | |
| 376 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 372 | 377 | <a href="/files/actions" tabindex="-1"> | |
| 373 | 378 | <span class="lbl">actions</span> | |
| 374 | 379 | <span class="k">section</span> | |
| 375 | 380 | </a> | |
| 376 | 381 | </li> | |
| 377 | - | <li data-k="branch feature/x"> | |
| 382 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 378 | 383 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 379 | 384 | <span class="lbl">feature/x</span> | |
| 380 | 385 | <span class="k">branch</span> | |
| 381 | 386 | </a> | |
| 382 | 387 | </li> | |
| 383 | - | <li data-k="branch main"> | |
| 388 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 384 | 389 | <a href="/files/tree/main" tabindex="-1"> | |
| 385 | 390 | <span class="lbl">main</span> | |
| 386 | 391 | <span class="k">branch</span> | |
| 387 | 392 | </a> | |
| 388 | 393 | </li> | |
| 389 | - | <li data-k="tag v1.0"> | |
| 394 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 390 | 395 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 391 | 396 | <span class="lbl">v1.0</span> | |
| 392 | 397 | <span class="k">tag</span> | |
| 393 | 398 | </a> | |
| 394 | 399 | </li> | |
| 400 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 401 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 402 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 403 | + | <span class="k">action</span> | |
| 404 | + | </a> | |
| 405 | + | </li> | |
| 395 | 406 | </ul> | |
| 396 | 407 | <p class="panel-foot"> | |
| 397 | 408 | <span> | |
| @@ -430,7 +441,8 @@ | |||
| 430 | 441 | k-permalink from:body -> ermite.permalink(); | |
| 431 | 442 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 432 | 443 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 433 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 444 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 445 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 434 | 446 | </div> | |
| 435 | 447 | </body> | |
| 436 | 448 | </html> | |
M internal/app/testdata/web/blob-too-large.html +25 -13
| @@ -153,6 +153,11 @@ | |||
| 153 | 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 154 | 154 | <kbd>esc</kbd> close</button> | |
| 155 | 155 | </div> | |
| 156 | + | <div class="keys-pref"> | |
| 157 | + | <label> | |
| 158 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 159 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 160 | + | </div> | |
| 156 | 161 | <dl class="keys"> | |
| 157 | 162 | <div> | |
| 158 | 163 | <dt> | |
| @@ -306,72 +311,78 @@ | |||
| 306 | 311 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 307 | 312 | <div class="panel-head"> | |
| 308 | 313 | <label for="cmd-q" class="sr">go to</label> | |
| 309 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 314 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 310 | 315 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 311 | 316 | <kbd>esc</kbd> | |
| 312 | 317 | </button> | |
| 313 | 318 | </div> | |
| 314 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 315 | - | <li data-k="go home" data-cursor> | |
| 319 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 320 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 316 | 321 | <a href="/" tabindex="-1"> | |
| 317 | 322 | <span class="lbl">home</span> | |
| 318 | 323 | <span class="k">go</span> | |
| 319 | 324 | </a> | |
| 320 | 325 | </li> | |
| 321 | - | <li data-k="section summary"> | |
| 326 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 322 | 327 | <a href="/files" tabindex="-1"> | |
| 323 | 328 | <span class="lbl">summary</span> | |
| 324 | 329 | <span class="k">section</span> | |
| 325 | 330 | </a> | |
| 326 | 331 | </li> | |
| 327 | - | <li data-k="section files"> | |
| 332 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 328 | 333 | <a href="/files/tree/main" tabindex="-1"> | |
| 329 | 334 | <span class="lbl">files</span> | |
| 330 | 335 | <span class="k">section</span> | |
| 331 | 336 | </a> | |
| 332 | 337 | </li> | |
| 333 | - | <li data-k="section commits"> | |
| 338 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 334 | 339 | <a href="/files/commits/main" tabindex="-1"> | |
| 335 | 340 | <span class="lbl">commits</span> | |
| 336 | 341 | <span class="k">section</span> | |
| 337 | 342 | </a> | |
| 338 | 343 | </li> | |
| 339 | - | <li data-k="section branches & tags"> | |
| 344 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 340 | 345 | <a href="/files/refs" tabindex="-1"> | |
| 341 | 346 | <span class="lbl">branches & tags</span> | |
| 342 | 347 | <span class="k">section</span> | |
| 343 | 348 | </a> | |
| 344 | 349 | </li> | |
| 345 | - | <li data-k="section merge requests"> | |
| 350 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 346 | 351 | <a href="/files/merge-requests" tabindex="-1"> | |
| 347 | 352 | <span class="lbl">merge requests</span> | |
| 348 | 353 | <span class="k">section</span> | |
| 349 | 354 | </a> | |
| 350 | 355 | </li> | |
| 351 | - | <li data-k="section actions"> | |
| 356 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 352 | 357 | <a href="/files/actions" tabindex="-1"> | |
| 353 | 358 | <span class="lbl">actions</span> | |
| 354 | 359 | <span class="k">section</span> | |
| 355 | 360 | </a> | |
| 356 | 361 | </li> | |
| 357 | - | <li data-k="branch feature/x"> | |
| 362 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 358 | 363 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 359 | 364 | <span class="lbl">feature/x</span> | |
| 360 | 365 | <span class="k">branch</span> | |
| 361 | 366 | </a> | |
| 362 | 367 | </li> | |
| 363 | - | <li data-k="branch main"> | |
| 368 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 364 | 369 | <a href="/files/tree/main" tabindex="-1"> | |
| 365 | 370 | <span class="lbl">main</span> | |
| 366 | 371 | <span class="k">branch</span> | |
| 367 | 372 | </a> | |
| 368 | 373 | </li> | |
| 369 | - | <li data-k="tag v1.0"> | |
| 374 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 370 | 375 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 371 | 376 | <span class="lbl">v1.0</span> | |
| 372 | 377 | <span class="k">tag</span> | |
| 373 | 378 | </a> | |
| 374 | 379 | </li> | |
| 380 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 381 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 382 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 383 | + | <span class="k">action</span> | |
| 384 | + | </a> | |
| 385 | + | </li> | |
| 375 | 386 | </ul> | |
| 376 | 387 | <p class="panel-foot"> | |
| 377 | 388 | <span> | |
| @@ -410,7 +421,8 @@ | |||
| 410 | 421 | k-permalink from:body -> ermite.permalink(); | |
| 411 | 422 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 412 | 423 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 413 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 424 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 425 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 414 | 426 | </div> | |
| 415 | 427 | </body> | |
| 416 | 428 | </html> | |
M internal/app/testdata/web/blob-trojan.html +25 -13
| @@ -220,6 +220,11 @@ | |||
| 220 | 220 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 221 | 221 | <kbd>esc</kbd> close</button> | |
| 222 | 222 | </div> | |
| 223 | + | <div class="keys-pref"> | |
| 224 | + | <label> | |
| 225 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 226 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 227 | + | </div> | |
| 223 | 228 | <dl class="keys"> | |
| 224 | 229 | <div> | |
| 225 | 230 | <dt> | |
| @@ -373,72 +378,78 @@ | |||
| 373 | 378 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 374 | 379 | <div class="panel-head"> | |
| 375 | 380 | <label for="cmd-q" class="sr">go to</label> | |
| 376 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 381 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 377 | 382 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 378 | 383 | <kbd>esc</kbd> | |
| 379 | 384 | </button> | |
| 380 | 385 | </div> | |
| 381 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 382 | - | <li data-k="go home" data-cursor> | |
| 386 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 387 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 383 | 388 | <a href="/" tabindex="-1"> | |
| 384 | 389 | <span class="lbl">home</span> | |
| 385 | 390 | <span class="k">go</span> | |
| 386 | 391 | </a> | |
| 387 | 392 | </li> | |
| 388 | - | <li data-k="section summary"> | |
| 393 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 389 | 394 | <a href="/files" tabindex="-1"> | |
| 390 | 395 | <span class="lbl">summary</span> | |
| 391 | 396 | <span class="k">section</span> | |
| 392 | 397 | </a> | |
| 393 | 398 | </li> | |
| 394 | - | <li data-k="section files"> | |
| 399 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 395 | 400 | <a href="/files/tree/main" tabindex="-1"> | |
| 396 | 401 | <span class="lbl">files</span> | |
| 397 | 402 | <span class="k">section</span> | |
| 398 | 403 | </a> | |
| 399 | 404 | </li> | |
| 400 | - | <li data-k="section commits"> | |
| 405 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 401 | 406 | <a href="/files/commits/main" tabindex="-1"> | |
| 402 | 407 | <span class="lbl">commits</span> | |
| 403 | 408 | <span class="k">section</span> | |
| 404 | 409 | </a> | |
| 405 | 410 | </li> | |
| 406 | - | <li data-k="section branches & tags"> | |
| 411 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 407 | 412 | <a href="/files/refs" tabindex="-1"> | |
| 408 | 413 | <span class="lbl">branches & tags</span> | |
| 409 | 414 | <span class="k">section</span> | |
| 410 | 415 | </a> | |
| 411 | 416 | </li> | |
| 412 | - | <li data-k="section merge requests"> | |
| 417 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 413 | 418 | <a href="/files/merge-requests" tabindex="-1"> | |
| 414 | 419 | <span class="lbl">merge requests</span> | |
| 415 | 420 | <span class="k">section</span> | |
| 416 | 421 | </a> | |
| 417 | 422 | </li> | |
| 418 | - | <li data-k="section actions"> | |
| 423 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 419 | 424 | <a href="/files/actions" tabindex="-1"> | |
| 420 | 425 | <span class="lbl">actions</span> | |
| 421 | 426 | <span class="k">section</span> | |
| 422 | 427 | </a> | |
| 423 | 428 | </li> | |
| 424 | - | <li data-k="branch feature/x"> | |
| 429 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 425 | 430 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 426 | 431 | <span class="lbl">feature/x</span> | |
| 427 | 432 | <span class="k">branch</span> | |
| 428 | 433 | </a> | |
| 429 | 434 | </li> | |
| 430 | - | <li data-k="branch main"> | |
| 435 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 431 | 436 | <a href="/files/tree/main" tabindex="-1"> | |
| 432 | 437 | <span class="lbl">main</span> | |
| 433 | 438 | <span class="k">branch</span> | |
| 434 | 439 | </a> | |
| 435 | 440 | </li> | |
| 436 | - | <li data-k="tag v1.0"> | |
| 441 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 437 | 442 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 438 | 443 | <span class="lbl">v1.0</span> | |
| 439 | 444 | <span class="k">tag</span> | |
| 440 | 445 | </a> | |
| 441 | 446 | </li> | |
| 447 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 448 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 449 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 450 | + | <span class="k">action</span> | |
| 451 | + | </a> | |
| 452 | + | </li> | |
| 442 | 453 | </ul> | |
| 443 | 454 | <p class="panel-foot"> | |
| 444 | 455 | <span> | |
| @@ -477,7 +488,8 @@ | |||
| 477 | 488 | k-permalink from:body -> ermite.permalink(); | |
| 478 | 489 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 479 | 490 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 480 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 491 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 492 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 481 | 493 | </div> | |
| 482 | 494 | </body> | |
| 483 | 495 | </html> | |
M internal/app/testdata/web/commit-big.html +26 -14
| @@ -117,7 +117,7 @@ | |||
| 117 | 117 | </dd> | |
| 118 | 118 | </dl> | |
| 119 | 119 | </header> | |
| 120 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 120 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 121 | 121 | <span class="dim">1 file · <span class="plus">+2600</span> | |
| 122 | 122 | <span class="minus">-0</span> | |
| 123 | 123 | </span> | |
| @@ -191,6 +191,11 @@ | |||
| 191 | 191 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 192 | 192 | <kbd>esc</kbd> close</button> | |
| 193 | 193 | </div> | |
| 194 | + | <div class="keys-pref"> | |
| 195 | + | <label> | |
| 196 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 197 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 198 | + | </div> | |
| 194 | 199 | <dl class="keys"> | |
| 195 | 200 | <div> | |
| 196 | 201 | <dt> | |
| @@ -344,72 +349,78 @@ | |||
| 344 | 349 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 345 | 350 | <div class="panel-head"> | |
| 346 | 351 | <label for="cmd-q" class="sr">go to</label> | |
| 347 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 352 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 348 | 353 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 349 | 354 | <kbd>esc</kbd> | |
| 350 | 355 | </button> | |
| 351 | 356 | </div> | |
| 352 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 353 | - | <li data-k="go home" data-cursor> | |
| 357 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 358 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 354 | 359 | <a href="/" tabindex="-1"> | |
| 355 | 360 | <span class="lbl">home</span> | |
| 356 | 361 | <span class="k">go</span> | |
| 357 | 362 | </a> | |
| 358 | 363 | </li> | |
| 359 | - | <li data-k="section summary"> | |
| 364 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 360 | 365 | <a href="/diffs" tabindex="-1"> | |
| 361 | 366 | <span class="lbl">summary</span> | |
| 362 | 367 | <span class="k">section</span> | |
| 363 | 368 | </a> | |
| 364 | 369 | </li> | |
| 365 | - | <li data-k="section files"> | |
| 370 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 366 | 371 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 367 | 372 | <span class="lbl">files</span> | |
| 368 | 373 | <span class="k">section</span> | |
| 369 | 374 | </a> | |
| 370 | 375 | </li> | |
| 371 | - | <li data-k="section commits"> | |
| 376 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 372 | 377 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 373 | 378 | <span class="lbl">commits</span> | |
| 374 | 379 | <span class="k">section</span> | |
| 375 | 380 | </a> | |
| 376 | 381 | </li> | |
| 377 | - | <li data-k="section branches & tags"> | |
| 382 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 378 | 383 | <a href="/diffs/refs" tabindex="-1"> | |
| 379 | 384 | <span class="lbl">branches & tags</span> | |
| 380 | 385 | <span class="k">section</span> | |
| 381 | 386 | </a> | |
| 382 | 387 | </li> | |
| 383 | - | <li data-k="section merge requests"> | |
| 388 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 384 | 389 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 385 | 390 | <span class="lbl">merge requests</span> | |
| 386 | 391 | <span class="k">section</span> | |
| 387 | 392 | </a> | |
| 388 | 393 | </li> | |
| 389 | - | <li data-k="section actions"> | |
| 394 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 390 | 395 | <a href="/diffs/actions" tabindex="-1"> | |
| 391 | 396 | <span class="lbl">actions</span> | |
| 392 | 397 | <span class="k">section</span> | |
| 393 | 398 | </a> | |
| 394 | 399 | </li> | |
| 395 | - | <li data-k="branch main"> | |
| 400 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 396 | 401 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 397 | 402 | <span class="lbl">main</span> | |
| 398 | 403 | <span class="k">branch</span> | |
| 399 | 404 | </a> | |
| 400 | 405 | </li> | |
| 401 | - | <li data-k="branch side"> | |
| 406 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 402 | 407 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 403 | 408 | <span class="lbl">side</span> | |
| 404 | 409 | <span class="k">branch</span> | |
| 405 | 410 | </a> | |
| 406 | 411 | </li> | |
| 407 | - | <li data-k="tag v1.0.0"> | |
| 412 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 408 | 413 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 409 | 414 | <span class="lbl">v1.0.0</span> | |
| 410 | 415 | <span class="k">tag</span> | |
| 411 | 416 | </a> | |
| 412 | 417 | </li> | |
| 418 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 419 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 420 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 421 | + | <span class="k">action</span> | |
| 422 | + | </a> | |
| 423 | + | </li> | |
| 413 | 424 | </ul> | |
| 414 | 425 | <p class="panel-foot"> | |
| 415 | 426 | <span> | |
| @@ -448,7 +459,8 @@ | |||
| 448 | 459 | k-permalink from:body -> ermite.permalink(); | |
| 449 | 460 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 450 | 461 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 451 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 462 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 463 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 452 | 464 | </div> | |
| 453 | 465 | </body> | |
| 454 | 466 | </html> | |
M internal/app/testdata/web/commit-binary.html +26 -14
| @@ -113,7 +113,7 @@ | |||
| 113 | 113 | </dd> | |
| 114 | 114 | </dl> | |
| 115 | 115 | </header> | |
| 116 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 116 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 117 | 117 | <span class="dim">1 file · <span class="plus">+0</span> | |
| 118 | 118 | <span class="minus">-0</span> | |
| 119 | 119 | </span> | |
| @@ -183,6 +183,11 @@ | |||
| 183 | 183 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 184 | 184 | <kbd>esc</kbd> close</button> | |
| 185 | 185 | </div> | |
| 186 | + | <div class="keys-pref"> | |
| 187 | + | <label> | |
| 188 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 189 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 190 | + | </div> | |
| 186 | 191 | <dl class="keys"> | |
| 187 | 192 | <div> | |
| 188 | 193 | <dt> | |
| @@ -336,72 +341,78 @@ | |||
| 336 | 341 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 337 | 342 | <div class="panel-head"> | |
| 338 | 343 | <label for="cmd-q" class="sr">go to</label> | |
| 339 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 344 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 340 | 345 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 341 | 346 | <kbd>esc</kbd> | |
| 342 | 347 | </button> | |
| 343 | 348 | </div> | |
| 344 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 345 | - | <li data-k="go home" data-cursor> | |
| 349 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 350 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 346 | 351 | <a href="/" tabindex="-1"> | |
| 347 | 352 | <span class="lbl">home</span> | |
| 348 | 353 | <span class="k">go</span> | |
| 349 | 354 | </a> | |
| 350 | 355 | </li> | |
| 351 | - | <li data-k="section summary"> | |
| 356 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 352 | 357 | <a href="/diffs" tabindex="-1"> | |
| 353 | 358 | <span class="lbl">summary</span> | |
| 354 | 359 | <span class="k">section</span> | |
| 355 | 360 | </a> | |
| 356 | 361 | </li> | |
| 357 | - | <li data-k="section files"> | |
| 362 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 358 | 363 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 359 | 364 | <span class="lbl">files</span> | |
| 360 | 365 | <span class="k">section</span> | |
| 361 | 366 | </a> | |
| 362 | 367 | </li> | |
| 363 | - | <li data-k="section commits"> | |
| 368 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 364 | 369 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 365 | 370 | <span class="lbl">commits</span> | |
| 366 | 371 | <span class="k">section</span> | |
| 367 | 372 | </a> | |
| 368 | 373 | </li> | |
| 369 | - | <li data-k="section branches & tags"> | |
| 374 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 370 | 375 | <a href="/diffs/refs" tabindex="-1"> | |
| 371 | 376 | <span class="lbl">branches & tags</span> | |
| 372 | 377 | <span class="k">section</span> | |
| 373 | 378 | </a> | |
| 374 | 379 | </li> | |
| 375 | - | <li data-k="section merge requests"> | |
| 380 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 376 | 381 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 377 | 382 | <span class="lbl">merge requests</span> | |
| 378 | 383 | <span class="k">section</span> | |
| 379 | 384 | </a> | |
| 380 | 385 | </li> | |
| 381 | - | <li data-k="section actions"> | |
| 386 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 382 | 387 | <a href="/diffs/actions" tabindex="-1"> | |
| 383 | 388 | <span class="lbl">actions</span> | |
| 384 | 389 | <span class="k">section</span> | |
| 385 | 390 | </a> | |
| 386 | 391 | </li> | |
| 387 | - | <li data-k="branch main"> | |
| 392 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 388 | 393 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 389 | 394 | <span class="lbl">main</span> | |
| 390 | 395 | <span class="k">branch</span> | |
| 391 | 396 | </a> | |
| 392 | 397 | </li> | |
| 393 | - | <li data-k="branch side"> | |
| 398 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 394 | 399 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 395 | 400 | <span class="lbl">side</span> | |
| 396 | 401 | <span class="k">branch</span> | |
| 397 | 402 | </a> | |
| 398 | 403 | </li> | |
| 399 | - | <li data-k="tag v1.0.0"> | |
| 404 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 400 | 405 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 401 | 406 | <span class="lbl">v1.0.0</span> | |
| 402 | 407 | <span class="k">tag</span> | |
| 403 | 408 | </a> | |
| 404 | 409 | </li> | |
| 410 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 411 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 412 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 413 | + | <span class="k">action</span> | |
| 414 | + | </a> | |
| 415 | + | </li> | |
| 405 | 416 | </ul> | |
| 406 | 417 | <p class="panel-foot"> | |
| 407 | 418 | <span> | |
| @@ -440,7 +451,8 @@ | |||
| 440 | 451 | k-permalink from:body -> ermite.permalink(); | |
| 441 | 452 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 442 | 453 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 443 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 454 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 455 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 444 | 456 | </div> | |
| 445 | 457 | </body> | |
| 446 | 458 | </html> | |
M internal/app/testdata/web/commit-merge.html +26 -14
| @@ -112,7 +112,7 @@ | |||
| 112 | 112 | <a href="/diffs/commit/SHA40">SHA7</a> + <a href="/diffs/commit/SHA40">SHA7</a> · diff against the first parent</dd> | |
| 113 | 113 | </dl> | |
| 114 | 114 | </header> | |
| 115 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 115 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 116 | 116 | <span class="dim">1 file · <span class="plus">+1</span> | |
| 117 | 117 | <span class="minus">-0</span> | |
| 118 | 118 | </span> | |
| @@ -225,6 +225,11 @@ | |||
| 225 | 225 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 226 | 226 | <kbd>esc</kbd> close</button> | |
| 227 | 227 | </div> | |
| 228 | + | <div class="keys-pref"> | |
| 229 | + | <label> | |
| 230 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 231 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 232 | + | </div> | |
| 228 | 233 | <dl class="keys"> | |
| 229 | 234 | <div> | |
| 230 | 235 | <dt> | |
| @@ -378,72 +383,78 @@ | |||
| 378 | 383 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 379 | 384 | <div class="panel-head"> | |
| 380 | 385 | <label for="cmd-q" class="sr">go to</label> | |
| 381 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 386 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 382 | 387 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 383 | 388 | <kbd>esc</kbd> | |
| 384 | 389 | </button> | |
| 385 | 390 | </div> | |
| 386 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 387 | - | <li data-k="go home" data-cursor> | |
| 391 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 392 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 388 | 393 | <a href="/" tabindex="-1"> | |
| 389 | 394 | <span class="lbl">home</span> | |
| 390 | 395 | <span class="k">go</span> | |
| 391 | 396 | </a> | |
| 392 | 397 | </li> | |
| 393 | - | <li data-k="section summary"> | |
| 398 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 394 | 399 | <a href="/diffs" tabindex="-1"> | |
| 395 | 400 | <span class="lbl">summary</span> | |
| 396 | 401 | <span class="k">section</span> | |
| 397 | 402 | </a> | |
| 398 | 403 | </li> | |
| 399 | - | <li data-k="section files"> | |
| 404 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 400 | 405 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 401 | 406 | <span class="lbl">files</span> | |
| 402 | 407 | <span class="k">section</span> | |
| 403 | 408 | </a> | |
| 404 | 409 | </li> | |
| 405 | - | <li data-k="section commits"> | |
| 410 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 406 | 411 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 407 | 412 | <span class="lbl">commits</span> | |
| 408 | 413 | <span class="k">section</span> | |
| 409 | 414 | </a> | |
| 410 | 415 | </li> | |
| 411 | - | <li data-k="section branches & tags"> | |
| 416 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 412 | 417 | <a href="/diffs/refs" tabindex="-1"> | |
| 413 | 418 | <span class="lbl">branches & tags</span> | |
| 414 | 419 | <span class="k">section</span> | |
| 415 | 420 | </a> | |
| 416 | 421 | </li> | |
| 417 | - | <li data-k="section merge requests"> | |
| 422 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 418 | 423 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 419 | 424 | <span class="lbl">merge requests</span> | |
| 420 | 425 | <span class="k">section</span> | |
| 421 | 426 | </a> | |
| 422 | 427 | </li> | |
| 423 | - | <li data-k="section actions"> | |
| 428 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 424 | 429 | <a href="/diffs/actions" tabindex="-1"> | |
| 425 | 430 | <span class="lbl">actions</span> | |
| 426 | 431 | <span class="k">section</span> | |
| 427 | 432 | </a> | |
| 428 | 433 | </li> | |
| 429 | - | <li data-k="branch main"> | |
| 434 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 430 | 435 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 431 | 436 | <span class="lbl">main</span> | |
| 432 | 437 | <span class="k">branch</span> | |
| 433 | 438 | </a> | |
| 434 | 439 | </li> | |
| 435 | - | <li data-k="branch side"> | |
| 440 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 436 | 441 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 437 | 442 | <span class="lbl">side</span> | |
| 438 | 443 | <span class="k">branch</span> | |
| 439 | 444 | </a> | |
| 440 | 445 | </li> | |
| 441 | - | <li data-k="tag v1.0.0"> | |
| 446 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 442 | 447 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 443 | 448 | <span class="lbl">v1.0.0</span> | |
| 444 | 449 | <span class="k">tag</span> | |
| 445 | 450 | </a> | |
| 446 | 451 | </li> | |
| 452 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 453 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 454 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 455 | + | <span class="k">action</span> | |
| 456 | + | </a> | |
| 457 | + | </li> | |
| 447 | 458 | </ul> | |
| 448 | 459 | <p class="panel-foot"> | |
| 449 | 460 | <span> | |
| @@ -482,7 +493,8 @@ | |||
| 482 | 493 | k-permalink from:body -> ermite.permalink(); | |
| 483 | 494 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 484 | 495 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 485 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 496 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 497 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 486 | 498 | </div> | |
| 487 | 499 | </body> | |
| 488 | 500 | </html> | |
M internal/app/testdata/web/commit-modify-split.html +26 -14
| @@ -119,7 +119,7 @@ And <b>html</b> & "quotes".</pre> | |||
| 119 | 119 | </dd> | |
| 120 | 120 | </dl> | |
| 121 | 121 | </header> | |
| 122 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 122 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 123 | 123 | <span class="dim">2 files · <span class="plus">+2</span> | |
| 124 | 124 | <span class="minus">-1</span> | |
| 125 | 125 | </span> | |
| @@ -348,6 +348,11 @@ And <b>html</b> & "quotes".</pre> | |||
| 348 | 348 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 349 | 349 | <kbd>esc</kbd> close</button> | |
| 350 | 350 | </div> | |
| 351 | + | <div class="keys-pref"> | |
| 352 | + | <label> | |
| 353 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 354 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 355 | + | </div> | |
| 351 | 356 | <dl class="keys"> | |
| 352 | 357 | <div> | |
| 353 | 358 | <dt> | |
| @@ -501,72 +506,78 @@ And <b>html</b> & "quotes".</pre> | |||
| 501 | 506 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 502 | 507 | <div class="panel-head"> | |
| 503 | 508 | <label for="cmd-q" class="sr">go to</label> | |
| 504 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 509 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 505 | 510 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 506 | 511 | <kbd>esc</kbd> | |
| 507 | 512 | </button> | |
| 508 | 513 | </div> | |
| 509 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 510 | - | <li data-k="go home" data-cursor> | |
| 514 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 515 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 511 | 516 | <a href="/" tabindex="-1"> | |
| 512 | 517 | <span class="lbl">home</span> | |
| 513 | 518 | <span class="k">go</span> | |
| 514 | 519 | </a> | |
| 515 | 520 | </li> | |
| 516 | - | <li data-k="section summary"> | |
| 521 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 517 | 522 | <a href="/diffs" tabindex="-1"> | |
| 518 | 523 | <span class="lbl">summary</span> | |
| 519 | 524 | <span class="k">section</span> | |
| 520 | 525 | </a> | |
| 521 | 526 | </li> | |
| 522 | - | <li data-k="section files"> | |
| 527 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 523 | 528 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 524 | 529 | <span class="lbl">files</span> | |
| 525 | 530 | <span class="k">section</span> | |
| 526 | 531 | </a> | |
| 527 | 532 | </li> | |
| 528 | - | <li data-k="section commits"> | |
| 533 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 529 | 534 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 530 | 535 | <span class="lbl">commits</span> | |
| 531 | 536 | <span class="k">section</span> | |
| 532 | 537 | </a> | |
| 533 | 538 | </li> | |
| 534 | - | <li data-k="section branches & tags"> | |
| 539 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 535 | 540 | <a href="/diffs/refs" tabindex="-1"> | |
| 536 | 541 | <span class="lbl">branches & tags</span> | |
| 537 | 542 | <span class="k">section</span> | |
| 538 | 543 | </a> | |
| 539 | 544 | </li> | |
| 540 | - | <li data-k="section merge requests"> | |
| 545 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 541 | 546 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 542 | 547 | <span class="lbl">merge requests</span> | |
| 543 | 548 | <span class="k">section</span> | |
| 544 | 549 | </a> | |
| 545 | 550 | </li> | |
| 546 | - | <li data-k="section actions"> | |
| 551 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 547 | 552 | <a href="/diffs/actions" tabindex="-1"> | |
| 548 | 553 | <span class="lbl">actions</span> | |
| 549 | 554 | <span class="k">section</span> | |
| 550 | 555 | </a> | |
| 551 | 556 | </li> | |
| 552 | - | <li data-k="branch main"> | |
| 557 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 553 | 558 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 554 | 559 | <span class="lbl">main</span> | |
| 555 | 560 | <span class="k">branch</span> | |
| 556 | 561 | </a> | |
| 557 | 562 | </li> | |
| 558 | - | <li data-k="branch side"> | |
| 563 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 559 | 564 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 560 | 565 | <span class="lbl">side</span> | |
| 561 | 566 | <span class="k">branch</span> | |
| 562 | 567 | </a> | |
| 563 | 568 | </li> | |
| 564 | - | <li data-k="tag v1.0.0"> | |
| 569 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 565 | 570 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 566 | 571 | <span class="lbl">v1.0.0</span> | |
| 567 | 572 | <span class="k">tag</span> | |
| 568 | 573 | </a> | |
| 569 | 574 | </li> | |
| 575 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 576 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 577 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 578 | + | <span class="k">action</span> | |
| 579 | + | </a> | |
| 580 | + | </li> | |
| 570 | 581 | </ul> | |
| 571 | 582 | <p class="panel-foot"> | |
| 572 | 583 | <span> | |
| @@ -605,7 +616,8 @@ And <b>html</b> & "quotes".</pre> | |||
| 605 | 616 | k-permalink from:body -> ermite.permalink(); | |
| 606 | 617 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 607 | 618 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 608 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 619 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 620 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 609 | 621 | </div> | |
| 610 | 622 | </body> | |
| 611 | 623 | </html> | |
M internal/app/testdata/web/commit-modify.html +26 -14
| @@ -119,7 +119,7 @@ And <b>html</b> & "quotes".</pre> | |||
| 119 | 119 | </dd> | |
| 120 | 120 | </dl> | |
| 121 | 121 | </header> | |
| 122 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 122 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 123 | 123 | <span class="dim">2 files · <span class="plus">+2</span> | |
| 124 | 124 | <span class="minus">-1</span> | |
| 125 | 125 | </span> | |
| @@ -348,6 +348,11 @@ And <b>html</b> & "quotes".</pre> | |||
| 348 | 348 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 349 | 349 | <kbd>esc</kbd> close</button> | |
| 350 | 350 | </div> | |
| 351 | + | <div class="keys-pref"> | |
| 352 | + | <label> | |
| 353 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 354 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 355 | + | </div> | |
| 351 | 356 | <dl class="keys"> | |
| 352 | 357 | <div> | |
| 353 | 358 | <dt> | |
| @@ -501,72 +506,78 @@ And <b>html</b> & "quotes".</pre> | |||
| 501 | 506 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 502 | 507 | <div class="panel-head"> | |
| 503 | 508 | <label for="cmd-q" class="sr">go to</label> | |
| 504 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 509 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 505 | 510 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 506 | 511 | <kbd>esc</kbd> | |
| 507 | 512 | </button> | |
| 508 | 513 | </div> | |
| 509 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 510 | - | <li data-k="go home" data-cursor> | |
| 514 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 515 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 511 | 516 | <a href="/" tabindex="-1"> | |
| 512 | 517 | <span class="lbl">home</span> | |
| 513 | 518 | <span class="k">go</span> | |
| 514 | 519 | </a> | |
| 515 | 520 | </li> | |
| 516 | - | <li data-k="section summary"> | |
| 521 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 517 | 522 | <a href="/diffs" tabindex="-1"> | |
| 518 | 523 | <span class="lbl">summary</span> | |
| 519 | 524 | <span class="k">section</span> | |
| 520 | 525 | </a> | |
| 521 | 526 | </li> | |
| 522 | - | <li data-k="section files"> | |
| 527 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 523 | 528 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 524 | 529 | <span class="lbl">files</span> | |
| 525 | 530 | <span class="k">section</span> | |
| 526 | 531 | </a> | |
| 527 | 532 | </li> | |
| 528 | - | <li data-k="section commits"> | |
| 533 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 529 | 534 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 530 | 535 | <span class="lbl">commits</span> | |
| 531 | 536 | <span class="k">section</span> | |
| 532 | 537 | </a> | |
| 533 | 538 | </li> | |
| 534 | - | <li data-k="section branches & tags"> | |
| 539 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 535 | 540 | <a href="/diffs/refs" tabindex="-1"> | |
| 536 | 541 | <span class="lbl">branches & tags</span> | |
| 537 | 542 | <span class="k">section</span> | |
| 538 | 543 | </a> | |
| 539 | 544 | </li> | |
| 540 | - | <li data-k="section merge requests"> | |
| 545 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 541 | 546 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 542 | 547 | <span class="lbl">merge requests</span> | |
| 543 | 548 | <span class="k">section</span> | |
| 544 | 549 | </a> | |
| 545 | 550 | </li> | |
| 546 | - | <li data-k="section actions"> | |
| 551 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 547 | 552 | <a href="/diffs/actions" tabindex="-1"> | |
| 548 | 553 | <span class="lbl">actions</span> | |
| 549 | 554 | <span class="k">section</span> | |
| 550 | 555 | </a> | |
| 551 | 556 | </li> | |
| 552 | - | <li data-k="branch main"> | |
| 557 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 553 | 558 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 554 | 559 | <span class="lbl">main</span> | |
| 555 | 560 | <span class="k">branch</span> | |
| 556 | 561 | </a> | |
| 557 | 562 | </li> | |
| 558 | - | <li data-k="branch side"> | |
| 563 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 559 | 564 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 560 | 565 | <span class="lbl">side</span> | |
| 561 | 566 | <span class="k">branch</span> | |
| 562 | 567 | </a> | |
| 563 | 568 | </li> | |
| 564 | - | <li data-k="tag v1.0.0"> | |
| 569 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 565 | 570 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 566 | 571 | <span class="lbl">v1.0.0</span> | |
| 567 | 572 | <span class="k">tag</span> | |
| 568 | 573 | </a> | |
| 569 | 574 | </li> | |
| 575 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 576 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 577 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 578 | + | <span class="k">action</span> | |
| 579 | + | </a> | |
| 580 | + | </li> | |
| 570 | 581 | </ul> | |
| 571 | 582 | <p class="panel-foot"> | |
| 572 | 583 | <span> | |
| @@ -605,7 +616,8 @@ And <b>html</b> & "quotes".</pre> | |||
| 605 | 616 | k-permalink from:body -> ermite.permalink(); | |
| 606 | 617 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 607 | 618 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 608 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 619 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 620 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 609 | 621 | </div> | |
| 610 | 622 | </body> | |
| 611 | 623 | </html> | |
M internal/app/testdata/web/commit-rename.html +26 -14
| @@ -113,7 +113,7 @@ | |||
| 113 | 113 | </dd> | |
| 114 | 114 | </dl> | |
| 115 | 115 | </header> | |
| 116 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 116 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 117 | 117 | <span class="dim">2 files · <span class="plus">+0</span> | |
| 118 | 118 | <span class="minus">-1</span> | |
| 119 | 119 | </span> | |
| @@ -252,6 +252,11 @@ | |||
| 252 | 252 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 253 | 253 | <kbd>esc</kbd> close</button> | |
| 254 | 254 | </div> | |
| 255 | + | <div class="keys-pref"> | |
| 256 | + | <label> | |
| 257 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 258 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 259 | + | </div> | |
| 255 | 260 | <dl class="keys"> | |
| 256 | 261 | <div> | |
| 257 | 262 | <dt> | |
| @@ -405,72 +410,78 @@ | |||
| 405 | 410 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 406 | 411 | <div class="panel-head"> | |
| 407 | 412 | <label for="cmd-q" class="sr">go to</label> | |
| 408 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 413 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 409 | 414 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 410 | 415 | <kbd>esc</kbd> | |
| 411 | 416 | </button> | |
| 412 | 417 | </div> | |
| 413 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 414 | - | <li data-k="go home" data-cursor> | |
| 418 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 419 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 415 | 420 | <a href="/" tabindex="-1"> | |
| 416 | 421 | <span class="lbl">home</span> | |
| 417 | 422 | <span class="k">go</span> | |
| 418 | 423 | </a> | |
| 419 | 424 | </li> | |
| 420 | - | <li data-k="section summary"> | |
| 425 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 421 | 426 | <a href="/diffs" tabindex="-1"> | |
| 422 | 427 | <span class="lbl">summary</span> | |
| 423 | 428 | <span class="k">section</span> | |
| 424 | 429 | </a> | |
| 425 | 430 | </li> | |
| 426 | - | <li data-k="section files"> | |
| 431 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 427 | 432 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 428 | 433 | <span class="lbl">files</span> | |
| 429 | 434 | <span class="k">section</span> | |
| 430 | 435 | </a> | |
| 431 | 436 | </li> | |
| 432 | - | <li data-k="section commits"> | |
| 437 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 433 | 438 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 434 | 439 | <span class="lbl">commits</span> | |
| 435 | 440 | <span class="k">section</span> | |
| 436 | 441 | </a> | |
| 437 | 442 | </li> | |
| 438 | - | <li data-k="section branches & tags"> | |
| 443 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 439 | 444 | <a href="/diffs/refs" tabindex="-1"> | |
| 440 | 445 | <span class="lbl">branches & tags</span> | |
| 441 | 446 | <span class="k">section</span> | |
| 442 | 447 | </a> | |
| 443 | 448 | </li> | |
| 444 | - | <li data-k="section merge requests"> | |
| 449 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 445 | 450 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 446 | 451 | <span class="lbl">merge requests</span> | |
| 447 | 452 | <span class="k">section</span> | |
| 448 | 453 | </a> | |
| 449 | 454 | </li> | |
| 450 | - | <li data-k="section actions"> | |
| 455 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 451 | 456 | <a href="/diffs/actions" tabindex="-1"> | |
| 452 | 457 | <span class="lbl">actions</span> | |
| 453 | 458 | <span class="k">section</span> | |
| 454 | 459 | </a> | |
| 455 | 460 | </li> | |
| 456 | - | <li data-k="branch main"> | |
| 461 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 457 | 462 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 458 | 463 | <span class="lbl">main</span> | |
| 459 | 464 | <span class="k">branch</span> | |
| 460 | 465 | </a> | |
| 461 | 466 | </li> | |
| 462 | - | <li data-k="branch side"> | |
| 467 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 463 | 468 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 464 | 469 | <span class="lbl">side</span> | |
| 465 | 470 | <span class="k">branch</span> | |
| 466 | 471 | </a> | |
| 467 | 472 | </li> | |
| 468 | - | <li data-k="tag v1.0.0"> | |
| 473 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 469 | 474 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 470 | 475 | <span class="lbl">v1.0.0</span> | |
| 471 | 476 | <span class="k">tag</span> | |
| 472 | 477 | </a> | |
| 473 | 478 | </li> | |
| 479 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 480 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 481 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 482 | + | <span class="k">action</span> | |
| 483 | + | </a> | |
| 484 | + | </li> | |
| 474 | 485 | </ul> | |
| 475 | 486 | <p class="panel-foot"> | |
| 476 | 487 | <span> | |
| @@ -509,7 +520,8 @@ | |||
| 509 | 520 | k-permalink from:body -> ermite.permalink(); | |
| 510 | 521 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 511 | 522 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 512 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 523 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 524 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 513 | 525 | </div> | |
| 514 | 526 | </body> | |
| 515 | 527 | </html> | |
M internal/app/testdata/web/commit-root.html +26 -14
| @@ -111,7 +111,7 @@ | |||
| 111 | 111 | <dd>none (root commit) </dd> | |
| 112 | 112 | </dl> | |
| 113 | 113 | </header> | |
| 114 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 114 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 115 | 115 | <span class="dim">5 files · <span class="plus">+11</span> | |
| 116 | 116 | <span class="minus">-0</span> | |
| 117 | 117 | </span> | |
| @@ -571,6 +571,11 @@ | |||
| 571 | 571 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 572 | 572 | <kbd>esc</kbd> close</button> | |
| 573 | 573 | </div> | |
| 574 | + | <div class="keys-pref"> | |
| 575 | + | <label> | |
| 576 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 577 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 578 | + | </div> | |
| 574 | 579 | <dl class="keys"> | |
| 575 | 580 | <div> | |
| 576 | 581 | <dt> | |
| @@ -724,72 +729,78 @@ | |||
| 724 | 729 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 725 | 730 | <div class="panel-head"> | |
| 726 | 731 | <label for="cmd-q" class="sr">go to</label> | |
| 727 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 732 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 728 | 733 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 729 | 734 | <kbd>esc</kbd> | |
| 730 | 735 | </button> | |
| 731 | 736 | </div> | |
| 732 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 733 | - | <li data-k="go home" data-cursor> | |
| 737 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 738 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 734 | 739 | <a href="/" tabindex="-1"> | |
| 735 | 740 | <span class="lbl">home</span> | |
| 736 | 741 | <span class="k">go</span> | |
| 737 | 742 | </a> | |
| 738 | 743 | </li> | |
| 739 | - | <li data-k="section summary"> | |
| 744 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 740 | 745 | <a href="/diffs" tabindex="-1"> | |
| 741 | 746 | <span class="lbl">summary</span> | |
| 742 | 747 | <span class="k">section</span> | |
| 743 | 748 | </a> | |
| 744 | 749 | </li> | |
| 745 | - | <li data-k="section files"> | |
| 750 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 746 | 751 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 747 | 752 | <span class="lbl">files</span> | |
| 748 | 753 | <span class="k">section</span> | |
| 749 | 754 | </a> | |
| 750 | 755 | </li> | |
| 751 | - | <li data-k="section commits"> | |
| 756 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 752 | 757 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 753 | 758 | <span class="lbl">commits</span> | |
| 754 | 759 | <span class="k">section</span> | |
| 755 | 760 | </a> | |
| 756 | 761 | </li> | |
| 757 | - | <li data-k="section branches & tags"> | |
| 762 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 758 | 763 | <a href="/diffs/refs" tabindex="-1"> | |
| 759 | 764 | <span class="lbl">branches & tags</span> | |
| 760 | 765 | <span class="k">section</span> | |
| 761 | 766 | </a> | |
| 762 | 767 | </li> | |
| 763 | - | <li data-k="section merge requests"> | |
| 768 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 764 | 769 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 765 | 770 | <span class="lbl">merge requests</span> | |
| 766 | 771 | <span class="k">section</span> | |
| 767 | 772 | </a> | |
| 768 | 773 | </li> | |
| 769 | - | <li data-k="section actions"> | |
| 774 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 770 | 775 | <a href="/diffs/actions" tabindex="-1"> | |
| 771 | 776 | <span class="lbl">actions</span> | |
| 772 | 777 | <span class="k">section</span> | |
| 773 | 778 | </a> | |
| 774 | 779 | </li> | |
| 775 | - | <li data-k="branch main"> | |
| 780 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 776 | 781 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 777 | 782 | <span class="lbl">main</span> | |
| 778 | 783 | <span class="k">branch</span> | |
| 779 | 784 | </a> | |
| 780 | 785 | </li> | |
| 781 | - | <li data-k="branch side"> | |
| 786 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 782 | 787 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 783 | 788 | <span class="lbl">side</span> | |
| 784 | 789 | <span class="k">branch</span> | |
| 785 | 790 | </a> | |
| 786 | 791 | </li> | |
| 787 | - | <li data-k="tag v1.0.0"> | |
| 792 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 788 | 793 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 789 | 794 | <span class="lbl">v1.0.0</span> | |
| 790 | 795 | <span class="k">tag</span> | |
| 791 | 796 | </a> | |
| 792 | 797 | </li> | |
| 798 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 799 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 800 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 801 | + | <span class="k">action</span> | |
| 802 | + | </a> | |
| 803 | + | </li> | |
| 793 | 804 | </ul> | |
| 794 | 805 | <p class="panel-foot"> | |
| 795 | 806 | <span> | |
| @@ -828,7 +839,8 @@ | |||
| 828 | 839 | k-permalink from:body -> ermite.permalink(); | |
| 829 | 840 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 830 | 841 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 831 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 842 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 843 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 832 | 844 | </div> | |
| 833 | 845 | </body> | |
| 834 | 846 | </html> | |
M internal/app/testdata/web/commit-trojan.html +26 -14
| @@ -114,7 +114,7 @@ | |||
| 114 | 114 | </dd> | |
| 115 | 115 | </dl> | |
| 116 | 116 | </header> | |
| 117 | - | <div class="diff-tools" role="toolbar" aria-label="Diff view"> | |
| 117 | + | <div class="diff-tools" role="group" aria-label="Diff view"> | |
| 118 | 118 | <span class="dim">1 file · <span class="plus">+8</span> | |
| 119 | 119 | <span class="minus">-0</span> | |
| 120 | 120 | </span> | |
| @@ -360,6 +360,11 @@ | |||
| 360 | 360 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 361 | 361 | <kbd>esc</kbd> close</button> | |
| 362 | 362 | </div> | |
| 363 | + | <div class="keys-pref"> | |
| 364 | + | <label> | |
| 365 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 366 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 367 | + | </div> | |
| 363 | 368 | <dl class="keys"> | |
| 364 | 369 | <div> | |
| 365 | 370 | <dt> | |
| @@ -513,72 +518,78 @@ | |||
| 513 | 518 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 514 | 519 | <div class="panel-head"> | |
| 515 | 520 | <label for="cmd-q" class="sr">go to</label> | |
| 516 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 521 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 517 | 522 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 518 | 523 | <kbd>esc</kbd> | |
| 519 | 524 | </button> | |
| 520 | 525 | </div> | |
| 521 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 522 | - | <li data-k="go home" data-cursor> | |
| 526 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 527 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 523 | 528 | <a href="/" tabindex="-1"> | |
| 524 | 529 | <span class="lbl">home</span> | |
| 525 | 530 | <span class="k">go</span> | |
| 526 | 531 | </a> | |
| 527 | 532 | </li> | |
| 528 | - | <li data-k="section summary"> | |
| 533 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 529 | 534 | <a href="/diffs" tabindex="-1"> | |
| 530 | 535 | <span class="lbl">summary</span> | |
| 531 | 536 | <span class="k">section</span> | |
| 532 | 537 | </a> | |
| 533 | 538 | </li> | |
| 534 | - | <li data-k="section files"> | |
| 539 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 535 | 540 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 536 | 541 | <span class="lbl">files</span> | |
| 537 | 542 | <span class="k">section</span> | |
| 538 | 543 | </a> | |
| 539 | 544 | </li> | |
| 540 | - | <li data-k="section commits"> | |
| 545 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 541 | 546 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 542 | 547 | <span class="lbl">commits</span> | |
| 543 | 548 | <span class="k">section</span> | |
| 544 | 549 | </a> | |
| 545 | 550 | </li> | |
| 546 | - | <li data-k="section branches & tags"> | |
| 551 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 547 | 552 | <a href="/diffs/refs" tabindex="-1"> | |
| 548 | 553 | <span class="lbl">branches & tags</span> | |
| 549 | 554 | <span class="k">section</span> | |
| 550 | 555 | </a> | |
| 551 | 556 | </li> | |
| 552 | - | <li data-k="section merge requests"> | |
| 557 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 553 | 558 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 554 | 559 | <span class="lbl">merge requests</span> | |
| 555 | 560 | <span class="k">section</span> | |
| 556 | 561 | </a> | |
| 557 | 562 | </li> | |
| 558 | - | <li data-k="section actions"> | |
| 563 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 559 | 564 | <a href="/diffs/actions" tabindex="-1"> | |
| 560 | 565 | <span class="lbl">actions</span> | |
| 561 | 566 | <span class="k">section</span> | |
| 562 | 567 | </a> | |
| 563 | 568 | </li> | |
| 564 | - | <li data-k="branch main"> | |
| 569 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 565 | 570 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 566 | 571 | <span class="lbl">main</span> | |
| 567 | 572 | <span class="k">branch</span> | |
| 568 | 573 | </a> | |
| 569 | 574 | </li> | |
| 570 | - | <li data-k="branch side"> | |
| 575 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 571 | 576 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 572 | 577 | <span class="lbl">side</span> | |
| 573 | 578 | <span class="k">branch</span> | |
| 574 | 579 | </a> | |
| 575 | 580 | </li> | |
| 576 | - | <li data-k="tag v1.0.0"> | |
| 581 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 577 | 582 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 578 | 583 | <span class="lbl">v1.0.0</span> | |
| 579 | 584 | <span class="k">tag</span> | |
| 580 | 585 | </a> | |
| 581 | 586 | </li> | |
| 587 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 588 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 589 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 590 | + | <span class="k">action</span> | |
| 591 | + | </a> | |
| 592 | + | </li> | |
| 582 | 593 | </ul> | |
| 583 | 594 | <p class="panel-foot"> | |
| 584 | 595 | <span> | |
| @@ -617,7 +628,8 @@ | |||
| 617 | 628 | k-permalink from:body -> ermite.permalink(); | |
| 618 | 629 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 619 | 630 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 620 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 631 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 632 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 621 | 633 | </div> | |
| 622 | 634 | </body> | |
| 623 | 635 | </html> | |
M internal/app/testdata/web/commits-page2.html +24 -12
| @@ -184,6 +184,11 @@ | |||
| 184 | 184 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 185 | 185 | <kbd>esc</kbd> close</button> | |
| 186 | 186 | </div> | |
| 187 | + | <div class="keys-pref"> | |
| 188 | + | <label> | |
| 189 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 190 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 191 | + | </div> | |
| 187 | 192 | <dl class="keys"> | |
| 188 | 193 | <div> | |
| 189 | 194 | <dt> | |
| @@ -329,66 +334,72 @@ | |||
| 329 | 334 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 330 | 335 | <div class="panel-head"> | |
| 331 | 336 | <label for="cmd-q" class="sr">go to</label> | |
| 332 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 337 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 333 | 338 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 334 | 339 | <kbd>esc</kbd> | |
| 335 | 340 | </button> | |
| 336 | 341 | </div> | |
| 337 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 338 | - | <li data-k="go home" data-cursor> | |
| 342 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 343 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 339 | 344 | <a href="/" tabindex="-1"> | |
| 340 | 345 | <span class="lbl">home</span> | |
| 341 | 346 | <span class="k">go</span> | |
| 342 | 347 | </a> | |
| 343 | 348 | </li> | |
| 344 | - | <li data-k="section summary"> | |
| 349 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 345 | 350 | <a href="/long" tabindex="-1"> | |
| 346 | 351 | <span class="lbl">summary</span> | |
| 347 | 352 | <span class="k">section</span> | |
| 348 | 353 | </a> | |
| 349 | 354 | </li> | |
| 350 | - | <li data-k="section files"> | |
| 355 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 351 | 356 | <a href="/long/tree/main" tabindex="-1"> | |
| 352 | 357 | <span class="lbl">files</span> | |
| 353 | 358 | <span class="k">section</span> | |
| 354 | 359 | </a> | |
| 355 | 360 | </li> | |
| 356 | - | <li data-k="section commits"> | |
| 361 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 357 | 362 | <a href="/long/commits/main" tabindex="-1"> | |
| 358 | 363 | <span class="lbl">commits</span> | |
| 359 | 364 | <span class="k">section</span> | |
| 360 | 365 | </a> | |
| 361 | 366 | </li> | |
| 362 | - | <li data-k="section branches & tags"> | |
| 367 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 363 | 368 | <a href="/long/refs" tabindex="-1"> | |
| 364 | 369 | <span class="lbl">branches & tags</span> | |
| 365 | 370 | <span class="k">section</span> | |
| 366 | 371 | </a> | |
| 367 | 372 | </li> | |
| 368 | - | <li data-k="section merge requests"> | |
| 373 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 369 | 374 | <a href="/long/merge-requests" tabindex="-1"> | |
| 370 | 375 | <span class="lbl">merge requests</span> | |
| 371 | 376 | <span class="k">section</span> | |
| 372 | 377 | </a> | |
| 373 | 378 | </li> | |
| 374 | - | <li data-k="section actions"> | |
| 379 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 375 | 380 | <a href="/long/actions" tabindex="-1"> | |
| 376 | 381 | <span class="lbl">actions</span> | |
| 377 | 382 | <span class="k">section</span> | |
| 378 | 383 | </a> | |
| 379 | 384 | </li> | |
| 380 | - | <li data-k="branch main"> | |
| 385 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 381 | 386 | <a href="/long/tree/main" tabindex="-1"> | |
| 382 | 387 | <span class="lbl">main</span> | |
| 383 | 388 | <span class="k">branch</span> | |
| 384 | 389 | </a> | |
| 385 | 390 | </li> | |
| 386 | - | <li data-k="repo diffs"> | |
| 391 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="repo diffs"> | |
| 387 | 392 | <a href="/diffs" tabindex="-1"> | |
| 388 | 393 | <span class="lbl">diffs</span> | |
| 389 | 394 | <span class="k">repo</span> | |
| 390 | 395 | </a> | |
| 391 | 396 | </li> | |
| 397 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 398 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 399 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 400 | + | <span class="k">action</span> | |
| 401 | + | </a> | |
| 402 | + | </li> | |
| 392 | 403 | </ul> | |
| 393 | 404 | <p class="panel-foot"> | |
| 394 | 405 | <span> | |
| @@ -427,7 +438,8 @@ | |||
| 427 | 438 | k-permalink from:body -> ermite.permalink(); | |
| 428 | 439 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 429 | 440 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 430 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 441 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 442 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 431 | 443 | </div> | |
| 432 | 444 | </body> | |
| 433 | 445 | </html> | |
M internal/app/testdata/web/commits-path.html +24 -12
| @@ -199,6 +199,11 @@ | |||
| 199 | 199 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 200 | 200 | <kbd>esc</kbd> close</button> | |
| 201 | 201 | </div> | |
| 202 | + | <div class="keys-pref"> | |
| 203 | + | <label> | |
| 204 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 205 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 206 | + | </div> | |
| 202 | 207 | <dl class="keys"> | |
| 203 | 208 | <div> | |
| 204 | 209 | <dt> | |
| @@ -344,66 +349,72 @@ | |||
| 344 | 349 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 345 | 350 | <div class="panel-head"> | |
| 346 | 351 | <label for="cmd-q" class="sr">go to</label> | |
| 347 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 352 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 348 | 353 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 349 | 354 | <kbd>esc</kbd> | |
| 350 | 355 | </button> | |
| 351 | 356 | </div> | |
| 352 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 353 | - | <li data-k="go home" data-cursor> | |
| 357 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 358 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 354 | 359 | <a href="/" tabindex="-1"> | |
| 355 | 360 | <span class="lbl">home</span> | |
| 356 | 361 | <span class="k">go</span> | |
| 357 | 362 | </a> | |
| 358 | 363 | </li> | |
| 359 | - | <li data-k="section summary"> | |
| 364 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 360 | 365 | <a href="/long" tabindex="-1"> | |
| 361 | 366 | <span class="lbl">summary</span> | |
| 362 | 367 | <span class="k">section</span> | |
| 363 | 368 | </a> | |
| 364 | 369 | </li> | |
| 365 | - | <li data-k="section files"> | |
| 370 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 366 | 371 | <a href="/long/tree/main" tabindex="-1"> | |
| 367 | 372 | <span class="lbl">files</span> | |
| 368 | 373 | <span class="k">section</span> | |
| 369 | 374 | </a> | |
| 370 | 375 | </li> | |
| 371 | - | <li data-k="section commits"> | |
| 376 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 372 | 377 | <a href="/long/commits/main" tabindex="-1"> | |
| 373 | 378 | <span class="lbl">commits</span> | |
| 374 | 379 | <span class="k">section</span> | |
| 375 | 380 | </a> | |
| 376 | 381 | </li> | |
| 377 | - | <li data-k="section branches & tags"> | |
| 382 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 378 | 383 | <a href="/long/refs" tabindex="-1"> | |
| 379 | 384 | <span class="lbl">branches & tags</span> | |
| 380 | 385 | <span class="k">section</span> | |
| 381 | 386 | </a> | |
| 382 | 387 | </li> | |
| 383 | - | <li data-k="section merge requests"> | |
| 388 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 384 | 389 | <a href="/long/merge-requests" tabindex="-1"> | |
| 385 | 390 | <span class="lbl">merge requests</span> | |
| 386 | 391 | <span class="k">section</span> | |
| 387 | 392 | </a> | |
| 388 | 393 | </li> | |
| 389 | - | <li data-k="section actions"> | |
| 394 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 390 | 395 | <a href="/long/actions" tabindex="-1"> | |
| 391 | 396 | <span class="lbl">actions</span> | |
| 392 | 397 | <span class="k">section</span> | |
| 393 | 398 | </a> | |
| 394 | 399 | </li> | |
| 395 | - | <li data-k="branch main"> | |
| 400 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 396 | 401 | <a href="/long/tree/main" tabindex="-1"> | |
| 397 | 402 | <span class="lbl">main</span> | |
| 398 | 403 | <span class="k">branch</span> | |
| 399 | 404 | </a> | |
| 400 | 405 | </li> | |
| 401 | - | <li data-k="repo diffs"> | |
| 406 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="repo diffs"> | |
| 402 | 407 | <a href="/diffs" tabindex="-1"> | |
| 403 | 408 | <span class="lbl">diffs</span> | |
| 404 | 409 | <span class="k">repo</span> | |
| 405 | 410 | </a> | |
| 406 | 411 | </li> | |
| 412 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 413 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 414 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 415 | + | <span class="k">action</span> | |
| 416 | + | </a> | |
| 417 | + | </li> | |
| 407 | 418 | </ul> | |
| 408 | 419 | <p class="panel-foot"> | |
| 409 | 420 | <span> | |
| @@ -442,7 +453,8 @@ | |||
| 442 | 453 | k-permalink from:body -> ermite.permalink(); | |
| 443 | 454 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 444 | 455 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 445 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 456 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 457 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 446 | 458 | </div> | |
| 447 | 459 | </body> | |
| 448 | 460 | </html> | |
M internal/app/testdata/web/commits-root.html +24 -12
| @@ -1084,6 +1084,11 @@ | |||
| 1084 | 1084 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 1085 | 1085 | <kbd>esc</kbd> close</button> | |
| 1086 | 1086 | </div> | |
| 1087 | + | <div class="keys-pref"> | |
| 1088 | + | <label> | |
| 1089 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 1090 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 1091 | + | </div> | |
| 1087 | 1092 | <dl class="keys"> | |
| 1088 | 1093 | <div> | |
| 1089 | 1094 | <dt> | |
| @@ -1229,66 +1234,72 @@ | |||
| 1229 | 1234 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 1230 | 1235 | <div class="panel-head"> | |
| 1231 | 1236 | <label for="cmd-q" class="sr">go to</label> | |
| 1232 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 1237 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 1233 | 1238 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 1234 | 1239 | <kbd>esc</kbd> | |
| 1235 | 1240 | </button> | |
| 1236 | 1241 | </div> | |
| 1237 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 1238 | - | <li data-k="go home" data-cursor> | |
| 1242 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 1243 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 1239 | 1244 | <a href="/" tabindex="-1"> | |
| 1240 | 1245 | <span class="lbl">home</span> | |
| 1241 | 1246 | <span class="k">go</span> | |
| 1242 | 1247 | </a> | |
| 1243 | 1248 | </li> | |
| 1244 | - | <li data-k="section summary"> | |
| 1249 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 1245 | 1250 | <a href="/long" tabindex="-1"> | |
| 1246 | 1251 | <span class="lbl">summary</span> | |
| 1247 | 1252 | <span class="k">section</span> | |
| 1248 | 1253 | </a> | |
| 1249 | 1254 | </li> | |
| 1250 | - | <li data-k="section files"> | |
| 1255 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 1251 | 1256 | <a href="/long/tree/main" tabindex="-1"> | |
| 1252 | 1257 | <span class="lbl">files</span> | |
| 1253 | 1258 | <span class="k">section</span> | |
| 1254 | 1259 | </a> | |
| 1255 | 1260 | </li> | |
| 1256 | - | <li data-k="section commits"> | |
| 1261 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 1257 | 1262 | <a href="/long/commits/main" tabindex="-1"> | |
| 1258 | 1263 | <span class="lbl">commits</span> | |
| 1259 | 1264 | <span class="k">section</span> | |
| 1260 | 1265 | </a> | |
| 1261 | 1266 | </li> | |
| 1262 | - | <li data-k="section branches & tags"> | |
| 1267 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 1263 | 1268 | <a href="/long/refs" tabindex="-1"> | |
| 1264 | 1269 | <span class="lbl">branches & tags</span> | |
| 1265 | 1270 | <span class="k">section</span> | |
| 1266 | 1271 | </a> | |
| 1267 | 1272 | </li> | |
| 1268 | - | <li data-k="section merge requests"> | |
| 1273 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 1269 | 1274 | <a href="/long/merge-requests" tabindex="-1"> | |
| 1270 | 1275 | <span class="lbl">merge requests</span> | |
| 1271 | 1276 | <span class="k">section</span> | |
| 1272 | 1277 | </a> | |
| 1273 | 1278 | </li> | |
| 1274 | - | <li data-k="section actions"> | |
| 1279 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 1275 | 1280 | <a href="/long/actions" tabindex="-1"> | |
| 1276 | 1281 | <span class="lbl">actions</span> | |
| 1277 | 1282 | <span class="k">section</span> | |
| 1278 | 1283 | </a> | |
| 1279 | 1284 | </li> | |
| 1280 | - | <li data-k="branch main"> | |
| 1285 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 1281 | 1286 | <a href="/long/tree/main" tabindex="-1"> | |
| 1282 | 1287 | <span class="lbl">main</span> | |
| 1283 | 1288 | <span class="k">branch</span> | |
| 1284 | 1289 | </a> | |
| 1285 | 1290 | </li> | |
| 1286 | - | <li data-k="repo diffs"> | |
| 1291 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="repo diffs"> | |
| 1287 | 1292 | <a href="/diffs" tabindex="-1"> | |
| 1288 | 1293 | <span class="lbl">diffs</span> | |
| 1289 | 1294 | <span class="k">repo</span> | |
| 1290 | 1295 | </a> | |
| 1291 | 1296 | </li> | |
| 1297 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 1298 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 1299 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 1300 | + | <span class="k">action</span> | |
| 1301 | + | </a> | |
| 1302 | + | </li> | |
| 1292 | 1303 | </ul> | |
| 1293 | 1304 | <p class="panel-foot"> | |
| 1294 | 1305 | <span> | |
| @@ -1327,7 +1338,8 @@ | |||
| 1327 | 1338 | k-permalink from:body -> ermite.permalink(); | |
| 1328 | 1339 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 1329 | 1340 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 1330 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 1341 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 1342 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 1331 | 1343 | </div> | |
| 1332 | 1344 | </body> | |
| 1333 | 1345 | </html> | |
M internal/app/testdata/web/commits-small.html +26 -14
| @@ -345,6 +345,11 @@ | |||
| 345 | 345 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 346 | 346 | <kbd>esc</kbd> close</button> | |
| 347 | 347 | </div> | |
| 348 | + | <div class="keys-pref"> | |
| 349 | + | <label> | |
| 350 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 351 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 352 | + | </div> | |
| 348 | 353 | <dl class="keys"> | |
| 349 | 354 | <div> | |
| 350 | 355 | <dt> | |
| @@ -498,78 +503,84 @@ | |||
| 498 | 503 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 499 | 504 | <div class="panel-head"> | |
| 500 | 505 | <label for="cmd-q" class="sr">go to</label> | |
| 501 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 506 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 502 | 507 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 503 | 508 | <kbd>esc</kbd> | |
| 504 | 509 | </button> | |
| 505 | 510 | </div> | |
| 506 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 507 | - | <li data-k="go home" data-cursor> | |
| 511 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 512 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 508 | 513 | <a href="/" tabindex="-1"> | |
| 509 | 514 | <span class="lbl">home</span> | |
| 510 | 515 | <span class="k">go</span> | |
| 511 | 516 | </a> | |
| 512 | 517 | </li> | |
| 513 | - | <li data-k="section summary"> | |
| 518 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 514 | 519 | <a href="/diffs" tabindex="-1"> | |
| 515 | 520 | <span class="lbl">summary</span> | |
| 516 | 521 | <span class="k">section</span> | |
| 517 | 522 | </a> | |
| 518 | 523 | </li> | |
| 519 | - | <li data-k="section files"> | |
| 524 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 520 | 525 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 521 | 526 | <span class="lbl">files</span> | |
| 522 | 527 | <span class="k">section</span> | |
| 523 | 528 | </a> | |
| 524 | 529 | </li> | |
| 525 | - | <li data-k="section commits"> | |
| 530 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 526 | 531 | <a href="/diffs/commits/main" tabindex="-1"> | |
| 527 | 532 | <span class="lbl">commits</span> | |
| 528 | 533 | <span class="k">section</span> | |
| 529 | 534 | </a> | |
| 530 | 535 | </li> | |
| 531 | - | <li data-k="section branches & tags"> | |
| 536 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 532 | 537 | <a href="/diffs/refs" tabindex="-1"> | |
| 533 | 538 | <span class="lbl">branches & tags</span> | |
| 534 | 539 | <span class="k">section</span> | |
| 535 | 540 | </a> | |
| 536 | 541 | </li> | |
| 537 | - | <li data-k="section merge requests"> | |
| 542 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 538 | 543 | <a href="/diffs/merge-requests" tabindex="-1"> | |
| 539 | 544 | <span class="lbl">merge requests</span> | |
| 540 | 545 | <span class="k">section</span> | |
| 541 | 546 | </a> | |
| 542 | 547 | </li> | |
| 543 | - | <li data-k="section actions"> | |
| 548 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 544 | 549 | <a href="/diffs/actions" tabindex="-1"> | |
| 545 | 550 | <span class="lbl">actions</span> | |
| 546 | 551 | <span class="k">section</span> | |
| 547 | 552 | </a> | |
| 548 | 553 | </li> | |
| 549 | - | <li data-k="branch main"> | |
| 554 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> | |
| 550 | 555 | <a href="/diffs/tree/main" tabindex="-1"> | |
| 551 | 556 | <span class="lbl">main</span> | |
| 552 | 557 | <span class="k">branch</span> | |
| 553 | 558 | </a> | |
| 554 | 559 | </li> | |
| 555 | - | <li data-k="branch side"> | |
| 560 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> | |
| 556 | 561 | <a href="/diffs/tree/side" tabindex="-1"> | |
| 557 | 562 | <span class="lbl">side</span> | |
| 558 | 563 | <span class="k">branch</span> | |
| 559 | 564 | </a> | |
| 560 | 565 | </li> | |
| 561 | - | <li data-k="tag v1.0.0"> | |
| 566 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0.0"> | |
| 562 | 567 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | |
| 563 | 568 | <span class="lbl">v1.0.0</span> | |
| 564 | 569 | <span class="k">tag</span> | |
| 565 | 570 | </a> | |
| 566 | 571 | </li> | |
| 567 | - | <li data-k="repo long"> | |
| 572 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="repo long"> | |
| 568 | 573 | <a href="/long" tabindex="-1"> | |
| 569 | 574 | <span class="lbl">long</span> | |
| 570 | 575 | <span class="k">repo</span> | |
| 571 | 576 | </a> | |
| 572 | 577 | </li> | |
| 578 | + | <li id="cmd-o11" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 579 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 580 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 581 | + | <span class="k">action</span> | |
| 582 | + | </a> | |
| 583 | + | </li> | |
| 573 | 584 | </ul> | |
| 574 | 585 | <p class="panel-foot"> | |
| 575 | 586 | <span> | |
| @@ -608,7 +619,8 @@ | |||
| 608 | 619 | k-permalink from:body -> ermite.permalink(); | |
| 609 | 620 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 610 | 621 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 611 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 622 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 623 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 612 | 624 | </div> | |
| 613 | 625 | </body> | |
| 614 | 626 | </html> | |
M internal/app/testdata/web/empty.html +17 -5
| @@ -102,6 +102,11 @@ | |||
| 102 | 102 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 103 | 103 | <kbd>esc</kbd> close</button> | |
| 104 | 104 | </div> | |
| 105 | + | <div class="keys-pref"> | |
| 106 | + | <label> | |
| 107 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 108 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 109 | + | </div> | |
| 105 | 110 | <dl class="keys"> | |
| 106 | 111 | <div> | |
| 107 | 112 | <dt> | |
| @@ -220,24 +225,30 @@ | |||
| 220 | 225 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 221 | 226 | <div class="panel-head"> | |
| 222 | 227 | <label for="cmd-q" class="sr">go to</label> | |
| 223 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 228 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 224 | 229 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 225 | 230 | <kbd>esc</kbd> | |
| 226 | 231 | </button> | |
| 227 | 232 | </div> | |
| 228 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 229 | - | <li data-k="go home" data-cursor> | |
| 233 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 234 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 230 | 235 | <a href="/" tabindex="-1"> | |
| 231 | 236 | <span class="lbl">home</span> | |
| 232 | 237 | <span class="k">go</span> | |
| 233 | 238 | </a> | |
| 234 | 239 | </li> | |
| 235 | - | <li data-k="repo demo"> | |
| 240 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo"> | |
| 236 | 241 | <a href="/demo" tabindex="-1"> | |
| 237 | 242 | <span class="lbl">demo</span> | |
| 238 | 243 | <span class="k">repo</span> | |
| 239 | 244 | </a> | |
| 240 | 245 | </li> | |
| 246 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 247 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 248 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 249 | + | <span class="k">action</span> | |
| 250 | + | </a> | |
| 251 | + | </li> | |
| 241 | 252 | </ul> | |
| 242 | 253 | <p class="panel-foot"> | |
| 243 | 254 | <span> | |
| @@ -276,7 +287,8 @@ | |||
| 276 | 287 | k-permalink from:body -> ermite.permalink(); | |
| 277 | 288 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 278 | 289 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 279 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 290 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 291 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 280 | 292 | </div> | |
| 281 | 293 | </body> | |
| 282 | 294 | </html> | |
M internal/app/testdata/web/home.html +18 -6
| @@ -127,6 +127,11 @@ | |||
| 127 | 127 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 128 | 128 | <kbd>esc</kbd> close</button> | |
| 129 | 129 | </div> | |
| 130 | + | <div class="keys-pref"> | |
| 131 | + | <label> | |
| 132 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 133 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 134 | + | </div> | |
| 130 | 135 | <dl class="keys"> | |
| 131 | 136 | <div> | |
| 132 | 137 | <dt> | |
| @@ -245,30 +250,36 @@ | |||
| 245 | 250 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 246 | 251 | <div class="panel-head"> | |
| 247 | 252 | <label for="cmd-q" class="sr">go to</label> | |
| 248 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 253 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 249 | 254 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 250 | 255 | <kbd>esc</kbd> | |
| 251 | 256 | </button> | |
| 252 | 257 | </div> | |
| 253 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 254 | - | <li data-k="go home" data-cursor> | |
| 258 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 259 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 255 | 260 | <a href="/" tabindex="-1"> | |
| 256 | 261 | <span class="lbl">home</span> | |
| 257 | 262 | <span class="k">go</span> | |
| 258 | 263 | </a> | |
| 259 | 264 | </li> | |
| 260 | - | <li data-k="repo demo"> | |
| 265 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo"> | |
| 261 | 266 | <a href="/demo" tabindex="-1"> | |
| 262 | 267 | <span class="lbl">demo</span> | |
| 263 | 268 | <span class="k">repo</span> | |
| 264 | 269 | </a> | |
| 265 | 270 | </li> | |
| 266 | - | <li data-k="repo quiet"> | |
| 271 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="repo quiet"> | |
| 267 | 272 | <a href="/quiet" tabindex="-1"> | |
| 268 | 273 | <span class="lbl">quiet</span> | |
| 269 | 274 | <span class="k">repo</span> | |
| 270 | 275 | </a> | |
| 271 | 276 | </li> | |
| 277 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 278 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 279 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 280 | + | <span class="k">action</span> | |
| 281 | + | </a> | |
| 282 | + | </li> | |
| 272 | 283 | </ul> | |
| 273 | 284 | <p class="panel-foot"> | |
| 274 | 285 | <span> | |
| @@ -307,7 +318,8 @@ | |||
| 307 | 318 | k-permalink from:body -> ermite.permalink(); | |
| 308 | 319 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 309 | 320 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 310 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 321 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 322 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 311 | 323 | </div> | |
| 312 | 324 | </body> | |
| 313 | 325 | </html> | |
M internal/app/testdata/web/not-found-page.html +16 -4
| @@ -91,6 +91,11 @@ | |||
| 91 | 91 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 92 | 92 | <kbd>esc</kbd> close</button> | |
| 93 | 93 | </div> | |
| 94 | + | <div class="keys-pref"> | |
| 95 | + | <label> | |
| 96 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 97 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 98 | + | </div> | |
| 94 | 99 | <dl class="keys"> | |
| 95 | 100 | <div> | |
| 96 | 101 | <dt> | |
| @@ -209,18 +214,24 @@ | |||
| 209 | 214 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 210 | 215 | <div class="panel-head"> | |
| 211 | 216 | <label for="cmd-q" class="sr">go to</label> | |
| 212 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 217 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 213 | 218 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 214 | 219 | <kbd>esc</kbd> | |
| 215 | 220 | </button> | |
| 216 | 221 | </div> | |
| 217 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 218 | - | <li data-k="go home" data-cursor> | |
| 222 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 223 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 219 | 224 | <a href="/" tabindex="-1"> | |
| 220 | 225 | <span class="lbl">home</span> | |
| 221 | 226 | <span class="k">go</span> | |
| 222 | 227 | </a> | |
| 223 | 228 | </li> | |
| 229 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 230 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 231 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 232 | + | <span class="k">action</span> | |
| 233 | + | </a> | |
| 234 | + | </li> | |
| 224 | 235 | </ul> | |
| 225 | 236 | <p class="panel-foot"> | |
| 226 | 237 | <span> | |
| @@ -259,7 +270,8 @@ | |||
| 259 | 270 | k-permalink from:body -> ermite.permalink(); | |
| 260 | 271 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 261 | 272 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 262 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 273 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 274 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 263 | 275 | </div> | |
| 264 | 276 | </body> | |
| 265 | 277 | </html> | |
M internal/app/testdata/web/not-found.html +16 -4
| @@ -91,6 +91,11 @@ | |||
| 91 | 91 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 92 | 92 | <kbd>esc</kbd> close</button> | |
| 93 | 93 | </div> | |
| 94 | + | <div class="keys-pref"> | |
| 95 | + | <label> | |
| 96 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 97 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 98 | + | </div> | |
| 94 | 99 | <dl class="keys"> | |
| 95 | 100 | <div> | |
| 96 | 101 | <dt> | |
| @@ -209,18 +214,24 @@ | |||
| 209 | 214 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 210 | 215 | <div class="panel-head"> | |
| 211 | 216 | <label for="cmd-q" class="sr">go to</label> | |
| 212 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 217 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 213 | 218 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 214 | 219 | <kbd>esc</kbd> | |
| 215 | 220 | </button> | |
| 216 | 221 | </div> | |
| 217 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 218 | - | <li data-k="go home" data-cursor> | |
| 222 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 223 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 219 | 224 | <a href="/" tabindex="-1"> | |
| 220 | 225 | <span class="lbl">home</span> | |
| 221 | 226 | <span class="k">go</span> | |
| 222 | 227 | </a> | |
| 223 | 228 | </li> | |
| 229 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 230 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 231 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 232 | + | <span class="k">action</span> | |
| 233 | + | </a> | |
| 234 | + | </li> | |
| 224 | 235 | </ul> | |
| 225 | 236 | <p class="panel-foot"> | |
| 226 | 237 | <span> | |
| @@ -259,7 +270,8 @@ | |||
| 259 | 270 | k-permalink from:body -> ermite.permalink(); | |
| 260 | 271 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 261 | 272 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 262 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 273 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 274 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 263 | 275 | </div> | |
| 264 | 276 | </body> | |
| 265 | 277 | </html> | |
M internal/app/testdata/web/refs.html +27 -15
| @@ -221,6 +221,11 @@ | |||
| 221 | 221 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 222 | 222 | <kbd>esc</kbd> close</button> | |
| 223 | 223 | </div> | |
| 224 | + | <div class="keys-pref"> | |
| 225 | + | <label> | |
| 226 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 227 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 228 | + | </div> | |
| 224 | 229 | <dl class="keys"> | |
| 225 | 230 | <div> | |
| 226 | 231 | <dt> | |
| @@ -378,84 +383,90 @@ | |||
| 378 | 383 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 379 | 384 | <div class="panel-head"> | |
| 380 | 385 | <label for="cmd-q" class="sr">go to</label> | |
| 381 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 386 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 382 | 387 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 383 | 388 | <kbd>esc</kbd> | |
| 384 | 389 | </button> | |
| 385 | 390 | </div> | |
| 386 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 387 | - | <li data-k="go home" data-cursor> | |
| 391 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 392 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 388 | 393 | <a href="/" tabindex="-1"> | |
| 389 | 394 | <span class="lbl">home</span> | |
| 390 | 395 | <span class="k">go</span> | |
| 391 | 396 | </a> | |
| 392 | 397 | </li> | |
| 393 | - | <li data-k="section summary"> | |
| 398 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 394 | 399 | <a href="/demo" tabindex="-1"> | |
| 395 | 400 | <span class="lbl">summary</span> | |
| 396 | 401 | <span class="k">section</span> | |
| 397 | 402 | </a> | |
| 398 | 403 | </li> | |
| 399 | - | <li data-k="section files"> | |
| 404 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 400 | 405 | <a href="/demo/tree/main" tabindex="-1"> | |
| 401 | 406 | <span class="lbl">files</span> | |
| 402 | 407 | <span class="k">section</span> | |
| 403 | 408 | </a> | |
| 404 | 409 | </li> | |
| 405 | - | <li data-k="section commits"> | |
| 410 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 406 | 411 | <a href="/demo/commits/main" tabindex="-1"> | |
| 407 | 412 | <span class="lbl">commits</span> | |
| 408 | 413 | <span class="k">section</span> | |
| 409 | 414 | </a> | |
| 410 | 415 | </li> | |
| 411 | - | <li data-k="section branches & tags"> | |
| 416 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 412 | 417 | <a href="/demo/refs" tabindex="-1"> | |
| 413 | 418 | <span class="lbl">branches & tags</span> | |
| 414 | 419 | <span class="k">section</span> | |
| 415 | 420 | </a> | |
| 416 | 421 | </li> | |
| 417 | - | <li data-k="section merge requests"> | |
| 422 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 418 | 423 | <a href="/demo/merge-requests" tabindex="-1"> | |
| 419 | 424 | <span class="lbl">merge requests</span> | |
| 420 | 425 | <span class="k">section</span> | |
| 421 | 426 | </a> | |
| 422 | 427 | </li> | |
| 423 | - | <li data-k="section actions"> | |
| 428 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 424 | 429 | <a href="/demo/actions" tabindex="-1"> | |
| 425 | 430 | <span class="lbl">actions</span> | |
| 426 | 431 | <span class="k">section</span> | |
| 427 | 432 | </a> | |
| 428 | 433 | </li> | |
| 429 | - | <li data-k="branch feature/x"> | |
| 434 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 430 | 435 | <a href="/demo/tree/feature/x" tabindex="-1"> | |
| 431 | 436 | <span class="lbl">feature/x</span> | |
| 432 | 437 | <span class="k">branch</span> | |
| 433 | 438 | </a> | |
| 434 | 439 | </li> | |
| 435 | - | <li data-k="branch main"> | |
| 440 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 436 | 441 | <a href="/demo/tree/main" tabindex="-1"> | |
| 437 | 442 | <span class="lbl">main</span> | |
| 438 | 443 | <span class="k">branch</span> | |
| 439 | 444 | </a> | |
| 440 | 445 | </li> | |
| 441 | - | <li data-k="tag v0.1.0"> | |
| 446 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v0.1.0"> | |
| 442 | 447 | <a href="/demo/tree/v0.1.0" tabindex="-1"> | |
| 443 | 448 | <span class="lbl">v0.1.0</span> | |
| 444 | 449 | <span class="k">tag</span> | |
| 445 | 450 | </a> | |
| 446 | 451 | </li> | |
| 447 | - | <li data-k="tag v0.2.0"> | |
| 452 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v0.2.0"> | |
| 448 | 453 | <a href="/demo/tree/v0.2.0" tabindex="-1"> | |
| 449 | 454 | <span class="lbl">v0.2.0</span> | |
| 450 | 455 | <span class="k">tag</span> | |
| 451 | 456 | </a> | |
| 452 | 457 | </li> | |
| 453 | - | <li data-k="repo quiet"> | |
| 458 | + | <li id="cmd-o11" role="option" aria-selected="false" data-k="repo quiet"> | |
| 454 | 459 | <a href="/quiet" tabindex="-1"> | |
| 455 | 460 | <span class="lbl">quiet</span> | |
| 456 | 461 | <span class="k">repo</span> | |
| 457 | 462 | </a> | |
| 458 | 463 | </li> | |
| 464 | + | <li id="cmd-o12" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 465 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 466 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 467 | + | <span class="k">action</span> | |
| 468 | + | </a> | |
| 469 | + | </li> | |
| 459 | 470 | </ul> | |
| 460 | 471 | <p class="panel-foot"> | |
| 461 | 472 | <span> | |
| @@ -494,7 +505,8 @@ | |||
| 494 | 505 | k-permalink from:body -> ermite.permalink(); | |
| 495 | 506 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 496 | 507 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 497 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 508 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 509 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 498 | 510 | </div> | |
| 499 | 511 | </body> | |
| 500 | 512 | </html> | |
M internal/app/testdata/web/summary.html +27 -15
| @@ -245,6 +245,11 @@ | |||
| 245 | 245 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 246 | 246 | <kbd>esc</kbd> close</button> | |
| 247 | 247 | </div> | |
| 248 | + | <div class="keys-pref"> | |
| 249 | + | <label> | |
| 250 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 251 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 252 | + | </div> | |
| 248 | 253 | <dl class="keys"> | |
| 249 | 254 | <div> | |
| 250 | 255 | <dt> | |
| @@ -402,84 +407,90 @@ | |||
| 402 | 407 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 403 | 408 | <div class="panel-head"> | |
| 404 | 409 | <label for="cmd-q" class="sr">go to</label> | |
| 405 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 410 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 406 | 411 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 407 | 412 | <kbd>esc</kbd> | |
| 408 | 413 | </button> | |
| 409 | 414 | </div> | |
| 410 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 411 | - | <li data-k="go home" data-cursor> | |
| 415 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 416 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 412 | 417 | <a href="/" tabindex="-1"> | |
| 413 | 418 | <span class="lbl">home</span> | |
| 414 | 419 | <span class="k">go</span> | |
| 415 | 420 | </a> | |
| 416 | 421 | </li> | |
| 417 | - | <li data-k="section summary"> | |
| 422 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 418 | 423 | <a href="/demo" tabindex="-1"> | |
| 419 | 424 | <span class="lbl">summary</span> | |
| 420 | 425 | <span class="k">section</span> | |
| 421 | 426 | </a> | |
| 422 | 427 | </li> | |
| 423 | - | <li data-k="section files"> | |
| 428 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 424 | 429 | <a href="/demo/tree/main" tabindex="-1"> | |
| 425 | 430 | <span class="lbl">files</span> | |
| 426 | 431 | <span class="k">section</span> | |
| 427 | 432 | </a> | |
| 428 | 433 | </li> | |
| 429 | - | <li data-k="section commits"> | |
| 434 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 430 | 435 | <a href="/demo/commits/main" tabindex="-1"> | |
| 431 | 436 | <span class="lbl">commits</span> | |
| 432 | 437 | <span class="k">section</span> | |
| 433 | 438 | </a> | |
| 434 | 439 | </li> | |
| 435 | - | <li data-k="section branches & tags"> | |
| 440 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 436 | 441 | <a href="/demo/refs" tabindex="-1"> | |
| 437 | 442 | <span class="lbl">branches & tags</span> | |
| 438 | 443 | <span class="k">section</span> | |
| 439 | 444 | </a> | |
| 440 | 445 | </li> | |
| 441 | - | <li data-k="section merge requests"> | |
| 446 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 442 | 447 | <a href="/demo/merge-requests" tabindex="-1"> | |
| 443 | 448 | <span class="lbl">merge requests</span> | |
| 444 | 449 | <span class="k">section</span> | |
| 445 | 450 | </a> | |
| 446 | 451 | </li> | |
| 447 | - | <li data-k="section actions"> | |
| 452 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 448 | 453 | <a href="/demo/actions" tabindex="-1"> | |
| 449 | 454 | <span class="lbl">actions</span> | |
| 450 | 455 | <span class="k">section</span> | |
| 451 | 456 | </a> | |
| 452 | 457 | </li> | |
| 453 | - | <li data-k="branch feature/x"> | |
| 458 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 454 | 459 | <a href="/demo/tree/feature/x" tabindex="-1"> | |
| 455 | 460 | <span class="lbl">feature/x</span> | |
| 456 | 461 | <span class="k">branch</span> | |
| 457 | 462 | </a> | |
| 458 | 463 | </li> | |
| 459 | - | <li data-k="branch main"> | |
| 464 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 460 | 465 | <a href="/demo/tree/main" tabindex="-1"> | |
| 461 | 466 | <span class="lbl">main</span> | |
| 462 | 467 | <span class="k">branch</span> | |
| 463 | 468 | </a> | |
| 464 | 469 | </li> | |
| 465 | - | <li data-k="tag v0.1.0"> | |
| 470 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v0.1.0"> | |
| 466 | 471 | <a href="/demo/tree/v0.1.0" tabindex="-1"> | |
| 467 | 472 | <span class="lbl">v0.1.0</span> | |
| 468 | 473 | <span class="k">tag</span> | |
| 469 | 474 | </a> | |
| 470 | 475 | </li> | |
| 471 | - | <li data-k="tag v0.2.0"> | |
| 476 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="tag v0.2.0"> | |
| 472 | 477 | <a href="/demo/tree/v0.2.0" tabindex="-1"> | |
| 473 | 478 | <span class="lbl">v0.2.0</span> | |
| 474 | 479 | <span class="k">tag</span> | |
| 475 | 480 | </a> | |
| 476 | 481 | </li> | |
| 477 | - | <li data-k="repo quiet"> | |
| 482 | + | <li id="cmd-o11" role="option" aria-selected="false" data-k="repo quiet"> | |
| 478 | 483 | <a href="/quiet" tabindex="-1"> | |
| 479 | 484 | <span class="lbl">quiet</span> | |
| 480 | 485 | <span class="k">repo</span> | |
| 481 | 486 | </a> | |
| 482 | 487 | </li> | |
| 488 | + | <li id="cmd-o12" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 489 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 490 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 491 | + | <span class="k">action</span> | |
| 492 | + | </a> | |
| 493 | + | </li> | |
| 483 | 494 | </ul> | |
| 484 | 495 | <p class="panel-foot"> | |
| 485 | 496 | <span> | |
| @@ -518,7 +529,8 @@ | |||
| 518 | 529 | k-permalink from:body -> ermite.permalink(); | |
| 519 | 530 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 520 | 531 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 521 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 532 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 533 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 522 | 534 | </div> | |
| 523 | 535 | </body> | |
| 524 | 536 | </html> | |
M internal/app/testdata/web/tree-nested.html +25 -13
| @@ -168,6 +168,11 @@ | |||
| 168 | 168 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 169 | 169 | <kbd>esc</kbd> close</button> | |
| 170 | 170 | </div> | |
| 171 | + | <div class="keys-pref"> | |
| 172 | + | <label> | |
| 173 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 174 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 175 | + | </div> | |
| 171 | 176 | <dl class="keys"> | |
| 172 | 177 | <div> | |
| 173 | 178 | <dt> | |
| @@ -321,72 +326,78 @@ | |||
| 321 | 326 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 322 | 327 | <div class="panel-head"> | |
| 323 | 328 | <label for="cmd-q" class="sr">go to</label> | |
| 324 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 329 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 325 | 330 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 326 | 331 | <kbd>esc</kbd> | |
| 327 | 332 | </button> | |
| 328 | 333 | </div> | |
| 329 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 330 | - | <li data-k="go home" data-cursor> | |
| 334 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 335 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 331 | 336 | <a href="/" tabindex="-1"> | |
| 332 | 337 | <span class="lbl">home</span> | |
| 333 | 338 | <span class="k">go</span> | |
| 334 | 339 | </a> | |
| 335 | 340 | </li> | |
| 336 | - | <li data-k="section summary"> | |
| 341 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 337 | 342 | <a href="/files" tabindex="-1"> | |
| 338 | 343 | <span class="lbl">summary</span> | |
| 339 | 344 | <span class="k">section</span> | |
| 340 | 345 | </a> | |
| 341 | 346 | </li> | |
| 342 | - | <li data-k="section files"> | |
| 347 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 343 | 348 | <a href="/files/tree/main" tabindex="-1"> | |
| 344 | 349 | <span class="lbl">files</span> | |
| 345 | 350 | <span class="k">section</span> | |
| 346 | 351 | </a> | |
| 347 | 352 | </li> | |
| 348 | - | <li data-k="section commits"> | |
| 353 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 349 | 354 | <a href="/files/commits/main" tabindex="-1"> | |
| 350 | 355 | <span class="lbl">commits</span> | |
| 351 | 356 | <span class="k">section</span> | |
| 352 | 357 | </a> | |
| 353 | 358 | </li> | |
| 354 | - | <li data-k="section branches & tags"> | |
| 359 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 355 | 360 | <a href="/files/refs" tabindex="-1"> | |
| 356 | 361 | <span class="lbl">branches & tags</span> | |
| 357 | 362 | <span class="k">section</span> | |
| 358 | 363 | </a> | |
| 359 | 364 | </li> | |
| 360 | - | <li data-k="section merge requests"> | |
| 365 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 361 | 366 | <a href="/files/merge-requests" tabindex="-1"> | |
| 362 | 367 | <span class="lbl">merge requests</span> | |
| 363 | 368 | <span class="k">section</span> | |
| 364 | 369 | </a> | |
| 365 | 370 | </li> | |
| 366 | - | <li data-k="section actions"> | |
| 371 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 367 | 372 | <a href="/files/actions" tabindex="-1"> | |
| 368 | 373 | <span class="lbl">actions</span> | |
| 369 | 374 | <span class="k">section</span> | |
| 370 | 375 | </a> | |
| 371 | 376 | </li> | |
| 372 | - | <li data-k="branch feature/x"> | |
| 377 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 373 | 378 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 374 | 379 | <span class="lbl">feature/x</span> | |
| 375 | 380 | <span class="k">branch</span> | |
| 376 | 381 | </a> | |
| 377 | 382 | </li> | |
| 378 | - | <li data-k="branch main"> | |
| 383 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 379 | 384 | <a href="/files/tree/main" tabindex="-1"> | |
| 380 | 385 | <span class="lbl">main</span> | |
| 381 | 386 | <span class="k">branch</span> | |
| 382 | 387 | </a> | |
| 383 | 388 | </li> | |
| 384 | - | <li data-k="tag v1.0"> | |
| 389 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 385 | 390 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 386 | 391 | <span class="lbl">v1.0</span> | |
| 387 | 392 | <span class="k">tag</span> | |
| 388 | 393 | </a> | |
| 389 | 394 | </li> | |
| 395 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 396 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 397 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 398 | + | <span class="k">action</span> | |
| 399 | + | </a> | |
| 400 | + | </li> | |
| 390 | 401 | </ul> | |
| 391 | 402 | <p class="panel-foot"> | |
| 392 | 403 | <span> | |
| @@ -425,7 +436,8 @@ | |||
| 425 | 436 | k-permalink from:body -> ermite.permalink(); | |
| 426 | 437 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 427 | 438 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 428 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 439 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 440 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 429 | 441 | </div> | |
| 430 | 442 | </body> | |
| 431 | 443 | </html> | |
M internal/app/testdata/web/tree-root.html +25 -13
| @@ -389,6 +389,11 @@ | |||
| 389 | 389 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 390 | 390 | <kbd>esc</kbd> close</button> | |
| 391 | 391 | </div> | |
| 392 | + | <div class="keys-pref"> | |
| 393 | + | <label> | |
| 394 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 395 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 396 | + | </div> | |
| 392 | 397 | <dl class="keys"> | |
| 393 | 398 | <div> | |
| 394 | 399 | <dt> | |
| @@ -542,72 +547,78 @@ | |||
| 542 | 547 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 543 | 548 | <div class="panel-head"> | |
| 544 | 549 | <label for="cmd-q" class="sr">go to</label> | |
| 545 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 550 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 546 | 551 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 547 | 552 | <kbd>esc</kbd> | |
| 548 | 553 | </button> | |
| 549 | 554 | </div> | |
| 550 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 551 | - | <li data-k="go home" data-cursor> | |
| 555 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 556 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 552 | 557 | <a href="/" tabindex="-1"> | |
| 553 | 558 | <span class="lbl">home</span> | |
| 554 | 559 | <span class="k">go</span> | |
| 555 | 560 | </a> | |
| 556 | 561 | </li> | |
| 557 | - | <li data-k="section summary"> | |
| 562 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 558 | 563 | <a href="/files" tabindex="-1"> | |
| 559 | 564 | <span class="lbl">summary</span> | |
| 560 | 565 | <span class="k">section</span> | |
| 561 | 566 | </a> | |
| 562 | 567 | </li> | |
| 563 | - | <li data-k="section files"> | |
| 568 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 564 | 569 | <a href="/files/tree/main" tabindex="-1"> | |
| 565 | 570 | <span class="lbl">files</span> | |
| 566 | 571 | <span class="k">section</span> | |
| 567 | 572 | </a> | |
| 568 | 573 | </li> | |
| 569 | - | <li data-k="section commits"> | |
| 574 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 570 | 575 | <a href="/files/commits/main" tabindex="-1"> | |
| 571 | 576 | <span class="lbl">commits</span> | |
| 572 | 577 | <span class="k">section</span> | |
| 573 | 578 | </a> | |
| 574 | 579 | </li> | |
| 575 | - | <li data-k="section branches & tags"> | |
| 580 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 576 | 581 | <a href="/files/refs" tabindex="-1"> | |
| 577 | 582 | <span class="lbl">branches & tags</span> | |
| 578 | 583 | <span class="k">section</span> | |
| 579 | 584 | </a> | |
| 580 | 585 | </li> | |
| 581 | - | <li data-k="section merge requests"> | |
| 586 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 582 | 587 | <a href="/files/merge-requests" tabindex="-1"> | |
| 583 | 588 | <span class="lbl">merge requests</span> | |
| 584 | 589 | <span class="k">section</span> | |
| 585 | 590 | </a> | |
| 586 | 591 | </li> | |
| 587 | - | <li data-k="section actions"> | |
| 592 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 588 | 593 | <a href="/files/actions" tabindex="-1"> | |
| 589 | 594 | <span class="lbl">actions</span> | |
| 590 | 595 | <span class="k">section</span> | |
| 591 | 596 | </a> | |
| 592 | 597 | </li> | |
| 593 | - | <li data-k="branch feature/x"> | |
| 598 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 594 | 599 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 595 | 600 | <span class="lbl">feature/x</span> | |
| 596 | 601 | <span class="k">branch</span> | |
| 597 | 602 | </a> | |
| 598 | 603 | </li> | |
| 599 | - | <li data-k="branch main"> | |
| 604 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 600 | 605 | <a href="/files/tree/main" tabindex="-1"> | |
| 601 | 606 | <span class="lbl">main</span> | |
| 602 | 607 | <span class="k">branch</span> | |
| 603 | 608 | </a> | |
| 604 | 609 | </li> | |
| 605 | - | <li data-k="tag v1.0"> | |
| 610 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 606 | 611 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 607 | 612 | <span class="lbl">v1.0</span> | |
| 608 | 613 | <span class="k">tag</span> | |
| 609 | 614 | </a> | |
| 610 | 615 | </li> | |
| 616 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 617 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 618 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 619 | + | <span class="k">action</span> | |
| 620 | + | </a> | |
| 621 | + | </li> | |
| 611 | 622 | </ul> | |
| 612 | 623 | <p class="panel-foot"> | |
| 613 | 624 | <span> | |
| @@ -646,7 +657,8 @@ | |||
| 646 | 657 | k-permalink from:body -> ermite.permalink(); | |
| 647 | 658 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 648 | 659 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 649 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 660 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 661 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 650 | 662 | </div> | |
| 651 | 663 | </body> | |
| 652 | 664 | </html> | |
M internal/app/testdata/web/tree-slash-ref.html +25 -13
| @@ -165,6 +165,11 @@ | |||
| 165 | 165 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 166 | 166 | <kbd>esc</kbd> close</button> | |
| 167 | 167 | </div> | |
| 168 | + | <div class="keys-pref"> | |
| 169 | + | <label> | |
| 170 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 171 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 172 | + | </div> | |
| 168 | 173 | <dl class="keys"> | |
| 169 | 174 | <div> | |
| 170 | 175 | <dt> | |
| @@ -318,72 +323,78 @@ | |||
| 318 | 323 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 319 | 324 | <div class="panel-head"> | |
| 320 | 325 | <label for="cmd-q" class="sr">go to</label> | |
| 321 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 326 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 322 | 327 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 323 | 328 | <kbd>esc</kbd> | |
| 324 | 329 | </button> | |
| 325 | 330 | </div> | |
| 326 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 327 | - | <li data-k="go home" data-cursor> | |
| 331 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 332 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 328 | 333 | <a href="/" tabindex="-1"> | |
| 329 | 334 | <span class="lbl">home</span> | |
| 330 | 335 | <span class="k">go</span> | |
| 331 | 336 | </a> | |
| 332 | 337 | </li> | |
| 333 | - | <li data-k="section summary"> | |
| 338 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 334 | 339 | <a href="/files" tabindex="-1"> | |
| 335 | 340 | <span class="lbl">summary</span> | |
| 336 | 341 | <span class="k">section</span> | |
| 337 | 342 | </a> | |
| 338 | 343 | </li> | |
| 339 | - | <li data-k="section files"> | |
| 344 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 340 | 345 | <a href="/files/tree/main" tabindex="-1"> | |
| 341 | 346 | <span class="lbl">files</span> | |
| 342 | 347 | <span class="k">section</span> | |
| 343 | 348 | </a> | |
| 344 | 349 | </li> | |
| 345 | - | <li data-k="section commits"> | |
| 350 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 346 | 351 | <a href="/files/commits/main" tabindex="-1"> | |
| 347 | 352 | <span class="lbl">commits</span> | |
| 348 | 353 | <span class="k">section</span> | |
| 349 | 354 | </a> | |
| 350 | 355 | </li> | |
| 351 | - | <li data-k="section branches & tags"> | |
| 356 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 352 | 357 | <a href="/files/refs" tabindex="-1"> | |
| 353 | 358 | <span class="lbl">branches & tags</span> | |
| 354 | 359 | <span class="k">section</span> | |
| 355 | 360 | </a> | |
| 356 | 361 | </li> | |
| 357 | - | <li data-k="section merge requests"> | |
| 362 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 358 | 363 | <a href="/files/merge-requests" tabindex="-1"> | |
| 359 | 364 | <span class="lbl">merge requests</span> | |
| 360 | 365 | <span class="k">section</span> | |
| 361 | 366 | </a> | |
| 362 | 367 | </li> | |
| 363 | - | <li data-k="section actions"> | |
| 368 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 364 | 369 | <a href="/files/actions" tabindex="-1"> | |
| 365 | 370 | <span class="lbl">actions</span> | |
| 366 | 371 | <span class="k">section</span> | |
| 367 | 372 | </a> | |
| 368 | 373 | </li> | |
| 369 | - | <li data-k="branch feature/x"> | |
| 374 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 370 | 375 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 371 | 376 | <span class="lbl">feature/x</span> | |
| 372 | 377 | <span class="k">branch</span> | |
| 373 | 378 | </a> | |
| 374 | 379 | </li> | |
| 375 | - | <li data-k="branch main"> | |
| 380 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 376 | 381 | <a href="/files/tree/main" tabindex="-1"> | |
| 377 | 382 | <span class="lbl">main</span> | |
| 378 | 383 | <span class="k">branch</span> | |
| 379 | 384 | </a> | |
| 380 | 385 | </li> | |
| 381 | - | <li data-k="tag v1.0"> | |
| 386 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 382 | 387 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 383 | 388 | <span class="lbl">v1.0</span> | |
| 384 | 389 | <span class="k">tag</span> | |
| 385 | 390 | </a> | |
| 386 | 391 | </li> | |
| 392 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 393 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 394 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 395 | + | <span class="k">action</span> | |
| 396 | + | </a> | |
| 397 | + | </li> | |
| 387 | 398 | </ul> | |
| 388 | 399 | <p class="panel-foot"> | |
| 389 | 400 | <span> | |
| @@ -422,7 +433,8 @@ | |||
| 422 | 433 | k-permalink from:body -> ermite.permalink(); | |
| 423 | 434 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 424 | 435 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 425 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 436 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 437 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 426 | 438 | </div> | |
| 427 | 439 | </body> | |
| 428 | 440 | </html> | |
M internal/app/testdata/web/tree-subdir.html +25 -13
| @@ -181,6 +181,11 @@ | |||
| 181 | 181 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 182 | 182 | <kbd>esc</kbd> close</button> | |
| 183 | 183 | </div> | |
| 184 | + | <div class="keys-pref"> | |
| 185 | + | <label> | |
| 186 | + | <input type="checkbox" id="keys-off" aria-describedby="keys-note" hx-on:change="ermite.setKeysOff(this.checked)"> Turn off single-key shortcuts</label> | |
| 187 | + | <p id="keys-note" class="dim">Single-key shortcuts are on. ctrl k and esc work whatever this says.</p> | |
| 188 | + | </div> | |
| 184 | 189 | <dl class="keys"> | |
| 185 | 190 | <div> | |
| 186 | 191 | <dt> | |
| @@ -334,72 +339,78 @@ | |||
| 334 | 339 | <dialog id="panel-cmd" class="panel modal" aria-label="Go to" hx-on:close="ermiteFinder.paletteClose()" hx-on:keydown="event.key === 'Escape' && (event.preventDefault(), this.close())" hx-on:click="event.target === this && this.close()"> | |
| 335 | 340 | <div class="panel-head"> | |
| 336 | 341 | <label for="cmd-q" class="sr">go to</label> | |
| 337 | - | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" aria-controls="cmd-list" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 342 | + | <input id="cmd-q" type="search" placeholder="Go to a section, branch, tag, file or repository…" autocomplete="off" spellcheck="false" role="combobox" aria-autocomplete="list" aria-expanded="true" aria-controls="cmd-list" aria-activedescendant="cmd-o0" hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" hx-on:keydown="ermite.cmdKey(event)"> | |
| 338 | 343 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | |
| 339 | 344 | <kbd>esc</kbd> | |
| 340 | 345 | </button> | |
| 341 | 346 | </div> | |
| 342 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | |
| 343 | - | <li data-k="go home" data-cursor> | |
| 347 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> | |
| 348 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> | |
| 344 | 349 | <a href="/" tabindex="-1"> | |
| 345 | 350 | <span class="lbl">home</span> | |
| 346 | 351 | <span class="k">go</span> | |
| 347 | 352 | </a> | |
| 348 | 353 | </li> | |
| 349 | - | <li data-k="section summary"> | |
| 354 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> | |
| 350 | 355 | <a href="/files" tabindex="-1"> | |
| 351 | 356 | <span class="lbl">summary</span> | |
| 352 | 357 | <span class="k">section</span> | |
| 353 | 358 | </a> | |
| 354 | 359 | </li> | |
| 355 | - | <li data-k="section files"> | |
| 360 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> | |
| 356 | 361 | <a href="/files/tree/main" tabindex="-1"> | |
| 357 | 362 | <span class="lbl">files</span> | |
| 358 | 363 | <span class="k">section</span> | |
| 359 | 364 | </a> | |
| 360 | 365 | </li> | |
| 361 | - | <li data-k="section commits"> | |
| 366 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> | |
| 362 | 367 | <a href="/files/commits/main" tabindex="-1"> | |
| 363 | 368 | <span class="lbl">commits</span> | |
| 364 | 369 | <span class="k">section</span> | |
| 365 | 370 | </a> | |
| 366 | 371 | </li> | |
| 367 | - | <li data-k="section branches & tags"> | |
| 372 | + | <li id="cmd-o4" role="option" aria-selected="false" data-k="section branches & tags"> | |
| 368 | 373 | <a href="/files/refs" tabindex="-1"> | |
| 369 | 374 | <span class="lbl">branches & tags</span> | |
| 370 | 375 | <span class="k">section</span> | |
| 371 | 376 | </a> | |
| 372 | 377 | </li> | |
| 373 | - | <li data-k="section merge requests"> | |
| 378 | + | <li id="cmd-o5" role="option" aria-selected="false" data-k="section merge requests"> | |
| 374 | 379 | <a href="/files/merge-requests" tabindex="-1"> | |
| 375 | 380 | <span class="lbl">merge requests</span> | |
| 376 | 381 | <span class="k">section</span> | |
| 377 | 382 | </a> | |
| 378 | 383 | </li> | |
| 379 | - | <li data-k="section actions"> | |
| 384 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> | |
| 380 | 385 | <a href="/files/actions" tabindex="-1"> | |
| 381 | 386 | <span class="lbl">actions</span> | |
| 382 | 387 | <span class="k">section</span> | |
| 383 | 388 | </a> | |
| 384 | 389 | </li> | |
| 385 | - | <li data-k="branch feature/x"> | |
| 390 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch feature/x"> | |
| 386 | 391 | <a href="/files/tree/feature/x" tabindex="-1"> | |
| 387 | 392 | <span class="lbl">feature/x</span> | |
| 388 | 393 | <span class="k">branch</span> | |
| 389 | 394 | </a> | |
| 390 | 395 | </li> | |
| 391 | - | <li data-k="branch main"> | |
| 396 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> | |
| 392 | 397 | <a href="/files/tree/main" tabindex="-1"> | |
| 393 | 398 | <span class="lbl">main</span> | |
| 394 | 399 | <span class="k">branch</span> | |
| 395 | 400 | </a> | |
| 396 | 401 | </li> | |
| 397 | - | <li data-k="tag v1.0"> | |
| 402 | + | <li id="cmd-o9" role="option" aria-selected="false" data-k="tag v1.0"> | |
| 398 | 403 | <a href="/files/tree/v1.0" tabindex="-1"> | |
| 399 | 404 | <span class="lbl">v1.0</span> | |
| 400 | 405 | <span class="k">tag</span> | |
| 401 | 406 | </a> | |
| 402 | 407 | </li> | |
| 408 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="action turn single-key shortcuts on off keyboard"> | |
| 409 | + | <a href="#" data-fire="keys" tabindex="-1"> | |
| 410 | + | <span class="lbl">turn single-key shortcuts off</span> | |
| 411 | + | <span class="k">action</span> | |
| 412 | + | </a> | |
| 413 | + | </li> | |
| 403 | 414 | </ul> | |
| 404 | 415 | <p class="panel-foot"> | |
| 405 | 416 | <span> | |
| @@ -438,7 +449,8 @@ | |||
| 438 | 449 | k-permalink from:body -> ermite.permalink(); | |
| 439 | 450 | k-merge from:body -> window.ermiteMR?.openMerge(); | |
| 440 | 451 | k-read from:body -> window.ermiteMR?.markRead(); | |
| 441 | - | k-gf from:body -> window.ermiteMR?.focusFiles()"> | |
| 452 | + | k-gf from:body -> window.ermiteMR?.focusFiles(); | |
| 453 | + | k-keys from:body -> ermite.toggleKeys()"> | |
| 442 | 454 | </div> | |
| 443 | 455 | </body> | |
| 444 | 456 | </html> | |