Post Detail
manifest-ui/post-detailFreeBlock
Full post view with cover image, author info, content, tags and related posts section.
Install it
npx shadcn@latest add https://ui.manifest.build/r/post-detail.jsonSource
1'use client';23import { Button } from '@/components/ui/button';4import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';5import { Calendar, Clock, ExternalLink, Maximize2 } from 'lucide-react';6import { useMemo } from 'react';7import type { Post } from './types';8import { demoPostDetailData } from './demo/blogging';910// DOM-based allowlist HTML sanitizer for post content11const ALLOWED_TAGS = new Set([12 'p', 'br', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li',13 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'code', 'pre',14 'span', 'div', 'img', 'figure', 'figcaption', 'hr',15]);16const ALLOWED_ATTRS: Record<string, Set<string>> = {17 a: new Set(['href', 'target', 'rel', 'title']),18 img: new Set(['src', 'alt', 'width', 'height']),19 '*': new Set(['class', 'id']),20};21const DANGEROUS_URL = /^\s*(javascript|data):/i;2223function sanitizeNode(node: Node, doc: Document): void {24 const children = Array.from(node.childNodes);25 for (const child of children) {26 if (child.nodeType === 3 /* TEXT */) continue;27 if (child.nodeType !== 1 /* ELEMENT */) {28 child.remove();29 continue;30 }31 const el = child as Element;32 const tag = el.tagName.toLowerCase();33 if (!ALLOWED_TAGS.has(tag)) {34 // Unwrap: keep text content, discard the tag35 while (el.firstChild) node.insertBefore(el.firstChild, el);36 el.remove();37 continue;38 }39 // Strip disallowed attributes40 const tagAllowed = ALLOWED_ATTRS[tag];41 const globalAllowed = ALLOWED_ATTRS['*'];42 for (const attr of Array.from(el.attributes)) {43 const name = attr.name.toLowerCase();44 if (!tagAllowed?.has(name) && !globalAllowed?.has(name)) {45 el.removeAttribute(attr.name);46 }47 }48 // Block dangerous URL schemes on href/src49 for (const urlAttr of ['href', 'src']) {50 const val = el.getAttribute(urlAttr);51 if (val && DANGEROUS_URL.test(val)) el.removeAttribute(urlAttr);52 }53 sanitizeNode(el, doc);54 }55}5657function sanitizeHtml(html: string): string {58 if (typeof document === 'undefined') return html;59 const doc = new DOMParser().parseFromString(html, 'text/html');60 sanitizeNode(doc.body, doc);61 return doc.body.innerHTML;62}6364function TagList({65 tags,66 maxVisible = 2,67 size = 'default',68}: {69 tags: string[];70 maxVisible?: number;71 size?: 'small' | 'default';72}) {73 const visibleTags = tags.slice(0, maxVisible);74 const remainingTags = tags.slice(maxVisible);75// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from Manifest UI
All 32 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Amount Inputamount-input | Manifest UI | Blocks | Free | 1 dep · 2 files | |
| Chat Conversationchat-conversation | Manifest UI | Blocks | Free | 1 dep · 3 files | |
| Contact Formcontact-form | Manifest UI | Blocks | Free | 1 dep · 3 files | |
| Date & Time Pickerdate-time-picker | Manifest UI | Blocks | Free | 1 dep · 2 files | |
| Event Cardevent-card | Manifest UI | Blocks | Free | 1 dep · 2 files | |
| Event Confirmationevent-confirmation | Manifest UI | Blocks | Free | 1 dep · 2 files | |
| Event Detailevent-detail | Manifest UI | Blocks | Free | 3 deps · 2 files | |
| Event Listevent-list | Manifest UI | Blocks | Free | 3 deps · 2 files |
