[object Object]

← back to Gated Queue Runner

add List view + full 8-column sortable Table (every field click-to-sort)

c0ab2e8351689516285289cd9ca3ec4d13f1343e · 2026-08-18 13:45:24 -0700 · steve

Files touched

Diff

commit c0ab2e8351689516285289cd9ca3ec4d13f1343e
Author: steve <steve@designerwallcoverings.com>
Date:   Tue Aug 18 13:45:24 2026 -0700

    add List view + full 8-column sortable Table (every field click-to-sort)
---
 index.html | 69 +++++++++++++++++++++++++++++++++++++++++---------------------
 1 file changed, 46 insertions(+), 23 deletions(-)

diff --git a/index.html b/index.html
index e1816e2..5c2d1fc 100644
--- a/index.html
+++ b/index.html
@@ -43,6 +43,9 @@
   td.effect { color: #445; max-width: 260px; font-size: .92em; }
   td.file { font-size: .78em; color: #99a; word-break: break-all; max-width: 220px; }
   td .link { color: #2d5bff; cursor: pointer; text-decoration: underline; font-size: .85em; }
+  .listtbl tbody td { padding: 4px 10px; font-size: .88em; }
+  .listtbl tr.cpt td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 480px; }
+  .ttl { font-weight: 600; }
   dialog { border: none; border-radius: 14px; max-width: 760px; width: 92vw; padding: 0; }
   dialog .dh { padding: 12px 16px; border-bottom: 1px solid #eee; font-weight: 700; display: flex; justify-content: space-between; }
   dialog pre { margin: 0; padding: 16px; white-space: pre-wrap; font-size: .82em; max-height: 70vh; overflow: auto; }
@@ -56,7 +59,7 @@
   <div class="controls">
     <input type="search" id="q" placeholder="🔎 search…">
     <label>View
-      <select id="view"><option value="cards">🃏 Cards</option><option value="table">▦ Table</option></select>
+      <select id="view"><option value="cards">🃏 Cards</option><option value="list">📋 List</option><option value="table">▦ Table</option></select>
     </label>
     <label>Sort
       <select id="sort">
@@ -74,6 +77,7 @@
 <main>
   <div class="grid" id="grid"></div>
   <table id="tbl" style="display:none"><thead id="thead"></thead><tbody id="tbody"></tbody></table>
+  <table id="ltbl" class="listtbl" style="display:none"><thead id="lhead"></thead><tbody id="lbody"></tbody></table>
 </main>
 <div id="selbar" style="display:none;position:fixed;bottom:0;left:0;right:0;background:#1c2333;color:#fff;padding:12px 18px;align-items:center;gap:10px;flex-wrap:wrap;box-shadow:0 -2px 10px rgba(0,0,0,.25);z-index:20">
   <span id="selcount" style="font-weight:700;margin-right:6px"></span>
@@ -144,15 +148,18 @@ function getRows(){
   return DATA.gates.filter(x=> (active.size===0||active.has(x.category)) &&
     (!q || (x.about+x.effect+x.file+x.title+x.catLabel).toLowerCase().includes(q)));
 }
+function sortByCol(g){
+  const key={kind:'catLabel',pick:'file'}[tSort.field]||tSort.field;
+  g.sort((a,b)=>{let va=a[key],vb=b[key]; if(typeof va==='string'){va=va.toLowerCase();vb=(vb||'').toLowerCase();} return (va<vb?-1:va>vb?1:0)*tSort.dir;});
+  return g;
+}
 function render(){
   const view=$('view').value; let g=getRows();
-  if(view==='table'){
-    $('grid').style.display='none'; $('tbl').style.display='';
-    const key={kind:'catLabel',pick:'file'}[tSort.field]||tSort.field;
-    g.sort((a,b)=>{let va=a[key],vb=b[key]; if(typeof va==='string'){va=va.toLowerCase();vb=(vb||'').toLowerCase();} return (va<vb?-1:va>vb?1:0)*tSort.dir;});
-    renderTable(g);
-  } else {
-    $('tbl').style.display='none'; $('grid').style.display='';
+  $('grid').style.display='none'; $('tbl').style.display='none'; $('ltbl').style.display='none';
+  if(view==='table'){ $('tbl').style.display=''; renderTable(sortByCol(g)); }
+  else if(view==='list'){ $('ltbl').style.display=''; renderList(sortByCol(g)); }
+  else {
+    $('grid').style.display='';
     const s=$('sort').value;
     if(s==='new')g.sort((a,b)=>b.created-a.created); else if(s==='old')g.sort((a,b)=>a.created-b.created);
     else if(s==='az')g.sort((a,b)=>a.title.localeCompare(b.title)); else if(s==='cat')g.sort((a,b)=>a.category.localeCompare(b.category)||b.created-a.created);
@@ -175,25 +182,41 @@ function renderCards(g){
   });
   if(!g.length) grid.innerHTML='<p style="color:#889">Nothing matches.</p>';
 }
-const COLS=[{f:'pick',l:'✔'},{f:'kind',l:'Kind'},{f:'about',l:"What it's about"},{f:'effect',l:'If you say YES'},{f:'created',l:'When'},{f:'file',l:'File'}];
-function renderTable(g){
-  $('thead').innerHTML='<tr>'+COLS.map(c=>{const ar=tSort.field===c.f?`<span class="ar">${tSort.dir>0?'▲':'▼'}</span>`:'';return `<th data-f="${c.f}">${c.l}${ar}</th>`;}).join('')+'</tr>';
-  $('thead').querySelectorAll('th').forEach(th=>th.onclick=()=>{const f=th.dataset.f; if(tSort.field===f)tSort.dir*=-1; else{tSort.field=f;tSort.dir=(f==='created'?-1:1);} render();});
-  const tb=$('tbody'); tb.innerHTML='';
+// full column set for TABLE; compact subset for LIST — every column is click-to-sort
+const COLS_TABLE=[{f:'pick',l:'✔'},{f:'kind',l:'Kind'},{f:'title',l:'Title'},{f:'about',l:"What it's about"},{f:'effect',l:'If you say YES'},{f:'created',l:'When'},{f:'size',l:'Size'},{f:'file',l:'File'}];
+const COLS_LIST =[{f:'pick',l:'✔'},{f:'kind',l:'Kind'},{f:'title',l:'Title'},{f:'created',l:'When'}];
+function cellFor(f,x){
+  switch(f){
+    case 'pick': return `<input type="checkbox" ${SEL.has(x.file)?'checked':''}>`;
+    case 'kind': return `<span style="white-space:nowrap">${x.emoji} ${esc(x.catLabel)}</span>`;
+    case 'title': return `<span class="ttl">${esc(x.title)}</span>`;
+    case 'about': return esc(x.about);
+    case 'effect': return esc(x.effect);
+    case 'created': return `<span style="white-space:nowrap">${fmtWhen(x.created)}</span>`;
+    case 'size': return (x.size/1024).toFixed(1)+' KB';
+    case 'file': return `${esc(x.file)} <span class="link">open</span>`;
+  }
+  return '';
+}
+function renderGrid(g, cols, tblId, compact){
+  const th=$(tblId+'head'), tb=$(tblId+'body');
+  th.innerHTML='<tr>'+cols.map(c=>{const ar=tSort.field===c.f?`<span class="ar">${tSort.dir>0?'▲':'▼'}</span>`:'';return `<th data-f="${c.f}">${c.l}${ar}</th>`;}).join('')+'</tr>';
+  th.querySelectorAll('th').forEach(t=>t.onclick=()=>{const f=t.dataset.f; if(tSort.field===f)tSort.dir*=-1; else{tSort.field=f;tSort.dir=(f==='created'?-1:1);} render();});
+  tb.innerHTML='';
   g.forEach(x=>{
-    const tr=document.createElement('tr'); if(SEL.has(x.file))tr.className='sel';
-    tr.innerHTML=`<td><input type="checkbox" ${SEL.has(x.file)?'checked':''}></td>
-      <td style="white-space:nowrap">${x.emoji} ${esc(x.catLabel)}</td>
-      <td class="about">${esc(x.about)}</td>
-      <td class="effect">${esc(x.effect)}</td>
-      <td style="white-space:nowrap">${fmtWhen(x.created)}</td>
-      <td class="file">${esc(x.file)} <span class="link">open</span></td>`;
-    tr.querySelector('input').onchange=e=>{ e.target.checked?SEL.add(x.file):SEL.delete(x.file); tr.classList.toggle('sel',e.target.checked); saveSel(); bar(); };
-    tr.querySelector('.link').onclick=()=>openMemo(x.file,x.title);
+    const tr=document.createElement('tr'); if(SEL.has(x.file))tr.className='sel'; if(compact)tr.className+=' cpt';
+    tr.innerHTML=cols.map(c=>{
+      const cls = c.f==='about'?' class="about"':c.f==='effect'?' class="effect"':c.f==='file'?' class="file"':'';
+      return `<td${cls}>${cellFor(c.f,x)}</td>`;
+    }).join('');
+    const cb=tr.querySelector('input[type=checkbox]'); if(cb) cb.onchange=e=>{ e.target.checked?SEL.add(x.file):SEL.delete(x.file); tr.classList.toggle('sel',e.target.checked); saveSel(); bar(); };
+    const lk=tr.querySelector('.link'); if(lk) lk.onclick=()=>openMemo(x.file,x.title);
     tb.appendChild(tr);
   });
-  if(!g.length) tb.innerHTML='<tr><td colspan="6" style="color:#889">Nothing matches.</td></tr>';
+  if(!g.length) tb.innerHTML=`<tr><td colspan="${cols.length}" style="color:#889">Nothing matches.</td></tr>`;
 }
+function renderTable(g){ renderGrid(g, COLS_TABLE, 't', false); }
+function renderList(g){ renderGrid(g, COLS_LIST, 'l', true); }
 function openMemo(f,t){ fetch('/api/memo?f='+encodeURIComponent(f)).then(r=>r.text()).then(txt=>{ $('dt').textContent=t; $('dp').textContent=txt; $('dlg').showModal(); }); }
 function esc(s){ return (s||'').replace(/[&<>]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;'}[c])); }
 

← dac79e4 GO actions: Allow/Never-run(archive)/Run-now(iTerm2 per item  ·  back to Gated Queue Runner  ·  queue viewer: add priority RANKING + per-item RATINGS (value f843c57 →