Skip to content

Commit fa24c44

Browse files
Merge pull request #72 from JavaScriptSolidServer/issue-71-bookmark-pane-enrichment
Bookmark pane: render enrichment (summary, tags, hero image)
2 parents a57b66e + dc3a7a7 commit fa24c44

1 file changed

Lines changed: 69 additions & 13 deletions

File tree

examples/panes/bookmark.js

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
// Pod-local pane for bookmark:Bookmark (dc:title / bookmark:recalls / dcterms:created).
1+
// Pod-local pane for bookmark:Bookmark.
2+
// Core: dc:title / bookmark:recalls / dcterms:created.
3+
// Enrichment (optional, rendered when present): dc:description (summary),
4+
// schema:keywords (tags), schema:image (hero), schema:datePublished.
25
// Loaded by the `--browser panes` data browser from /public/panes/.
36
// Contract: export default { canHandle(node, h) -> bool, render(node, h) -> htmlString }
4-
// h = { escape, prop, propAll, idOf, types, host, fmtDate, localName }.
7+
// h = { escape, prop, propAll, first, idOf, types, host, fmtDate, localName }.
58

69
export default {
710
canHandle(node, h) {
@@ -14,18 +17,71 @@ export default {
1417
const site = h.host(url);
1518
const date = h.fmtDate(h.first(h.prop(node, 'created')));
1619
const fav = url ? h.escape(new URL('/favicon.ico', url).href) : '';
17-
return `<div style="font-family:Inter,-apple-system,sans-serif;padding:24px 0 8px;">
18-
<div style="font-family:Georgia,serif;font-size:14px;font-style:italic;color:#999;margin-bottom:18px;">Bookmark</div>
19-
<a href="${h.escape(url)}" target="_blank" rel="noopener" style="display:flex;gap:18px;align-items:flex-start;text-decoration:none;color:inherit;border:1px solid rgba(127,127,127,0.18);border-radius:14px;padding:24px;background:#fff;">
20-
<img src="${fav}" alt="" width="44" height="44" onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🔖',style:'font-size:32px;line-height:44px;width:44px;text-align:center'}))" style="width:44px;height:44px;border-radius:9px;flex:0 0 auto;background:rgba(127,127,127,0.08);object-fit:contain;" />
21-
<div style="min-width:0;">
22-
<div style="font-size:21px;font-weight:600;color:#1a1a1a;line-height:1.3;margin-bottom:6px;overflow-wrap:anywhere;">${h.escape(title)}</div>
23-
<div style="font-size:13px;color:#7c3aed;font-family:monospace;overflow-wrap:anywhere;">${h.escape(site)}</div>
20+
21+
// Enrichment — all optional.
22+
const desc = h.first(h.prop(node, 'description'));
23+
const tags = h.propAll(node, 'keywords').map(t => h.first(t)).filter(Boolean);
24+
const img = h.idOf(h.prop(node, 'image'));
25+
const published = h.fmtDate(h.first(h.prop(node, 'datePublished')));
26+
27+
const hero = img
28+
? `<img class="bm-hero" src="${h.escape(img)}" alt="" onerror="this.style.display='none'">`
29+
: '';
30+
const rule = desc ? `<div class="bm-rule"></div>` : '';
31+
// Render the summary as paragraphs (split on blank lines); first is the lead.
32+
const paras = desc ? String(desc).split(/\n\s*\n/).map(p => p.trim()).filter(Boolean) : [];
33+
const summary = paras.length
34+
? `<div class="bm-body">${paras.map((p, i) =>
35+
`<p class="${i === 0 ? 'bm-lead' : 'bm-para'}">${h.escape(p)}</p>`).join('')}</div>`
36+
: '';
37+
const chips = tags.length
38+
? `<div class="bm-tags">${tags.map(t => `<span class="bm-tag">${h.escape(t)}</span>`).join('')}</div>`
39+
: '';
40+
const meta = [published ? `published ${h.escape(published)}` : '', date ? `saved ${h.escape(date)}` : '']
41+
.filter(Boolean).join(' &middot; ');
42+
43+
return `<style>
44+
.bm-wrap{max-width:680px;margin:0 auto;padding:8px 0 28px;font-family:Inter,-apple-system,system-ui,sans-serif;}
45+
.bm-eyebrow{font-family:Georgia,serif;font-size:13px;font-style:italic;color:#a1a1aa;margin-bottom:18px;}
46+
.bm-card{background:#fff;border:1px solid rgba(24,24,27,.07);border-radius:18px;box-shadow:0 1px 3px rgba(24,24,27,.04),0 10px 40px rgba(24,24,27,.07);padding:32px 34px;}
47+
.bm-head{display:flex;gap:16px;align-items:flex-start;}
48+
.bm-fav{width:40px;height:40px;border-radius:10px;flex:0 0 auto;background:rgba(127,127,127,.06);object-fit:contain;}
49+
.bm-titlewrap{min-width:0;flex:1;}
50+
.bm-title{font-size:22px;font-weight:650;color:#18181b;line-height:1.3;text-decoration:none;overflow-wrap:anywhere;}
51+
.bm-title:hover{color:#7c3aed;}
52+
.bm-host{display:block;font-size:12.5px;color:#7c3aed;font-family:ui-monospace,SFMono-Regular,monospace;margin-top:7px;text-decoration:none;overflow-wrap:anywhere;}
53+
.bm-host:hover{text-decoration:underline;}
54+
.bm-hero{width:88px;height:88px;border-radius:12px;object-fit:cover;flex:0 0 auto;margin-left:auto;border:1px solid rgba(24,24,27,.06);background:#fafafa;}
55+
.bm-rule{height:1px;background:rgba(24,24,27,.07);margin:22px 0;}
56+
.bm-body{display:flex;flex-direction:column;gap:15px;}
57+
.bm-lead{font-size:16px;line-height:1.7;color:#27272a;margin:0;}
58+
.bm-para{font-size:15px;line-height:1.72;color:#52525b;margin:0;}
59+
.bm-tags{display:flex;flex-wrap:wrap;gap:7px;margin-top:24px;}
60+
.bm-tag{font-size:12px;color:#6d28d9;background:rgba(124,58,237,.07);border:1px solid rgba(124,58,237,.13);padding:4px 11px;border-radius:999px;}
61+
.bm-foot{display:flex;align-items:center;margin-top:26px;}
62+
.bm-open{font-size:13px;font-weight:600;color:#fff;background:#7c3aed;padding:10px 18px;border-radius:10px;text-decoration:none;box-shadow:0 2px 10px rgba(124,58,237,.28);transition:background .15s,transform .05s;}
63+
.bm-open:hover{background:#6d28d9;}
64+
.bm-open:active{transform:translateY(1px);}
65+
.bm-meta{margin-left:auto;font-size:12px;color:#a1a1aa;}
66+
</style>
67+
<div class="bm-wrap">
68+
<div class="bm-eyebrow">Bookmark</div>
69+
<div class="bm-card">
70+
<div class="bm-head">
71+
<img class="bm-fav" src="${fav}" alt="" onerror="this.replaceWith(Object.assign(document.createElement('div'),{textContent:'🔖',style:'font-size:30px;line-height:40px;width:40px;text-align:center'}))">
72+
<div class="bm-titlewrap">
73+
<a class="bm-title" href="${h.escape(url)}" target="_blank" rel="noopener">${h.escape(title)}</a>
74+
<a class="bm-host" href="${h.escape(url)}" target="_blank" rel="noopener">${h.escape(site)}</a>
75+
</div>
76+
${hero}
77+
</div>
78+
${rule}
79+
${summary}
80+
${chips}
81+
<div class="bm-foot">
82+
<a class="bm-open" href="${h.escape(url)}" target="_blank" rel="noopener">Open ↗</a>
83+
${meta ? `<span class="bm-meta">${meta}</span>` : ''}
2484
</div>
25-
</a>
26-
<div style="display:flex;gap:10px;align-items:center;margin-top:18px;">
27-
<a href="${h.escape(url)}" target="_blank" rel="noopener" style="font-size:13px;font-weight:600;color:#fff;background:#7c3aed;padding:9px 16px;border-radius:8px;text-decoration:none;">Open ↗</a>
28-
${date ? `<span style="margin-left:auto;font-size:12px;color:#aaa;">saved ${h.escape(date)}</span>` : ''}
2985
</div>
3086
</div>`;
3187
}

0 commit comments

Comments
 (0)