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 | </dl> | 55 | </dl> | ||
| 56 | </header> | 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 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | 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 | <span class="grow"></span> | 59 | <span class="grow"></span> | ||
| 60 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 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 | const key = (k, extra = {}) => { | 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 | return e; | 53 | return e; | ||
| 54 | }; | 54 | }; | ||
| 55 | const status = () => document.getElementById('finder-status').textContent; | 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 | assert.equal(body.children[1].getAttribute('tabindex'), '-1'); | 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 | test('l and h move the focus with the cursor, and the tab stop with them', async () => { | 291 | test('l and h move the focus with the cursor, and the tab stop with them', async () => { | ||
| 280 | pending.clear(); | 292 | pending.clear(); | ||
| 281 | const body = table('P/', 'f'); | 293 | const body = table('P/', 'f'); | ||
M internal/adapters/web/keys.templ +2 -1
| @@ -36,6 +36,7 @@ templ Keys() { | |||||
| 36 | k-permalink from:body -> ermite.permalink(); | 36 | k-permalink from:body -> ermite.permalink(); | ||
| 37 | k-merge from:body -> window.ermiteMR?.openMerge(); | 37 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 38 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | ></div> | 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 | // request, the merge menu and the other actions. Each menu is a <details>: it | 358 | // request, the merge menu and the other actions. Each menu is a <details>: it | ||
| 359 | // works without scripts; with them, M and the keys of the menu drive it. | 359 | // works without scripts; with them, M and the keys of the menu drive it. | ||
| 360 | templ mrBar(p MRPage) { | 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 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | 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 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 363 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | ||
| 364 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 364 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | ||
| @@ -488,7 +488,7 @@ templ Compare(l Layout, p ComparePage) { | |||||
| 488 | </form> | 488 | </form> | ||
| 489 | } | 489 | } | ||
| 490 | if p.HasDiff { | 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 | <span class="dim">{ plural(p.NFiles, "file", "files") } · <span class="plus">+{ itoa(p.Adds) }</span> <span class="minus">-{ itoa(p.Dels) }</span></span> | 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 | <span class="grow"></span> | 493 | <span class="grow"></span> | ||
| 494 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 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 | package web | 3 | package web | ||
| 4 | 4 | ||||
| 5 | - | import "strings" | 5 | + | import ( |
| 6 | + | "strconv" | |||
| 7 | + | "strings" | |||
| 8 | + | ) | |||
| 6 | 9 | ||||
| 7 | // OverlayHelp is the keyboard map panel; the keys of the merge request page | 10 | // OverlayHelp is the keyboard map panel; the keys of the merge request page | ||
| 8 | // show on that page only. | 11 | // show on that page only. | ||
| 9 | templ OverlayHelp(mr bool) { | 12 | templ OverlayHelp(mr bool) { | ||
| 10 | <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()"> | 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 | <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> | 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 | <dl class="keys"> | 19 | <dl class="keys"> | ||
| 13 | <div><dt><kbd>ctrl k</kbd> <kbd>:</kbd></dt><dd>go to a section, branch, tag, file or repository</dd></div> | 20 | <div><dt><kbd>ctrl k</kbd> <kbd>:</kbd></dt><dd>go to a section, branch, tag, file or repository</dd></div> | ||
| 14 | <div><dt><kbd>?</kbd></dt><dd>this list</dd></div> | 21 | <div><dt><kbd>?</kbd></dt><dd>this list</dd></div> | ||
| @@ -104,16 +111,21 @@ templ OverlayCmd(items []CmdItem) { | |||||
| 104 | placeholder="Go to a section, branch, tag, file or repository…" | 111 | placeholder="Go to a section, branch, tag, file or repository…" | ||
| 105 | autocomplete="off" | 112 | autocomplete="off" | ||
| 106 | spellcheck="false" | 113 | spellcheck="false" | ||
| 114 | + | role="combobox" | |||
| 115 | + | aria-autocomplete="list" | |||
| 116 | + | aria-expanded="true" | |||
| 107 | aria-controls="cmd-list" | 117 | aria-controls="cmd-list" | ||
| 118 | + | aria-activedescendant="cmd-o0" | |||
| 108 | hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" | 119 | hx-on:input="ermite.cmdFilter(this); ermiteFinder.palette(this)" | ||
| 109 | hx-on:keydown="ermite.cmdKey(event)" | 120 | hx-on:keydown="ermite.cmdKey(event)" | ||
| 110 | /> | 121 | /> | ||
| 111 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button> | 122 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"><kbd>esc</kbd></button> | ||
| 112 | </div> | 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 | for i, c := range items { | 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 | </ul> | 129 | </ul> | ||
| 118 | <p class="panel-foot"><span><kbd>↑</kbd><kbd>↓</kbd> move</span><span><kbd>enter</kbd> open</span><span><kbd>esc</kbd> close</span></p> | 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 | </dialog> | 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 | & h2 { font-size: var(--fs); flex: 1; } | 21 | & h2 { font-size: var(--fs); flex: 1; } | ||
| 22 | & input { flex: 1; min-width: 0; height: 32px; font-size: 16px; } | 22 | & input { flex: 1; min-width: 0; height: 32px; font-size: 16px; } | ||
| 23 | & input::placeholder { color: var(--ink-mute); opacity: 1; } | 23 | & input::placeholder { color: var(--ink-mute); opacity: 1; } | ||
| 24 | - | & input:focus-visible { outline: none; } | |||
| 25 | } | 24 | } | ||
| 26 | .panel-x { color: var(--ink-mute); font-size: var(--fs-s); display: flex; gap: var(--s2); align-items: center; } | 25 | .panel-x { color: var(--ink-mute); font-size: var(--fs-s); display: flex; gap: var(--s2); align-items: center; } | ||
| 27 | .panel-x:hover { color: var(--ink-hi); } | 26 | .panel-x:hover { color: var(--ink-hi); } | ||
| @@ -60,10 +59,18 @@ | |||||
| 60 | } | 59 | } | ||
| 61 | 60 | ||||
| 62 | .keys { | 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 | padding: var(--s3) var(--s4) var(--s4) !important; | 63 | padding: var(--s3) var(--s4) var(--s4) !important; | ||
| 65 | & > div { display: grid; grid-template-columns: 130px 1fr; gap: var(--s3); align-items: baseline; padding: var(--s1) 0; border-bottom: 1px solid var(--border); } | 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); } | 65 | + | & dt { display: flex; flex-wrap: wrap; gap: var(--s1); min-width: 0; } |
| 67 | - | & dd { color: var(--ink-dim); font-size: var(--fs-s); } | 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 | setCursor(rows[j], $$('[data-rows] > [data-cursor]')); | 64 | setCursor(rows[j], $$('[data-rows] > [data-cursor]')); | ||
| 65 | rows[j].scrollIntoView({ block: 'nearest' }); | 65 | rows[j].scrollIntoView({ block: 'nearest' }); | ||
| 66 | window.ermiteTree?.sync(rows[j]); | 66 | window.ermiteTree?.sync(rows[j]); | ||
| 67 | + | window.ermiteFinder?.sync(); // the rows may be the file finder's options | |||
| 67 | const mark = j + 1 + '/' + rows.length; | 68 | const mark = j + 1 + '/' + rows.length; | ||
| 68 | body().dataset.nav = mark; | 69 | body().dataset.nav = mark; | ||
| 69 | setTimeout(() => body().dataset.nav === mark && (body().dataset.nav = ''), 1500); | 70 | setTimeout(() => body().dataset.nav === mark && (body().dataset.nav = ''), 1500); | ||
| @@ -76,6 +77,7 @@ | |||||
| 76 | setCursor(row, $$('[data-rows] > [data-cursor]')); | 77 | setCursor(row, $$('[data-rows] > [data-cursor]')); | ||
| 77 | row.scrollIntoView({ block: 'nearest' }); | 78 | row.scrollIntoView({ block: 'nearest' }); | ||
| 78 | window.ermiteTree?.sync(row); | 79 | window.ermiteTree?.sync(row); | ||
| 80 | + | window.ermiteFinder?.sync(); | |||
| 79 | }, | 81 | }, | ||
| 80 | open() { | 82 | open() { | ||
| 81 | if ($('dialog[open]') || window.ermiteTree?.open()) return; | 83 | if ($('dialog[open]') || window.ermiteTree?.open()) return; | ||
| @@ -108,6 +110,18 @@ | |||||
| 108 | $(`.tabs [data-key=${key}]`)?.click(); | 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 | // Dialogs. | 125 | // Dialogs. | ||
| 112 | closeDialogs() { | 126 | closeDialogs() { | ||
| 113 | for (const d of $$('dialog[open]')) d.close(); | 127 | for (const d of $$('dialog[open]')) d.close(); | ||
| @@ -117,6 +131,7 @@ | |||||
| 117 | if (!d) return; | 131 | if (!d) return; | ||
| 118 | ermite.closeDialogs(); | 132 | ermite.closeDialogs(); | ||
| 119 | d.showModal(); | 133 | d.showModal(); | ||
| 134 | + | if (id === 'panel-cmd') window.ermiteCmd?.sync(); | |||
| 120 | if (focusId) document.getElementById(focusId)?.focus(); | 135 | if (focusId) document.getElementById(focusId)?.focus(); | ||
| 121 | }, | 136 | }, | ||
| 122 | 137 | ||||
| @@ -146,31 +161,12 @@ | |||||
| 146 | } | 161 | } | ||
| 147 | return rows; | 162 | return rows; | ||
| 148 | }, | 163 | }, | ||
| 149 | - | // The "go to" palette. | 164 | + | // The "go to" palette (ermiteCmd below). |
| 150 | cmdFilter(input) { | 165 | cmdFilter(input) { | ||
| 151 | - | const t = input.value.toLowerCase().trim(); | 166 | + | window.ermiteCmd.filter(input); |
| 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')); | |||
| 158 | }, | 167 | }, | ||
| 159 | cmdKey(e) { | 168 | cmdKey(e) { | ||
| 160 | - | const vis = $$('#cmd-list > li:not([hidden])'); | 169 | + | window.ermiteCmd.key(e); |
| 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 | - | } | |||
| 174 | }, | 170 | }, | ||
| 175 | 171 | ||||
| 176 | // Diff. | 172 | // Diff. | ||
| @@ -253,6 +249,69 @@ | |||||
| 253 | }; | 249 | }; | ||
| 254 | window.ermite = ermite; | 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 | // The side panel (aside.rail) by keyboard: l enters it on its main action | 315 | // The side panel (aside.rail) by keyboard: l enters it on its main action | ||
| 257 | // (the primary button) or its first control, j / k step between its shown | 316 | // (the primary button) or its first control, j / k step between its shown | ||
| 258 | // controls (not hidden, not in a closed <details>), h leaves it for <main>. | 317 | // controls (not hidden, not in a closed <details>), h leaves it for <main>. | ||
| @@ -458,6 +517,50 @@ | |||||
| 458 | return { load, file, current }; | 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 | // 1. Keys. | 564 | // 1. Keys. | ||
| 462 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | 565 | const single = { j: 'j', k: 'k', G: 'bottom', '/': 'filter', '?': 'help', b: 'refs', f: 'cmd', ':': 'cmd', y: 'permalink', | ||
| 463 | 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', | 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 | if (typing) e.target.blur(); | 583 | if (typing) e.target.blur(); | ||
| 481 | return ermite.fire('esc'); | 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 | // An open merge menu takes its keys (1-9, D) before the field check: its | 588 | // An open merge menu takes its keys (1-9, D) before the field check: its | ||
| 484 | // radios and its checkbox hold the focus. | 589 | // radios and its checkbox hold the focus. | ||
| 485 | if (mr?.menuKey(e)) return; | 590 | if (mr?.menuKey(e)) return; | ||
| @@ -607,6 +712,50 @@ window.ermiteLive = (() => { | |||||
| 607 | // server closing it, a network hiccup) is not worth a message. | 712 | // server closing it, a network hiccup) is not worth a message. | ||
| 608 | const LOST_AFTER = 5000; | 713 | const LOST_AFTER = 5000; | ||
| 609 | let lostTimer = 0; | 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 | const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; }; | 759 | const setState = (s) => { const el = document.querySelector('.hint-live'); if (el) el.dataset.state = s; }; | ||
| 611 | return { | 760 | return { | ||
| 612 | // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows | 761 | // lost is htmx:sse:close and htmx:sse:error: "live updates paused" shows | ||
| @@ -628,16 +777,27 @@ window.ermiteLive = (() => { | |||||
| 628 | const cursorKey = cursor?.id || cursor?.querySelector('a')?.getAttribute('href'); | 777 | const cursorKey = cursor?.id || cursor?.querySelector('a')?.getAttribute('href'); | ||
| 629 | for (const el of [...t.content.children]) { | 778 | for (const el of [...t.content.children]) { | ||
| 630 | const old = el.id && document.getElementById(el.id); | 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 | if (!old) { | 787 | if (!old) { | ||
| 633 | const rows = document.getElementById('repo-rows'); | 788 | const rows = document.getElementById('repo-rows'); | ||
| 634 | if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); } | 789 | if (rows && el.id.startsWith('repo-')) { rows.prepend(el); window.htmx?.process(el); } | ||
| 635 | continue; | 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 | if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', ''); | 797 | if (old.hasAttribute('data-cursor')) el.setAttribute('data-cursor', ''); | ||
| 639 | old.replaceWith(el); | 798 | old.replaceWith(el); | ||
| 640 | window.htmx?.process(el); | 799 | window.htmx?.process(el); | ||
| 800 | + | if (had) refocus(el, had); | |||
| 641 | } | 801 | } | ||
| 642 | // The rows may have changed under an active filter: apply it again and | 802 | // The rows may have changed under an active filter: apply it again and | ||
| 643 | // keep the cursor on its row while that row is still shown. | 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 | if (!this.active()) return false; | 232 | if (!this.active()) return false; | ||
| 233 | const li = cursorRow(); | 233 | const li = cursorRow(); | ||
| 234 | if (!li) return false; | 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 | if (into) { | 238 | if (into) { | ||
| 236 | if (isDir(li)) { | 239 | if (isDir(li)) { | ||
| 237 | if (!isOpen(li)) return toggle(li), true; | 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 | // showTree: no query, the directory again, with j/k and Enter on its rows. | 685 | // showTree: no query, the directory again, with j/k and Enter on its rows. | ||
| 676 | function showTree(el) { | 686 | function showTree(el) { | ||
| 677 | rendered = null; | 687 | rendered = null; | ||
| @@ -755,6 +765,7 @@ window.ermiteFinder = (() => { | |||||
| 755 | function palette(el) { | 765 | function palette(el) { | ||
| 756 | if (!document.body.dataset.paths) return; | 766 | if (!document.body.dataset.paths) return; | ||
| 757 | clearHits(); | 767 | clearHits(); | ||
| 768 | + | window.ermiteCmd?.sync(); // the cursor may have been on a hit | |||
| 758 | if (pFrame) cancelAnimationFrame(pFrame); | 769 | if (pFrame) cancelAnimationFrame(pFrame); | ||
| 759 | pFrame = 0; | 770 | pFrame = 0; | ||
| 760 | if (!el.value.trim()) { | 771 | if (!el.value.trim()) { | ||
| @@ -802,6 +813,7 @@ window.ermiteFinder = (() => { | |||||
| 802 | list.querySelectorAll(':scope > li[data-cursor]').forEach((li) => li.removeAttribute('data-cursor')); | 813 | list.querySelectorAll(':scope > li[data-cursor]').forEach((li) => li.removeAttribute('data-cursor')); | ||
| 803 | shown[0].setAttribute('data-cursor', ''); | 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 | // input runs the search of the field, once per frame. | 819 | // input runs the search of the field, once per frame. | ||
| @@ -816,12 +828,17 @@ window.ermiteFinder = (() => { | |||||
| 816 | } | 828 | } | ||
| 817 | 829 | ||||
| 818 | return { | 830 | return { | ||
| 819 | - | index, search, positions, visible, label, load, palette, paletteClose, input, | 831 | + | index, search, positions, visible, label, load, palette, paletteClose, input, sync, |
| 820 | // key moves the cursor of the results, opens one, or clears the field. | 832 | // key moves the cursor of the results, opens one, or clears the field. | ||
| 821 | key(el, e) { | 833 | key(el, e) { | ||
| 822 | if (e.isComposing) return; // the Enter or Escape of an input method is its own | 834 | if (e.isComposing) return; // the Enter or Escape of an input method is its own | ||
| 823 | const results = !$('finder').hidden; | 835 | const results = !$('finder').hidden; | ||
| 824 | if (e.key === 'Escape') { | 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 | el.value = ''; | 842 | el.value = ''; | ||
| 826 | input(el); | 843 | input(el); | ||
| 827 | return; | 844 | return; | ||
M internal/app/testdata/web/blob-binary.html +25 -13
| @@ -153,6 +153,11 @@ | |||||
| 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 154 | <kbd>esc</kbd> close</button> | 154 | <kbd>esc</kbd> close</button> | ||
| 155 | </div> | 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 | <dl class="keys"> | 161 | <dl class="keys"> | ||
| 157 | <div> | 162 | <div> | ||
| 158 | <dt> | 163 | <dt> | ||
| @@ -306,72 +311,78 @@ | |||||
| 306 | <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()"> | 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 | <div class="panel-head"> | 312 | <div class="panel-head"> | ||
| 308 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 315 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 311 | <kbd>esc</kbd> | 316 | <kbd>esc</kbd> | ||
| 312 | </button> | 317 | </button> | ||
| 313 | </div> | 318 | </div> | ||
| 314 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 319 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 315 | - | <li data-k="go home" data-cursor> | 320 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 316 | <a href="/" tabindex="-1"> | 321 | <a href="/" tabindex="-1"> | ||
| 317 | <span class="lbl">home</span> | 322 | <span class="lbl">home</span> | ||
| 318 | <span class="k">go</span> | 323 | <span class="k">go</span> | ||
| 319 | </a> | 324 | </a> | ||
| 320 | </li> | 325 | </li> | ||
| 321 | - | <li data-k="section summary"> | 326 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 322 | <a href="/files" tabindex="-1"> | 327 | <a href="/files" tabindex="-1"> | ||
| 323 | <span class="lbl">summary</span> | 328 | <span class="lbl">summary</span> | ||
| 324 | <span class="k">section</span> | 329 | <span class="k">section</span> | ||
| 325 | </a> | 330 | </a> | ||
| 326 | </li> | 331 | </li> | ||
| 327 | - | <li data-k="section files"> | 332 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 328 | <a href="/files/tree/main" tabindex="-1"> | 333 | <a href="/files/tree/main" tabindex="-1"> | ||
| 329 | <span class="lbl">files</span> | 334 | <span class="lbl">files</span> | ||
| 330 | <span class="k">section</span> | 335 | <span class="k">section</span> | ||
| 331 | </a> | 336 | </a> | ||
| 332 | </li> | 337 | </li> | ||
| 333 | - | <li data-k="section commits"> | 338 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 334 | <a href="/files/commits/main" tabindex="-1"> | 339 | <a href="/files/commits/main" tabindex="-1"> | ||
| 335 | <span class="lbl">commits</span> | 340 | <span class="lbl">commits</span> | ||
| 336 | <span class="k">section</span> | 341 | <span class="k">section</span> | ||
| 337 | </a> | 342 | </a> | ||
| 338 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 345 | <a href="/files/refs" tabindex="-1"> | ||
| 341 | <span class="lbl">branches & tags</span> | 346 | <span class="lbl">branches & tags</span> | ||
| 342 | <span class="k">section</span> | 347 | <span class="k">section</span> | ||
| 343 | </a> | 348 | </a> | ||
| 344 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 351 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 347 | <span class="lbl">merge requests</span> | 352 | <span class="lbl">merge requests</span> | ||
| 348 | <span class="k">section</span> | 353 | <span class="k">section</span> | ||
| 349 | </a> | 354 | </a> | ||
| 350 | </li> | 355 | </li> | ||
| 351 | - | <li data-k="section actions"> | 356 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 352 | <a href="/files/actions" tabindex="-1"> | 357 | <a href="/files/actions" tabindex="-1"> | ||
| 353 | <span class="lbl">actions</span> | 358 | <span class="lbl">actions</span> | ||
| 354 | <span class="k">section</span> | 359 | <span class="k">section</span> | ||
| 355 | </a> | 360 | </a> | ||
| 356 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 363 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 359 | <span class="lbl">feature/x</span> | 364 | <span class="lbl">feature/x</span> | ||
| 360 | <span class="k">branch</span> | 365 | <span class="k">branch</span> | ||
| 361 | </a> | 366 | </a> | ||
| 362 | </li> | 367 | </li> | ||
| 363 | - | <li data-k="branch main"> | 368 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 364 | <a href="/files/tree/main" tabindex="-1"> | 369 | <a href="/files/tree/main" tabindex="-1"> | ||
| 365 | <span class="lbl">main</span> | 370 | <span class="lbl">main</span> | ||
| 366 | <span class="k">branch</span> | 371 | <span class="k">branch</span> | ||
| 367 | </a> | 372 | </a> | ||
| 368 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 375 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 371 | <span class="lbl">v1.0</span> | 376 | <span class="lbl">v1.0</span> | ||
| 372 | <span class="k">tag</span> | 377 | <span class="k">tag</span> | ||
| 373 | </a> | 378 | </a> | ||
| 374 | </li> | 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 | </ul> | 386 | </ul> | ||
| 376 | <p class="panel-foot"> | 387 | <p class="panel-foot"> | ||
| 377 | <span> | 388 | <span> | ||
| @@ -410,7 +421,8 @@ | |||||
| 410 | k-permalink from:body -> ermite.permalink(); | 421 | k-permalink from:body -> ermite.permalink(); | ||
| 411 | k-merge from:body -> window.ermiteMR?.openMerge(); | 422 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 412 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 426 | </div> | ||
| 415 | </body> | 427 | </body> | ||
| 416 | </html> | 428 | </html> | ||
M internal/app/testdata/web/blob-code.html +25 -13
| @@ -223,6 +223,11 @@ | |||||
| 223 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 223 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 224 | <kbd>esc</kbd> close</button> | 224 | <kbd>esc</kbd> close</button> | ||
| 225 | </div> | 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 | <dl class="keys"> | 231 | <dl class="keys"> | ||
| 227 | <div> | 232 | <div> | ||
| 228 | <dt> | 233 | <dt> | ||
| @@ -376,72 +381,78 @@ | |||||
| 376 | <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()"> | 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 | <div class="panel-head"> | 382 | <div class="panel-head"> | ||
| 378 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 385 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 381 | <kbd>esc</kbd> | 386 | <kbd>esc</kbd> | ||
| 382 | </button> | 387 | </button> | ||
| 383 | </div> | 388 | </div> | ||
| 384 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 389 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 385 | - | <li data-k="go home" data-cursor> | 390 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 386 | <a href="/" tabindex="-1"> | 391 | <a href="/" tabindex="-1"> | ||
| 387 | <span class="lbl">home</span> | 392 | <span class="lbl">home</span> | ||
| 388 | <span class="k">go</span> | 393 | <span class="k">go</span> | ||
| 389 | </a> | 394 | </a> | ||
| 390 | </li> | 395 | </li> | ||
| 391 | - | <li data-k="section summary"> | 396 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 392 | <a href="/files" tabindex="-1"> | 397 | <a href="/files" tabindex="-1"> | ||
| 393 | <span class="lbl">summary</span> | 398 | <span class="lbl">summary</span> | ||
| 394 | <span class="k">section</span> | 399 | <span class="k">section</span> | ||
| 395 | </a> | 400 | </a> | ||
| 396 | </li> | 401 | </li> | ||
| 397 | - | <li data-k="section files"> | 402 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 398 | <a href="/files/tree/main" tabindex="-1"> | 403 | <a href="/files/tree/main" tabindex="-1"> | ||
| 399 | <span class="lbl">files</span> | 404 | <span class="lbl">files</span> | ||
| 400 | <span class="k">section</span> | 405 | <span class="k">section</span> | ||
| 401 | </a> | 406 | </a> | ||
| 402 | </li> | 407 | </li> | ||
| 403 | - | <li data-k="section commits"> | 408 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 404 | <a href="/files/commits/main" tabindex="-1"> | 409 | <a href="/files/commits/main" tabindex="-1"> | ||
| 405 | <span class="lbl">commits</span> | 410 | <span class="lbl">commits</span> | ||
| 406 | <span class="k">section</span> | 411 | <span class="k">section</span> | ||
| 407 | </a> | 412 | </a> | ||
| 408 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 415 | <a href="/files/refs" tabindex="-1"> | ||
| 411 | <span class="lbl">branches & tags</span> | 416 | <span class="lbl">branches & tags</span> | ||
| 412 | <span class="k">section</span> | 417 | <span class="k">section</span> | ||
| 413 | </a> | 418 | </a> | ||
| 414 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 421 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 417 | <span class="lbl">merge requests</span> | 422 | <span class="lbl">merge requests</span> | ||
| 418 | <span class="k">section</span> | 423 | <span class="k">section</span> | ||
| 419 | </a> | 424 | </a> | ||
| 420 | </li> | 425 | </li> | ||
| 421 | - | <li data-k="section actions"> | 426 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 422 | <a href="/files/actions" tabindex="-1"> | 427 | <a href="/files/actions" tabindex="-1"> | ||
| 423 | <span class="lbl">actions</span> | 428 | <span class="lbl">actions</span> | ||
| 424 | <span class="k">section</span> | 429 | <span class="k">section</span> | ||
| 425 | </a> | 430 | </a> | ||
| 426 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 433 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 429 | <span class="lbl">feature/x</span> | 434 | <span class="lbl">feature/x</span> | ||
| 430 | <span class="k">branch</span> | 435 | <span class="k">branch</span> | ||
| 431 | </a> | 436 | </a> | ||
| 432 | </li> | 437 | </li> | ||
| 433 | - | <li data-k="branch main"> | 438 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 434 | <a href="/files/tree/main" tabindex="-1"> | 439 | <a href="/files/tree/main" tabindex="-1"> | ||
| 435 | <span class="lbl">main</span> | 440 | <span class="lbl">main</span> | ||
| 436 | <span class="k">branch</span> | 441 | <span class="k">branch</span> | ||
| 437 | </a> | 442 | </a> | ||
| 438 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 445 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 441 | <span class="lbl">v1.0</span> | 446 | <span class="lbl">v1.0</span> | ||
| 442 | <span class="k">tag</span> | 447 | <span class="k">tag</span> | ||
| 443 | </a> | 448 | </a> | ||
| 444 | </li> | 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 | </ul> | 456 | </ul> | ||
| 446 | <p class="panel-foot"> | 457 | <p class="panel-foot"> | ||
| 447 | <span> | 458 | <span> | ||
| @@ -480,7 +491,8 @@ | |||||
| 480 | k-permalink from:body -> ermite.permalink(); | 491 | k-permalink from:body -> ermite.permalink(); | ||
| 481 | k-merge from:body -> window.ermiteMR?.openMerge(); | 492 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 482 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 496 | </div> | ||
| 485 | </body> | 497 | </body> | ||
| 486 | </html> | 498 | </html> | ||
M internal/app/testdata/web/blob-empty.html +25 -13
| @@ -153,6 +153,11 @@ | |||||
| 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 154 | <kbd>esc</kbd> close</button> | 154 | <kbd>esc</kbd> close</button> | ||
| 155 | </div> | 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 | <dl class="keys"> | 161 | <dl class="keys"> | ||
| 157 | <div> | 162 | <div> | ||
| 158 | <dt> | 163 | <dt> | ||
| @@ -306,72 +311,78 @@ | |||||
| 306 | <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()"> | 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 | <div class="panel-head"> | 312 | <div class="panel-head"> | ||
| 308 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 315 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 311 | <kbd>esc</kbd> | 316 | <kbd>esc</kbd> | ||
| 312 | </button> | 317 | </button> | ||
| 313 | </div> | 318 | </div> | ||
| 314 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 319 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 315 | - | <li data-k="go home" data-cursor> | 320 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 316 | <a href="/" tabindex="-1"> | 321 | <a href="/" tabindex="-1"> | ||
| 317 | <span class="lbl">home</span> | 322 | <span class="lbl">home</span> | ||
| 318 | <span class="k">go</span> | 323 | <span class="k">go</span> | ||
| 319 | </a> | 324 | </a> | ||
| 320 | </li> | 325 | </li> | ||
| 321 | - | <li data-k="section summary"> | 326 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 322 | <a href="/files" tabindex="-1"> | 327 | <a href="/files" tabindex="-1"> | ||
| 323 | <span class="lbl">summary</span> | 328 | <span class="lbl">summary</span> | ||
| 324 | <span class="k">section</span> | 329 | <span class="k">section</span> | ||
| 325 | </a> | 330 | </a> | ||
| 326 | </li> | 331 | </li> | ||
| 327 | - | <li data-k="section files"> | 332 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 328 | <a href="/files/tree/main" tabindex="-1"> | 333 | <a href="/files/tree/main" tabindex="-1"> | ||
| 329 | <span class="lbl">files</span> | 334 | <span class="lbl">files</span> | ||
| 330 | <span class="k">section</span> | 335 | <span class="k">section</span> | ||
| 331 | </a> | 336 | </a> | ||
| 332 | </li> | 337 | </li> | ||
| 333 | - | <li data-k="section commits"> | 338 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 334 | <a href="/files/commits/main" tabindex="-1"> | 339 | <a href="/files/commits/main" tabindex="-1"> | ||
| 335 | <span class="lbl">commits</span> | 340 | <span class="lbl">commits</span> | ||
| 336 | <span class="k">section</span> | 341 | <span class="k">section</span> | ||
| 337 | </a> | 342 | </a> | ||
| 338 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 345 | <a href="/files/refs" tabindex="-1"> | ||
| 341 | <span class="lbl">branches & tags</span> | 346 | <span class="lbl">branches & tags</span> | ||
| 342 | <span class="k">section</span> | 347 | <span class="k">section</span> | ||
| 343 | </a> | 348 | </a> | ||
| 344 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 351 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 347 | <span class="lbl">merge requests</span> | 352 | <span class="lbl">merge requests</span> | ||
| 348 | <span class="k">section</span> | 353 | <span class="k">section</span> | ||
| 349 | </a> | 354 | </a> | ||
| 350 | </li> | 355 | </li> | ||
| 351 | - | <li data-k="section actions"> | 356 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 352 | <a href="/files/actions" tabindex="-1"> | 357 | <a href="/files/actions" tabindex="-1"> | ||
| 353 | <span class="lbl">actions</span> | 358 | <span class="lbl">actions</span> | ||
| 354 | <span class="k">section</span> | 359 | <span class="k">section</span> | ||
| 355 | </a> | 360 | </a> | ||
| 356 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 363 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 359 | <span class="lbl">feature/x</span> | 364 | <span class="lbl">feature/x</span> | ||
| 360 | <span class="k">branch</span> | 365 | <span class="k">branch</span> | ||
| 361 | </a> | 366 | </a> | ||
| 362 | </li> | 367 | </li> | ||
| 363 | - | <li data-k="branch main"> | 368 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 364 | <a href="/files/tree/main" tabindex="-1"> | 369 | <a href="/files/tree/main" tabindex="-1"> | ||
| 365 | <span class="lbl">main</span> | 370 | <span class="lbl">main</span> | ||
| 366 | <span class="k">branch</span> | 371 | <span class="k">branch</span> | ||
| 367 | </a> | 372 | </a> | ||
| 368 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 375 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 371 | <span class="lbl">v1.0</span> | 376 | <span class="lbl">v1.0</span> | ||
| 372 | <span class="k">tag</span> | 377 | <span class="k">tag</span> | ||
| 373 | </a> | 378 | </a> | ||
| 374 | </li> | 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 | </ul> | 386 | </ul> | ||
| 376 | <p class="panel-foot"> | 387 | <p class="panel-foot"> | ||
| 377 | <span> | 388 | <span> | ||
| @@ -410,7 +421,8 @@ | |||||
| 410 | k-permalink from:body -> ermite.permalink(); | 421 | k-permalink from:body -> ermite.permalink(); | ||
| 411 | k-merge from:body -> window.ermiteMR?.openMerge(); | 422 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 412 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 426 | </div> | ||
| 415 | </body> | 427 | </body> | ||
| 416 | </html> | 428 | </html> | ||
M internal/app/testdata/web/blob-image.html +25 -13
| @@ -155,6 +155,11 @@ | |||||
| 155 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 155 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 156 | <kbd>esc</kbd> close</button> | 156 | <kbd>esc</kbd> close</button> | ||
| 157 | </div> | 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 | <dl class="keys"> | 163 | <dl class="keys"> | ||
| 159 | <div> | 164 | <div> | ||
| 160 | <dt> | 165 | <dt> | ||
| @@ -308,72 +313,78 @@ | |||||
| 308 | <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()"> | 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 | <div class="panel-head"> | 314 | <div class="panel-head"> | ||
| 310 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 317 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 313 | <kbd>esc</kbd> | 318 | <kbd>esc</kbd> | ||
| 314 | </button> | 319 | </button> | ||
| 315 | </div> | 320 | </div> | ||
| 316 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 321 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 317 | - | <li data-k="go home" data-cursor> | 322 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 318 | <a href="/" tabindex="-1"> | 323 | <a href="/" tabindex="-1"> | ||
| 319 | <span class="lbl">home</span> | 324 | <span class="lbl">home</span> | ||
| 320 | <span class="k">go</span> | 325 | <span class="k">go</span> | ||
| 321 | </a> | 326 | </a> | ||
| 322 | </li> | 327 | </li> | ||
| 323 | - | <li data-k="section summary"> | 328 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 324 | <a href="/files" tabindex="-1"> | 329 | <a href="/files" tabindex="-1"> | ||
| 325 | <span class="lbl">summary</span> | 330 | <span class="lbl">summary</span> | ||
| 326 | <span class="k">section</span> | 331 | <span class="k">section</span> | ||
| 327 | </a> | 332 | </a> | ||
| 328 | </li> | 333 | </li> | ||
| 329 | - | <li data-k="section files"> | 334 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 330 | <a href="/files/tree/main" tabindex="-1"> | 335 | <a href="/files/tree/main" tabindex="-1"> | ||
| 331 | <span class="lbl">files</span> | 336 | <span class="lbl">files</span> | ||
| 332 | <span class="k">section</span> | 337 | <span class="k">section</span> | ||
| 333 | </a> | 338 | </a> | ||
| 334 | </li> | 339 | </li> | ||
| 335 | - | <li data-k="section commits"> | 340 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 336 | <a href="/files/commits/main" tabindex="-1"> | 341 | <a href="/files/commits/main" tabindex="-1"> | ||
| 337 | <span class="lbl">commits</span> | 342 | <span class="lbl">commits</span> | ||
| 338 | <span class="k">section</span> | 343 | <span class="k">section</span> | ||
| 339 | </a> | 344 | </a> | ||
| 340 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 347 | <a href="/files/refs" tabindex="-1"> | ||
| 343 | <span class="lbl">branches & tags</span> | 348 | <span class="lbl">branches & tags</span> | ||
| 344 | <span class="k">section</span> | 349 | <span class="k">section</span> | ||
| 345 | </a> | 350 | </a> | ||
| 346 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 353 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 349 | <span class="lbl">merge requests</span> | 354 | <span class="lbl">merge requests</span> | ||
| 350 | <span class="k">section</span> | 355 | <span class="k">section</span> | ||
| 351 | </a> | 356 | </a> | ||
| 352 | </li> | 357 | </li> | ||
| 353 | - | <li data-k="section actions"> | 358 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 354 | <a href="/files/actions" tabindex="-1"> | 359 | <a href="/files/actions" tabindex="-1"> | ||
| 355 | <span class="lbl">actions</span> | 360 | <span class="lbl">actions</span> | ||
| 356 | <span class="k">section</span> | 361 | <span class="k">section</span> | ||
| 357 | </a> | 362 | </a> | ||
| 358 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 365 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 361 | <span class="lbl">feature/x</span> | 366 | <span class="lbl">feature/x</span> | ||
| 362 | <span class="k">branch</span> | 367 | <span class="k">branch</span> | ||
| 363 | </a> | 368 | </a> | ||
| 364 | </li> | 369 | </li> | ||
| 365 | - | <li data-k="branch main"> | 370 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 366 | <a href="/files/tree/main" tabindex="-1"> | 371 | <a href="/files/tree/main" tabindex="-1"> | ||
| 367 | <span class="lbl">main</span> | 372 | <span class="lbl">main</span> | ||
| 368 | <span class="k">branch</span> | 373 | <span class="k">branch</span> | ||
| 369 | </a> | 374 | </a> | ||
| 370 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 377 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 373 | <span class="lbl">v1.0</span> | 378 | <span class="lbl">v1.0</span> | ||
| 374 | <span class="k">tag</span> | 379 | <span class="k">tag</span> | ||
| 375 | </a> | 380 | </a> | ||
| 376 | </li> | 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 | </ul> | 388 | </ul> | ||
| 378 | <p class="panel-foot"> | 389 | <p class="panel-foot"> | ||
| 379 | <span> | 390 | <span> | ||
| @@ -412,7 +423,8 @@ | |||||
| 412 | k-permalink from:body -> ermite.permalink(); | 423 | k-permalink from:body -> ermite.permalink(); | ||
| 413 | k-merge from:body -> window.ermiteMR?.openMerge(); | 424 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 414 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 428 | </div> | ||
| 417 | </body> | 429 | </body> | ||
| 418 | </html> | 430 | </html> | ||
M internal/app/testdata/web/blob-markdown.html +25 -13
| @@ -220,6 +220,11 @@ | |||||
| 220 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 220 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 221 | <kbd>esc</kbd> close</button> | 221 | <kbd>esc</kbd> close</button> | ||
| 222 | </div> | 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 | <dl class="keys"> | 228 | <dl class="keys"> | ||
| 224 | <div> | 229 | <div> | ||
| 225 | <dt> | 230 | <dt> | ||
| @@ -373,72 +378,78 @@ | |||||
| 373 | <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()"> | 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 | <div class="panel-head"> | 379 | <div class="panel-head"> | ||
| 375 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 382 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 378 | <kbd>esc</kbd> | 383 | <kbd>esc</kbd> | ||
| 379 | </button> | 384 | </button> | ||
| 380 | </div> | 385 | </div> | ||
| 381 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 386 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 382 | - | <li data-k="go home" data-cursor> | 387 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 383 | <a href="/" tabindex="-1"> | 388 | <a href="/" tabindex="-1"> | ||
| 384 | <span class="lbl">home</span> | 389 | <span class="lbl">home</span> | ||
| 385 | <span class="k">go</span> | 390 | <span class="k">go</span> | ||
| 386 | </a> | 391 | </a> | ||
| 387 | </li> | 392 | </li> | ||
| 388 | - | <li data-k="section summary"> | 393 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 389 | <a href="/files" tabindex="-1"> | 394 | <a href="/files" tabindex="-1"> | ||
| 390 | <span class="lbl">summary</span> | 395 | <span class="lbl">summary</span> | ||
| 391 | <span class="k">section</span> | 396 | <span class="k">section</span> | ||
| 392 | </a> | 397 | </a> | ||
| 393 | </li> | 398 | </li> | ||
| 394 | - | <li data-k="section files"> | 399 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 395 | <a href="/files/tree/main" tabindex="-1"> | 400 | <a href="/files/tree/main" tabindex="-1"> | ||
| 396 | <span class="lbl">files</span> | 401 | <span class="lbl">files</span> | ||
| 397 | <span class="k">section</span> | 402 | <span class="k">section</span> | ||
| 398 | </a> | 403 | </a> | ||
| 399 | </li> | 404 | </li> | ||
| 400 | - | <li data-k="section commits"> | 405 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 401 | <a href="/files/commits/main" tabindex="-1"> | 406 | <a href="/files/commits/main" tabindex="-1"> | ||
| 402 | <span class="lbl">commits</span> | 407 | <span class="lbl">commits</span> | ||
| 403 | <span class="k">section</span> | 408 | <span class="k">section</span> | ||
| 404 | </a> | 409 | </a> | ||
| 405 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 412 | <a href="/files/refs" tabindex="-1"> | ||
| 408 | <span class="lbl">branches & tags</span> | 413 | <span class="lbl">branches & tags</span> | ||
| 409 | <span class="k">section</span> | 414 | <span class="k">section</span> | ||
| 410 | </a> | 415 | </a> | ||
| 411 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 418 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 414 | <span class="lbl">merge requests</span> | 419 | <span class="lbl">merge requests</span> | ||
| 415 | <span class="k">section</span> | 420 | <span class="k">section</span> | ||
| 416 | </a> | 421 | </a> | ||
| 417 | </li> | 422 | </li> | ||
| 418 | - | <li data-k="section actions"> | 423 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 419 | <a href="/files/actions" tabindex="-1"> | 424 | <a href="/files/actions" tabindex="-1"> | ||
| 420 | <span class="lbl">actions</span> | 425 | <span class="lbl">actions</span> | ||
| 421 | <span class="k">section</span> | 426 | <span class="k">section</span> | ||
| 422 | </a> | 427 | </a> | ||
| 423 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 430 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 426 | <span class="lbl">feature/x</span> | 431 | <span class="lbl">feature/x</span> | ||
| 427 | <span class="k">branch</span> | 432 | <span class="k">branch</span> | ||
| 428 | </a> | 433 | </a> | ||
| 429 | </li> | 434 | </li> | ||
| 430 | - | <li data-k="branch main"> | 435 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 431 | <a href="/files/tree/main" tabindex="-1"> | 436 | <a href="/files/tree/main" tabindex="-1"> | ||
| 432 | <span class="lbl">main</span> | 437 | <span class="lbl">main</span> | ||
| 433 | <span class="k">branch</span> | 438 | <span class="k">branch</span> | ||
| 434 | </a> | 439 | </a> | ||
| 435 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 442 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 438 | <span class="lbl">v1.0</span> | 443 | <span class="lbl">v1.0</span> | ||
| 439 | <span class="k">tag</span> | 444 | <span class="k">tag</span> | ||
| 440 | </a> | 445 | </a> | ||
| 441 | </li> | 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 | </ul> | 453 | </ul> | ||
| 443 | <p class="panel-foot"> | 454 | <p class="panel-foot"> | ||
| 444 | <span> | 455 | <span> | ||
| @@ -477,7 +488,8 @@ | |||||
| 477 | k-permalink from:body -> ermite.permalink(); | 488 | k-permalink from:body -> ermite.permalink(); | ||
| 478 | k-merge from:body -> window.ermiteMR?.openMerge(); | 489 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 479 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 493 | </div> | ||
| 482 | </body> | 494 | </body> | ||
| 483 | </html> | 495 | </html> | ||
M internal/app/testdata/web/blob-svg.html +25 -13
| @@ -178,6 +178,11 @@ | |||||
| 178 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 178 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 179 | <kbd>esc</kbd> close</button> | 179 | <kbd>esc</kbd> close</button> | ||
| 180 | </div> | 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 | <dl class="keys"> | 186 | <dl class="keys"> | ||
| 182 | <div> | 187 | <div> | ||
| 183 | <dt> | 188 | <dt> | ||
| @@ -331,72 +336,78 @@ | |||||
| 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()"> | 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 | <div class="panel-head"> | 337 | <div class="panel-head"> | ||
| 333 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 340 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 336 | <kbd>esc</kbd> | 341 | <kbd>esc</kbd> | ||
| 337 | </button> | 342 | </button> | ||
| 338 | </div> | 343 | </div> | ||
| 339 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 344 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 340 | - | <li data-k="go home" data-cursor> | 345 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 341 | <a href="/" tabindex="-1"> | 346 | <a href="/" tabindex="-1"> | ||
| 342 | <span class="lbl">home</span> | 347 | <span class="lbl">home</span> | ||
| 343 | <span class="k">go</span> | 348 | <span class="k">go</span> | ||
| 344 | </a> | 349 | </a> | ||
| 345 | </li> | 350 | </li> | ||
| 346 | - | <li data-k="section summary"> | 351 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 347 | <a href="/files" tabindex="-1"> | 352 | <a href="/files" tabindex="-1"> | ||
| 348 | <span class="lbl">summary</span> | 353 | <span class="lbl">summary</span> | ||
| 349 | <span class="k">section</span> | 354 | <span class="k">section</span> | ||
| 350 | </a> | 355 | </a> | ||
| 351 | </li> | 356 | </li> | ||
| 352 | - | <li data-k="section files"> | 357 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 353 | <a href="/files/tree/main" tabindex="-1"> | 358 | <a href="/files/tree/main" tabindex="-1"> | ||
| 354 | <span class="lbl">files</span> | 359 | <span class="lbl">files</span> | ||
| 355 | <span class="k">section</span> | 360 | <span class="k">section</span> | ||
| 356 | </a> | 361 | </a> | ||
| 357 | </li> | 362 | </li> | ||
| 358 | - | <li data-k="section commits"> | 363 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 359 | <a href="/files/commits/main" tabindex="-1"> | 364 | <a href="/files/commits/main" tabindex="-1"> | ||
| 360 | <span class="lbl">commits</span> | 365 | <span class="lbl">commits</span> | ||
| 361 | <span class="k">section</span> | 366 | <span class="k">section</span> | ||
| 362 | </a> | 367 | </a> | ||
| 363 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 370 | <a href="/files/refs" tabindex="-1"> | ||
| 366 | <span class="lbl">branches & tags</span> | 371 | <span class="lbl">branches & tags</span> | ||
| 367 | <span class="k">section</span> | 372 | <span class="k">section</span> | ||
| 368 | </a> | 373 | </a> | ||
| 369 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 376 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 372 | <span class="lbl">merge requests</span> | 377 | <span class="lbl">merge requests</span> | ||
| 373 | <span class="k">section</span> | 378 | <span class="k">section</span> | ||
| 374 | </a> | 379 | </a> | ||
| 375 | </li> | 380 | </li> | ||
| 376 | - | <li data-k="section actions"> | 381 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 377 | <a href="/files/actions" tabindex="-1"> | 382 | <a href="/files/actions" tabindex="-1"> | ||
| 378 | <span class="lbl">actions</span> | 383 | <span class="lbl">actions</span> | ||
| 379 | <span class="k">section</span> | 384 | <span class="k">section</span> | ||
| 380 | </a> | 385 | </a> | ||
| 381 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 388 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 384 | <span class="lbl">feature/x</span> | 389 | <span class="lbl">feature/x</span> | ||
| 385 | <span class="k">branch</span> | 390 | <span class="k">branch</span> | ||
| 386 | </a> | 391 | </a> | ||
| 387 | </li> | 392 | </li> | ||
| 388 | - | <li data-k="branch main"> | 393 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 389 | <a href="/files/tree/main" tabindex="-1"> | 394 | <a href="/files/tree/main" tabindex="-1"> | ||
| 390 | <span class="lbl">main</span> | 395 | <span class="lbl">main</span> | ||
| 391 | <span class="k">branch</span> | 396 | <span class="k">branch</span> | ||
| 392 | </a> | 397 | </a> | ||
| 393 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 400 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 396 | <span class="lbl">v1.0</span> | 401 | <span class="lbl">v1.0</span> | ||
| 397 | <span class="k">tag</span> | 402 | <span class="k">tag</span> | ||
| 398 | </a> | 403 | </a> | ||
| 399 | </li> | 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 | </ul> | 411 | </ul> | ||
| 401 | <p class="panel-foot"> | 412 | <p class="panel-foot"> | ||
| 402 | <span> | 413 | <span> | ||
| @@ -435,7 +446,8 @@ | |||||
| 435 | k-permalink from:body -> ermite.permalink(); | 446 | k-permalink from:body -> ermite.permalink(); | ||
| 436 | k-merge from:body -> window.ermiteMR?.openMerge(); | 447 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 437 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 451 | </div> | ||
| 440 | </body> | 452 | </body> | ||
| 441 | </html> | 453 | </html> | ||
M internal/app/testdata/web/blob-text.html +25 -13
| @@ -173,6 +173,11 @@ | |||||
| 173 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 173 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 174 | <kbd>esc</kbd> close</button> | 174 | <kbd>esc</kbd> close</button> | ||
| 175 | </div> | 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 | <dl class="keys"> | 181 | <dl class="keys"> | ||
| 177 | <div> | 182 | <div> | ||
| 178 | <dt> | 183 | <dt> | ||
| @@ -326,72 +331,78 @@ | |||||
| 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()"> | 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 | <div class="panel-head"> | 332 | <div class="panel-head"> | ||
| 328 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 335 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 331 | <kbd>esc</kbd> | 336 | <kbd>esc</kbd> | ||
| 332 | </button> | 337 | </button> | ||
| 333 | </div> | 338 | </div> | ||
| 334 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 339 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 335 | - | <li data-k="go home" data-cursor> | 340 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 336 | <a href="/" tabindex="-1"> | 341 | <a href="/" tabindex="-1"> | ||
| 337 | <span class="lbl">home</span> | 342 | <span class="lbl">home</span> | ||
| 338 | <span class="k">go</span> | 343 | <span class="k">go</span> | ||
| 339 | </a> | 344 | </a> | ||
| 340 | </li> | 345 | </li> | ||
| 341 | - | <li data-k="section summary"> | 346 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 342 | <a href="/files" tabindex="-1"> | 347 | <a href="/files" tabindex="-1"> | ||
| 343 | <span class="lbl">summary</span> | 348 | <span class="lbl">summary</span> | ||
| 344 | <span class="k">section</span> | 349 | <span class="k">section</span> | ||
| 345 | </a> | 350 | </a> | ||
| 346 | </li> | 351 | </li> | ||
| 347 | - | <li data-k="section files"> | 352 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 348 | <a href="/files/tree/main" tabindex="-1"> | 353 | <a href="/files/tree/main" tabindex="-1"> | ||
| 349 | <span class="lbl">files</span> | 354 | <span class="lbl">files</span> | ||
| 350 | <span class="k">section</span> | 355 | <span class="k">section</span> | ||
| 351 | </a> | 356 | </a> | ||
| 352 | </li> | 357 | </li> | ||
| 353 | - | <li data-k="section commits"> | 358 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 354 | <a href="/files/commits/main" tabindex="-1"> | 359 | <a href="/files/commits/main" tabindex="-1"> | ||
| 355 | <span class="lbl">commits</span> | 360 | <span class="lbl">commits</span> | ||
| 356 | <span class="k">section</span> | 361 | <span class="k">section</span> | ||
| 357 | </a> | 362 | </a> | ||
| 358 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 365 | <a href="/files/refs" tabindex="-1"> | ||
| 361 | <span class="lbl">branches & tags</span> | 366 | <span class="lbl">branches & tags</span> | ||
| 362 | <span class="k">section</span> | 367 | <span class="k">section</span> | ||
| 363 | </a> | 368 | </a> | ||
| 364 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 371 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 367 | <span class="lbl">merge requests</span> | 372 | <span class="lbl">merge requests</span> | ||
| 368 | <span class="k">section</span> | 373 | <span class="k">section</span> | ||
| 369 | </a> | 374 | </a> | ||
| 370 | </li> | 375 | </li> | ||
| 371 | - | <li data-k="section actions"> | 376 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 372 | <a href="/files/actions" tabindex="-1"> | 377 | <a href="/files/actions" tabindex="-1"> | ||
| 373 | <span class="lbl">actions</span> | 378 | <span class="lbl">actions</span> | ||
| 374 | <span class="k">section</span> | 379 | <span class="k">section</span> | ||
| 375 | </a> | 380 | </a> | ||
| 376 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 383 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 379 | <span class="lbl">feature/x</span> | 384 | <span class="lbl">feature/x</span> | ||
| 380 | <span class="k">branch</span> | 385 | <span class="k">branch</span> | ||
| 381 | </a> | 386 | </a> | ||
| 382 | </li> | 387 | </li> | ||
| 383 | - | <li data-k="branch main"> | 388 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 384 | <a href="/files/tree/main" tabindex="-1"> | 389 | <a href="/files/tree/main" tabindex="-1"> | ||
| 385 | <span class="lbl">main</span> | 390 | <span class="lbl">main</span> | ||
| 386 | <span class="k">branch</span> | 391 | <span class="k">branch</span> | ||
| 387 | </a> | 392 | </a> | ||
| 388 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 395 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 391 | <span class="lbl">v1.0</span> | 396 | <span class="lbl">v1.0</span> | ||
| 392 | <span class="k">tag</span> | 397 | <span class="k">tag</span> | ||
| 393 | </a> | 398 | </a> | ||
| 394 | </li> | 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 | </ul> | 406 | </ul> | ||
| 396 | <p class="panel-foot"> | 407 | <p class="panel-foot"> | ||
| 397 | <span> | 408 | <span> | ||
| @@ -430,7 +441,8 @@ | |||||
| 430 | k-permalink from:body -> ermite.permalink(); | 441 | k-permalink from:body -> ermite.permalink(); | ||
| 431 | k-merge from:body -> window.ermiteMR?.openMerge(); | 442 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 432 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 446 | </div> | ||
| 435 | </body> | 447 | </body> | ||
| 436 | </html> | 448 | </html> | ||
M internal/app/testdata/web/blob-too-large.html +25 -13
| @@ -153,6 +153,11 @@ | |||||
| 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 153 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 154 | <kbd>esc</kbd> close</button> | 154 | <kbd>esc</kbd> close</button> | ||
| 155 | </div> | 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 | <dl class="keys"> | 161 | <dl class="keys"> | ||
| 157 | <div> | 162 | <div> | ||
| 158 | <dt> | 163 | <dt> | ||
| @@ -306,72 +311,78 @@ | |||||
| 306 | <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()"> | 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 | <div class="panel-head"> | 312 | <div class="panel-head"> | ||
| 308 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 315 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 311 | <kbd>esc</kbd> | 316 | <kbd>esc</kbd> | ||
| 312 | </button> | 317 | </button> | ||
| 313 | </div> | 318 | </div> | ||
| 314 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 319 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 315 | - | <li data-k="go home" data-cursor> | 320 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 316 | <a href="/" tabindex="-1"> | 321 | <a href="/" tabindex="-1"> | ||
| 317 | <span class="lbl">home</span> | 322 | <span class="lbl">home</span> | ||
| 318 | <span class="k">go</span> | 323 | <span class="k">go</span> | ||
| 319 | </a> | 324 | </a> | ||
| 320 | </li> | 325 | </li> | ||
| 321 | - | <li data-k="section summary"> | 326 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 322 | <a href="/files" tabindex="-1"> | 327 | <a href="/files" tabindex="-1"> | ||
| 323 | <span class="lbl">summary</span> | 328 | <span class="lbl">summary</span> | ||
| 324 | <span class="k">section</span> | 329 | <span class="k">section</span> | ||
| 325 | </a> | 330 | </a> | ||
| 326 | </li> | 331 | </li> | ||
| 327 | - | <li data-k="section files"> | 332 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 328 | <a href="/files/tree/main" tabindex="-1"> | 333 | <a href="/files/tree/main" tabindex="-1"> | ||
| 329 | <span class="lbl">files</span> | 334 | <span class="lbl">files</span> | ||
| 330 | <span class="k">section</span> | 335 | <span class="k">section</span> | ||
| 331 | </a> | 336 | </a> | ||
| 332 | </li> | 337 | </li> | ||
| 333 | - | <li data-k="section commits"> | 338 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 334 | <a href="/files/commits/main" tabindex="-1"> | 339 | <a href="/files/commits/main" tabindex="-1"> | ||
| 335 | <span class="lbl">commits</span> | 340 | <span class="lbl">commits</span> | ||
| 336 | <span class="k">section</span> | 341 | <span class="k">section</span> | ||
| 337 | </a> | 342 | </a> | ||
| 338 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 345 | <a href="/files/refs" tabindex="-1"> | ||
| 341 | <span class="lbl">branches & tags</span> | 346 | <span class="lbl">branches & tags</span> | ||
| 342 | <span class="k">section</span> | 347 | <span class="k">section</span> | ||
| 343 | </a> | 348 | </a> | ||
| 344 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 351 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 347 | <span class="lbl">merge requests</span> | 352 | <span class="lbl">merge requests</span> | ||
| 348 | <span class="k">section</span> | 353 | <span class="k">section</span> | ||
| 349 | </a> | 354 | </a> | ||
| 350 | </li> | 355 | </li> | ||
| 351 | - | <li data-k="section actions"> | 356 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 352 | <a href="/files/actions" tabindex="-1"> | 357 | <a href="/files/actions" tabindex="-1"> | ||
| 353 | <span class="lbl">actions</span> | 358 | <span class="lbl">actions</span> | ||
| 354 | <span class="k">section</span> | 359 | <span class="k">section</span> | ||
| 355 | </a> | 360 | </a> | ||
| 356 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 363 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 359 | <span class="lbl">feature/x</span> | 364 | <span class="lbl">feature/x</span> | ||
| 360 | <span class="k">branch</span> | 365 | <span class="k">branch</span> | ||
| 361 | </a> | 366 | </a> | ||
| 362 | </li> | 367 | </li> | ||
| 363 | - | <li data-k="branch main"> | 368 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 364 | <a href="/files/tree/main" tabindex="-1"> | 369 | <a href="/files/tree/main" tabindex="-1"> | ||
| 365 | <span class="lbl">main</span> | 370 | <span class="lbl">main</span> | ||
| 366 | <span class="k">branch</span> | 371 | <span class="k">branch</span> | ||
| 367 | </a> | 372 | </a> | ||
| 368 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 375 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 371 | <span class="lbl">v1.0</span> | 376 | <span class="lbl">v1.0</span> | ||
| 372 | <span class="k">tag</span> | 377 | <span class="k">tag</span> | ||
| 373 | </a> | 378 | </a> | ||
| 374 | </li> | 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 | </ul> | 386 | </ul> | ||
| 376 | <p class="panel-foot"> | 387 | <p class="panel-foot"> | ||
| 377 | <span> | 388 | <span> | ||
| @@ -410,7 +421,8 @@ | |||||
| 410 | k-permalink from:body -> ermite.permalink(); | 421 | k-permalink from:body -> ermite.permalink(); | ||
| 411 | k-merge from:body -> window.ermiteMR?.openMerge(); | 422 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 412 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 426 | </div> | ||
| 415 | </body> | 427 | </body> | ||
| 416 | </html> | 428 | </html> | ||
M internal/app/testdata/web/blob-trojan.html +25 -13
| @@ -220,6 +220,11 @@ | |||||
| 220 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 220 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 221 | <kbd>esc</kbd> close</button> | 221 | <kbd>esc</kbd> close</button> | ||
| 222 | </div> | 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 | <dl class="keys"> | 228 | <dl class="keys"> | ||
| 224 | <div> | 229 | <div> | ||
| 225 | <dt> | 230 | <dt> | ||
| @@ -373,72 +378,78 @@ | |||||
| 373 | <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()"> | 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 | <div class="panel-head"> | 379 | <div class="panel-head"> | ||
| 375 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 382 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 378 | <kbd>esc</kbd> | 383 | <kbd>esc</kbd> | ||
| 379 | </button> | 384 | </button> | ||
| 380 | </div> | 385 | </div> | ||
| 381 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 386 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 382 | - | <li data-k="go home" data-cursor> | 387 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 383 | <a href="/" tabindex="-1"> | 388 | <a href="/" tabindex="-1"> | ||
| 384 | <span class="lbl">home</span> | 389 | <span class="lbl">home</span> | ||
| 385 | <span class="k">go</span> | 390 | <span class="k">go</span> | ||
| 386 | </a> | 391 | </a> | ||
| 387 | </li> | 392 | </li> | ||
| 388 | - | <li data-k="section summary"> | 393 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 389 | <a href="/files" tabindex="-1"> | 394 | <a href="/files" tabindex="-1"> | ||
| 390 | <span class="lbl">summary</span> | 395 | <span class="lbl">summary</span> | ||
| 391 | <span class="k">section</span> | 396 | <span class="k">section</span> | ||
| 392 | </a> | 397 | </a> | ||
| 393 | </li> | 398 | </li> | ||
| 394 | - | <li data-k="section files"> | 399 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 395 | <a href="/files/tree/main" tabindex="-1"> | 400 | <a href="/files/tree/main" tabindex="-1"> | ||
| 396 | <span class="lbl">files</span> | 401 | <span class="lbl">files</span> | ||
| 397 | <span class="k">section</span> | 402 | <span class="k">section</span> | ||
| 398 | </a> | 403 | </a> | ||
| 399 | </li> | 404 | </li> | ||
| 400 | - | <li data-k="section commits"> | 405 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 401 | <a href="/files/commits/main" tabindex="-1"> | 406 | <a href="/files/commits/main" tabindex="-1"> | ||
| 402 | <span class="lbl">commits</span> | 407 | <span class="lbl">commits</span> | ||
| 403 | <span class="k">section</span> | 408 | <span class="k">section</span> | ||
| 404 | </a> | 409 | </a> | ||
| 405 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 412 | <a href="/files/refs" tabindex="-1"> | ||
| 408 | <span class="lbl">branches & tags</span> | 413 | <span class="lbl">branches & tags</span> | ||
| 409 | <span class="k">section</span> | 414 | <span class="k">section</span> | ||
| 410 | </a> | 415 | </a> | ||
| 411 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 418 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 414 | <span class="lbl">merge requests</span> | 419 | <span class="lbl">merge requests</span> | ||
| 415 | <span class="k">section</span> | 420 | <span class="k">section</span> | ||
| 416 | </a> | 421 | </a> | ||
| 417 | </li> | 422 | </li> | ||
| 418 | - | <li data-k="section actions"> | 423 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 419 | <a href="/files/actions" tabindex="-1"> | 424 | <a href="/files/actions" tabindex="-1"> | ||
| 420 | <span class="lbl">actions</span> | 425 | <span class="lbl">actions</span> | ||
| 421 | <span class="k">section</span> | 426 | <span class="k">section</span> | ||
| 422 | </a> | 427 | </a> | ||
| 423 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 430 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 426 | <span class="lbl">feature/x</span> | 431 | <span class="lbl">feature/x</span> | ||
| 427 | <span class="k">branch</span> | 432 | <span class="k">branch</span> | ||
| 428 | </a> | 433 | </a> | ||
| 429 | </li> | 434 | </li> | ||
| 430 | - | <li data-k="branch main"> | 435 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 431 | <a href="/files/tree/main" tabindex="-1"> | 436 | <a href="/files/tree/main" tabindex="-1"> | ||
| 432 | <span class="lbl">main</span> | 437 | <span class="lbl">main</span> | ||
| 433 | <span class="k">branch</span> | 438 | <span class="k">branch</span> | ||
| 434 | </a> | 439 | </a> | ||
| 435 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 442 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 438 | <span class="lbl">v1.0</span> | 443 | <span class="lbl">v1.0</span> | ||
| 439 | <span class="k">tag</span> | 444 | <span class="k">tag</span> | ||
| 440 | </a> | 445 | </a> | ||
| 441 | </li> | 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 | </ul> | 453 | </ul> | ||
| 443 | <p class="panel-foot"> | 454 | <p class="panel-foot"> | ||
| 444 | <span> | 455 | <span> | ||
| @@ -477,7 +488,8 @@ | |||||
| 477 | k-permalink from:body -> ermite.permalink(); | 488 | k-permalink from:body -> ermite.permalink(); | ||
| 478 | k-merge from:body -> window.ermiteMR?.openMerge(); | 489 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 479 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 493 | </div> | ||
| 482 | </body> | 494 | </body> | ||
| 483 | </html> | 495 | </html> | ||
M internal/app/testdata/web/commit-big.html +26 -14
| @@ -117,7 +117,7 @@ | |||||
| 117 | </dd> | 117 | </dd> | ||
| 118 | </dl> | 118 | </dl> | ||
| 119 | </header> | 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 | <span class="dim">1 file · <span class="plus">+2600</span> | 121 | <span class="dim">1 file · <span class="plus">+2600</span> | ||
| 122 | <span class="minus">-0</span> | 122 | <span class="minus">-0</span> | ||
| 123 | </span> | 123 | </span> | ||
| @@ -191,6 +191,11 @@ | |||||
| 191 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 191 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 192 | <kbd>esc</kbd> close</button> | 192 | <kbd>esc</kbd> close</button> | ||
| 193 | </div> | 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 | <dl class="keys"> | 199 | <dl class="keys"> | ||
| 195 | <div> | 200 | <div> | ||
| 196 | <dt> | 201 | <dt> | ||
| @@ -344,72 +349,78 @@ | |||||
| 344 | <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()"> | 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 | <div class="panel-head"> | 350 | <div class="panel-head"> | ||
| 346 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 353 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 349 | <kbd>esc</kbd> | 354 | <kbd>esc</kbd> | ||
| 350 | </button> | 355 | </button> | ||
| 351 | </div> | 356 | </div> | ||
| 352 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 357 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 353 | - | <li data-k="go home" data-cursor> | 358 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 354 | <a href="/" tabindex="-1"> | 359 | <a href="/" tabindex="-1"> | ||
| 355 | <span class="lbl">home</span> | 360 | <span class="lbl">home</span> | ||
| 356 | <span class="k">go</span> | 361 | <span class="k">go</span> | ||
| 357 | </a> | 362 | </a> | ||
| 358 | </li> | 363 | </li> | ||
| 359 | - | <li data-k="section summary"> | 364 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 360 | <a href="/diffs" tabindex="-1"> | 365 | <a href="/diffs" tabindex="-1"> | ||
| 361 | <span class="lbl">summary</span> | 366 | <span class="lbl">summary</span> | ||
| 362 | <span class="k">section</span> | 367 | <span class="k">section</span> | ||
| 363 | </a> | 368 | </a> | ||
| 364 | </li> | 369 | </li> | ||
| 365 | - | <li data-k="section files"> | 370 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 366 | <a href="/diffs/tree/main" tabindex="-1"> | 371 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 367 | <span class="lbl">files</span> | 372 | <span class="lbl">files</span> | ||
| 368 | <span class="k">section</span> | 373 | <span class="k">section</span> | ||
| 369 | </a> | 374 | </a> | ||
| 370 | </li> | 375 | </li> | ||
| 371 | - | <li data-k="section commits"> | 376 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 372 | <a href="/diffs/commits/main" tabindex="-1"> | 377 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 373 | <span class="lbl">commits</span> | 378 | <span class="lbl">commits</span> | ||
| 374 | <span class="k">section</span> | 379 | <span class="k">section</span> | ||
| 375 | </a> | 380 | </a> | ||
| 376 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 383 | <a href="/diffs/refs" tabindex="-1"> | ||
| 379 | <span class="lbl">branches & tags</span> | 384 | <span class="lbl">branches & tags</span> | ||
| 380 | <span class="k">section</span> | 385 | <span class="k">section</span> | ||
| 381 | </a> | 386 | </a> | ||
| 382 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 389 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 385 | <span class="lbl">merge requests</span> | 390 | <span class="lbl">merge requests</span> | ||
| 386 | <span class="k">section</span> | 391 | <span class="k">section</span> | ||
| 387 | </a> | 392 | </a> | ||
| 388 | </li> | 393 | </li> | ||
| 389 | - | <li data-k="section actions"> | 394 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 390 | <a href="/diffs/actions" tabindex="-1"> | 395 | <a href="/diffs/actions" tabindex="-1"> | ||
| 391 | <span class="lbl">actions</span> | 396 | <span class="lbl">actions</span> | ||
| 392 | <span class="k">section</span> | 397 | <span class="k">section</span> | ||
| 393 | </a> | 398 | </a> | ||
| 394 | </li> | 399 | </li> | ||
| 395 | - | <li data-k="branch main"> | 400 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 396 | <a href="/diffs/tree/main" tabindex="-1"> | 401 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 397 | <span class="lbl">main</span> | 402 | <span class="lbl">main</span> | ||
| 398 | <span class="k">branch</span> | 403 | <span class="k">branch</span> | ||
| 399 | </a> | 404 | </a> | ||
| 400 | </li> | 405 | </li> | ||
| 401 | - | <li data-k="branch side"> | 406 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 402 | <a href="/diffs/tree/side" tabindex="-1"> | 407 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 403 | <span class="lbl">side</span> | 408 | <span class="lbl">side</span> | ||
| 404 | <span class="k">branch</span> | 409 | <span class="k">branch</span> | ||
| 405 | </a> | 410 | </a> | ||
| 406 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 413 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 409 | <span class="lbl">v1.0.0</span> | 414 | <span class="lbl">v1.0.0</span> | ||
| 410 | <span class="k">tag</span> | 415 | <span class="k">tag</span> | ||
| 411 | </a> | 416 | </a> | ||
| 412 | </li> | 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 | </ul> | 424 | </ul> | ||
| 414 | <p class="panel-foot"> | 425 | <p class="panel-foot"> | ||
| 415 | <span> | 426 | <span> | ||
| @@ -448,7 +459,8 @@ | |||||
| 448 | k-permalink from:body -> ermite.permalink(); | 459 | k-permalink from:body -> ermite.permalink(); | ||
| 449 | k-merge from:body -> window.ermiteMR?.openMerge(); | 460 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 450 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 464 | </div> | ||
| 453 | </body> | 465 | </body> | ||
| 454 | </html> | 466 | </html> | ||
M internal/app/testdata/web/commit-binary.html +26 -14
| @@ -113,7 +113,7 @@ | |||||
| 113 | </dd> | 113 | </dd> | ||
| 114 | </dl> | 114 | </dl> | ||
| 115 | </header> | 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 | <span class="dim">1 file · <span class="plus">+0</span> | 117 | <span class="dim">1 file · <span class="plus">+0</span> | ||
| 118 | <span class="minus">-0</span> | 118 | <span class="minus">-0</span> | ||
| 119 | </span> | 119 | </span> | ||
| @@ -183,6 +183,11 @@ | |||||
| 183 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 183 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 184 | <kbd>esc</kbd> close</button> | 184 | <kbd>esc</kbd> close</button> | ||
| 185 | </div> | 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 | <dl class="keys"> | 191 | <dl class="keys"> | ||
| 187 | <div> | 192 | <div> | ||
| 188 | <dt> | 193 | <dt> | ||
| @@ -336,72 +341,78 @@ | |||||
| 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()"> | 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 | <div class="panel-head"> | 342 | <div class="panel-head"> | ||
| 338 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 345 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 341 | <kbd>esc</kbd> | 346 | <kbd>esc</kbd> | ||
| 342 | </button> | 347 | </button> | ||
| 343 | </div> | 348 | </div> | ||
| 344 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 349 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 345 | - | <li data-k="go home" data-cursor> | 350 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 346 | <a href="/" tabindex="-1"> | 351 | <a href="/" tabindex="-1"> | ||
| 347 | <span class="lbl">home</span> | 352 | <span class="lbl">home</span> | ||
| 348 | <span class="k">go</span> | 353 | <span class="k">go</span> | ||
| 349 | </a> | 354 | </a> | ||
| 350 | </li> | 355 | </li> | ||
| 351 | - | <li data-k="section summary"> | 356 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 352 | <a href="/diffs" tabindex="-1"> | 357 | <a href="/diffs" tabindex="-1"> | ||
| 353 | <span class="lbl">summary</span> | 358 | <span class="lbl">summary</span> | ||
| 354 | <span class="k">section</span> | 359 | <span class="k">section</span> | ||
| 355 | </a> | 360 | </a> | ||
| 356 | </li> | 361 | </li> | ||
| 357 | - | <li data-k="section files"> | 362 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 358 | <a href="/diffs/tree/main" tabindex="-1"> | 363 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 359 | <span class="lbl">files</span> | 364 | <span class="lbl">files</span> | ||
| 360 | <span class="k">section</span> | 365 | <span class="k">section</span> | ||
| 361 | </a> | 366 | </a> | ||
| 362 | </li> | 367 | </li> | ||
| 363 | - | <li data-k="section commits"> | 368 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 364 | <a href="/diffs/commits/main" tabindex="-1"> | 369 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 365 | <span class="lbl">commits</span> | 370 | <span class="lbl">commits</span> | ||
| 366 | <span class="k">section</span> | 371 | <span class="k">section</span> | ||
| 367 | </a> | 372 | </a> | ||
| 368 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 375 | <a href="/diffs/refs" tabindex="-1"> | ||
| 371 | <span class="lbl">branches & tags</span> | 376 | <span class="lbl">branches & tags</span> | ||
| 372 | <span class="k">section</span> | 377 | <span class="k">section</span> | ||
| 373 | </a> | 378 | </a> | ||
| 374 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 381 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 377 | <span class="lbl">merge requests</span> | 382 | <span class="lbl">merge requests</span> | ||
| 378 | <span class="k">section</span> | 383 | <span class="k">section</span> | ||
| 379 | </a> | 384 | </a> | ||
| 380 | </li> | 385 | </li> | ||
| 381 | - | <li data-k="section actions"> | 386 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 382 | <a href="/diffs/actions" tabindex="-1"> | 387 | <a href="/diffs/actions" tabindex="-1"> | ||
| 383 | <span class="lbl">actions</span> | 388 | <span class="lbl">actions</span> | ||
| 384 | <span class="k">section</span> | 389 | <span class="k">section</span> | ||
| 385 | </a> | 390 | </a> | ||
| 386 | </li> | 391 | </li> | ||
| 387 | - | <li data-k="branch main"> | 392 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 388 | <a href="/diffs/tree/main" tabindex="-1"> | 393 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 389 | <span class="lbl">main</span> | 394 | <span class="lbl">main</span> | ||
| 390 | <span class="k">branch</span> | 395 | <span class="k">branch</span> | ||
| 391 | </a> | 396 | </a> | ||
| 392 | </li> | 397 | </li> | ||
| 393 | - | <li data-k="branch side"> | 398 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 394 | <a href="/diffs/tree/side" tabindex="-1"> | 399 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 395 | <span class="lbl">side</span> | 400 | <span class="lbl">side</span> | ||
| 396 | <span class="k">branch</span> | 401 | <span class="k">branch</span> | ||
| 397 | </a> | 402 | </a> | ||
| 398 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 405 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 401 | <span class="lbl">v1.0.0</span> | 406 | <span class="lbl">v1.0.0</span> | ||
| 402 | <span class="k">tag</span> | 407 | <span class="k">tag</span> | ||
| 403 | </a> | 408 | </a> | ||
| 404 | </li> | 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 | </ul> | 416 | </ul> | ||
| 406 | <p class="panel-foot"> | 417 | <p class="panel-foot"> | ||
| 407 | <span> | 418 | <span> | ||
| @@ -440,7 +451,8 @@ | |||||
| 440 | k-permalink from:body -> ermite.permalink(); | 451 | k-permalink from:body -> ermite.permalink(); | ||
| 441 | k-merge from:body -> window.ermiteMR?.openMerge(); | 452 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 442 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 456 | </div> | ||
| 445 | </body> | 457 | </body> | ||
| 446 | </html> | 458 | </html> | ||
M internal/app/testdata/web/commit-merge.html +26 -14
| @@ -112,7 +112,7 @@ | |||||
| 112 | <a href="/diffs/commit/SHA40">SHA7</a> + <a href="/diffs/commit/SHA40">SHA7</a> · diff against the first parent</dd> | 112 | <a href="/diffs/commit/SHA40">SHA7</a> + <a href="/diffs/commit/SHA40">SHA7</a> · diff against the first parent</dd> | ||
| 113 | </dl> | 113 | </dl> | ||
| 114 | </header> | 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 | <span class="dim">1 file · <span class="plus">+1</span> | 116 | <span class="dim">1 file · <span class="plus">+1</span> | ||
| 117 | <span class="minus">-0</span> | 117 | <span class="minus">-0</span> | ||
| 118 | </span> | 118 | </span> | ||
| @@ -225,6 +225,11 @@ | |||||
| 225 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 225 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 226 | <kbd>esc</kbd> close</button> | 226 | <kbd>esc</kbd> close</button> | ||
| 227 | </div> | 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 | <dl class="keys"> | 233 | <dl class="keys"> | ||
| 229 | <div> | 234 | <div> | ||
| 230 | <dt> | 235 | <dt> | ||
| @@ -378,72 +383,78 @@ | |||||
| 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()"> | 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 | <div class="panel-head"> | 384 | <div class="panel-head"> | ||
| 380 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 387 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 383 | <kbd>esc</kbd> | 388 | <kbd>esc</kbd> | ||
| 384 | </button> | 389 | </button> | ||
| 385 | </div> | 390 | </div> | ||
| 386 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 391 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 387 | - | <li data-k="go home" data-cursor> | 392 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 388 | <a href="/" tabindex="-1"> | 393 | <a href="/" tabindex="-1"> | ||
| 389 | <span class="lbl">home</span> | 394 | <span class="lbl">home</span> | ||
| 390 | <span class="k">go</span> | 395 | <span class="k">go</span> | ||
| 391 | </a> | 396 | </a> | ||
| 392 | </li> | 397 | </li> | ||
| 393 | - | <li data-k="section summary"> | 398 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 394 | <a href="/diffs" tabindex="-1"> | 399 | <a href="/diffs" tabindex="-1"> | ||
| 395 | <span class="lbl">summary</span> | 400 | <span class="lbl">summary</span> | ||
| 396 | <span class="k">section</span> | 401 | <span class="k">section</span> | ||
| 397 | </a> | 402 | </a> | ||
| 398 | </li> | 403 | </li> | ||
| 399 | - | <li data-k="section files"> | 404 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 400 | <a href="/diffs/tree/main" tabindex="-1"> | 405 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 401 | <span class="lbl">files</span> | 406 | <span class="lbl">files</span> | ||
| 402 | <span class="k">section</span> | 407 | <span class="k">section</span> | ||
| 403 | </a> | 408 | </a> | ||
| 404 | </li> | 409 | </li> | ||
| 405 | - | <li data-k="section commits"> | 410 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 406 | <a href="/diffs/commits/main" tabindex="-1"> | 411 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 407 | <span class="lbl">commits</span> | 412 | <span class="lbl">commits</span> | ||
| 408 | <span class="k">section</span> | 413 | <span class="k">section</span> | ||
| 409 | </a> | 414 | </a> | ||
| 410 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 417 | <a href="/diffs/refs" tabindex="-1"> | ||
| 413 | <span class="lbl">branches & tags</span> | 418 | <span class="lbl">branches & tags</span> | ||
| 414 | <span class="k">section</span> | 419 | <span class="k">section</span> | ||
| 415 | </a> | 420 | </a> | ||
| 416 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 423 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 419 | <span class="lbl">merge requests</span> | 424 | <span class="lbl">merge requests</span> | ||
| 420 | <span class="k">section</span> | 425 | <span class="k">section</span> | ||
| 421 | </a> | 426 | </a> | ||
| 422 | </li> | 427 | </li> | ||
| 423 | - | <li data-k="section actions"> | 428 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 424 | <a href="/diffs/actions" tabindex="-1"> | 429 | <a href="/diffs/actions" tabindex="-1"> | ||
| 425 | <span class="lbl">actions</span> | 430 | <span class="lbl">actions</span> | ||
| 426 | <span class="k">section</span> | 431 | <span class="k">section</span> | ||
| 427 | </a> | 432 | </a> | ||
| 428 | </li> | 433 | </li> | ||
| 429 | - | <li data-k="branch main"> | 434 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 430 | <a href="/diffs/tree/main" tabindex="-1"> | 435 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 431 | <span class="lbl">main</span> | 436 | <span class="lbl">main</span> | ||
| 432 | <span class="k">branch</span> | 437 | <span class="k">branch</span> | ||
| 433 | </a> | 438 | </a> | ||
| 434 | </li> | 439 | </li> | ||
| 435 | - | <li data-k="branch side"> | 440 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 436 | <a href="/diffs/tree/side" tabindex="-1"> | 441 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 437 | <span class="lbl">side</span> | 442 | <span class="lbl">side</span> | ||
| 438 | <span class="k">branch</span> | 443 | <span class="k">branch</span> | ||
| 439 | </a> | 444 | </a> | ||
| 440 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 447 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 443 | <span class="lbl">v1.0.0</span> | 448 | <span class="lbl">v1.0.0</span> | ||
| 444 | <span class="k">tag</span> | 449 | <span class="k">tag</span> | ||
| 445 | </a> | 450 | </a> | ||
| 446 | </li> | 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 | </ul> | 458 | </ul> | ||
| 448 | <p class="panel-foot"> | 459 | <p class="panel-foot"> | ||
| 449 | <span> | 460 | <span> | ||
| @@ -482,7 +493,8 @@ | |||||
| 482 | k-permalink from:body -> ermite.permalink(); | 493 | k-permalink from:body -> ermite.permalink(); | ||
| 483 | k-merge from:body -> window.ermiteMR?.openMerge(); | 494 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 484 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 498 | </div> | ||
| 487 | </body> | 499 | </body> | ||
| 488 | </html> | 500 | </html> | ||
M internal/app/testdata/web/commit-modify-split.html +26 -14
| @@ -119,7 +119,7 @@ And <b>html</b> & "quotes".</pre> | |||||
| 119 | </dd> | 119 | </dd> | ||
| 120 | </dl> | 120 | </dl> | ||
| 121 | </header> | 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 | <span class="dim">2 files · <span class="plus">+2</span> | 123 | <span class="dim">2 files · <span class="plus">+2</span> | ||
| 124 | <span class="minus">-1</span> | 124 | <span class="minus">-1</span> | ||
| 125 | </span> | 125 | </span> | ||
| @@ -348,6 +348,11 @@ And <b>html</b> & "quotes".</pre> | |||||
| 348 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 348 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 349 | <kbd>esc</kbd> close</button> | 349 | <kbd>esc</kbd> close</button> | ||
| 350 | </div> | 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 | <dl class="keys"> | 356 | <dl class="keys"> | ||
| 352 | <div> | 357 | <div> | ||
| 353 | <dt> | 358 | <dt> | ||
| @@ -501,72 +506,78 @@ And <b>html</b> & "quotes".</pre> | |||||
| 501 | <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()"> | 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 | <div class="panel-head"> | 507 | <div class="panel-head"> | ||
| 503 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 510 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 506 | <kbd>esc</kbd> | 511 | <kbd>esc</kbd> | ||
| 507 | </button> | 512 | </button> | ||
| 508 | </div> | 513 | </div> | ||
| 509 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 514 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 510 | - | <li data-k="go home" data-cursor> | 515 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 511 | <a href="/" tabindex="-1"> | 516 | <a href="/" tabindex="-1"> | ||
| 512 | <span class="lbl">home</span> | 517 | <span class="lbl">home</span> | ||
| 513 | <span class="k">go</span> | 518 | <span class="k">go</span> | ||
| 514 | </a> | 519 | </a> | ||
| 515 | </li> | 520 | </li> | ||
| 516 | - | <li data-k="section summary"> | 521 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 517 | <a href="/diffs" tabindex="-1"> | 522 | <a href="/diffs" tabindex="-1"> | ||
| 518 | <span class="lbl">summary</span> | 523 | <span class="lbl">summary</span> | ||
| 519 | <span class="k">section</span> | 524 | <span class="k">section</span> | ||
| 520 | </a> | 525 | </a> | ||
| 521 | </li> | 526 | </li> | ||
| 522 | - | <li data-k="section files"> | 527 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 523 | <a href="/diffs/tree/main" tabindex="-1"> | 528 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 524 | <span class="lbl">files</span> | 529 | <span class="lbl">files</span> | ||
| 525 | <span class="k">section</span> | 530 | <span class="k">section</span> | ||
| 526 | </a> | 531 | </a> | ||
| 527 | </li> | 532 | </li> | ||
| 528 | - | <li data-k="section commits"> | 533 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 529 | <a href="/diffs/commits/main" tabindex="-1"> | 534 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 530 | <span class="lbl">commits</span> | 535 | <span class="lbl">commits</span> | ||
| 531 | <span class="k">section</span> | 536 | <span class="k">section</span> | ||
| 532 | </a> | 537 | </a> | ||
| 533 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 540 | <a href="/diffs/refs" tabindex="-1"> | ||
| 536 | <span class="lbl">branches & tags</span> | 541 | <span class="lbl">branches & tags</span> | ||
| 537 | <span class="k">section</span> | 542 | <span class="k">section</span> | ||
| 538 | </a> | 543 | </a> | ||
| 539 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 546 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 542 | <span class="lbl">merge requests</span> | 547 | <span class="lbl">merge requests</span> | ||
| 543 | <span class="k">section</span> | 548 | <span class="k">section</span> | ||
| 544 | </a> | 549 | </a> | ||
| 545 | </li> | 550 | </li> | ||
| 546 | - | <li data-k="section actions"> | 551 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 547 | <a href="/diffs/actions" tabindex="-1"> | 552 | <a href="/diffs/actions" tabindex="-1"> | ||
| 548 | <span class="lbl">actions</span> | 553 | <span class="lbl">actions</span> | ||
| 549 | <span class="k">section</span> | 554 | <span class="k">section</span> | ||
| 550 | </a> | 555 | </a> | ||
| 551 | </li> | 556 | </li> | ||
| 552 | - | <li data-k="branch main"> | 557 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 553 | <a href="/diffs/tree/main" tabindex="-1"> | 558 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 554 | <span class="lbl">main</span> | 559 | <span class="lbl">main</span> | ||
| 555 | <span class="k">branch</span> | 560 | <span class="k">branch</span> | ||
| 556 | </a> | 561 | </a> | ||
| 557 | </li> | 562 | </li> | ||
| 558 | - | <li data-k="branch side"> | 563 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 559 | <a href="/diffs/tree/side" tabindex="-1"> | 564 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 560 | <span class="lbl">side</span> | 565 | <span class="lbl">side</span> | ||
| 561 | <span class="k">branch</span> | 566 | <span class="k">branch</span> | ||
| 562 | </a> | 567 | </a> | ||
| 563 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 570 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 566 | <span class="lbl">v1.0.0</span> | 571 | <span class="lbl">v1.0.0</span> | ||
| 567 | <span class="k">tag</span> | 572 | <span class="k">tag</span> | ||
| 568 | </a> | 573 | </a> | ||
| 569 | </li> | 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 | </ul> | 581 | </ul> | ||
| 571 | <p class="panel-foot"> | 582 | <p class="panel-foot"> | ||
| 572 | <span> | 583 | <span> | ||
| @@ -605,7 +616,8 @@ And <b>html</b> & "quotes".</pre> | |||||
| 605 | k-permalink from:body -> ermite.permalink(); | 616 | k-permalink from:body -> ermite.permalink(); | ||
| 606 | k-merge from:body -> window.ermiteMR?.openMerge(); | 617 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 607 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 621 | </div> | ||
| 610 | </body> | 622 | </body> | ||
| 611 | </html> | 623 | </html> | ||
M internal/app/testdata/web/commit-modify.html +26 -14
| @@ -119,7 +119,7 @@ And <b>html</b> & "quotes".</pre> | |||||
| 119 | </dd> | 119 | </dd> | ||
| 120 | </dl> | 120 | </dl> | ||
| 121 | </header> | 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 | <span class="dim">2 files · <span class="plus">+2</span> | 123 | <span class="dim">2 files · <span class="plus">+2</span> | ||
| 124 | <span class="minus">-1</span> | 124 | <span class="minus">-1</span> | ||
| 125 | </span> | 125 | </span> | ||
| @@ -348,6 +348,11 @@ And <b>html</b> & "quotes".</pre> | |||||
| 348 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 348 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 349 | <kbd>esc</kbd> close</button> | 349 | <kbd>esc</kbd> close</button> | ||
| 350 | </div> | 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 | <dl class="keys"> | 356 | <dl class="keys"> | ||
| 352 | <div> | 357 | <div> | ||
| 353 | <dt> | 358 | <dt> | ||
| @@ -501,72 +506,78 @@ And <b>html</b> & "quotes".</pre> | |||||
| 501 | <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()"> | 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 | <div class="panel-head"> | 507 | <div class="panel-head"> | ||
| 503 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 510 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 506 | <kbd>esc</kbd> | 511 | <kbd>esc</kbd> | ||
| 507 | </button> | 512 | </button> | ||
| 508 | </div> | 513 | </div> | ||
| 509 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 514 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 510 | - | <li data-k="go home" data-cursor> | 515 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 511 | <a href="/" tabindex="-1"> | 516 | <a href="/" tabindex="-1"> | ||
| 512 | <span class="lbl">home</span> | 517 | <span class="lbl">home</span> | ||
| 513 | <span class="k">go</span> | 518 | <span class="k">go</span> | ||
| 514 | </a> | 519 | </a> | ||
| 515 | </li> | 520 | </li> | ||
| 516 | - | <li data-k="section summary"> | 521 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 517 | <a href="/diffs" tabindex="-1"> | 522 | <a href="/diffs" tabindex="-1"> | ||
| 518 | <span class="lbl">summary</span> | 523 | <span class="lbl">summary</span> | ||
| 519 | <span class="k">section</span> | 524 | <span class="k">section</span> | ||
| 520 | </a> | 525 | </a> | ||
| 521 | </li> | 526 | </li> | ||
| 522 | - | <li data-k="section files"> | 527 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 523 | <a href="/diffs/tree/main" tabindex="-1"> | 528 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 524 | <span class="lbl">files</span> | 529 | <span class="lbl">files</span> | ||
| 525 | <span class="k">section</span> | 530 | <span class="k">section</span> | ||
| 526 | </a> | 531 | </a> | ||
| 527 | </li> | 532 | </li> | ||
| 528 | - | <li data-k="section commits"> | 533 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 529 | <a href="/diffs/commits/main" tabindex="-1"> | 534 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 530 | <span class="lbl">commits</span> | 535 | <span class="lbl">commits</span> | ||
| 531 | <span class="k">section</span> | 536 | <span class="k">section</span> | ||
| 532 | </a> | 537 | </a> | ||
| 533 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 540 | <a href="/diffs/refs" tabindex="-1"> | ||
| 536 | <span class="lbl">branches & tags</span> | 541 | <span class="lbl">branches & tags</span> | ||
| 537 | <span class="k">section</span> | 542 | <span class="k">section</span> | ||
| 538 | </a> | 543 | </a> | ||
| 539 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 546 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 542 | <span class="lbl">merge requests</span> | 547 | <span class="lbl">merge requests</span> | ||
| 543 | <span class="k">section</span> | 548 | <span class="k">section</span> | ||
| 544 | </a> | 549 | </a> | ||
| 545 | </li> | 550 | </li> | ||
| 546 | - | <li data-k="section actions"> | 551 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 547 | <a href="/diffs/actions" tabindex="-1"> | 552 | <a href="/diffs/actions" tabindex="-1"> | ||
| 548 | <span class="lbl">actions</span> | 553 | <span class="lbl">actions</span> | ||
| 549 | <span class="k">section</span> | 554 | <span class="k">section</span> | ||
| 550 | </a> | 555 | </a> | ||
| 551 | </li> | 556 | </li> | ||
| 552 | - | <li data-k="branch main"> | 557 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 553 | <a href="/diffs/tree/main" tabindex="-1"> | 558 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 554 | <span class="lbl">main</span> | 559 | <span class="lbl">main</span> | ||
| 555 | <span class="k">branch</span> | 560 | <span class="k">branch</span> | ||
| 556 | </a> | 561 | </a> | ||
| 557 | </li> | 562 | </li> | ||
| 558 | - | <li data-k="branch side"> | 563 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 559 | <a href="/diffs/tree/side" tabindex="-1"> | 564 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 560 | <span class="lbl">side</span> | 565 | <span class="lbl">side</span> | ||
| 561 | <span class="k">branch</span> | 566 | <span class="k">branch</span> | ||
| 562 | </a> | 567 | </a> | ||
| 563 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 570 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 566 | <span class="lbl">v1.0.0</span> | 571 | <span class="lbl">v1.0.0</span> | ||
| 567 | <span class="k">tag</span> | 572 | <span class="k">tag</span> | ||
| 568 | </a> | 573 | </a> | ||
| 569 | </li> | 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 | </ul> | 581 | </ul> | ||
| 571 | <p class="panel-foot"> | 582 | <p class="panel-foot"> | ||
| 572 | <span> | 583 | <span> | ||
| @@ -605,7 +616,8 @@ And <b>html</b> & "quotes".</pre> | |||||
| 605 | k-permalink from:body -> ermite.permalink(); | 616 | k-permalink from:body -> ermite.permalink(); | ||
| 606 | k-merge from:body -> window.ermiteMR?.openMerge(); | 617 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 607 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 621 | </div> | ||
| 610 | </body> | 622 | </body> | ||
| 611 | </html> | 623 | </html> | ||
M internal/app/testdata/web/commit-rename.html +26 -14
| @@ -113,7 +113,7 @@ | |||||
| 113 | </dd> | 113 | </dd> | ||
| 114 | </dl> | 114 | </dl> | ||
| 115 | </header> | 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 | <span class="dim">2 files · <span class="plus">+0</span> | 117 | <span class="dim">2 files · <span class="plus">+0</span> | ||
| 118 | <span class="minus">-1</span> | 118 | <span class="minus">-1</span> | ||
| 119 | </span> | 119 | </span> | ||
| @@ -252,6 +252,11 @@ | |||||
| 252 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 252 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 253 | <kbd>esc</kbd> close</button> | 253 | <kbd>esc</kbd> close</button> | ||
| 254 | </div> | 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 | <dl class="keys"> | 260 | <dl class="keys"> | ||
| 256 | <div> | 261 | <div> | ||
| 257 | <dt> | 262 | <dt> | ||
| @@ -405,72 +410,78 @@ | |||||
| 405 | <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()"> | 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 | <div class="panel-head"> | 411 | <div class="panel-head"> | ||
| 407 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 414 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 410 | <kbd>esc</kbd> | 415 | <kbd>esc</kbd> | ||
| 411 | </button> | 416 | </button> | ||
| 412 | </div> | 417 | </div> | ||
| 413 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 418 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 414 | - | <li data-k="go home" data-cursor> | 419 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 415 | <a href="/" tabindex="-1"> | 420 | <a href="/" tabindex="-1"> | ||
| 416 | <span class="lbl">home</span> | 421 | <span class="lbl">home</span> | ||
| 417 | <span class="k">go</span> | 422 | <span class="k">go</span> | ||
| 418 | </a> | 423 | </a> | ||
| 419 | </li> | 424 | </li> | ||
| 420 | - | <li data-k="section summary"> | 425 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 421 | <a href="/diffs" tabindex="-1"> | 426 | <a href="/diffs" tabindex="-1"> | ||
| 422 | <span class="lbl">summary</span> | 427 | <span class="lbl">summary</span> | ||
| 423 | <span class="k">section</span> | 428 | <span class="k">section</span> | ||
| 424 | </a> | 429 | </a> | ||
| 425 | </li> | 430 | </li> | ||
| 426 | - | <li data-k="section files"> | 431 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 427 | <a href="/diffs/tree/main" tabindex="-1"> | 432 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 428 | <span class="lbl">files</span> | 433 | <span class="lbl">files</span> | ||
| 429 | <span class="k">section</span> | 434 | <span class="k">section</span> | ||
| 430 | </a> | 435 | </a> | ||
| 431 | </li> | 436 | </li> | ||
| 432 | - | <li data-k="section commits"> | 437 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 433 | <a href="/diffs/commits/main" tabindex="-1"> | 438 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 434 | <span class="lbl">commits</span> | 439 | <span class="lbl">commits</span> | ||
| 435 | <span class="k">section</span> | 440 | <span class="k">section</span> | ||
| 436 | </a> | 441 | </a> | ||
| 437 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 444 | <a href="/diffs/refs" tabindex="-1"> | ||
| 440 | <span class="lbl">branches & tags</span> | 445 | <span class="lbl">branches & tags</span> | ||
| 441 | <span class="k">section</span> | 446 | <span class="k">section</span> | ||
| 442 | </a> | 447 | </a> | ||
| 443 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 450 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 446 | <span class="lbl">merge requests</span> | 451 | <span class="lbl">merge requests</span> | ||
| 447 | <span class="k">section</span> | 452 | <span class="k">section</span> | ||
| 448 | </a> | 453 | </a> | ||
| 449 | </li> | 454 | </li> | ||
| 450 | - | <li data-k="section actions"> | 455 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 451 | <a href="/diffs/actions" tabindex="-1"> | 456 | <a href="/diffs/actions" tabindex="-1"> | ||
| 452 | <span class="lbl">actions</span> | 457 | <span class="lbl">actions</span> | ||
| 453 | <span class="k">section</span> | 458 | <span class="k">section</span> | ||
| 454 | </a> | 459 | </a> | ||
| 455 | </li> | 460 | </li> | ||
| 456 | - | <li data-k="branch main"> | 461 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 457 | <a href="/diffs/tree/main" tabindex="-1"> | 462 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 458 | <span class="lbl">main</span> | 463 | <span class="lbl">main</span> | ||
| 459 | <span class="k">branch</span> | 464 | <span class="k">branch</span> | ||
| 460 | </a> | 465 | </a> | ||
| 461 | </li> | 466 | </li> | ||
| 462 | - | <li data-k="branch side"> | 467 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 463 | <a href="/diffs/tree/side" tabindex="-1"> | 468 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 464 | <span class="lbl">side</span> | 469 | <span class="lbl">side</span> | ||
| 465 | <span class="k">branch</span> | 470 | <span class="k">branch</span> | ||
| 466 | </a> | 471 | </a> | ||
| 467 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 474 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 470 | <span class="lbl">v1.0.0</span> | 475 | <span class="lbl">v1.0.0</span> | ||
| 471 | <span class="k">tag</span> | 476 | <span class="k">tag</span> | ||
| 472 | </a> | 477 | </a> | ||
| 473 | </li> | 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 | </ul> | 485 | </ul> | ||
| 475 | <p class="panel-foot"> | 486 | <p class="panel-foot"> | ||
| 476 | <span> | 487 | <span> | ||
| @@ -509,7 +520,8 @@ | |||||
| 509 | k-permalink from:body -> ermite.permalink(); | 520 | k-permalink from:body -> ermite.permalink(); | ||
| 510 | k-merge from:body -> window.ermiteMR?.openMerge(); | 521 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 511 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 525 | </div> | ||
| 514 | </body> | 526 | </body> | ||
| 515 | </html> | 527 | </html> | ||
M internal/app/testdata/web/commit-root.html +26 -14
| @@ -111,7 +111,7 @@ | |||||
| 111 | <dd>none (root commit) </dd> | 111 | <dd>none (root commit) </dd> | ||
| 112 | </dl> | 112 | </dl> | ||
| 113 | </header> | 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 | <span class="dim">5 files · <span class="plus">+11</span> | 115 | <span class="dim">5 files · <span class="plus">+11</span> | ||
| 116 | <span class="minus">-0</span> | 116 | <span class="minus">-0</span> | ||
| 117 | </span> | 117 | </span> | ||
| @@ -571,6 +571,11 @@ | |||||
| 571 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 571 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 572 | <kbd>esc</kbd> close</button> | 572 | <kbd>esc</kbd> close</button> | ||
| 573 | </div> | 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 | <dl class="keys"> | 579 | <dl class="keys"> | ||
| 575 | <div> | 580 | <div> | ||
| 576 | <dt> | 581 | <dt> | ||
| @@ -724,72 +729,78 @@ | |||||
| 724 | <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()"> | 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 | <div class="panel-head"> | 730 | <div class="panel-head"> | ||
| 726 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 733 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 729 | <kbd>esc</kbd> | 734 | <kbd>esc</kbd> | ||
| 730 | </button> | 735 | </button> | ||
| 731 | </div> | 736 | </div> | ||
| 732 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 737 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 733 | - | <li data-k="go home" data-cursor> | 738 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 734 | <a href="/" tabindex="-1"> | 739 | <a href="/" tabindex="-1"> | ||
| 735 | <span class="lbl">home</span> | 740 | <span class="lbl">home</span> | ||
| 736 | <span class="k">go</span> | 741 | <span class="k">go</span> | ||
| 737 | </a> | 742 | </a> | ||
| 738 | </li> | 743 | </li> | ||
| 739 | - | <li data-k="section summary"> | 744 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 740 | <a href="/diffs" tabindex="-1"> | 745 | <a href="/diffs" tabindex="-1"> | ||
| 741 | <span class="lbl">summary</span> | 746 | <span class="lbl">summary</span> | ||
| 742 | <span class="k">section</span> | 747 | <span class="k">section</span> | ||
| 743 | </a> | 748 | </a> | ||
| 744 | </li> | 749 | </li> | ||
| 745 | - | <li data-k="section files"> | 750 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 746 | <a href="/diffs/tree/main" tabindex="-1"> | 751 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 747 | <span class="lbl">files</span> | 752 | <span class="lbl">files</span> | ||
| 748 | <span class="k">section</span> | 753 | <span class="k">section</span> | ||
| 749 | </a> | 754 | </a> | ||
| 750 | </li> | 755 | </li> | ||
| 751 | - | <li data-k="section commits"> | 756 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 752 | <a href="/diffs/commits/main" tabindex="-1"> | 757 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 753 | <span class="lbl">commits</span> | 758 | <span class="lbl">commits</span> | ||
| 754 | <span class="k">section</span> | 759 | <span class="k">section</span> | ||
| 755 | </a> | 760 | </a> | ||
| 756 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 763 | <a href="/diffs/refs" tabindex="-1"> | ||
| 759 | <span class="lbl">branches & tags</span> | 764 | <span class="lbl">branches & tags</span> | ||
| 760 | <span class="k">section</span> | 765 | <span class="k">section</span> | ||
| 761 | </a> | 766 | </a> | ||
| 762 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 769 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 765 | <span class="lbl">merge requests</span> | 770 | <span class="lbl">merge requests</span> | ||
| 766 | <span class="k">section</span> | 771 | <span class="k">section</span> | ||
| 767 | </a> | 772 | </a> | ||
| 768 | </li> | 773 | </li> | ||
| 769 | - | <li data-k="section actions"> | 774 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 770 | <a href="/diffs/actions" tabindex="-1"> | 775 | <a href="/diffs/actions" tabindex="-1"> | ||
| 771 | <span class="lbl">actions</span> | 776 | <span class="lbl">actions</span> | ||
| 772 | <span class="k">section</span> | 777 | <span class="k">section</span> | ||
| 773 | </a> | 778 | </a> | ||
| 774 | </li> | 779 | </li> | ||
| 775 | - | <li data-k="branch main"> | 780 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 776 | <a href="/diffs/tree/main" tabindex="-1"> | 781 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 777 | <span class="lbl">main</span> | 782 | <span class="lbl">main</span> | ||
| 778 | <span class="k">branch</span> | 783 | <span class="k">branch</span> | ||
| 779 | </a> | 784 | </a> | ||
| 780 | </li> | 785 | </li> | ||
| 781 | - | <li data-k="branch side"> | 786 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 782 | <a href="/diffs/tree/side" tabindex="-1"> | 787 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 783 | <span class="lbl">side</span> | 788 | <span class="lbl">side</span> | ||
| 784 | <span class="k">branch</span> | 789 | <span class="k">branch</span> | ||
| 785 | </a> | 790 | </a> | ||
| 786 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 793 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 789 | <span class="lbl">v1.0.0</span> | 794 | <span class="lbl">v1.0.0</span> | ||
| 790 | <span class="k">tag</span> | 795 | <span class="k">tag</span> | ||
| 791 | </a> | 796 | </a> | ||
| 792 | </li> | 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 | </ul> | 804 | </ul> | ||
| 794 | <p class="panel-foot"> | 805 | <p class="panel-foot"> | ||
| 795 | <span> | 806 | <span> | ||
| @@ -828,7 +839,8 @@ | |||||
| 828 | k-permalink from:body -> ermite.permalink(); | 839 | k-permalink from:body -> ermite.permalink(); | ||
| 829 | k-merge from:body -> window.ermiteMR?.openMerge(); | 840 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 830 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 844 | </div> | ||
| 833 | </body> | 845 | </body> | ||
| 834 | </html> | 846 | </html> | ||
M internal/app/testdata/web/commit-trojan.html +26 -14
| @@ -114,7 +114,7 @@ | |||||
| 114 | </dd> | 114 | </dd> | ||
| 115 | </dl> | 115 | </dl> | ||
| 116 | </header> | 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 | <span class="dim">1 file · <span class="plus">+8</span> | 118 | <span class="dim">1 file · <span class="plus">+8</span> | ||
| 119 | <span class="minus">-0</span> | 119 | <span class="minus">-0</span> | ||
| 120 | </span> | 120 | </span> | ||
| @@ -360,6 +360,11 @@ | |||||
| 360 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 360 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 361 | <kbd>esc</kbd> close</button> | 361 | <kbd>esc</kbd> close</button> | ||
| 362 | </div> | 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 | <dl class="keys"> | 368 | <dl class="keys"> | ||
| 364 | <div> | 369 | <div> | ||
| 365 | <dt> | 370 | <dt> | ||
| @@ -513,72 +518,78 @@ | |||||
| 513 | <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()"> | 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 | <div class="panel-head"> | 519 | <div class="panel-head"> | ||
| 515 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 522 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 518 | <kbd>esc</kbd> | 523 | <kbd>esc</kbd> | ||
| 519 | </button> | 524 | </button> | ||
| 520 | </div> | 525 | </div> | ||
| 521 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 526 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 522 | - | <li data-k="go home" data-cursor> | 527 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 523 | <a href="/" tabindex="-1"> | 528 | <a href="/" tabindex="-1"> | ||
| 524 | <span class="lbl">home</span> | 529 | <span class="lbl">home</span> | ||
| 525 | <span class="k">go</span> | 530 | <span class="k">go</span> | ||
| 526 | </a> | 531 | </a> | ||
| 527 | </li> | 532 | </li> | ||
| 528 | - | <li data-k="section summary"> | 533 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 529 | <a href="/diffs" tabindex="-1"> | 534 | <a href="/diffs" tabindex="-1"> | ||
| 530 | <span class="lbl">summary</span> | 535 | <span class="lbl">summary</span> | ||
| 531 | <span class="k">section</span> | 536 | <span class="k">section</span> | ||
| 532 | </a> | 537 | </a> | ||
| 533 | </li> | 538 | </li> | ||
| 534 | - | <li data-k="section files"> | 539 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 535 | <a href="/diffs/tree/main" tabindex="-1"> | 540 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 536 | <span class="lbl">files</span> | 541 | <span class="lbl">files</span> | ||
| 537 | <span class="k">section</span> | 542 | <span class="k">section</span> | ||
| 538 | </a> | 543 | </a> | ||
| 539 | </li> | 544 | </li> | ||
| 540 | - | <li data-k="section commits"> | 545 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 541 | <a href="/diffs/commits/main" tabindex="-1"> | 546 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 542 | <span class="lbl">commits</span> | 547 | <span class="lbl">commits</span> | ||
| 543 | <span class="k">section</span> | 548 | <span class="k">section</span> | ||
| 544 | </a> | 549 | </a> | ||
| 545 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 552 | <a href="/diffs/refs" tabindex="-1"> | ||
| 548 | <span class="lbl">branches & tags</span> | 553 | <span class="lbl">branches & tags</span> | ||
| 549 | <span class="k">section</span> | 554 | <span class="k">section</span> | ||
| 550 | </a> | 555 | </a> | ||
| 551 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 558 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 554 | <span class="lbl">merge requests</span> | 559 | <span class="lbl">merge requests</span> | ||
| 555 | <span class="k">section</span> | 560 | <span class="k">section</span> | ||
| 556 | </a> | 561 | </a> | ||
| 557 | </li> | 562 | </li> | ||
| 558 | - | <li data-k="section actions"> | 563 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 559 | <a href="/diffs/actions" tabindex="-1"> | 564 | <a href="/diffs/actions" tabindex="-1"> | ||
| 560 | <span class="lbl">actions</span> | 565 | <span class="lbl">actions</span> | ||
| 561 | <span class="k">section</span> | 566 | <span class="k">section</span> | ||
| 562 | </a> | 567 | </a> | ||
| 563 | </li> | 568 | </li> | ||
| 564 | - | <li data-k="branch main"> | 569 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 565 | <a href="/diffs/tree/main" tabindex="-1"> | 570 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 566 | <span class="lbl">main</span> | 571 | <span class="lbl">main</span> | ||
| 567 | <span class="k">branch</span> | 572 | <span class="k">branch</span> | ||
| 568 | </a> | 573 | </a> | ||
| 569 | </li> | 574 | </li> | ||
| 570 | - | <li data-k="branch side"> | 575 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 571 | <a href="/diffs/tree/side" tabindex="-1"> | 576 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 572 | <span class="lbl">side</span> | 577 | <span class="lbl">side</span> | ||
| 573 | <span class="k">branch</span> | 578 | <span class="k">branch</span> | ||
| 574 | </a> | 579 | </a> | ||
| 575 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 582 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 578 | <span class="lbl">v1.0.0</span> | 583 | <span class="lbl">v1.0.0</span> | ||
| 579 | <span class="k">tag</span> | 584 | <span class="k">tag</span> | ||
| 580 | </a> | 585 | </a> | ||
| 581 | </li> | 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 | </ul> | 593 | </ul> | ||
| 583 | <p class="panel-foot"> | 594 | <p class="panel-foot"> | ||
| 584 | <span> | 595 | <span> | ||
| @@ -617,7 +628,8 @@ | |||||
| 617 | k-permalink from:body -> ermite.permalink(); | 628 | k-permalink from:body -> ermite.permalink(); | ||
| 618 | k-merge from:body -> window.ermiteMR?.openMerge(); | 629 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 619 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 633 | </div> | ||
| 622 | </body> | 634 | </body> | ||
| 623 | </html> | 635 | </html> | ||
M internal/app/testdata/web/commits-page2.html +24 -12
| @@ -184,6 +184,11 @@ | |||||
| 184 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 184 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 185 | <kbd>esc</kbd> close</button> | 185 | <kbd>esc</kbd> close</button> | ||
| 186 | </div> | 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 | <dl class="keys"> | 192 | <dl class="keys"> | ||
| 188 | <div> | 193 | <div> | ||
| 189 | <dt> | 194 | <dt> | ||
| @@ -329,66 +334,72 @@ | |||||
| 329 | <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()"> | 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 | <div class="panel-head"> | 335 | <div class="panel-head"> | ||
| 331 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 338 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 334 | <kbd>esc</kbd> | 339 | <kbd>esc</kbd> | ||
| 335 | </button> | 340 | </button> | ||
| 336 | </div> | 341 | </div> | ||
| 337 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 342 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 338 | - | <li data-k="go home" data-cursor> | 343 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 339 | <a href="/" tabindex="-1"> | 344 | <a href="/" tabindex="-1"> | ||
| 340 | <span class="lbl">home</span> | 345 | <span class="lbl">home</span> | ||
| 341 | <span class="k">go</span> | 346 | <span class="k">go</span> | ||
| 342 | </a> | 347 | </a> | ||
| 343 | </li> | 348 | </li> | ||
| 344 | - | <li data-k="section summary"> | 349 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 345 | <a href="/long" tabindex="-1"> | 350 | <a href="/long" tabindex="-1"> | ||
| 346 | <span class="lbl">summary</span> | 351 | <span class="lbl">summary</span> | ||
| 347 | <span class="k">section</span> | 352 | <span class="k">section</span> | ||
| 348 | </a> | 353 | </a> | ||
| 349 | </li> | 354 | </li> | ||
| 350 | - | <li data-k="section files"> | 355 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 351 | <a href="/long/tree/main" tabindex="-1"> | 356 | <a href="/long/tree/main" tabindex="-1"> | ||
| 352 | <span class="lbl">files</span> | 357 | <span class="lbl">files</span> | ||
| 353 | <span class="k">section</span> | 358 | <span class="k">section</span> | ||
| 354 | </a> | 359 | </a> | ||
| 355 | </li> | 360 | </li> | ||
| 356 | - | <li data-k="section commits"> | 361 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 357 | <a href="/long/commits/main" tabindex="-1"> | 362 | <a href="/long/commits/main" tabindex="-1"> | ||
| 358 | <span class="lbl">commits</span> | 363 | <span class="lbl">commits</span> | ||
| 359 | <span class="k">section</span> | 364 | <span class="k">section</span> | ||
| 360 | </a> | 365 | </a> | ||
| 361 | </li> | 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 | <a href="/long/refs" tabindex="-1"> | 368 | <a href="/long/refs" tabindex="-1"> | ||
| 364 | <span class="lbl">branches & tags</span> | 369 | <span class="lbl">branches & tags</span> | ||
| 365 | <span class="k">section</span> | 370 | <span class="k">section</span> | ||
| 366 | </a> | 371 | </a> | ||
| 367 | </li> | 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 | <a href="/long/merge-requests" tabindex="-1"> | 374 | <a href="/long/merge-requests" tabindex="-1"> | ||
| 370 | <span class="lbl">merge requests</span> | 375 | <span class="lbl">merge requests</span> | ||
| 371 | <span class="k">section</span> | 376 | <span class="k">section</span> | ||
| 372 | </a> | 377 | </a> | ||
| 373 | </li> | 378 | </li> | ||
| 374 | - | <li data-k="section actions"> | 379 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 375 | <a href="/long/actions" tabindex="-1"> | 380 | <a href="/long/actions" tabindex="-1"> | ||
| 376 | <span class="lbl">actions</span> | 381 | <span class="lbl">actions</span> | ||
| 377 | <span class="k">section</span> | 382 | <span class="k">section</span> | ||
| 378 | </a> | 383 | </a> | ||
| 379 | </li> | 384 | </li> | ||
| 380 | - | <li data-k="branch main"> | 385 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 381 | <a href="/long/tree/main" tabindex="-1"> | 386 | <a href="/long/tree/main" tabindex="-1"> | ||
| 382 | <span class="lbl">main</span> | 387 | <span class="lbl">main</span> | ||
| 383 | <span class="k">branch</span> | 388 | <span class="k">branch</span> | ||
| 384 | </a> | 389 | </a> | ||
| 385 | </li> | 390 | </li> | ||
| 386 | - | <li data-k="repo diffs"> | 391 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="repo diffs"> |
| 387 | <a href="/diffs" tabindex="-1"> | 392 | <a href="/diffs" tabindex="-1"> | ||
| 388 | <span class="lbl">diffs</span> | 393 | <span class="lbl">diffs</span> | ||
| 389 | <span class="k">repo</span> | 394 | <span class="k">repo</span> | ||
| 390 | </a> | 395 | </a> | ||
| 391 | </li> | 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 | </ul> | 403 | </ul> | ||
| 393 | <p class="panel-foot"> | 404 | <p class="panel-foot"> | ||
| 394 | <span> | 405 | <span> | ||
| @@ -427,7 +438,8 @@ | |||||
| 427 | k-permalink from:body -> ermite.permalink(); | 438 | k-permalink from:body -> ermite.permalink(); | ||
| 428 | k-merge from:body -> window.ermiteMR?.openMerge(); | 439 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 429 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 443 | </div> | ||
| 432 | </body> | 444 | </body> | ||
| 433 | </html> | 445 | </html> | ||
M internal/app/testdata/web/commits-path.html +24 -12
| @@ -199,6 +199,11 @@ | |||||
| 199 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 199 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 200 | <kbd>esc</kbd> close</button> | 200 | <kbd>esc</kbd> close</button> | ||
| 201 | </div> | 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 | <dl class="keys"> | 207 | <dl class="keys"> | ||
| 203 | <div> | 208 | <div> | ||
| 204 | <dt> | 209 | <dt> | ||
| @@ -344,66 +349,72 @@ | |||||
| 344 | <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()"> | 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 | <div class="panel-head"> | 350 | <div class="panel-head"> | ||
| 346 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 353 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 349 | <kbd>esc</kbd> | 354 | <kbd>esc</kbd> | ||
| 350 | </button> | 355 | </button> | ||
| 351 | </div> | 356 | </div> | ||
| 352 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 357 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 353 | - | <li data-k="go home" data-cursor> | 358 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 354 | <a href="/" tabindex="-1"> | 359 | <a href="/" tabindex="-1"> | ||
| 355 | <span class="lbl">home</span> | 360 | <span class="lbl">home</span> | ||
| 356 | <span class="k">go</span> | 361 | <span class="k">go</span> | ||
| 357 | </a> | 362 | </a> | ||
| 358 | </li> | 363 | </li> | ||
| 359 | - | <li data-k="section summary"> | 364 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 360 | <a href="/long" tabindex="-1"> | 365 | <a href="/long" tabindex="-1"> | ||
| 361 | <span class="lbl">summary</span> | 366 | <span class="lbl">summary</span> | ||
| 362 | <span class="k">section</span> | 367 | <span class="k">section</span> | ||
| 363 | </a> | 368 | </a> | ||
| 364 | </li> | 369 | </li> | ||
| 365 | - | <li data-k="section files"> | 370 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 366 | <a href="/long/tree/main" tabindex="-1"> | 371 | <a href="/long/tree/main" tabindex="-1"> | ||
| 367 | <span class="lbl">files</span> | 372 | <span class="lbl">files</span> | ||
| 368 | <span class="k">section</span> | 373 | <span class="k">section</span> | ||
| 369 | </a> | 374 | </a> | ||
| 370 | </li> | 375 | </li> | ||
| 371 | - | <li data-k="section commits"> | 376 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 372 | <a href="/long/commits/main" tabindex="-1"> | 377 | <a href="/long/commits/main" tabindex="-1"> | ||
| 373 | <span class="lbl">commits</span> | 378 | <span class="lbl">commits</span> | ||
| 374 | <span class="k">section</span> | 379 | <span class="k">section</span> | ||
| 375 | </a> | 380 | </a> | ||
| 376 | </li> | 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 | <a href="/long/refs" tabindex="-1"> | 383 | <a href="/long/refs" tabindex="-1"> | ||
| 379 | <span class="lbl">branches & tags</span> | 384 | <span class="lbl">branches & tags</span> | ||
| 380 | <span class="k">section</span> | 385 | <span class="k">section</span> | ||
| 381 | </a> | 386 | </a> | ||
| 382 | </li> | 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 | <a href="/long/merge-requests" tabindex="-1"> | 389 | <a href="/long/merge-requests" tabindex="-1"> | ||
| 385 | <span class="lbl">merge requests</span> | 390 | <span class="lbl">merge requests</span> | ||
| 386 | <span class="k">section</span> | 391 | <span class="k">section</span> | ||
| 387 | </a> | 392 | </a> | ||
| 388 | </li> | 393 | </li> | ||
| 389 | - | <li data-k="section actions"> | 394 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 390 | <a href="/long/actions" tabindex="-1"> | 395 | <a href="/long/actions" tabindex="-1"> | ||
| 391 | <span class="lbl">actions</span> | 396 | <span class="lbl">actions</span> | ||
| 392 | <span class="k">section</span> | 397 | <span class="k">section</span> | ||
| 393 | </a> | 398 | </a> | ||
| 394 | </li> | 399 | </li> | ||
| 395 | - | <li data-k="branch main"> | 400 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 396 | <a href="/long/tree/main" tabindex="-1"> | 401 | <a href="/long/tree/main" tabindex="-1"> | ||
| 397 | <span class="lbl">main</span> | 402 | <span class="lbl">main</span> | ||
| 398 | <span class="k">branch</span> | 403 | <span class="k">branch</span> | ||
| 399 | </a> | 404 | </a> | ||
| 400 | </li> | 405 | </li> | ||
| 401 | - | <li data-k="repo diffs"> | 406 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="repo diffs"> |
| 402 | <a href="/diffs" tabindex="-1"> | 407 | <a href="/diffs" tabindex="-1"> | ||
| 403 | <span class="lbl">diffs</span> | 408 | <span class="lbl">diffs</span> | ||
| 404 | <span class="k">repo</span> | 409 | <span class="k">repo</span> | ||
| 405 | </a> | 410 | </a> | ||
| 406 | </li> | 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 | </ul> | 418 | </ul> | ||
| 408 | <p class="panel-foot"> | 419 | <p class="panel-foot"> | ||
| 409 | <span> | 420 | <span> | ||
| @@ -442,7 +453,8 @@ | |||||
| 442 | k-permalink from:body -> ermite.permalink(); | 453 | k-permalink from:body -> ermite.permalink(); | ||
| 443 | k-merge from:body -> window.ermiteMR?.openMerge(); | 454 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 444 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 458 | </div> | ||
| 447 | </body> | 459 | </body> | ||
| 448 | </html> | 460 | </html> | ||
M internal/app/testdata/web/commits-root.html +24 -12
| @@ -1084,6 +1084,11 @@ | |||||
| 1084 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 1084 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 1085 | <kbd>esc</kbd> close</button> | 1085 | <kbd>esc</kbd> close</button> | ||
| 1086 | </div> | 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 | <dl class="keys"> | 1092 | <dl class="keys"> | ||
| 1088 | <div> | 1093 | <div> | ||
| 1089 | <dt> | 1094 | <dt> | ||
| @@ -1229,66 +1234,72 @@ | |||||
| 1229 | <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()"> | 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 | <div class="panel-head"> | 1235 | <div class="panel-head"> | ||
| 1231 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 1238 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 1234 | <kbd>esc</kbd> | 1239 | <kbd>esc</kbd> | ||
| 1235 | </button> | 1240 | </button> | ||
| 1236 | </div> | 1241 | </div> | ||
| 1237 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 1242 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 1238 | - | <li data-k="go home" data-cursor> | 1243 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 1239 | <a href="/" tabindex="-1"> | 1244 | <a href="/" tabindex="-1"> | ||
| 1240 | <span class="lbl">home</span> | 1245 | <span class="lbl">home</span> | ||
| 1241 | <span class="k">go</span> | 1246 | <span class="k">go</span> | ||
| 1242 | </a> | 1247 | </a> | ||
| 1243 | </li> | 1248 | </li> | ||
| 1244 | - | <li data-k="section summary"> | 1249 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 1245 | <a href="/long" tabindex="-1"> | 1250 | <a href="/long" tabindex="-1"> | ||
| 1246 | <span class="lbl">summary</span> | 1251 | <span class="lbl">summary</span> | ||
| 1247 | <span class="k">section</span> | 1252 | <span class="k">section</span> | ||
| 1248 | </a> | 1253 | </a> | ||
| 1249 | </li> | 1254 | </li> | ||
| 1250 | - | <li data-k="section files"> | 1255 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 1251 | <a href="/long/tree/main" tabindex="-1"> | 1256 | <a href="/long/tree/main" tabindex="-1"> | ||
| 1252 | <span class="lbl">files</span> | 1257 | <span class="lbl">files</span> | ||
| 1253 | <span class="k">section</span> | 1258 | <span class="k">section</span> | ||
| 1254 | </a> | 1259 | </a> | ||
| 1255 | </li> | 1260 | </li> | ||
| 1256 | - | <li data-k="section commits"> | 1261 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 1257 | <a href="/long/commits/main" tabindex="-1"> | 1262 | <a href="/long/commits/main" tabindex="-1"> | ||
| 1258 | <span class="lbl">commits</span> | 1263 | <span class="lbl">commits</span> | ||
| 1259 | <span class="k">section</span> | 1264 | <span class="k">section</span> | ||
| 1260 | </a> | 1265 | </a> | ||
| 1261 | </li> | 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 | <a href="/long/refs" tabindex="-1"> | 1268 | <a href="/long/refs" tabindex="-1"> | ||
| 1264 | <span class="lbl">branches & tags</span> | 1269 | <span class="lbl">branches & tags</span> | ||
| 1265 | <span class="k">section</span> | 1270 | <span class="k">section</span> | ||
| 1266 | </a> | 1271 | </a> | ||
| 1267 | </li> | 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 | <a href="/long/merge-requests" tabindex="-1"> | 1274 | <a href="/long/merge-requests" tabindex="-1"> | ||
| 1270 | <span class="lbl">merge requests</span> | 1275 | <span class="lbl">merge requests</span> | ||
| 1271 | <span class="k">section</span> | 1276 | <span class="k">section</span> | ||
| 1272 | </a> | 1277 | </a> | ||
| 1273 | </li> | 1278 | </li> | ||
| 1274 | - | <li data-k="section actions"> | 1279 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 1275 | <a href="/long/actions" tabindex="-1"> | 1280 | <a href="/long/actions" tabindex="-1"> | ||
| 1276 | <span class="lbl">actions</span> | 1281 | <span class="lbl">actions</span> | ||
| 1277 | <span class="k">section</span> | 1282 | <span class="k">section</span> | ||
| 1278 | </a> | 1283 | </a> | ||
| 1279 | </li> | 1284 | </li> | ||
| 1280 | - | <li data-k="branch main"> | 1285 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 1281 | <a href="/long/tree/main" tabindex="-1"> | 1286 | <a href="/long/tree/main" tabindex="-1"> | ||
| 1282 | <span class="lbl">main</span> | 1287 | <span class="lbl">main</span> | ||
| 1283 | <span class="k">branch</span> | 1288 | <span class="k">branch</span> | ||
| 1284 | </a> | 1289 | </a> | ||
| 1285 | </li> | 1290 | </li> | ||
| 1286 | - | <li data-k="repo diffs"> | 1291 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="repo diffs"> |
| 1287 | <a href="/diffs" tabindex="-1"> | 1292 | <a href="/diffs" tabindex="-1"> | ||
| 1288 | <span class="lbl">diffs</span> | 1293 | <span class="lbl">diffs</span> | ||
| 1289 | <span class="k">repo</span> | 1294 | <span class="k">repo</span> | ||
| 1290 | </a> | 1295 | </a> | ||
| 1291 | </li> | 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 | </ul> | 1303 | </ul> | ||
| 1293 | <p class="panel-foot"> | 1304 | <p class="panel-foot"> | ||
| 1294 | <span> | 1305 | <span> | ||
| @@ -1327,7 +1338,8 @@ | |||||
| 1327 | k-permalink from:body -> ermite.permalink(); | 1338 | k-permalink from:body -> ermite.permalink(); | ||
| 1328 | k-merge from:body -> window.ermiteMR?.openMerge(); | 1339 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 1329 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 1343 | </div> | ||
| 1332 | </body> | 1344 | </body> | ||
| 1333 | </html> | 1345 | </html> | ||
M internal/app/testdata/web/commits-small.html +26 -14
| @@ -345,6 +345,11 @@ | |||||
| 345 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 345 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 346 | <kbd>esc</kbd> close</button> | 346 | <kbd>esc</kbd> close</button> | ||
| 347 | </div> | 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 | <dl class="keys"> | 353 | <dl class="keys"> | ||
| 349 | <div> | 354 | <div> | ||
| 350 | <dt> | 355 | <dt> | ||
| @@ -498,78 +503,84 @@ | |||||
| 498 | <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()"> | 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 | <div class="panel-head"> | 504 | <div class="panel-head"> | ||
| 500 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 507 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 503 | <kbd>esc</kbd> | 508 | <kbd>esc</kbd> | ||
| 504 | </button> | 509 | </button> | ||
| 505 | </div> | 510 | </div> | ||
| 506 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 511 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 507 | - | <li data-k="go home" data-cursor> | 512 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 508 | <a href="/" tabindex="-1"> | 513 | <a href="/" tabindex="-1"> | ||
| 509 | <span class="lbl">home</span> | 514 | <span class="lbl">home</span> | ||
| 510 | <span class="k">go</span> | 515 | <span class="k">go</span> | ||
| 511 | </a> | 516 | </a> | ||
| 512 | </li> | 517 | </li> | ||
| 513 | - | <li data-k="section summary"> | 518 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 514 | <a href="/diffs" tabindex="-1"> | 519 | <a href="/diffs" tabindex="-1"> | ||
| 515 | <span class="lbl">summary</span> | 520 | <span class="lbl">summary</span> | ||
| 516 | <span class="k">section</span> | 521 | <span class="k">section</span> | ||
| 517 | </a> | 522 | </a> | ||
| 518 | </li> | 523 | </li> | ||
| 519 | - | <li data-k="section files"> | 524 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 520 | <a href="/diffs/tree/main" tabindex="-1"> | 525 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 521 | <span class="lbl">files</span> | 526 | <span class="lbl">files</span> | ||
| 522 | <span class="k">section</span> | 527 | <span class="k">section</span> | ||
| 523 | </a> | 528 | </a> | ||
| 524 | </li> | 529 | </li> | ||
| 525 | - | <li data-k="section commits"> | 530 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 526 | <a href="/diffs/commits/main" tabindex="-1"> | 531 | <a href="/diffs/commits/main" tabindex="-1"> | ||
| 527 | <span class="lbl">commits</span> | 532 | <span class="lbl">commits</span> | ||
| 528 | <span class="k">section</span> | 533 | <span class="k">section</span> | ||
| 529 | </a> | 534 | </a> | ||
| 530 | </li> | 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 | <a href="/diffs/refs" tabindex="-1"> | 537 | <a href="/diffs/refs" tabindex="-1"> | ||
| 533 | <span class="lbl">branches & tags</span> | 538 | <span class="lbl">branches & tags</span> | ||
| 534 | <span class="k">section</span> | 539 | <span class="k">section</span> | ||
| 535 | </a> | 540 | </a> | ||
| 536 | </li> | 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 | <a href="/diffs/merge-requests" tabindex="-1"> | 543 | <a href="/diffs/merge-requests" tabindex="-1"> | ||
| 539 | <span class="lbl">merge requests</span> | 544 | <span class="lbl">merge requests</span> | ||
| 540 | <span class="k">section</span> | 545 | <span class="k">section</span> | ||
| 541 | </a> | 546 | </a> | ||
| 542 | </li> | 547 | </li> | ||
| 543 | - | <li data-k="section actions"> | 548 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 544 | <a href="/diffs/actions" tabindex="-1"> | 549 | <a href="/diffs/actions" tabindex="-1"> | ||
| 545 | <span class="lbl">actions</span> | 550 | <span class="lbl">actions</span> | ||
| 546 | <span class="k">section</span> | 551 | <span class="k">section</span> | ||
| 547 | </a> | 552 | </a> | ||
| 548 | </li> | 553 | </li> | ||
| 549 | - | <li data-k="branch main"> | 554 | + | <li id="cmd-o7" role="option" aria-selected="false" data-k="branch main"> |
| 550 | <a href="/diffs/tree/main" tabindex="-1"> | 555 | <a href="/diffs/tree/main" tabindex="-1"> | ||
| 551 | <span class="lbl">main</span> | 556 | <span class="lbl">main</span> | ||
| 552 | <span class="k">branch</span> | 557 | <span class="k">branch</span> | ||
| 553 | </a> | 558 | </a> | ||
| 554 | </li> | 559 | </li> | ||
| 555 | - | <li data-k="branch side"> | 560 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch side"> |
| 556 | <a href="/diffs/tree/side" tabindex="-1"> | 561 | <a href="/diffs/tree/side" tabindex="-1"> | ||
| 557 | <span class="lbl">side</span> | 562 | <span class="lbl">side</span> | ||
| 558 | <span class="k">branch</span> | 563 | <span class="k">branch</span> | ||
| 559 | </a> | 564 | </a> | ||
| 560 | </li> | 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 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | 567 | <a href="/diffs/tree/v1.0.0" tabindex="-1"> | ||
| 563 | <span class="lbl">v1.0.0</span> | 568 | <span class="lbl">v1.0.0</span> | ||
| 564 | <span class="k">tag</span> | 569 | <span class="k">tag</span> | ||
| 565 | </a> | 570 | </a> | ||
| 566 | </li> | 571 | </li> | ||
| 567 | - | <li data-k="repo long"> | 572 | + | <li id="cmd-o10" role="option" aria-selected="false" data-k="repo long"> |
| 568 | <a href="/long" tabindex="-1"> | 573 | <a href="/long" tabindex="-1"> | ||
| 569 | <span class="lbl">long</span> | 574 | <span class="lbl">long</span> | ||
| 570 | <span class="k">repo</span> | 575 | <span class="k">repo</span> | ||
| 571 | </a> | 576 | </a> | ||
| 572 | </li> | 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 | </ul> | 584 | </ul> | ||
| 574 | <p class="panel-foot"> | 585 | <p class="panel-foot"> | ||
| 575 | <span> | 586 | <span> | ||
| @@ -608,7 +619,8 @@ | |||||
| 608 | k-permalink from:body -> ermite.permalink(); | 619 | k-permalink from:body -> ermite.permalink(); | ||
| 609 | k-merge from:body -> window.ermiteMR?.openMerge(); | 620 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 610 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 624 | </div> | ||
| 613 | </body> | 625 | </body> | ||
| 614 | </html> | 626 | </html> | ||
M internal/app/testdata/web/empty.html +17 -5
| @@ -102,6 +102,11 @@ | |||||
| 102 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 102 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 103 | <kbd>esc</kbd> close</button> | 103 | <kbd>esc</kbd> close</button> | ||
| 104 | </div> | 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 | <dl class="keys"> | 110 | <dl class="keys"> | ||
| 106 | <div> | 111 | <div> | ||
| 107 | <dt> | 112 | <dt> | ||
| @@ -220,24 +225,30 @@ | |||||
| 220 | <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()"> | 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 | <div class="panel-head"> | 226 | <div class="panel-head"> | ||
| 222 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 229 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 225 | <kbd>esc</kbd> | 230 | <kbd>esc</kbd> | ||
| 226 | </button> | 231 | </button> | ||
| 227 | </div> | 232 | </div> | ||
| 228 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 233 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 229 | - | <li data-k="go home" data-cursor> | 234 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 230 | <a href="/" tabindex="-1"> | 235 | <a href="/" tabindex="-1"> | ||
| 231 | <span class="lbl">home</span> | 236 | <span class="lbl">home</span> | ||
| 232 | <span class="k">go</span> | 237 | <span class="k">go</span> | ||
| 233 | </a> | 238 | </a> | ||
| 234 | </li> | 239 | </li> | ||
| 235 | - | <li data-k="repo demo"> | 240 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo"> |
| 236 | <a href="/demo" tabindex="-1"> | 241 | <a href="/demo" tabindex="-1"> | ||
| 237 | <span class="lbl">demo</span> | 242 | <span class="lbl">demo</span> | ||
| 238 | <span class="k">repo</span> | 243 | <span class="k">repo</span> | ||
| 239 | </a> | 244 | </a> | ||
| 240 | </li> | 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 | </ul> | 252 | </ul> | ||
| 242 | <p class="panel-foot"> | 253 | <p class="panel-foot"> | ||
| 243 | <span> | 254 | <span> | ||
| @@ -276,7 +287,8 @@ | |||||
| 276 | k-permalink from:body -> ermite.permalink(); | 287 | k-permalink from:body -> ermite.permalink(); | ||
| 277 | k-merge from:body -> window.ermiteMR?.openMerge(); | 288 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 278 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 292 | </div> | ||
| 281 | </body> | 293 | </body> | ||
| 282 | </html> | 294 | </html> | ||
M internal/app/testdata/web/home.html +18 -6
| @@ -127,6 +127,11 @@ | |||||
| 127 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 127 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 128 | <kbd>esc</kbd> close</button> | 128 | <kbd>esc</kbd> close</button> | ||
| 129 | </div> | 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 | <dl class="keys"> | 135 | <dl class="keys"> | ||
| 131 | <div> | 136 | <div> | ||
| 132 | <dt> | 137 | <dt> | ||
| @@ -245,30 +250,36 @@ | |||||
| 245 | <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()"> | 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 | <div class="panel-head"> | 251 | <div class="panel-head"> | ||
| 247 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 254 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 250 | <kbd>esc</kbd> | 255 | <kbd>esc</kbd> | ||
| 251 | </button> | 256 | </button> | ||
| 252 | </div> | 257 | </div> | ||
| 253 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 258 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 254 | - | <li data-k="go home" data-cursor> | 259 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 255 | <a href="/" tabindex="-1"> | 260 | <a href="/" tabindex="-1"> | ||
| 256 | <span class="lbl">home</span> | 261 | <span class="lbl">home</span> | ||
| 257 | <span class="k">go</span> | 262 | <span class="k">go</span> | ||
| 258 | </a> | 263 | </a> | ||
| 259 | </li> | 264 | </li> | ||
| 260 | - | <li data-k="repo demo"> | 265 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="repo demo"> |
| 261 | <a href="/demo" tabindex="-1"> | 266 | <a href="/demo" tabindex="-1"> | ||
| 262 | <span class="lbl">demo</span> | 267 | <span class="lbl">demo</span> | ||
| 263 | <span class="k">repo</span> | 268 | <span class="k">repo</span> | ||
| 264 | </a> | 269 | </a> | ||
| 265 | </li> | 270 | </li> | ||
| 266 | - | <li data-k="repo quiet"> | 271 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="repo quiet"> |
| 267 | <a href="/quiet" tabindex="-1"> | 272 | <a href="/quiet" tabindex="-1"> | ||
| 268 | <span class="lbl">quiet</span> | 273 | <span class="lbl">quiet</span> | ||
| 269 | <span class="k">repo</span> | 274 | <span class="k">repo</span> | ||
| 270 | </a> | 275 | </a> | ||
| 271 | </li> | 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 | </ul> | 283 | </ul> | ||
| 273 | <p class="panel-foot"> | 284 | <p class="panel-foot"> | ||
| 274 | <span> | 285 | <span> | ||
| @@ -307,7 +318,8 @@ | |||||
| 307 | k-permalink from:body -> ermite.permalink(); | 318 | k-permalink from:body -> ermite.permalink(); | ||
| 308 | k-merge from:body -> window.ermiteMR?.openMerge(); | 319 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 309 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 323 | </div> | ||
| 312 | </body> | 324 | </body> | ||
| 313 | </html> | 325 | </html> | ||
M internal/app/testdata/web/not-found-page.html +16 -4
| @@ -91,6 +91,11 @@ | |||||
| 91 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 91 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 92 | <kbd>esc</kbd> close</button> | 92 | <kbd>esc</kbd> close</button> | ||
| 93 | </div> | 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 | <dl class="keys"> | 99 | <dl class="keys"> | ||
| 95 | <div> | 100 | <div> | ||
| 96 | <dt> | 101 | <dt> | ||
| @@ -209,18 +214,24 @@ | |||||
| 209 | <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()"> | 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 | <div class="panel-head"> | 215 | <div class="panel-head"> | ||
| 211 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 218 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 214 | <kbd>esc</kbd> | 219 | <kbd>esc</kbd> | ||
| 215 | </button> | 220 | </button> | ||
| 216 | </div> | 221 | </div> | ||
| 217 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 222 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 218 | - | <li data-k="go home" data-cursor> | 223 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 219 | <a href="/" tabindex="-1"> | 224 | <a href="/" tabindex="-1"> | ||
| 220 | <span class="lbl">home</span> | 225 | <span class="lbl">home</span> | ||
| 221 | <span class="k">go</span> | 226 | <span class="k">go</span> | ||
| 222 | </a> | 227 | </a> | ||
| 223 | </li> | 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 | </ul> | 235 | </ul> | ||
| 225 | <p class="panel-foot"> | 236 | <p class="panel-foot"> | ||
| 226 | <span> | 237 | <span> | ||
| @@ -259,7 +270,8 @@ | |||||
| 259 | k-permalink from:body -> ermite.permalink(); | 270 | k-permalink from:body -> ermite.permalink(); | ||
| 260 | k-merge from:body -> window.ermiteMR?.openMerge(); | 271 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 261 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 275 | </div> | ||
| 264 | </body> | 276 | </body> | ||
| 265 | </html> | 277 | </html> | ||
M internal/app/testdata/web/not-found.html +16 -4
| @@ -91,6 +91,11 @@ | |||||
| 91 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 91 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 92 | <kbd>esc</kbd> close</button> | 92 | <kbd>esc</kbd> close</button> | ||
| 93 | </div> | 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 | <dl class="keys"> | 99 | <dl class="keys"> | ||
| 95 | <div> | 100 | <div> | ||
| 96 | <dt> | 101 | <dt> | ||
| @@ -209,18 +214,24 @@ | |||||
| 209 | <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()"> | 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 | <div class="panel-head"> | 215 | <div class="panel-head"> | ||
| 211 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 218 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 214 | <kbd>esc</kbd> | 219 | <kbd>esc</kbd> | ||
| 215 | </button> | 220 | </button> | ||
| 216 | </div> | 221 | </div> | ||
| 217 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 222 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 218 | - | <li data-k="go home" data-cursor> | 223 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 219 | <a href="/" tabindex="-1"> | 224 | <a href="/" tabindex="-1"> | ||
| 220 | <span class="lbl">home</span> | 225 | <span class="lbl">home</span> | ||
| 221 | <span class="k">go</span> | 226 | <span class="k">go</span> | ||
| 222 | </a> | 227 | </a> | ||
| 223 | </li> | 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 | </ul> | 235 | </ul> | ||
| 225 | <p class="panel-foot"> | 236 | <p class="panel-foot"> | ||
| 226 | <span> | 237 | <span> | ||
| @@ -259,7 +270,8 @@ | |||||
| 259 | k-permalink from:body -> ermite.permalink(); | 270 | k-permalink from:body -> ermite.permalink(); | ||
| 260 | k-merge from:body -> window.ermiteMR?.openMerge(); | 271 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 261 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 275 | </div> | ||
| 264 | </body> | 276 | </body> | ||
| 265 | </html> | 277 | </html> | ||
M internal/app/testdata/web/refs.html +27 -15
| @@ -221,6 +221,11 @@ | |||||
| 221 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 221 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 222 | <kbd>esc</kbd> close</button> | 222 | <kbd>esc</kbd> close</button> | ||
| 223 | </div> | 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 | <dl class="keys"> | 229 | <dl class="keys"> | ||
| 225 | <div> | 230 | <div> | ||
| 226 | <dt> | 231 | <dt> | ||
| @@ -378,84 +383,90 @@ | |||||
| 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()"> | 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 | <div class="panel-head"> | 384 | <div class="panel-head"> | ||
| 380 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 387 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 383 | <kbd>esc</kbd> | 388 | <kbd>esc</kbd> | ||
| 384 | </button> | 389 | </button> | ||
| 385 | </div> | 390 | </div> | ||
| 386 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 391 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 387 | - | <li data-k="go home" data-cursor> | 392 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 388 | <a href="/" tabindex="-1"> | 393 | <a href="/" tabindex="-1"> | ||
| 389 | <span class="lbl">home</span> | 394 | <span class="lbl">home</span> | ||
| 390 | <span class="k">go</span> | 395 | <span class="k">go</span> | ||
| 391 | </a> | 396 | </a> | ||
| 392 | </li> | 397 | </li> | ||
| 393 | - | <li data-k="section summary"> | 398 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 394 | <a href="/demo" tabindex="-1"> | 399 | <a href="/demo" tabindex="-1"> | ||
| 395 | <span class="lbl">summary</span> | 400 | <span class="lbl">summary</span> | ||
| 396 | <span class="k">section</span> | 401 | <span class="k">section</span> | ||
| 397 | </a> | 402 | </a> | ||
| 398 | </li> | 403 | </li> | ||
| 399 | - | <li data-k="section files"> | 404 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 400 | <a href="/demo/tree/main" tabindex="-1"> | 405 | <a href="/demo/tree/main" tabindex="-1"> | ||
| 401 | <span class="lbl">files</span> | 406 | <span class="lbl">files</span> | ||
| 402 | <span class="k">section</span> | 407 | <span class="k">section</span> | ||
| 403 | </a> | 408 | </a> | ||
| 404 | </li> | 409 | </li> | ||
| 405 | - | <li data-k="section commits"> | 410 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 406 | <a href="/demo/commits/main" tabindex="-1"> | 411 | <a href="/demo/commits/main" tabindex="-1"> | ||
| 407 | <span class="lbl">commits</span> | 412 | <span class="lbl">commits</span> | ||
| 408 | <span class="k">section</span> | 413 | <span class="k">section</span> | ||
| 409 | </a> | 414 | </a> | ||
| 410 | </li> | 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 | <a href="/demo/refs" tabindex="-1"> | 417 | <a href="/demo/refs" tabindex="-1"> | ||
| 413 | <span class="lbl">branches & tags</span> | 418 | <span class="lbl">branches & tags</span> | ||
| 414 | <span class="k">section</span> | 419 | <span class="k">section</span> | ||
| 415 | </a> | 420 | </a> | ||
| 416 | </li> | 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 | <a href="/demo/merge-requests" tabindex="-1"> | 423 | <a href="/demo/merge-requests" tabindex="-1"> | ||
| 419 | <span class="lbl">merge requests</span> | 424 | <span class="lbl">merge requests</span> | ||
| 420 | <span class="k">section</span> | 425 | <span class="k">section</span> | ||
| 421 | </a> | 426 | </a> | ||
| 422 | </li> | 427 | </li> | ||
| 423 | - | <li data-k="section actions"> | 428 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 424 | <a href="/demo/actions" tabindex="-1"> | 429 | <a href="/demo/actions" tabindex="-1"> | ||
| 425 | <span class="lbl">actions</span> | 430 | <span class="lbl">actions</span> | ||
| 426 | <span class="k">section</span> | 431 | <span class="k">section</span> | ||
| 427 | </a> | 432 | </a> | ||
| 428 | </li> | 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 | <a href="/demo/tree/feature/x" tabindex="-1"> | 435 | <a href="/demo/tree/feature/x" tabindex="-1"> | ||
| 431 | <span class="lbl">feature/x</span> | 436 | <span class="lbl">feature/x</span> | ||
| 432 | <span class="k">branch</span> | 437 | <span class="k">branch</span> | ||
| 433 | </a> | 438 | </a> | ||
| 434 | </li> | 439 | </li> | ||
| 435 | - | <li data-k="branch main"> | 440 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 436 | <a href="/demo/tree/main" tabindex="-1"> | 441 | <a href="/demo/tree/main" tabindex="-1"> | ||
| 437 | <span class="lbl">main</span> | 442 | <span class="lbl">main</span> | ||
| 438 | <span class="k">branch</span> | 443 | <span class="k">branch</span> | ||
| 439 | </a> | 444 | </a> | ||
| 440 | </li> | 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 | <a href="/demo/tree/v0.1.0" tabindex="-1"> | 447 | <a href="/demo/tree/v0.1.0" tabindex="-1"> | ||
| 443 | <span class="lbl">v0.1.0</span> | 448 | <span class="lbl">v0.1.0</span> | ||
| 444 | <span class="k">tag</span> | 449 | <span class="k">tag</span> | ||
| 445 | </a> | 450 | </a> | ||
| 446 | </li> | 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 | <a href="/demo/tree/v0.2.0" tabindex="-1"> | 453 | <a href="/demo/tree/v0.2.0" tabindex="-1"> | ||
| 449 | <span class="lbl">v0.2.0</span> | 454 | <span class="lbl">v0.2.0</span> | ||
| 450 | <span class="k">tag</span> | 455 | <span class="k">tag</span> | ||
| 451 | </a> | 456 | </a> | ||
| 452 | </li> | 457 | </li> | ||
| 453 | - | <li data-k="repo quiet"> | 458 | + | <li id="cmd-o11" role="option" aria-selected="false" data-k="repo quiet"> |
| 454 | <a href="/quiet" tabindex="-1"> | 459 | <a href="/quiet" tabindex="-1"> | ||
| 455 | <span class="lbl">quiet</span> | 460 | <span class="lbl">quiet</span> | ||
| 456 | <span class="k">repo</span> | 461 | <span class="k">repo</span> | ||
| 457 | </a> | 462 | </a> | ||
| 458 | </li> | 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 | </ul> | 470 | </ul> | ||
| 460 | <p class="panel-foot"> | 471 | <p class="panel-foot"> | ||
| 461 | <span> | 472 | <span> | ||
| @@ -494,7 +505,8 @@ | |||||
| 494 | k-permalink from:body -> ermite.permalink(); | 505 | k-permalink from:body -> ermite.permalink(); | ||
| 495 | k-merge from:body -> window.ermiteMR?.openMerge(); | 506 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 496 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 510 | </div> | ||
| 499 | </body> | 511 | </body> | ||
| 500 | </html> | 512 | </html> | ||
M internal/app/testdata/web/summary.html +27 -15
| @@ -245,6 +245,11 @@ | |||||
| 245 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 245 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 246 | <kbd>esc</kbd> close</button> | 246 | <kbd>esc</kbd> close</button> | ||
| 247 | </div> | 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 | <dl class="keys"> | 253 | <dl class="keys"> | ||
| 249 | <div> | 254 | <div> | ||
| 250 | <dt> | 255 | <dt> | ||
| @@ -402,84 +407,90 @@ | |||||
| 402 | <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()"> | 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 | <div class="panel-head"> | 408 | <div class="panel-head"> | ||
| 404 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 411 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 407 | <kbd>esc</kbd> | 412 | <kbd>esc</kbd> | ||
| 408 | </button> | 413 | </button> | ||
| 409 | </div> | 414 | </div> | ||
| 410 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 415 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 411 | - | <li data-k="go home" data-cursor> | 416 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 412 | <a href="/" tabindex="-1"> | 417 | <a href="/" tabindex="-1"> | ||
| 413 | <span class="lbl">home</span> | 418 | <span class="lbl">home</span> | ||
| 414 | <span class="k">go</span> | 419 | <span class="k">go</span> | ||
| 415 | </a> | 420 | </a> | ||
| 416 | </li> | 421 | </li> | ||
| 417 | - | <li data-k="section summary"> | 422 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 418 | <a href="/demo" tabindex="-1"> | 423 | <a href="/demo" tabindex="-1"> | ||
| 419 | <span class="lbl">summary</span> | 424 | <span class="lbl">summary</span> | ||
| 420 | <span class="k">section</span> | 425 | <span class="k">section</span> | ||
| 421 | </a> | 426 | </a> | ||
| 422 | </li> | 427 | </li> | ||
| 423 | - | <li data-k="section files"> | 428 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 424 | <a href="/demo/tree/main" tabindex="-1"> | 429 | <a href="/demo/tree/main" tabindex="-1"> | ||
| 425 | <span class="lbl">files</span> | 430 | <span class="lbl">files</span> | ||
| 426 | <span class="k">section</span> | 431 | <span class="k">section</span> | ||
| 427 | </a> | 432 | </a> | ||
| 428 | </li> | 433 | </li> | ||
| 429 | - | <li data-k="section commits"> | 434 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 430 | <a href="/demo/commits/main" tabindex="-1"> | 435 | <a href="/demo/commits/main" tabindex="-1"> | ||
| 431 | <span class="lbl">commits</span> | 436 | <span class="lbl">commits</span> | ||
| 432 | <span class="k">section</span> | 437 | <span class="k">section</span> | ||
| 433 | </a> | 438 | </a> | ||
| 434 | </li> | 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 | <a href="/demo/refs" tabindex="-1"> | 441 | <a href="/demo/refs" tabindex="-1"> | ||
| 437 | <span class="lbl">branches & tags</span> | 442 | <span class="lbl">branches & tags</span> | ||
| 438 | <span class="k">section</span> | 443 | <span class="k">section</span> | ||
| 439 | </a> | 444 | </a> | ||
| 440 | </li> | 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 | <a href="/demo/merge-requests" tabindex="-1"> | 447 | <a href="/demo/merge-requests" tabindex="-1"> | ||
| 443 | <span class="lbl">merge requests</span> | 448 | <span class="lbl">merge requests</span> | ||
| 444 | <span class="k">section</span> | 449 | <span class="k">section</span> | ||
| 445 | </a> | 450 | </a> | ||
| 446 | </li> | 451 | </li> | ||
| 447 | - | <li data-k="section actions"> | 452 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 448 | <a href="/demo/actions" tabindex="-1"> | 453 | <a href="/demo/actions" tabindex="-1"> | ||
| 449 | <span class="lbl">actions</span> | 454 | <span class="lbl">actions</span> | ||
| 450 | <span class="k">section</span> | 455 | <span class="k">section</span> | ||
| 451 | </a> | 456 | </a> | ||
| 452 | </li> | 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 | <a href="/demo/tree/feature/x" tabindex="-1"> | 459 | <a href="/demo/tree/feature/x" tabindex="-1"> | ||
| 455 | <span class="lbl">feature/x</span> | 460 | <span class="lbl">feature/x</span> | ||
| 456 | <span class="k">branch</span> | 461 | <span class="k">branch</span> | ||
| 457 | </a> | 462 | </a> | ||
| 458 | </li> | 463 | </li> | ||
| 459 | - | <li data-k="branch main"> | 464 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 460 | <a href="/demo/tree/main" tabindex="-1"> | 465 | <a href="/demo/tree/main" tabindex="-1"> | ||
| 461 | <span class="lbl">main</span> | 466 | <span class="lbl">main</span> | ||
| 462 | <span class="k">branch</span> | 467 | <span class="k">branch</span> | ||
| 463 | </a> | 468 | </a> | ||
| 464 | </li> | 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 | <a href="/demo/tree/v0.1.0" tabindex="-1"> | 471 | <a href="/demo/tree/v0.1.0" tabindex="-1"> | ||
| 467 | <span class="lbl">v0.1.0</span> | 472 | <span class="lbl">v0.1.0</span> | ||
| 468 | <span class="k">tag</span> | 473 | <span class="k">tag</span> | ||
| 469 | </a> | 474 | </a> | ||
| 470 | </li> | 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 | <a href="/demo/tree/v0.2.0" tabindex="-1"> | 477 | <a href="/demo/tree/v0.2.0" tabindex="-1"> | ||
| 473 | <span class="lbl">v0.2.0</span> | 478 | <span class="lbl">v0.2.0</span> | ||
| 474 | <span class="k">tag</span> | 479 | <span class="k">tag</span> | ||
| 475 | </a> | 480 | </a> | ||
| 476 | </li> | 481 | </li> | ||
| 477 | - | <li data-k="repo quiet"> | 482 | + | <li id="cmd-o11" role="option" aria-selected="false" data-k="repo quiet"> |
| 478 | <a href="/quiet" tabindex="-1"> | 483 | <a href="/quiet" tabindex="-1"> | ||
| 479 | <span class="lbl">quiet</span> | 484 | <span class="lbl">quiet</span> | ||
| 480 | <span class="k">repo</span> | 485 | <span class="k">repo</span> | ||
| 481 | </a> | 486 | </a> | ||
| 482 | </li> | 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 | </ul> | 494 | </ul> | ||
| 484 | <p class="panel-foot"> | 495 | <p class="panel-foot"> | ||
| 485 | <span> | 496 | <span> | ||
| @@ -518,7 +529,8 @@ | |||||
| 518 | k-permalink from:body -> ermite.permalink(); | 529 | k-permalink from:body -> ermite.permalink(); | ||
| 519 | k-merge from:body -> window.ermiteMR?.openMerge(); | 530 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 520 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 534 | </div> | ||
| 523 | </body> | 535 | </body> | ||
| 524 | </html> | 536 | </html> | ||
M internal/app/testdata/web/tree-nested.html +25 -13
| @@ -168,6 +168,11 @@ | |||||
| 168 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 168 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 169 | <kbd>esc</kbd> close</button> | 169 | <kbd>esc</kbd> close</button> | ||
| 170 | </div> | 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 | <dl class="keys"> | 176 | <dl class="keys"> | ||
| 172 | <div> | 177 | <div> | ||
| 173 | <dt> | 178 | <dt> | ||
| @@ -321,72 +326,78 @@ | |||||
| 321 | <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()"> | 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 | <div class="panel-head"> | 327 | <div class="panel-head"> | ||
| 323 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 330 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 326 | <kbd>esc</kbd> | 331 | <kbd>esc</kbd> | ||
| 327 | </button> | 332 | </button> | ||
| 328 | </div> | 333 | </div> | ||
| 329 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 334 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 330 | - | <li data-k="go home" data-cursor> | 335 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 331 | <a href="/" tabindex="-1"> | 336 | <a href="/" tabindex="-1"> | ||
| 332 | <span class="lbl">home</span> | 337 | <span class="lbl">home</span> | ||
| 333 | <span class="k">go</span> | 338 | <span class="k">go</span> | ||
| 334 | </a> | 339 | </a> | ||
| 335 | </li> | 340 | </li> | ||
| 336 | - | <li data-k="section summary"> | 341 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 337 | <a href="/files" tabindex="-1"> | 342 | <a href="/files" tabindex="-1"> | ||
| 338 | <span class="lbl">summary</span> | 343 | <span class="lbl">summary</span> | ||
| 339 | <span class="k">section</span> | 344 | <span class="k">section</span> | ||
| 340 | </a> | 345 | </a> | ||
| 341 | </li> | 346 | </li> | ||
| 342 | - | <li data-k="section files"> | 347 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 343 | <a href="/files/tree/main" tabindex="-1"> | 348 | <a href="/files/tree/main" tabindex="-1"> | ||
| 344 | <span class="lbl">files</span> | 349 | <span class="lbl">files</span> | ||
| 345 | <span class="k">section</span> | 350 | <span class="k">section</span> | ||
| 346 | </a> | 351 | </a> | ||
| 347 | </li> | 352 | </li> | ||
| 348 | - | <li data-k="section commits"> | 353 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 349 | <a href="/files/commits/main" tabindex="-1"> | 354 | <a href="/files/commits/main" tabindex="-1"> | ||
| 350 | <span class="lbl">commits</span> | 355 | <span class="lbl">commits</span> | ||
| 351 | <span class="k">section</span> | 356 | <span class="k">section</span> | ||
| 352 | </a> | 357 | </a> | ||
| 353 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 360 | <a href="/files/refs" tabindex="-1"> | ||
| 356 | <span class="lbl">branches & tags</span> | 361 | <span class="lbl">branches & tags</span> | ||
| 357 | <span class="k">section</span> | 362 | <span class="k">section</span> | ||
| 358 | </a> | 363 | </a> | ||
| 359 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 366 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 362 | <span class="lbl">merge requests</span> | 367 | <span class="lbl">merge requests</span> | ||
| 363 | <span class="k">section</span> | 368 | <span class="k">section</span> | ||
| 364 | </a> | 369 | </a> | ||
| 365 | </li> | 370 | </li> | ||
| 366 | - | <li data-k="section actions"> | 371 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 367 | <a href="/files/actions" tabindex="-1"> | 372 | <a href="/files/actions" tabindex="-1"> | ||
| 368 | <span class="lbl">actions</span> | 373 | <span class="lbl">actions</span> | ||
| 369 | <span class="k">section</span> | 374 | <span class="k">section</span> | ||
| 370 | </a> | 375 | </a> | ||
| 371 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 378 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 374 | <span class="lbl">feature/x</span> | 379 | <span class="lbl">feature/x</span> | ||
| 375 | <span class="k">branch</span> | 380 | <span class="k">branch</span> | ||
| 376 | </a> | 381 | </a> | ||
| 377 | </li> | 382 | </li> | ||
| 378 | - | <li data-k="branch main"> | 383 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 379 | <a href="/files/tree/main" tabindex="-1"> | 384 | <a href="/files/tree/main" tabindex="-1"> | ||
| 380 | <span class="lbl">main</span> | 385 | <span class="lbl">main</span> | ||
| 381 | <span class="k">branch</span> | 386 | <span class="k">branch</span> | ||
| 382 | </a> | 387 | </a> | ||
| 383 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 390 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 386 | <span class="lbl">v1.0</span> | 391 | <span class="lbl">v1.0</span> | ||
| 387 | <span class="k">tag</span> | 392 | <span class="k">tag</span> | ||
| 388 | </a> | 393 | </a> | ||
| 389 | </li> | 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 | </ul> | 401 | </ul> | ||
| 391 | <p class="panel-foot"> | 402 | <p class="panel-foot"> | ||
| 392 | <span> | 403 | <span> | ||
| @@ -425,7 +436,8 @@ | |||||
| 425 | k-permalink from:body -> ermite.permalink(); | 436 | k-permalink from:body -> ermite.permalink(); | ||
| 426 | k-merge from:body -> window.ermiteMR?.openMerge(); | 437 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 427 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 441 | </div> | ||
| 430 | </body> | 442 | </body> | ||
| 431 | </html> | 443 | </html> | ||
M internal/app/testdata/web/tree-root.html +25 -13
| @@ -389,6 +389,11 @@ | |||||
| 389 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 389 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 390 | <kbd>esc</kbd> close</button> | 390 | <kbd>esc</kbd> close</button> | ||
| 391 | </div> | 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 | <dl class="keys"> | 397 | <dl class="keys"> | ||
| 393 | <div> | 398 | <div> | ||
| 394 | <dt> | 399 | <dt> | ||
| @@ -542,72 +547,78 @@ | |||||
| 542 | <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()"> | 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 | <div class="panel-head"> | 548 | <div class="panel-head"> | ||
| 544 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 551 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 547 | <kbd>esc</kbd> | 552 | <kbd>esc</kbd> | ||
| 548 | </button> | 553 | </button> | ||
| 549 | </div> | 554 | </div> | ||
| 550 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 555 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 551 | - | <li data-k="go home" data-cursor> | 556 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 552 | <a href="/" tabindex="-1"> | 557 | <a href="/" tabindex="-1"> | ||
| 553 | <span class="lbl">home</span> | 558 | <span class="lbl">home</span> | ||
| 554 | <span class="k">go</span> | 559 | <span class="k">go</span> | ||
| 555 | </a> | 560 | </a> | ||
| 556 | </li> | 561 | </li> | ||
| 557 | - | <li data-k="section summary"> | 562 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 558 | <a href="/files" tabindex="-1"> | 563 | <a href="/files" tabindex="-1"> | ||
| 559 | <span class="lbl">summary</span> | 564 | <span class="lbl">summary</span> | ||
| 560 | <span class="k">section</span> | 565 | <span class="k">section</span> | ||
| 561 | </a> | 566 | </a> | ||
| 562 | </li> | 567 | </li> | ||
| 563 | - | <li data-k="section files"> | 568 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 564 | <a href="/files/tree/main" tabindex="-1"> | 569 | <a href="/files/tree/main" tabindex="-1"> | ||
| 565 | <span class="lbl">files</span> | 570 | <span class="lbl">files</span> | ||
| 566 | <span class="k">section</span> | 571 | <span class="k">section</span> | ||
| 567 | </a> | 572 | </a> | ||
| 568 | </li> | 573 | </li> | ||
| 569 | - | <li data-k="section commits"> | 574 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 570 | <a href="/files/commits/main" tabindex="-1"> | 575 | <a href="/files/commits/main" tabindex="-1"> | ||
| 571 | <span class="lbl">commits</span> | 576 | <span class="lbl">commits</span> | ||
| 572 | <span class="k">section</span> | 577 | <span class="k">section</span> | ||
| 573 | </a> | 578 | </a> | ||
| 574 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 581 | <a href="/files/refs" tabindex="-1"> | ||
| 577 | <span class="lbl">branches & tags</span> | 582 | <span class="lbl">branches & tags</span> | ||
| 578 | <span class="k">section</span> | 583 | <span class="k">section</span> | ||
| 579 | </a> | 584 | </a> | ||
| 580 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 587 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 583 | <span class="lbl">merge requests</span> | 588 | <span class="lbl">merge requests</span> | ||
| 584 | <span class="k">section</span> | 589 | <span class="k">section</span> | ||
| 585 | </a> | 590 | </a> | ||
| 586 | </li> | 591 | </li> | ||
| 587 | - | <li data-k="section actions"> | 592 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 588 | <a href="/files/actions" tabindex="-1"> | 593 | <a href="/files/actions" tabindex="-1"> | ||
| 589 | <span class="lbl">actions</span> | 594 | <span class="lbl">actions</span> | ||
| 590 | <span class="k">section</span> | 595 | <span class="k">section</span> | ||
| 591 | </a> | 596 | </a> | ||
| 592 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 599 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 595 | <span class="lbl">feature/x</span> | 600 | <span class="lbl">feature/x</span> | ||
| 596 | <span class="k">branch</span> | 601 | <span class="k">branch</span> | ||
| 597 | </a> | 602 | </a> | ||
| 598 | </li> | 603 | </li> | ||
| 599 | - | <li data-k="branch main"> | 604 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 600 | <a href="/files/tree/main" tabindex="-1"> | 605 | <a href="/files/tree/main" tabindex="-1"> | ||
| 601 | <span class="lbl">main</span> | 606 | <span class="lbl">main</span> | ||
| 602 | <span class="k">branch</span> | 607 | <span class="k">branch</span> | ||
| 603 | </a> | 608 | </a> | ||
| 604 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 611 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 607 | <span class="lbl">v1.0</span> | 612 | <span class="lbl">v1.0</span> | ||
| 608 | <span class="k">tag</span> | 613 | <span class="k">tag</span> | ||
| 609 | </a> | 614 | </a> | ||
| 610 | </li> | 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 | </ul> | 622 | </ul> | ||
| 612 | <p class="panel-foot"> | 623 | <p class="panel-foot"> | ||
| 613 | <span> | 624 | <span> | ||
| @@ -646,7 +657,8 @@ | |||||
| 646 | k-permalink from:body -> ermite.permalink(); | 657 | k-permalink from:body -> ermite.permalink(); | ||
| 647 | k-merge from:body -> window.ermiteMR?.openMerge(); | 658 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 648 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 662 | </div> | ||
| 651 | </body> | 663 | </body> | ||
| 652 | </html> | 664 | </html> | ||
M internal/app/testdata/web/tree-slash-ref.html +25 -13
| @@ -165,6 +165,11 @@ | |||||
| 165 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 165 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 166 | <kbd>esc</kbd> close</button> | 166 | <kbd>esc</kbd> close</button> | ||
| 167 | </div> | 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 | <dl class="keys"> | 173 | <dl class="keys"> | ||
| 169 | <div> | 174 | <div> | ||
| 170 | <dt> | 175 | <dt> | ||
| @@ -318,72 +323,78 @@ | |||||
| 318 | <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()"> | 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 | <div class="panel-head"> | 324 | <div class="panel-head"> | ||
| 320 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 327 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 323 | <kbd>esc</kbd> | 328 | <kbd>esc</kbd> | ||
| 324 | </button> | 329 | </button> | ||
| 325 | </div> | 330 | </div> | ||
| 326 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 331 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 327 | - | <li data-k="go home" data-cursor> | 332 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 328 | <a href="/" tabindex="-1"> | 333 | <a href="/" tabindex="-1"> | ||
| 329 | <span class="lbl">home</span> | 334 | <span class="lbl">home</span> | ||
| 330 | <span class="k">go</span> | 335 | <span class="k">go</span> | ||
| 331 | </a> | 336 | </a> | ||
| 332 | </li> | 337 | </li> | ||
| 333 | - | <li data-k="section summary"> | 338 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 334 | <a href="/files" tabindex="-1"> | 339 | <a href="/files" tabindex="-1"> | ||
| 335 | <span class="lbl">summary</span> | 340 | <span class="lbl">summary</span> | ||
| 336 | <span class="k">section</span> | 341 | <span class="k">section</span> | ||
| 337 | </a> | 342 | </a> | ||
| 338 | </li> | 343 | </li> | ||
| 339 | - | <li data-k="section files"> | 344 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 340 | <a href="/files/tree/main" tabindex="-1"> | 345 | <a href="/files/tree/main" tabindex="-1"> | ||
| 341 | <span class="lbl">files</span> | 346 | <span class="lbl">files</span> | ||
| 342 | <span class="k">section</span> | 347 | <span class="k">section</span> | ||
| 343 | </a> | 348 | </a> | ||
| 344 | </li> | 349 | </li> | ||
| 345 | - | <li data-k="section commits"> | 350 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 346 | <a href="/files/commits/main" tabindex="-1"> | 351 | <a href="/files/commits/main" tabindex="-1"> | ||
| 347 | <span class="lbl">commits</span> | 352 | <span class="lbl">commits</span> | ||
| 348 | <span class="k">section</span> | 353 | <span class="k">section</span> | ||
| 349 | </a> | 354 | </a> | ||
| 350 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 357 | <a href="/files/refs" tabindex="-1"> | ||
| 353 | <span class="lbl">branches & tags</span> | 358 | <span class="lbl">branches & tags</span> | ||
| 354 | <span class="k">section</span> | 359 | <span class="k">section</span> | ||
| 355 | </a> | 360 | </a> | ||
| 356 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 363 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 359 | <span class="lbl">merge requests</span> | 364 | <span class="lbl">merge requests</span> | ||
| 360 | <span class="k">section</span> | 365 | <span class="k">section</span> | ||
| 361 | </a> | 366 | </a> | ||
| 362 | </li> | 367 | </li> | ||
| 363 | - | <li data-k="section actions"> | 368 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 364 | <a href="/files/actions" tabindex="-1"> | 369 | <a href="/files/actions" tabindex="-1"> | ||
| 365 | <span class="lbl">actions</span> | 370 | <span class="lbl">actions</span> | ||
| 366 | <span class="k">section</span> | 371 | <span class="k">section</span> | ||
| 367 | </a> | 372 | </a> | ||
| 368 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 375 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 371 | <span class="lbl">feature/x</span> | 376 | <span class="lbl">feature/x</span> | ||
| 372 | <span class="k">branch</span> | 377 | <span class="k">branch</span> | ||
| 373 | </a> | 378 | </a> | ||
| 374 | </li> | 379 | </li> | ||
| 375 | - | <li data-k="branch main"> | 380 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 376 | <a href="/files/tree/main" tabindex="-1"> | 381 | <a href="/files/tree/main" tabindex="-1"> | ||
| 377 | <span class="lbl">main</span> | 382 | <span class="lbl">main</span> | ||
| 378 | <span class="k">branch</span> | 383 | <span class="k">branch</span> | ||
| 379 | </a> | 384 | </a> | ||
| 380 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 387 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 383 | <span class="lbl">v1.0</span> | 388 | <span class="lbl">v1.0</span> | ||
| 384 | <span class="k">tag</span> | 389 | <span class="k">tag</span> | ||
| 385 | </a> | 390 | </a> | ||
| 386 | </li> | 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 | </ul> | 398 | </ul> | ||
| 388 | <p class="panel-foot"> | 399 | <p class="panel-foot"> | ||
| 389 | <span> | 400 | <span> | ||
| @@ -422,7 +433,8 @@ | |||||
| 422 | k-permalink from:body -> ermite.permalink(); | 433 | k-permalink from:body -> ermite.permalink(); | ||
| 423 | k-merge from:body -> window.ermiteMR?.openMerge(); | 434 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 424 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 438 | </div> | ||
| 427 | </body> | 439 | </body> | ||
| 428 | </html> | 440 | </html> | ||
M internal/app/testdata/web/tree-subdir.html +25 -13
| @@ -181,6 +181,11 @@ | |||||
| 181 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 181 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 182 | <kbd>esc</kbd> close</button> | 182 | <kbd>esc</kbd> close</button> | ||
| 183 | </div> | 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 | <dl class="keys"> | 189 | <dl class="keys"> | ||
| 185 | <div> | 190 | <div> | ||
| 186 | <dt> | 191 | <dt> | ||
| @@ -334,72 +339,78 @@ | |||||
| 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()"> | 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 | <div class="panel-head"> | 340 | <div class="panel-head"> | ||
| 336 | <label for="cmd-q" class="sr">go to</label> | 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 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | 343 | <button type="button" class="panel-x" aria-label="Close" hx-on:click="closest('dialog').close()"> | ||
| 339 | <kbd>esc</kbd> | 344 | <kbd>esc</kbd> | ||
| 340 | </button> | 345 | </button> | ||
| 341 | </div> | 346 | </div> | ||
| 342 | - | <ul id="cmd-list" class="cmd-list" aria-label="Targets"> | 347 | + | <ul id="cmd-list" class="cmd-list" role="listbox" aria-label="Targets"> |
| 343 | - | <li data-k="go home" data-cursor> | 348 | + | <li id="cmd-o0" role="option" aria-selected="true" data-k="go home" data-cursor> |
| 344 | <a href="/" tabindex="-1"> | 349 | <a href="/" tabindex="-1"> | ||
| 345 | <span class="lbl">home</span> | 350 | <span class="lbl">home</span> | ||
| 346 | <span class="k">go</span> | 351 | <span class="k">go</span> | ||
| 347 | </a> | 352 | </a> | ||
| 348 | </li> | 353 | </li> | ||
| 349 | - | <li data-k="section summary"> | 354 | + | <li id="cmd-o1" role="option" aria-selected="false" data-k="section summary"> |
| 350 | <a href="/files" tabindex="-1"> | 355 | <a href="/files" tabindex="-1"> | ||
| 351 | <span class="lbl">summary</span> | 356 | <span class="lbl">summary</span> | ||
| 352 | <span class="k">section</span> | 357 | <span class="k">section</span> | ||
| 353 | </a> | 358 | </a> | ||
| 354 | </li> | 359 | </li> | ||
| 355 | - | <li data-k="section files"> | 360 | + | <li id="cmd-o2" role="option" aria-selected="false" data-k="section files"> |
| 356 | <a href="/files/tree/main" tabindex="-1"> | 361 | <a href="/files/tree/main" tabindex="-1"> | ||
| 357 | <span class="lbl">files</span> | 362 | <span class="lbl">files</span> | ||
| 358 | <span class="k">section</span> | 363 | <span class="k">section</span> | ||
| 359 | </a> | 364 | </a> | ||
| 360 | </li> | 365 | </li> | ||
| 361 | - | <li data-k="section commits"> | 366 | + | <li id="cmd-o3" role="option" aria-selected="false" data-k="section commits"> |
| 362 | <a href="/files/commits/main" tabindex="-1"> | 367 | <a href="/files/commits/main" tabindex="-1"> | ||
| 363 | <span class="lbl">commits</span> | 368 | <span class="lbl">commits</span> | ||
| 364 | <span class="k">section</span> | 369 | <span class="k">section</span> | ||
| 365 | </a> | 370 | </a> | ||
| 366 | </li> | 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 | <a href="/files/refs" tabindex="-1"> | 373 | <a href="/files/refs" tabindex="-1"> | ||
| 369 | <span class="lbl">branches & tags</span> | 374 | <span class="lbl">branches & tags</span> | ||
| 370 | <span class="k">section</span> | 375 | <span class="k">section</span> | ||
| 371 | </a> | 376 | </a> | ||
| 372 | </li> | 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 | <a href="/files/merge-requests" tabindex="-1"> | 379 | <a href="/files/merge-requests" tabindex="-1"> | ||
| 375 | <span class="lbl">merge requests</span> | 380 | <span class="lbl">merge requests</span> | ||
| 376 | <span class="k">section</span> | 381 | <span class="k">section</span> | ||
| 377 | </a> | 382 | </a> | ||
| 378 | </li> | 383 | </li> | ||
| 379 | - | <li data-k="section actions"> | 384 | + | <li id="cmd-o6" role="option" aria-selected="false" data-k="section actions"> |
| 380 | <a href="/files/actions" tabindex="-1"> | 385 | <a href="/files/actions" tabindex="-1"> | ||
| 381 | <span class="lbl">actions</span> | 386 | <span class="lbl">actions</span> | ||
| 382 | <span class="k">section</span> | 387 | <span class="k">section</span> | ||
| 383 | </a> | 388 | </a> | ||
| 384 | </li> | 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 | <a href="/files/tree/feature/x" tabindex="-1"> | 391 | <a href="/files/tree/feature/x" tabindex="-1"> | ||
| 387 | <span class="lbl">feature/x</span> | 392 | <span class="lbl">feature/x</span> | ||
| 388 | <span class="k">branch</span> | 393 | <span class="k">branch</span> | ||
| 389 | </a> | 394 | </a> | ||
| 390 | </li> | 395 | </li> | ||
| 391 | - | <li data-k="branch main"> | 396 | + | <li id="cmd-o8" role="option" aria-selected="false" data-k="branch main"> |
| 392 | <a href="/files/tree/main" tabindex="-1"> | 397 | <a href="/files/tree/main" tabindex="-1"> | ||
| 393 | <span class="lbl">main</span> | 398 | <span class="lbl">main</span> | ||
| 394 | <span class="k">branch</span> | 399 | <span class="k">branch</span> | ||
| 395 | </a> | 400 | </a> | ||
| 396 | </li> | 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 | <a href="/files/tree/v1.0" tabindex="-1"> | 403 | <a href="/files/tree/v1.0" tabindex="-1"> | ||
| 399 | <span class="lbl">v1.0</span> | 404 | <span class="lbl">v1.0</span> | ||
| 400 | <span class="k">tag</span> | 405 | <span class="k">tag</span> | ||
| 401 | </a> | 406 | </a> | ||
| 402 | </li> | 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 | </ul> | 414 | </ul> | ||
| 404 | <p class="panel-foot"> | 415 | <p class="panel-foot"> | ||
| 405 | <span> | 416 | <span> | ||
| @@ -438,7 +449,8 @@ | |||||
| 438 | k-permalink from:body -> ermite.permalink(); | 449 | k-permalink from:body -> ermite.permalink(); | ||
| 439 | k-merge from:body -> window.ermiteMR?.openMerge(); | 450 | k-merge from:body -> window.ermiteMR?.openMerge(); | ||
| 440 | k-read from:body -> window.ermiteMR?.markRead(); | 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 | </div> | 454 | </div> | ||
| 443 | </body> | 455 | </body> | ||
| 444 | </html> | 456 | </html> | ||