← back to Rentv Sheet Enrich
All LinkedIn + Website cells href to exact site: cellRender links any URL/domain (not just http-prefixed); snapshot reads FORMULA render to recover URLs from =HYPERLINK(...,'LinkedIn') label cells (533/533 MVP LinkedIn now linked). Columns: resize-drag on every column + double-click-to-autofit + hover-title full content
c6e12e859ffb9804b9f038b334fd8d0e44a5a817 · 2026-08-13 14:15:48 -0700 · Steve Abrams
Files touched
M __pycache__/snapshot.cpython-314.pycM public_live/index.htmlM snapshot.py
Diff
commit c6e12e859ffb9804b9f038b334fd8d0e44a5a817
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu Aug 13 14:15:48 2026 -0700
All LinkedIn + Website cells href to exact site: cellRender links any URL/domain (not just http-prefixed); snapshot reads FORMULA render to recover URLs from =HYPERLINK(...,'LinkedIn') label cells (533/533 MVP LinkedIn now linked). Columns: resize-drag on every column + double-click-to-autofit + hover-title full content
---
__pycache__/snapshot.cpython-314.pyc | Bin 7775 -> 8411 bytes
public_live/index.html | 22 ++++++++++++++++++----
snapshot.py | 17 +++++++++++++----
3 files changed, 31 insertions(+), 8 deletions(-)
diff --git a/__pycache__/snapshot.cpython-314.pyc b/__pycache__/snapshot.cpython-314.pyc
index d8d224f..8a27397 100644
Binary files a/__pycache__/snapshot.cpython-314.pyc and b/__pycache__/snapshot.cpython-314.pyc differ
diff --git a/public_live/index.html b/public_live/index.html
index 45de2e1..2f9e668 100644
--- a/public_live/index.html
+++ b/public_live/index.html
@@ -145,7 +145,12 @@ function pk(){return 'p'+GID} // per-tab localStorage namespace
function findCol(preds){for(const p of preds){const i=DATA.headers.findIndex(h=>p.test(h));if(i>=0)return i}return -1}
function detectRoles(){ROLE={name:findCol([/^contact name$/i,/contact name/i,/\bname\b/i]),company:findCol([/company\/venue/i,/^company$/i,/venue/i,/firm/i]),position:findCol([/^position$/i,/title/i]),email:findCol([/^email$/i]),email2:findCol([/updated email/i]),phone:findCol([/^phone 1$/i,/^phone\(s\)$/i,/^phone$/i]),cphone:findCol([/company phone/i]),liC:findCol([/linkedin \(contact\)/i]),liCo:findCol([/linkedin \(company\)/i]),mvpN:findCol([/marketing\/vp name/i]),mvpP:findCol([/marketing\/vp position/i]),mvpL:findCol([/marketing\/vp linkedin/i])}}
const g=(r,i)=> (i>=0&&i<r.length)?String(r[i]||'').trim():'';
-function hrefOf(v){const m=String(v).match(/https?:\/\/[^\s"')]+/i);return m?m[0]:(/linkedin\.com/i.test(v)?('https://'+v.replace(/^https?:\/\//,'')):'')}
+function hrefOf(v){ v=String(v||'').trim();
+ const m=v.match(/https?:\/\/[^\s"')]+/i); if(m) return m[0].replace(/[),.]+$/,'');
+ // bare domain or linkedin/site path (no spaces) -> add https:// so it's clickable
+ if(/^(www\.)?[a-z0-9-]+(\.[a-z0-9-]+)+(\/\S*)?$/i.test(v)) return 'https://'+v;
+ return '';
+}
// ---- load (from OUR local copy) ---------------------------------------------
async function loadMeta(){
@@ -269,8 +274,8 @@ function tableHTML(rows){
for(const [r,ri] of 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;
- h+=`<td data-r="${ri}" data-c="${c.i}" contenteditable="true" class="${[isN?'namecell':'',gr?'enriched':''].filter(Boolean).join(' ')}">${cellRender(g(r,c.i))}</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+='</tr>';
}
return h+'</tbody></table>';
@@ -284,7 +289,7 @@ function cardsHTML(rows,mode){
return h+'</div>';
}
function badge(ic,v){return `<span class="badge${v?' ok':''}">${ic}${v?' ✓':''}</span>`}
-function cellRender(v){const u=hrefOf(v);if(u&&/^https?:/i.test(v))return `<a href="${esc(u)}" target="_blank" rel="noopener">${esc(v.length>40?u.replace(/^https?:\/\/(www\.)?/,''):v)}</a>`;return esc(v)}
+function cellRender(v){v=String(v==null?'':v);const u=hrefOf(v);if(u)return `<a href="${esc(u)}" target="_blank" rel="noopener">${esc(v.length>44?u.replace(/^https?:\/\/(www\.)?/,''):v)}</a>`;return esc(v)}
function esc(s){return String(s==null?'':s).replace(/[&<>"]/g,m=>({'&':'&','<':'<','>':'>','"':'"'}[m]))}
function contact(r){const nm=g(r,ROLE.name),co=g(r,ROLE.company);return{name:nm,first:(nm.split(/[\s,]+/)[0]||'').trim(),company:co,position:g(r,ROLE.position),email:g(r,ROLE.email)||g(r,ROLE.email2),phone:g(r,ROLE.phone)||g(r,ROLE.cphone),liC:hrefOf(g(r,ROLE.liC)),liCo:hrefOf(g(r,ROLE.liCo)),mvpN:g(r,ROLE.mvpN),mvpP:g(r,ROLE.mvpP),mvpL:hrefOf(g(r,ROLE.mvpL))}}
function wireGrid(){
@@ -301,6 +306,15 @@ function wireGrid(){
const up=()=>{document.removeEventListener('mousemove',mv);document.removeEventListener('mouseup',up);LS.set('colw.'+GID,COLW);};
document.addEventListener('mousemove',mv);document.addEventListener('mouseup',up);
}));
+ // double-click a resize handle to auto-fit the column to its content
+ $$('#grid th .rz').forEach(rz=>rz.addEventListener('dblclick',e=>{
+ e.preventDefault();e.stopPropagation();
+ const ci=+rz.dataset.c, cols=visCols(), idx=cols.findIndex(c=>c.i===ci);
+ const col=document.querySelectorAll('#grid colgroup col')[idx+1];
+ let max=String(DATA.headers[ci]||'').length;
+ 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.'+GID,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()}})});
}
async function saveCell(td){ editing=false; const v=td.textContent.trim(); if(v===String(td._o||'').trim())return;
diff --git a/snapshot.py b/snapshot.py
index 9ca09ad..09dcc86 100644
--- a/snapshot.py
+++ b/snapshot.py
@@ -18,10 +18,19 @@ def build(tok=None):
tok = tok or lib.access_token()
sheets = [(s["properties"]["sheetId"], s["properties"]["title"])
for s in lib.get_meta(tok)["sheets"]]
- # 1) all values in ONE call
+ # 1) all values in ONE call — FORMULA render so =HYPERLINK("url","label") keeps the URL
+ # (FORMATTED would collapse those to the label "LinkedIn" and lose the link)
qs = "&".join(rng(t) for _, t in sheets)
- vals = api(f"{lib.API}/{lib.SID}/values:batchGet?{qs}&majorDimension=ROWS", tok)
+ vals = api(f"{lib.API}/{lib.SID}/values:batchGet?{qs}&majorDimension=ROWS&valueRenderOption=FORMULA", tok)
value_ranges = vals.get("valueRanges", [])
+ import re as _re
+ _hl = _re.compile(r'=HYPERLINK\("([^"]+)"', _re.I)
+ def unlink(v):
+ if isinstance(v, str) and v.startswith("="):
+ m = _hl.match(v)
+ if m:
+ return m.group(1) # recover the real URL from the hyperlink formula
+ return v
# 2) all green (enriched) cell backgrounds in ONE call
G = lib.GREEN
def is_green(bg):
@@ -48,8 +57,8 @@ def build(tok=None):
continue
hr = header_row(rows)
width = max((len(r) for r in rows), default=0)
- headers = [(rows[hr][i].strip() if i < len(rows[hr]) else "") for i in range(width)]
- body = [[(r[i] if i < len(r) else "") for i in range(width)] for r in rows[hr + 1:]]
+ headers = [(str(rows[hr][i]).strip() if i < len(rows[hr]) else "") for i in range(width)]
+ body = [[unlink(r[i]) if i < len(r) else "" for i in range(width)] for r in rows[hr + 1:]]
green = []
for ri, row in enumerate(green_rows.get(gid, [])):
if ri <= hr: # header + any title rows above it
← 6f48a2b Console: resizable columns (drag edge, persists per tab via
·
back to Rentv Sheet Enrich
·
Console layout: FREEZE header row (border-collapse:separate 43cf368 →