this repo has no description
0
fork

Configure Feed

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

at main 134 lines 4.5 kB view raw
1--- 2import type { StandardBlock, Facet } from '../../lib/types'; 3import { escapeHtml } from '../../lib/render'; 4 5interface Props { 6 blocks: StandardBlock[]; 7} 8 9const { blocks } = Astro.props; 10 11function renderBlockFacets(text: string, facets?: Facet[]): string { 12 if (!facets?.length) return escapeHtml(text); 13 const encoder = new TextEncoder(); 14 const decoder = new TextDecoder(); 15 const bytes = encoder.encode(text); 16 const sorted = [...facets].sort((a, b) => a.index.byteStart - b.index.byteStart); 17 let result = ''; 18 let lastEnd = 0; 19 for (const facet of sorted) { 20 const { byteStart, byteEnd } = facet.index; 21 result += escapeHtml(decoder.decode(bytes.slice(lastEnd, byteStart))); 22 const facetText = escapeHtml(decoder.decode(bytes.slice(byteStart, byteEnd))); 23 const feature = facet.features?.[0]; 24 if (feature?.$type?.includes('#link') && feature.uri) { 25 result += `<a href="${escapeHtml(feature.uri)}" target="_blank" rel="noopener">${facetText}</a>`; 26 } else if (feature?.$type?.includes('#code')) { 27 result += `<code>${facetText}</code>`; 28 } else if (feature?.$type?.includes('#bold')) { 29 result += `<strong>${facetText}</strong>`; 30 } else if (feature?.$type?.includes('#italic')) { 31 result += `<em>${facetText}</em>`; 32 } else { 33 result += facetText; 34 } 35 lastEnd = byteEnd; 36 } 37 result += escapeHtml(decoder.decode(bytes.slice(lastEnd))); 38 return result; 39} 40 41function renderBlock(block: StandardBlock): string { 42 const type = block.$type || ''; 43 44 if (type.includes('text')) { 45 const text = block.text || block.plaintext || ''; 46 if (!text.trim()) return ''; 47 return `<p>${renderBlockFacets(text, block.facets)}</p>`; 48 } 49 50 if (type.includes('header') || type.includes('heading')) { 51 const text = block.text || block.plaintext || ''; 52 const level = block.level || 2; 53 const tag = level <= 3 ? `h${level}` : 'h4'; 54 return `<${tag}>${escapeHtml(text)}</${tag}>`; 55 } 56 57 if (type.includes('code')) { 58 const text = block.text || block.plaintext || ''; 59 const lang = block.language || ''; 60 return `<pre><code class="lang-${escapeHtml(lang)}">${escapeHtml(text)}</code></pre>`; 61 } 62 63 if (type.includes('image')) { 64 const alt = block.alt || ''; 65 // Skip images for now blob rendering is complex 66 return alt ? `<p class="image-placeholder">[Image: ${escapeHtml(alt)}]</p>` : ''; 67 } 68 69 if (type.includes('list') || type.includes('List')) { 70 const children = block.children || block.items || []; 71 const items = children.map((child: any) => { 72 const content = child.content || child; 73 const text = content.text || content.plaintext || ''; 74 return `<li>${renderBlockFacets(text, content.facets)}</li>`; 75 }).join(''); 76 return `<ul>${items}</ul>`; 77 } 78 79 return ''; 80} 81--- 82 83<div class="article-content"> 84 {blocks.map(block => { 85 // Handle wrapped blocks (pub.leaflet style) 86 const innerBlock = block.block || block; 87 const html = renderBlock(innerBlock); 88 return html ? <Fragment set:html={html} /> : null; 89 })} 90</div> 91 92<style> 93 .article-content { 94 font-size: 0.95rem; 95 line-height: 1.7; 96 } 97 .article-content :global(p) { margin-bottom: 1em; } 98 .article-content :global(h2) { 99 font-size: 1.6rem; font-weight: 900; text-transform: uppercase; 100 letter-spacing: 0.03em; margin: 1.5em 0 0.5em; color: var(--text); 101 border-bottom: 3px solid var(--black); padding-bottom: 0.3em; 102 } 103 .article-content :global(h3) { 104 font-size: 1.15rem; font-weight: 800; text-transform: uppercase; 105 margin: 1.2em 0 0.4em; color: var(--text); 106 } 107 .article-content :global(pre) { 108 background: var(--black); color: #fff; padding: 16px; 109 border: 3px solid var(--black); overflow-x: auto; margin: 1em 0; 110 font-size: 0.82rem; line-height: 1.5; 111 } 112 .article-content :global(code) { 113 font-family: 'SF Mono', 'Fira Code', monospace; 114 font-size: 0.85em; 115 } 116 .article-content :global(p code) { 117 background: rgba(0, 0, 255, 0.08); 118 padding: 2px 5px; 119 color: var(--text); 120 border: 1px solid rgba(0, 0, 0, 0.15); 121 } 122 .article-content :global(ul) { 123 margin: 0.5em 0 1em 1.5em; 124 } 125 .article-content :global(li) { margin-bottom: 0.3em; } 126 .article-content :global(a) { 127 color: var(--blue); 128 border-bottom: 2px solid var(--blue); 129 } 130 .article-content :global(a:hover) { 131 background: var(--yellow); 132 } 133 .image-placeholder { color: var(--text-muted); font-weight: 700; text-transform: uppercase; } 134</style>