experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(tags): use shared card-helpers for peek-card slot construction

Replace innerHTML-based card building in createItemCard with the slot-based
API using shared helpers from app/lib/card-helpers.js. Uses createHeaderSlot,
createFooterSlot, createFaviconEl, and createUrlSpan for consistent card
structure across extensions. Preserves all tags-specific logic including tag
chips, open button, and multi-type item handling.

+40 -40
+40 -40
extensions/tags/home.js
··· 13 13 14 14 import * as CodeMirror from 'peek://extensions/editor/codemirror.js'; 15 15 import { StatusLine } from 'peek://extensions/editor/status-line.js'; 16 + import { 17 + createHeaderSlot, createFooterSlot, createFaviconEl, createUrlSpan 18 + } from 'peek://app/lib/card-helpers.js'; 16 19 const { getCM, Vim } = CodeMirror; 17 20 18 21 const api = window.app; ··· 21 24 // View states (following groups pattern) 22 25 const VIEW_LIST = 'list'; 23 26 const VIEW_DETAIL = 'detail'; 24 - 25 - // Fallback favicon for broken/missing images (globe emoji as SVG data URI) 26 - const FALLBACK_FAVICON = 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">🌐</text></svg>'; 27 27 28 28 /** 29 29 * Extract the first URL from a text string. ··· 1199 1199 }; 1200 1200 1201 1201 /** 1202 - * Create a card element for an item 1202 + * Create a card element for an item using peek-card slots + shared card-helpers. 1203 1203 */ 1204 1204 const createItemCard = (item) => { 1205 1205 const card = document.createElement('peek-card'); ··· 1208 1208 1209 1209 const { tags, itemUrl, title, subtitle, faviconUrl } = getItemDisplayInfo(item); 1210 1210 1211 - // Build the open button for items with URLs (both url type and notes with URLs) 1212 - const openBtnHtml = itemUrl 1213 - ? `<button class="card-open-btn" data-url="${escapeHtml(itemUrl)}" title="Open page">` + 1214 - `<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">` + 1215 - `<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>` + 1216 - `<polyline points="15 3 21 3 21 9"></polyline>` + 1217 - `<line x1="10" y1="14" x2="21" y2="3"></line>` + 1218 - `</svg></button>` 1219 - : ''; 1211 + // Header slot: favicon + title + optional open button 1212 + const favicon = createFaviconEl(faviconUrl); 1213 + const header = createHeaderSlot(favicon, title); 1220 1214 1221 - card.innerHTML = ` 1222 - <div class="card-header"> 1223 - <img class="card-favicon" alt=""> 1224 - <div class="card-content"> 1225 - <div class="card-title">${escapeHtml(title)}</div> 1226 - <div class="card-url">${escapeHtml(subtitle)}</div> 1227 - </div> 1228 - ${openBtnHtml} 1229 - </div> 1230 - <div class="card-tags"> 1231 - ${tags.map(tag => `<span class="card-tag" data-tag-id="${tag.id}">${escapeHtml(tag.name)}</span>`).join('')} 1232 - </div> 1233 - `; 1234 - 1235 - // Set favicon src via DOM property to avoid escapeHtml breaking SVG data URIs 1236 - const faviconImg = card.querySelector('.card-favicon'); 1237 - if (faviconImg) { 1238 - faviconImg.src = faviconUrl; 1239 - faviconImg.onerror = () => { faviconImg.src = FALLBACK_FAVICON; faviconImg.onerror = null; }; 1240 - } 1241 - 1242 - // Click handler for the open button - opens the URL as a web page 1243 - const openBtn = card.querySelector('.card-open-btn'); 1244 - if (openBtn) { 1215 + // Add open button for items with URLs (both url type and notes with URLs) 1216 + if (itemUrl) { 1217 + const openBtn = document.createElement('button'); 1218 + openBtn.className = 'card-open-btn'; 1219 + openBtn.title = 'Open page'; 1220 + openBtn.innerHTML = 1221 + '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">' + 1222 + '<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path>' + 1223 + '<polyline points="15 3 21 3 21 9"></polyline>' + 1224 + '<line x1="10" y1="14" x2="21" y2="3"></line>' + 1225 + '</svg>'; 1226 + openBtn.style.flexShrink = '0'; 1245 1227 openBtn.addEventListener('click', (e) => { 1246 1228 e.stopPropagation(); 1247 - const url = openBtn.dataset.url; 1248 - if (url) openItemUrl(url); 1229 + openItemUrl(itemUrl); 1249 1230 }); 1231 + header.appendChild(openBtn); 1250 1232 } 1251 1233 1234 + card.appendChild(header); 1235 + 1236 + // Footer slot: URL/subtitle on the left, tag chips on the right 1237 + const urlEl = createUrlSpan(subtitle); 1238 + 1239 + const tagsContainer = document.createElement('span'); 1240 + tagsContainer.className = 'card-tags'; 1241 + tagsContainer.style.cssText = 'display:flex;gap:4px;flex-wrap:wrap;flex-shrink:0;'; 1242 + tags.forEach(tag => { 1243 + const chip = document.createElement('span'); 1244 + chip.className = 'card-tag'; 1245 + chip.dataset.tagId = tag.id; 1246 + chip.textContent = tag.name; 1247 + tagsContainer.appendChild(chip); 1248 + }); 1249 + 1250 + card.appendChild(createFooterSlot(urlEl, tagsContainer)); 1251 + 1252 1252 // Click on card to open inline detail view 1253 1253 card.addEventListener('click', (e) => { 1254 - // If clicking a tag, add it to filter (toggle behavior) 1254 + // If clicking a tag chip, add it to filter (toggle behavior) 1255 1255 if (e.target.classList.contains('card-tag')) { 1256 1256 const tagId = parseInt(e.target.dataset.tagId, 10); 1257 1257 const tag = state.tags.find(t => t.id === tagId);