From 20a6bac3d64662b0ef2f0545ba9de61cfe081093 Mon Sep 17 00:00:00 2001 From: luxick Date: Wed, 27 May 2026 20:09:52 +0200 Subject: [PATCH] Unify tables and listings --- assets/companion.js | 2 +- assets/page/main.html | 16 +++---- assets/search-suggest.js | 52 +++++++++++++---------- assets/style.css | 91 ++++++++++++++++++++-------------------- render.go | 6 ++- 5 files changed, 91 insertions(+), 76 deletions(-) diff --git a/assets/companion.js b/assets/companion.js index 30cdd09..0132c98 100644 --- a/assets/companion.js +++ b/assets/companion.js @@ -115,7 +115,7 @@ function wireFileLinks() { if (!state.available) return; document.addEventListener('click', function (e) { - var item = e.target.closest && e.target.closest('.listing-item'); + var item = e.target.closest && e.target.closest('.list-item'); if (!item) return; var anchor = e.target.closest('a'); if (!anchor) return; diff --git a/assets/page/main.html b/assets/page/main.html index a7ac154..1be9576 100644 --- a/assets/page/main.html +++ b/assets/page/main.html @@ -9,15 +9,17 @@ {{end}} {{if .Entries}}

Files

-
+ + {{range .Entries}} -
- {{.Icon}} - {{.Name}} - {{.Meta}} -
+ + + + + {{end}} - + +
{{.Icon}}{{.Name}}{{.Meta}}
{{else if not .Content}} {{if not .SpecialContent}}

Empty folder — [CREATE]

diff --git a/assets/search-suggest.js b/assets/search-suggest.js index e87acb4..a15f996 100644 --- a/assets/search-suggest.js +++ b/assets/search-suggest.js @@ -70,6 +70,15 @@ dropdown.className = 'suggest-dropdown'; host.appendChild(dropdown); + function makeRow(cls, tabbable) { + var tr = document.createElement('tr'); + tr.className = cls; + if (tabbable) tr.setAttribute('tabindex', '0'); + var td = document.createElement('td'); + tr.appendChild(td); + return { tr: tr, td: td }; + } + var state = { results: [], total: 0, @@ -102,49 +111,50 @@ dropdown.classList.remove('is-open'); return; } + var table = document.createElement('table'); + table.className = 'data-table'; + var tbody = document.createElement('tbody'); + table.appendChild(tbody); + var tokens = tokenize(state.query); if (state.results.length === 0) { - var empty = document.createElement('div'); - empty.className = 'suggest-row is-empty'; - empty.textContent = 'No matches'; - dropdown.appendChild(empty); + var empty = makeRow('is-empty', false); + empty.td.textContent = 'No matches'; + tbody.appendChild(empty.tr); } else { state.results.forEach(function (r, i) { - var row = document.createElement('button'); - row.type = 'button'; - row.className = 'suggest-row'; - row.setAttribute('data-idx', String(i)); + var row = makeRow('suggest-row', true); + row.tr.setAttribute('data-idx', String(i)); var nameEl = document.createElement('span'); nameEl.className = 'suggest-name'; nameEl.innerHTML = highlight(r.name, tokens); var pathEl = document.createElement('span'); pathEl.className = 'suggest-path'; pathEl.textContent = '/' + r.path; - row.appendChild(nameEl); - row.appendChild(pathEl); - if (i === state.activeIdx) row.classList.add('is-active'); - row.addEventListener('mousedown', function (e) { + row.td.appendChild(nameEl); + row.td.appendChild(pathEl); + if (i === state.activeIdx) row.tr.classList.add('is-active'); + row.tr.addEventListener('mousedown', function (e) { // mousedown (not click) so the input doesn't blur-close // the dropdown before the pick handler fires. e.preventDefault(); pick(i); }); - dropdown.appendChild(row); + tbody.appendChild(row.tr); }); if (opts.showFooter && state.total > state.results.length) { - var footer = document.createElement('button'); - footer.type = 'button'; - footer.className = 'suggest-row suggest-footer'; - footer.textContent = 'Show all ' + state.total + ' matches'; + var footer = makeRow('suggest-row suggest-footer', true); + footer.td.textContent = 'Show all ' + state.total + ' matches'; var footerIdx = state.results.length; - if (state.activeIdx === footerIdx) footer.classList.add('is-active'); - footer.addEventListener('mousedown', function (e) { + if (state.activeIdx === footerIdx) footer.tr.classList.add('is-active'); + footer.tr.addEventListener('mousedown', function (e) { e.preventDefault(); pickFooter(); }); - dropdown.appendChild(footer); + tbody.appendChild(footer.tr); } } + dropdown.appendChild(table); dropdown.classList.add('is-open'); } @@ -225,7 +235,7 @@ state.activeIdx = next; render(); // Keep the active row in view. - var active = dropdown.querySelector('.suggest-row.is-active'); + var active = dropdown.querySelector('tr.is-active'); if (active && active.scrollIntoView) { try { active.scrollIntoView({ block: 'nearest' }); } catch (e) {} } diff --git a/assets/style.css b/assets/style.css index 58ac7f5..a6ebf9d 100644 --- a/assets/style.css +++ b/assets/style.css @@ -257,18 +257,6 @@ main > h2 { margin: var(--space-3) 0; } .content pre code { background: none; padding: 0; } -.content table { - width: 100%; - border-collapse: collapse; - margin: var(--space-3) 0; - font-size: 0.9rem; -} -.content th, .content td { - border: var(--border); - padding: 0.4rem var(--space-3); - text-align: left; -} -.content th { background: var(--bg-panel); color: var(--text); } .content hr { margin: var(--space-5) 0; } .content img { max-width: 100%; } .content li:has(> input.task-checkbox:checked) { @@ -288,22 +276,38 @@ main > h2 { } .heading-anchor .dropdown-toggle:hover { color: var(--primary-hover); } -/* === Listing rows === */ -.listing-item { - display: flex; - align-items: center; - gap: var(--space-3); - padding: 0.6rem var(--space-4); - font-size: 0.95rem; +/* === Data tables === + Shared style for the file listing, search-suggestion dropdown, and + markdown content tables. .data-table-grid adds per-cell borders + a + header band for content (markdown) tables. */ +.data-table { width: 100%; border-collapse: collapse; } +.data-table th, +.data-table td { padding: 0.4rem var(--space-3); text-align: left; } +.data-table:not(.data-table-grid) tbody tr + tr { border-top: var(--border); } +.data-table:not(.data-table-grid) tbody tr:hover, +.data-table tr.is-active { background: var(--bg-panel-hover); } +.data-table tbody tr.is-empty, +.data-table tbody tr.is-empty:hover { + color: var(--text-muted); + background: none; + cursor: default; } -.listing-item + .listing-item { border-top: var(--border); } -.listing-item:hover { background: var(--bg-panel-hover); } -.listing-item .icon { width: 1.25rem; text-align: center; flex-shrink: 0; } -.listing-item a { flex: 1; overflow-wrap: anywhere; color: inherit; } -.listing-item .meta { +.data-table-grid th, +.data-table-grid td { border: var(--border); } +.data-table-grid th { background: var(--bg-panel); color: var(--text); } +.content .data-table-grid { margin: var(--space-3) 0; font-size: 0.9rem; } + +/* File listing rows */ +.list-item { font-size: 0.95rem; } +.list-item > td { padding: 0.6rem var(--space-4); } +.list-item td.icon { width: 1.25rem; text-align: center; } +.list-item td.name { overflow-wrap: anywhere; } +.list-item td.name a { color: inherit; display: block; } +.list-item td.meta { color: var(--text-muted); font-size: 0.8rem; white-space: nowrap; + text-align: right; } /* === Dropdown menu === */ @@ -339,27 +343,12 @@ main > h2 { display: none; } .suggest-dropdown.is-open { display: block; } -.suggest-row { - display: flex; - flex-direction: column; - gap: 0.1rem; - padding: 0.4rem 0.6rem; - cursor: pointer; - border: none; - background: none; - color: inherit; - font: inherit; - text-align: left; - width: 100%; -} -.suggest-row + .suggest-row { border-top: var(--border); } -.suggest-row:hover, -.suggest-row.is-active { background: var(--bg-panel-hover); } -.suggest-row.is-empty { color: var(--text-muted); cursor: default; } -.suggest-row.is-empty:hover { background: none; } +.suggest-row { cursor: pointer; } +.suggest-row > td { padding: 0.4rem 0.6rem; } +.suggest-name, .suggest-path { display: block; } .suggest-name { color: var(--text); } -.suggest-path { color: var(--text-muted); font-size: 0.8rem; } -.suggest-footer { color: var(--link); font-size: var(--font-sm); } +.suggest-path { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.1rem; } +.suggest-footer > td { color: var(--link); font-size: var(--font-sm); } /* === Editor toolbar === */ .editor-toolbar { @@ -650,6 +639,16 @@ aside.sidebar:empty { display: none; } .modal-backdrop { padding: var(--space-2); align-items: flex-start; } .modal { max-width: none; margin-top: var(--space-4); } .modal .panel-header { cursor: default; } - .listing-item { flex-wrap: wrap; } - .listing-item .meta { flex-basis: 100%; padding-left: calc(1.25rem + var(--space-3)); } + /* On mobile, switch .list-item from a table row to a CSS grid so the + meta cell wraps to its own line indented under the name. */ + .list-item { + display: grid; + grid-template-columns: 1.25rem 1fr; + gap: 0 var(--space-3); + padding: 0.6rem var(--space-4); + } + .list-item > td { padding: 0; } + .list-item td.icon { grid-row: 1; grid-column: 1; } + .list-item td.name { grid-row: 1; grid-column: 2; } + .list-item td.meta { grid-row: 2; grid-column: 2; text-align: left; } } diff --git a/render.go b/render.go index 730b207..ca791fd 100644 --- a/render.go +++ b/render.go @@ -71,7 +71,11 @@ func renderMarkdown(raw []byte) template.HTML { if err := md.Convert(raw, &buf); err != nil { return "" } - return template.HTML(rewriteTaskCheckboxes(buf.Bytes())) + out := rewriteTaskCheckboxes(buf.Bytes()) + // Goldmark emits a bare ``; tag it so it picks up the shared + // .data-table styling with the grid modifier (per-cell borders + header). + out = bytes.ReplaceAll(out, []byte("
"), []byte(`
`)) + return template.HTML(out) } // extractFirstHeading returns the text of the first ATX heading in raw markdown,