Unify tables and listings
This commit is contained in:
+1
-1
@@ -115,7 +115,7 @@
|
|||||||
function wireFileLinks() {
|
function wireFileLinks() {
|
||||||
if (!state.available) return;
|
if (!state.available) return;
|
||||||
document.addEventListener('click', function (e) {
|
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;
|
if (!item) return;
|
||||||
var anchor = e.target.closest('a');
|
var anchor = e.target.closest('a');
|
||||||
if (!anchor) return;
|
if (!anchor) return;
|
||||||
|
|||||||
@@ -9,15 +9,17 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
{{if .Entries}}
|
{{if .Entries}}
|
||||||
<h2 id="files">Files <button class="btn btn-small" data-companion-reveal hidden title="Open folder in file manager">open</button></h2>
|
<h2 id="files">Files <button class="btn btn-small" data-companion-reveal hidden title="Open folder in file manager">open</button></h2>
|
||||||
<div class="listing panel">
|
<table class="data-table panel">
|
||||||
|
<tbody>
|
||||||
{{range .Entries}}
|
{{range .Entries}}
|
||||||
<div class="listing-item" data-path="{{.URL}}">
|
<tr class="list-item" data-path="{{.URL}}">
|
||||||
<span class="icon">{{.Icon}}</span>
|
<td class="icon">{{.Icon}}</td>
|
||||||
<a href="{{.URL}}">{{.Name}}</a>
|
<td class="name"><a href="{{.URL}}">{{.Name}}</a></td>
|
||||||
<span class="meta">{{.Meta}}</span>
|
<td class="meta">{{.Meta}}</td>
|
||||||
</div>
|
</tr>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</tbody>
|
||||||
|
</table>
|
||||||
{{else if not .Content}}
|
{{else if not .Content}}
|
||||||
{{if not .SpecialContent}}
|
{{if not .SpecialContent}}
|
||||||
<p class="empty">Empty folder — <a href="?edit">[CREATE]</a></p>
|
<p class="empty">Empty folder — <a href="?edit">[CREATE]</a></p>
|
||||||
|
|||||||
+31
-21
@@ -70,6 +70,15 @@
|
|||||||
dropdown.className = 'suggest-dropdown';
|
dropdown.className = 'suggest-dropdown';
|
||||||
host.appendChild(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 = {
|
var state = {
|
||||||
results: [],
|
results: [],
|
||||||
total: 0,
|
total: 0,
|
||||||
@@ -102,49 +111,50 @@
|
|||||||
dropdown.classList.remove('is-open');
|
dropdown.classList.remove('is-open');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
var table = document.createElement('table');
|
||||||
|
table.className = 'data-table';
|
||||||
|
var tbody = document.createElement('tbody');
|
||||||
|
table.appendChild(tbody);
|
||||||
|
|
||||||
var tokens = tokenize(state.query);
|
var tokens = tokenize(state.query);
|
||||||
if (state.results.length === 0) {
|
if (state.results.length === 0) {
|
||||||
var empty = document.createElement('div');
|
var empty = makeRow('is-empty', false);
|
||||||
empty.className = 'suggest-row is-empty';
|
empty.td.textContent = 'No matches';
|
||||||
empty.textContent = 'No matches';
|
tbody.appendChild(empty.tr);
|
||||||
dropdown.appendChild(empty);
|
|
||||||
} else {
|
} else {
|
||||||
state.results.forEach(function (r, i) {
|
state.results.forEach(function (r, i) {
|
||||||
var row = document.createElement('button');
|
var row = makeRow('suggest-row', true);
|
||||||
row.type = 'button';
|
row.tr.setAttribute('data-idx', String(i));
|
||||||
row.className = 'suggest-row';
|
|
||||||
row.setAttribute('data-idx', String(i));
|
|
||||||
var nameEl = document.createElement('span');
|
var nameEl = document.createElement('span');
|
||||||
nameEl.className = 'suggest-name';
|
nameEl.className = 'suggest-name';
|
||||||
nameEl.innerHTML = highlight(r.name, tokens);
|
nameEl.innerHTML = highlight(r.name, tokens);
|
||||||
var pathEl = document.createElement('span');
|
var pathEl = document.createElement('span');
|
||||||
pathEl.className = 'suggest-path';
|
pathEl.className = 'suggest-path';
|
||||||
pathEl.textContent = '/' + r.path;
|
pathEl.textContent = '/' + r.path;
|
||||||
row.appendChild(nameEl);
|
row.td.appendChild(nameEl);
|
||||||
row.appendChild(pathEl);
|
row.td.appendChild(pathEl);
|
||||||
if (i === state.activeIdx) row.classList.add('is-active');
|
if (i === state.activeIdx) row.tr.classList.add('is-active');
|
||||||
row.addEventListener('mousedown', function (e) {
|
row.tr.addEventListener('mousedown', function (e) {
|
||||||
// mousedown (not click) so the input doesn't blur-close
|
// mousedown (not click) so the input doesn't blur-close
|
||||||
// the dropdown before the pick handler fires.
|
// the dropdown before the pick handler fires.
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
pick(i);
|
pick(i);
|
||||||
});
|
});
|
||||||
dropdown.appendChild(row);
|
tbody.appendChild(row.tr);
|
||||||
});
|
});
|
||||||
if (opts.showFooter && state.total > state.results.length) {
|
if (opts.showFooter && state.total > state.results.length) {
|
||||||
var footer = document.createElement('button');
|
var footer = makeRow('suggest-row suggest-footer', true);
|
||||||
footer.type = 'button';
|
footer.td.textContent = 'Show all ' + state.total + ' matches';
|
||||||
footer.className = 'suggest-row suggest-footer';
|
|
||||||
footer.textContent = 'Show all ' + state.total + ' matches';
|
|
||||||
var footerIdx = state.results.length;
|
var footerIdx = state.results.length;
|
||||||
if (state.activeIdx === footerIdx) footer.classList.add('is-active');
|
if (state.activeIdx === footerIdx) footer.tr.classList.add('is-active');
|
||||||
footer.addEventListener('mousedown', function (e) {
|
footer.tr.addEventListener('mousedown', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
pickFooter();
|
pickFooter();
|
||||||
});
|
});
|
||||||
dropdown.appendChild(footer);
|
tbody.appendChild(footer.tr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
dropdown.appendChild(table);
|
||||||
dropdown.classList.add('is-open');
|
dropdown.classList.add('is-open');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +235,7 @@
|
|||||||
state.activeIdx = next;
|
state.activeIdx = next;
|
||||||
render();
|
render();
|
||||||
// Keep the active row in view.
|
// 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) {
|
if (active && active.scrollIntoView) {
|
||||||
try { active.scrollIntoView({ block: 'nearest' }); } catch (e) {}
|
try { active.scrollIntoView({ block: 'nearest' }); } catch (e) {}
|
||||||
}
|
}
|
||||||
|
|||||||
+45
-46
@@ -257,18 +257,6 @@ main > h2 {
|
|||||||
margin: var(--space-3) 0;
|
margin: var(--space-3) 0;
|
||||||
}
|
}
|
||||||
.content pre code { background: none; padding: 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 hr { margin: var(--space-5) 0; }
|
||||||
.content img { max-width: 100%; }
|
.content img { max-width: 100%; }
|
||||||
.content li:has(> input.task-checkbox:checked) {
|
.content li:has(> input.task-checkbox:checked) {
|
||||||
@@ -288,22 +276,38 @@ main > h2 {
|
|||||||
}
|
}
|
||||||
.heading-anchor .dropdown-toggle:hover { color: var(--primary-hover); }
|
.heading-anchor .dropdown-toggle:hover { color: var(--primary-hover); }
|
||||||
|
|
||||||
/* === Listing rows === */
|
/* === Data tables ===
|
||||||
.listing-item {
|
Shared style for the file listing, search-suggestion dropdown, and
|
||||||
display: flex;
|
markdown content tables. .data-table-grid adds per-cell borders + a
|
||||||
align-items: center;
|
header band for content (markdown) tables. */
|
||||||
gap: var(--space-3);
|
.data-table { width: 100%; border-collapse: collapse; }
|
||||||
padding: 0.6rem var(--space-4);
|
.data-table th,
|
||||||
font-size: 0.95rem;
|
.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); }
|
.data-table-grid th,
|
||||||
.listing-item:hover { background: var(--bg-panel-hover); }
|
.data-table-grid td { border: var(--border); }
|
||||||
.listing-item .icon { width: 1.25rem; text-align: center; flex-shrink: 0; }
|
.data-table-grid th { background: var(--bg-panel); color: var(--text); }
|
||||||
.listing-item a { flex: 1; overflow-wrap: anywhere; color: inherit; }
|
.content .data-table-grid { margin: var(--space-3) 0; font-size: 0.9rem; }
|
||||||
.listing-item .meta {
|
|
||||||
|
/* 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);
|
color: var(--text-muted);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* === Dropdown menu === */
|
/* === Dropdown menu === */
|
||||||
@@ -339,27 +343,12 @@ main > h2 {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
.suggest-dropdown.is-open { display: block; }
|
.suggest-dropdown.is-open { display: block; }
|
||||||
.suggest-row {
|
.suggest-row { cursor: pointer; }
|
||||||
display: flex;
|
.suggest-row > td { padding: 0.4rem 0.6rem; }
|
||||||
flex-direction: column;
|
.suggest-name, .suggest-path { display: block; }
|
||||||
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-name { color: var(--text); }
|
.suggest-name { color: var(--text); }
|
||||||
.suggest-path { color: var(--text-muted); font-size: 0.8rem; }
|
.suggest-path { color: var(--text-muted); font-size: 0.8rem; margin-top: 0.1rem; }
|
||||||
.suggest-footer { color: var(--link); font-size: var(--font-sm); }
|
.suggest-footer > td { color: var(--link); font-size: var(--font-sm); }
|
||||||
|
|
||||||
/* === Editor toolbar === */
|
/* === Editor toolbar === */
|
||||||
.editor-toolbar {
|
.editor-toolbar {
|
||||||
@@ -650,6 +639,16 @@ aside.sidebar:empty { display: none; }
|
|||||||
.modal-backdrop { padding: var(--space-2); align-items: flex-start; }
|
.modal-backdrop { padding: var(--space-2); align-items: flex-start; }
|
||||||
.modal { max-width: none; margin-top: var(--space-4); }
|
.modal { max-width: none; margin-top: var(--space-4); }
|
||||||
.modal .panel-header { cursor: default; }
|
.modal .panel-header { cursor: default; }
|
||||||
.listing-item { flex-wrap: wrap; }
|
/* On mobile, switch .list-item from a table row to a CSS grid so the
|
||||||
.listing-item .meta { flex-basis: 100%; padding-left: calc(1.25rem + var(--space-3)); }
|
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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,11 @@ func renderMarkdown(raw []byte) template.HTML {
|
|||||||
if err := md.Convert(raw, &buf); err != nil {
|
if err := md.Convert(raw, &buf); err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return template.HTML(rewriteTaskCheckboxes(buf.Bytes()))
|
out := rewriteTaskCheckboxes(buf.Bytes())
|
||||||
|
// Goldmark emits a bare `<table>`; tag it so it picks up the shared
|
||||||
|
// .data-table styling with the grid modifier (per-cell borders + header).
|
||||||
|
out = bytes.ReplaceAll(out, []byte("<table>"), []byte(`<table class="data-table data-table-grid">`))
|
||||||
|
return template.HTML(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
// extractFirstHeading returns the text of the first ATX heading in raw markdown,
|
// extractFirstHeading returns the text of the first ATX heading in raw markdown,
|
||||||
|
|||||||
Reference in New Issue
Block a user