fix(web): reopen diff files on a layout switch and let x expand them again
- commit
- 5e5d8fbe2752b45a31f22547f931af6c206f0559
- author
- brice · · 2026-10-11 12:46 UTC
- parent
- e84f160
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +319 -268
M internal/adapters/web/diff.templ +9 -1
| @@ -55,7 +55,7 @@ templ Commit(l Layout, p CommitPage) { | |||
| 55 | 55 | <span class="grow"></span> | |
| 56 | 56 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 57 | 57 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| 58 | - | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd></button> | |
| 58 | + | @collapseButton() | |
| 59 | 59 | </div> | |
| 60 | 60 | if p.Invisible { | |
| 61 | 61 | <p class="notice"> | |
| @@ -112,6 +112,14 @@ templ viewLink(p DiffView, view, label, key, href string) { | |||
| 112 | 112 | >{ label }<kbd aria-hidden="true">{ key }</kbd></a> | |
| 113 | 113 | } | |
| 114 | 114 | ||
| 115 | + | // collapseButton folds every file of the diff, or opens them all again when | |
| 116 | + | // none is open; its label follows the state. | |
| 117 | + | templ collapseButton() { | |
| 118 | + | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn"> | |
| 119 | + | <span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span><kbd aria-hidden="true">x</kbd> | |
| 120 | + | </button> | |
| 121 | + | } | |
| 122 | + | ||
| 115 | 123 | templ personView(pr Person) { | |
| 116 | 124 | { pr.Name } | |
| 117 | 125 | if pr.WhenAt != "" { | |
| @@ -55,7 +55,7 @@ templ Commit(l Layout, p CommitPage) { | |||||
| 55 | <span class="grow"></span> | 55 | <span class="grow"></span> | ||
| 56 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 56 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | ||
| 57 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 57 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | ||
| 58 | - | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd></button> | 58 | + | @collapseButton() |
| 59 | </div> | 59 | </div> | ||
| 60 | if p.Invisible { | 60 | if p.Invisible { | ||
| 61 | <p class="notice"> | 61 | <p class="notice"> | ||
| @@ -112,6 +112,14 @@ templ viewLink(p DiffView, view, label, key, href string) { | |||||
| 112 | >{ label }<kbd aria-hidden="true">{ key }</kbd></a> | 112 | >{ label }<kbd aria-hidden="true">{ key }</kbd></a> | ||
| 113 | } | 113 | } | ||
| 114 | 114 | ||||
| 115 | + | // collapseButton folds every file of the diff, or opens them all again when | |||
| 116 | + | // none is open; its label follows the state. | |||
| 117 | + | templ collapseButton() { | |||
| 118 | + | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn"> | |||
| 119 | + | <span hx-live="this.textContent = q('[data-file][open]').count || !q('[data-file]').count ? 'Collapse all' : 'Expand all'">Collapse all</span><kbd aria-hidden="true">x</kbd> | |||
| 120 | + | </button> | |||
| 121 | + | } | |||
| 122 | + | ||||
| 115 | templ personView(pr Person) { | 123 | templ personView(pr Person) { | ||
| 116 | { pr.Name } | 124 | { pr.Name } | ||
| 117 | if pr.WhenAt != "" { | 125 | if pr.WhenAt != "" { | ||
M internal/adapters/web/diff_templ.go +292 -257 generated file: collapsed
M internal/adapters/web/keys.templ +3 -3
| @@ -52,9 +52,9 @@ templ Keys() { | |||
| 52 | 52 | let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3); | |
| 53 | 53 | let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' }) | |
| 54 | 54 | }; | |
| 55 | - | k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); | |
| 56 | - | k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); | |
| 57 | - | k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false); | |
| 55 | + | k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true); | |
| 56 | + | k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true); | |
| 57 | + | k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) }; | |
| 58 | 58 | k-permalink from:body -> { | |
| 59 | 59 | let b = document.body.dataset; | |
| 60 | 60 | if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return } | |
| @@ -52,9 +52,9 @@ templ Keys() { | |||||
| 52 | let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3); | 52 | let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3); | ||
| 53 | let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' }) | 53 | let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' }) | ||
| 54 | }; | 54 | }; | ||
| 55 | - | k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); | 55 | + | k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true); |
| 56 | - | k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); | 56 | + | k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true); |
| 57 | - | k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false); | 57 | + | k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) }; |
| 58 | k-permalink from:body -> { | 58 | k-permalink from:body -> { | ||
| 59 | let b = document.body.dataset; | 59 | let b = document.body.dataset; | ||
| 60 | if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return } | 60 | if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return } | ||
M internal/adapters/web/keys_templ.go +1 -1
| @@ -34,7 +34,7 @@ func Keys() templ.Component { | |||
| 34 | 34 | templ_7745c5c3_Var1 = templ.NopComponent | |
| 35 | 35 | } | |
| 36 | 36 | ctx = templ.ClearChildren(ctx) | |
| 37 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div id=\"keys\" hidden hx-on=\"k-j from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(1);\n k-k from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(-1);\n k-top from:body -> q('[data-rows] > :not([hidden])').arr()[0]?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 0), q('#main').arr()[0].scrollTo(0, 0));\n k-bottom from:body -> q('[data-rows] > :not([hidden])').arr().at(-1)?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 1e9), q('#main').arr()[0].scrollTo(0, 1e9));\n k-open from:body -> q('dialog[open]').count || document.querySelector('[data-cursor] a')?.click();\n k-esc from:body -> q('dialog[open]').arr().forEach(d => d.close());\n k-help from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-help').arr()[0].showModal();\n k-refs from:body -> q('#panel-refs').count && (q('dialog[open]').arr().forEach(d => d.close()), q('#panel-refs').arr()[0].showModal(), q('#refs-filter').arr()[0].focus());\n k-cmd from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-cmd').arr()[0].showModal(); q('#cmd-q').arr()[0].focus();\n k-filter from:body -> q('[data-filter]').arr()[0]?.focus();\n k-gh from:body -> document.querySelector('.brand')?.click();\n k-gt from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-gc from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-gr from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-s from:body -> document.querySelector('.tabs [data-key=s]')?.click();\n k-t from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-c from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-r from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-m from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-gm from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-next from:body -> {\n let ns = q('#diffs .cmt').arr(); if (!ns.length) return;\n let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);\n let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })\n };\n k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');\n k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');\n k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);\n k-permalink from:body -> {\n let b = document.body.dataset;\n if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }\n history.replaceState(null, '', location.pathname.replace('/' + b.ref + '/', '/' + b.sha + '/') + location.hash);\n navigator.clipboard?.writeText(location.href);\n b.flash = 'permalink ' + b.sha.slice(0, 7) + ' copied'; await timeout('2s'); b.flash = ''\n }\"></div>") | |
| 37 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div id=\"keys\" hidden hx-on=\"k-j from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(1);\n k-k from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(-1);\n k-top from:body -> q('[data-rows] > :not([hidden])').arr()[0]?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 0), q('#main').arr()[0].scrollTo(0, 0));\n k-bottom from:body -> q('[data-rows] > :not([hidden])').arr().at(-1)?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 1e9), q('#main').arr()[0].scrollTo(0, 1e9));\n k-open from:body -> q('dialog[open]').count || document.querySelector('[data-cursor] a')?.click();\n k-esc from:body -> q('dialog[open]').arr().forEach(d => d.close());\n k-help from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-help').arr()[0].showModal();\n k-refs from:body -> q('#panel-refs').count && (q('dialog[open]').arr().forEach(d => d.close()), q('#panel-refs').arr()[0].showModal(), q('#refs-filter').arr()[0].focus());\n k-cmd from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-cmd').arr()[0].showModal(); q('#cmd-q').arr()[0].focus();\n k-filter from:body -> q('[data-filter]').arr()[0]?.focus();\n k-gh from:body -> document.querySelector('.brand')?.click();\n k-gt from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-gc from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-gr from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-s from:body -> document.querySelector('.tabs [data-key=s]')?.click();\n k-t from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-c from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-r from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-m from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-gm from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-next from:body -> {\n let ns = q('#diffs .cmt').arr(); if (!ns.length) return;\n let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);\n let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })\n };\n k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);\n k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);\n k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };\n k-permalink from:body -> {\n let b = document.body.dataset;\n if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }\n history.replaceState(null, '', location.pathname.replace('/' + b.ref + '/', '/' + b.sha + '/') + location.hash);\n navigator.clipboard?.writeText(location.href);\n b.flash = 'permalink ' + b.sha.slice(0, 7) + ' copied'; await timeout('2s'); b.flash = ''\n }\"></div>") | |
| 38 | 38 | if templ_7745c5c3_Err != nil { | |
| 39 | 39 | return templ_7745c5c3_Err | |
| 40 | 40 | } | |
| @@ -34,7 +34,7 @@ func Keys() templ.Component { | |||||
| 34 | templ_7745c5c3_Var1 = templ.NopComponent | 34 | templ_7745c5c3_Var1 = templ.NopComponent | ||
| 35 | } | 35 | } | ||
| 36 | ctx = templ.ClearChildren(ctx) | 36 | ctx = templ.ClearChildren(ctx) | ||
| 37 | - | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div id=\"keys\" hidden hx-on=\"k-j from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(1);\n k-k from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(-1);\n k-top from:body -> q('[data-rows] > :not([hidden])').arr()[0]?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 0), q('#main').arr()[0].scrollTo(0, 0));\n k-bottom from:body -> q('[data-rows] > :not([hidden])').arr().at(-1)?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 1e9), q('#main').arr()[0].scrollTo(0, 1e9));\n k-open from:body -> q('dialog[open]').count || document.querySelector('[data-cursor] a')?.click();\n k-esc from:body -> q('dialog[open]').arr().forEach(d => d.close());\n k-help from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-help').arr()[0].showModal();\n k-refs from:body -> q('#panel-refs').count && (q('dialog[open]').arr().forEach(d => d.close()), q('#panel-refs').arr()[0].showModal(), q('#refs-filter').arr()[0].focus());\n k-cmd from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-cmd').arr()[0].showModal(); q('#cmd-q').arr()[0].focus();\n k-filter from:body -> q('[data-filter]').arr()[0]?.focus();\n k-gh from:body -> document.querySelector('.brand')?.click();\n k-gt from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-gc from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-gr from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-s from:body -> document.querySelector('.tabs [data-key=s]')?.click();\n k-t from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-c from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-r from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-m from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-gm from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-next from:body -> {\n let ns = q('#diffs .cmt').arr(); if (!ns.length) return;\n let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);\n let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })\n };\n k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split');\n k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified');\n k-collapse from:body -> q('[data-file]').arr().forEach(d => d.open = false);\n k-permalink from:body -> {\n let b = document.body.dataset;\n if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }\n history.replaceState(null, '', location.pathname.replace('/' + b.ref + '/', '/' + b.sha + '/') + location.hash);\n navigator.clipboard?.writeText(location.href);\n b.flash = 'permalink ' + b.sha.slice(0, 7) + ' copied'; await timeout('2s'); b.flash = ''\n }\"></div>") | 37 | + | templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div id=\"keys\" hidden hx-on=\"k-j from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(1);\n k-k from:body -> (async (d) => {\n if (q('dialog[open]').count) return;\n let rows = q('[data-rows] > :not([hidden])').arr();\n if (!rows.length) return (scrollBy(0, d * 180), q('#main').arr()[0].scrollBy(0, d * 180));\n let i = rows.findIndex(r => r.hasAttribute('data-cursor'));\n let j = Math.max(0, Math.min(rows.length - 1, i + d)), n = rows[j];\n q(n).take('data-cursor'); n.scrollIntoView({ block: 'nearest' });\n let b = document.body.dataset, mark = (j + 1) + '/' + rows.length; b.nav = mark;\n await timeout('1500ms'); if (b.nav === mark) b.nav = ''\n })(-1);\n k-top from:body -> q('[data-rows] > :not([hidden])').arr()[0]?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 0), q('#main').arr()[0].scrollTo(0, 0));\n k-bottom from:body -> q('[data-rows] > :not([hidden])').arr().at(-1)?.scrollIntoView({ block: 'nearest' }) ?? (scrollTo(0, 1e9), q('#main').arr()[0].scrollTo(0, 1e9));\n k-open from:body -> q('dialog[open]').count || document.querySelector('[data-cursor] a')?.click();\n k-esc from:body -> q('dialog[open]').arr().forEach(d => d.close());\n k-help from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-help').arr()[0].showModal();\n k-refs from:body -> q('#panel-refs').count && (q('dialog[open]').arr().forEach(d => d.close()), q('#panel-refs').arr()[0].showModal(), q('#refs-filter').arr()[0].focus());\n k-cmd from:body -> q('dialog[open]').arr().forEach(d => d.close()); q('#panel-cmd').arr()[0].showModal(); q('#cmd-q').arr()[0].focus();\n k-filter from:body -> q('[data-filter]').arr()[0]?.focus();\n k-gh from:body -> document.querySelector('.brand')?.click();\n k-gt from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-gc from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-gr from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-s from:body -> document.querySelector('.tabs [data-key=s]')?.click();\n k-t from:body -> document.querySelector('.tabs [data-key=t]')?.click();\n k-c from:body -> document.querySelector('.tabs [data-key=c]')?.click();\n k-r from:body -> document.querySelector('.tabs [data-key=r]')?.click();\n k-m from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-gm from:body -> document.querySelector('.tabs [data-key=m]')?.click();\n k-next from:body -> {\n let ns = q('#diffs .cmt').arr(); if (!ns.length) return;\n let i = ns.findIndex(n => n.getBoundingClientRect().top > innerHeight / 3);\n let n = ns[i < 0 ? 0 : i]; n.closest('details') && (n.closest('details').open = true); n.scrollIntoView({ block: 'center' })\n };\n k-split from:body -> q('[data-diff]').attr['data-diff'] = 'split'; sessionStorage.setItem('diff', 'split'); q('[data-file]').arr().forEach(d => d.open = true);\n k-unified from:body -> q('[data-diff]').attr['data-diff'] = 'unified'; sessionStorage.setItem('diff', 'unified'); q('[data-file]').arr().forEach(d => d.open = true);\n k-collapse from:body -> { let fs = q('[data-file]').arr(), open = !fs.some(d => d.open); fs.forEach(d => d.open = open) };\n k-permalink from:body -> {\n let b = document.body.dataset;\n if (!b.sha) { b.flash = 'no ref to pin on this page'; await timeout('2s'); b.flash = ''; return }\n history.replaceState(null, '', location.pathname.replace('/' + b.ref + '/', '/' + b.sha + '/') + location.hash);\n navigator.clipboard?.writeText(location.href);\n b.flash = 'permalink ' + b.sha.slice(0, 7) + ' copied'; await timeout('2s'); b.flash = ''\n }\"></div>") |
| 38 | if templ_7745c5c3_Err != nil { | 38 | if templ_7745c5c3_Err != nil { | ||
| 39 | return templ_7745c5c3_Err | 39 | return templ_7745c5c3_Err | ||
| 40 | } | 40 | } | ||
M internal/adapters/web/mr.templ +2 -2
| @@ -168,7 +168,7 @@ templ MergeRequest(l Layout, p MRPage) { | |||
| 168 | 168 | <span class="grow"></span> | |
| 169 | 169 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 170 | 170 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| 171 | - | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd></button> | |
| 171 | + | @collapseButton() | |
| 172 | 172 | </div> | |
| 173 | 173 | if p.Invisible { | |
| 174 | 174 | <p class="notice"> | |
| @@ -448,7 +448,7 @@ templ Compare(l Layout, p ComparePage) { | |||
| 448 | 448 | <span class="grow"></span> | |
| 449 | 449 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | |
| 450 | 450 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | |
| 451 | - | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd></button> | |
| 451 | + | @collapseButton() | |
| 452 | 452 | </div> | |
| 453 | 453 | if p.Invisible { | |
| 454 | 454 | <p class="notice"> | |
| @@ -168,7 +168,7 @@ templ MergeRequest(l Layout, p MRPage) { | |||||
| 168 | <span class="grow"></span> | 168 | <span class="grow"></span> | ||
| 169 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 169 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | ||
| 170 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 170 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | ||
| 171 | - | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd></button> | 171 | + | @collapseButton() |
| 172 | </div> | 172 | </div> | ||
| 173 | if p.Invisible { | 173 | if p.Invisible { | ||
| 174 | <p class="notice"> | 174 | <p class="notice"> | ||
| @@ -448,7 +448,7 @@ templ Compare(l Layout, p ComparePage) { | |||||
| 448 | <span class="grow"></span> | 448 | <span class="grow"></span> | ||
| 449 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | 449 | @viewLink(p.DiffView, "unified", "Unified", "u", p.UnifiedHref) | ||
| 450 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | 450 | @viewLink(p.DiffView, "split", "Split", "d", p.SplitHref) | ||
| 451 | - | <button type="button" aria-keyshortcuts="x" hx-on:click="document.body.dispatchEvent(new CustomEvent('k-collapse'))" class="btn">Collapse all<kbd aria-hidden="true">x</kbd></button> | 451 | + | @collapseButton() |
| 452 | </div> | 452 | </div> | ||
| 453 | if p.Invisible { | 453 | if p.Invisible { | ||
| 454 | <p class="notice"> | 454 | <p class="notice"> | ||
M internal/adapters/web/mr_templ.go +10 -2 generated file: collapsed
M internal/adapters/web/overlays.templ +1 -1
| @@ -19,7 +19,7 @@ templ OverlayHelp() { | |||
| 19 | 19 | <div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests</dd></div> | |
| 20 | 20 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | |
| 21 | 21 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | |
| 22 | - | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse all</dd></div> | |
| 22 | + | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> | |
| 23 | 23 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | |
| 24 | 24 | <div><dt><kbd>esc</kbd></dt><dd>close a dialog, leave a field</dd></div> | |
| 25 | 25 | </dl> | |
| @@ -19,7 +19,7 @@ templ OverlayHelp() { | |||||
| 19 | <div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests</dd></div> | 19 | <div><dt><kbd>s</kbd> <kbd>t</kbd> <kbd>c</kbd> <kbd>r</kbd> <kbd>m</kbd></dt><dd>tabs: summary, files, commits, branches & tags, merge requests</dd></div> | ||
| 20 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | 20 | <div><dt><kbd>g h</kbd></dt><dd>home</dd></div> | ||
| 21 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | 21 | <div><dt><kbd>y</kbd></dt><dd>copy a permalink pinned to the commit</dd></div> | ||
| 22 | - | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse all</dd></div> | 22 | + | <div><dt><kbd>d</kbd> <kbd>u</kbd> <kbd>x</kbd></dt><dd>diff: split / unified / collapse or expand all</dd></div> |
| 23 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | 23 | <div><dt><kbd>n</kbd></dt><dd>merge request: next note in the diff</dd></div> | ||
| 24 | <div><dt><kbd>esc</kbd></dt><dd>close a dialog, leave a field</dd></div> | 24 | <div><dt><kbd>esc</kbd></dt><dd>close a dialog, leave a field</dd></div> | ||
| 25 | </dl> | 25 | </dl> | ||