[object Object]

← back to Rentv Sheet Enrich

Live console: Website/LinkedIn/phone cells are now WHOLE-CELL clickable (leading icon never clipped) — opens popup / dials; text cells stay editable

4ff3c021a195b30f6d5a95c23565b29f4def39df · 2026-08-13 16:06:19 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 4ff3c021a195b30f6d5a95c23565b29f4def39df
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Aug 13 16:06:19 2026 -0700

    Live console: Website/LinkedIn/phone cells are now WHOLE-CELL clickable (leading icon never clipped) — opens popup / dials; text cells stay editable
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public_live/index.html | 37 +++++++++++++++++++++++++++++++------
 1 file changed, 31 insertions(+), 6 deletions(-)

diff --git a/public_live/index.html b/public_live/index.html
index 574914c..2d03cd8 100644
--- a/public_live/index.html
+++ b/public_live/index.html
@@ -82,6 +82,15 @@
   a.iconbtn svg,a.callbtn svg{width:1.05em;height:1.05em;display:block}
   a.callbtn{color:#16a34a} a.iconbtn.web{color:var(--accent)} a.iconbtn.li{color:#0a66c2}
   .linkval{color:inherit}
+  /* whole-cell clickable links: icon on the LEFT (never clipped), entire cell opens popup / dials */
+  td.linkcell,td.callcell{cursor:pointer}
+  td.linkcell .cicon,td.callcell .cicon{display:inline-flex;vertical-align:middle;margin-right:5px}
+  td.linkcell .cicon svg,td.callcell .cicon svg{width:1.05em;height:1.05em;display:block}
+  td.linkcell.web .cicon,td.linkcell.web .linkval{color:var(--accent)}
+  td.linkcell.li .cicon,td.linkcell.li .linkval{color:#4aa3ff}
+  td.callcell .cicon{color:#16a34a}
+  td.linkcell:hover,td.callcell:hover{background:#12283a}
+  td.linkcell:hover .linkval,td.callcell:hover .linkval{text-decoration:underline}
   /* FIELD LOOKUP: click a field in the left rail -> its full value list opens here in the main area */
   .lkhead{display:flex;align-items:center;gap:10px;padding:8px 10px;position:sticky;top:0;background:var(--panel2);border-bottom:1px solid var(--line);z-index:6}
   .lkhead b{font-size:14px;color:var(--accent)} .lksub{color:var(--dim);font-size:12px}
@@ -419,7 +428,7 @@ function tableHTML(rows){
     if(GROUP!==''){ const gv=g(r,+GROUP)||'(blank)'; if(gv!==lastG){ lastG=gv; h+=`<tr class="grouphdr"><td colspan="${cols.length+1}">${esc(gv)}</td></tr>`; } }
     h+=`<tr><td><button class="rowbtn" data-r="${ri}">✍</button></td>`;
     for(const c of cols){ const gr=GREENSET.has(ri+','+c.i), isN=c.i===ROLE.name, val=g(r,c.i);
-      h+=`<td data-r="${ri}" data-c="${c.i}" contenteditable="true" title="${esc(val)}" class="${[isN?'namecell':'',gr?'enriched':''].filter(Boolean).join(' ')}">${cellRender(val)}</td>`; }
+      h+=cellTD(ri,c.i,val,isN,gr); }
     h+='</tr>';
   }
   return h+'</tbody></table>';
@@ -442,6 +451,22 @@ function iconLink(url,kind){
   const svg=kind==='li'?ICON.li:ICON.web, label=kind==='li'?'Open LinkedIn profile':'Open website';
   return `<a class="iconbtn ${kind}" href="${esc(url)}" target="_blank" rel="noopener" contenteditable="false" title="${label}">${svg}</a>`;
 }
+// digits of a cell whose WHOLE value is one phone number (anchored, so a phone inside Notes prose is ignored)
+function telDigits(v){ v=String(v==null?'':v).trim();
+  if(!/^(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}(\s*(x|ext\.?)\s*\d+)?$/i.test(v))return null;
+  const core=v.match(/(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}/)[0];
+  let d=core.replace(/\D/g,''); if(d.length===10)d='1'+d; return d; }
+// build one <td>. Website/LinkedIn/phone => whole cell is CLICKABLE (icon on the LEFT so it's never clipped,
+// entire cell opens a popup / dials). Everything else stays contenteditable + expand-on-click.
+function cellTD(ri,ci,val,isN,gr){
+  const cls=[isN?'namecell':'',gr?'enriched':''].filter(Boolean).join(' ');
+  const s=String(val==null?'':val), u=hrefOf(s);
+  if(u){ const li=/linkedin\.com/i.test(u), disp=s.replace(/^https?:\/\/(www\.)?/i,'').replace(/\/+$/,'');
+    return `<td data-r="${ri}" data-c="${ci}" contenteditable="false" data-href="${esc(u)}" title="Open ${esc(u)}" class="linkcell ${li?'li':'web'} ${cls}"><span class="cicon">${li?ICON.li:ICON.web}</span><span class="linkval">${esc(disp)}</span></td>`; }
+  const d=telDigits(s);
+  if(d){ return `<td data-r="${ri}" data-c="${ci}" contenteditable="false" data-tel="+${d}" title="Call ${esc(s)}" class="callcell ${cls}"><span class="cicon">${ICON.phone}</span><span class="linkval">${esc(s)}</span></td>`; }
+  return `<td data-r="${ri}" data-c="${ci}" contenteditable="true" title="${esc(s)}" class="${cls}">${esc(s)}</td>`;
+}
 function cellRender(v){v=String(v==null?'':v);const u=hrefOf(v);
   if(u){ const li=/linkedin\.com/i.test(u);                            // value stays plain text; icon opens the link
     const disp=v.replace(/^https?:\/\/(www\.)?/i,'').replace(/\/+$/,'');
@@ -463,11 +488,11 @@ function wireGrid(){
   // (per-render) #grid element and open the link in a POPUP WINDOW. tel: links proceed as normal.
   const gridEl=$('#grid');
   if(gridEl){
-    gridEl.addEventListener('mousedown',e=>{ if(e.target.closest('a.iconbtn,a.callbtn'))e.stopPropagation(); });
     gridEl.addEventListener('click',e=>{
-      const a=e.target.closest('a.iconbtn'); if(!a)return;                 // callbtn (tel:) falls through to default
-      e.preventDefault(); e.stopPropagation();
-      window.open(a.getAttribute('href'),'linkpop','popup=yes,noopener,width=1100,height=850,left=140,top=90');
+      const lc=e.target.closest('td.linkcell');                            // Website / LinkedIn -> popup window
+      if(lc){ e.preventDefault(); window.open(lc.dataset.href,'linkpop','popup=yes,noopener,width=1100,height=850,left=140,top=90'); return; }
+      const cc=e.target.closest('td.callcell');                            // phone -> dial
+      if(cc){ location.href='tel:'+cc.dataset.tel; return; }
     });
   }
   $$('.card').forEach(c=>c.onclick=()=>openDraft(+c.dataset.r));
@@ -510,7 +535,7 @@ function wireGrid(){
     document.querySelectorAll('#grid tbody tr').forEach(tr=>{const t=tr.children[idx+1]?.textContent||'';if(t.length>max)max=t.length;});
     const w=Math.min(600,Math.max(70,Math.round(max*7.1)+26));col.style.width=w+'px';COLW[ci]=w;LS.set('colw.'+curKey(),COLW);
   }));
-  $$('td[contenteditable]').forEach(td=>{td.addEventListener('focus',()=>{editing=true;td._o=td.textContent});td.addEventListener('blur',()=>saveCell(td));td.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();td.blur()}})});
+  $$('td[contenteditable="true"]').forEach(td=>{td.addEventListener('focus',()=>{editing=true;td._o=td.textContent});td.addEventListener('blur',()=>saveCell(td));td.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();td.blur()}})});
 }
 function postWrite(r,c,value){return fetch('/api/write',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({gid:GID,row0:r,col0:c,value})}).then(x=>x.json())}
 async function saveCell(td){ editing=false; const v=td.textContent.trim(); if(v===String(td._o||'').trim())return;

← 1bff2a8 Live console: click a field in the left rail to open its ful  ·  back to Rentv Sheet Enrich  ·  Live console: theme-aware interaction colors (no more black- c5739ce →