diff --git a/apps/web/src/app/(main)/dashboard/newsletter/[id]/page.tsx b/apps/web/src/app/(main)/dashboard/newsletter/[id]/page.tsx new file mode 100644 index 0000000..61e60d9 --- /dev/null +++ b/apps/web/src/app/(main)/dashboard/newsletter/[id]/page.tsx @@ -0,0 +1,314 @@ +import { Button } from "@/components/ui/button"; +import { getNewsletterById } from "@/lib/newsletters"; +import { ChevronLeft } from "lucide-react"; +import Link from "next/link"; + +type params = { + params: { id: string }; +}; + +function renderInline(text: string) { + // inline tags with backticks like `good first issue`, bold **text** and links [text](url) + const parts = text.split(/(`[^`]+`)/g).filter(Boolean); + return parts.flatMap((part, idx) => { + // backtick tag -> render as pill/tag + if (/^`[^`]+`$/.test(part)) { + const inner = part.slice(1, -1); + return ( + + {inner} + + ); + } + + // handle links inside non-backtick segments + const boldParts = part.split(/(\*\*[^*]+\*\*)/g).filter(Boolean); + return boldParts.map((bp, j) => { + if (/^\*\*/.test(bp)) { + const inner = bp.replace(/\*\*/g, ""); + return ( + + {inner} + + ); + } + + // handle multiple links in the segment + const nodes: any[] = []; + const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g; + let lastIndex = 0; + let m: RegExpExecArray | null; + while ((m = linkRegex.exec(bp)) !== null) { + const [matchText, t, url] = m; + const index = m.index; + if (index > lastIndex) nodes.push(bp.slice(lastIndex, index)); + nodes.push( + + {t} + + ); + lastIndex = index + matchText.length; + } + if (lastIndex < bp.length) nodes.push(bp.slice(lastIndex)); + + return ( + + {nodes.map((n, k) => + typeof n === "string" ? {n} : n + )} + + ); + }); + }); +} + +function renderMarkdown(md: string) { + const lines = md.split("\n"); + const nodes: any[] = []; + let listBuffer: string[] = []; + let blockquoteBuffer: string[] = []; + + function flushList() { + if (listBuffer.length) { + nodes.push( + + ); + listBuffer = []; + } + } + + lines.forEach((raw, idx) => { + if (raw === "__CONSUMED__") return; + const line = raw.trim(); + // handle code blocks + if (line.startsWith("```")) { + flushList(); + const lang = line.slice(3).trim(); + const codeLines: string[] = []; + let j = idx + 1; + while (j < lines.length && !lines[j].trim().startsWith("```")) { + codeLines.push(lines[j]); + j++; + } + const codeContent = codeLines.join("\n"); + if (lang === "embed") { + // embed block expects a URL in the content + const src = codeContent.trim(); + try { + const url = new URL(src); + if (!["http:", "https:"].includes(url.protocol)) { + console.warn("Invalid embed URL protocol:", src); + return; // skip this embed + } + } catch { + console.warn("Invalid embed URL:", src); + } + nodes.push( +
+
+