mentionwell
v1.5.0
Published
Destination-side API reader + styles for Mentionwell-generated blog posts.
Maintainers
Readme
mentionwell
Destination-side reader + styles for Mentionwell-generated blog posts. The recommended setup reads published articles from the hosted Mentionwell API with a per-site MENTIONWELL_API_KEY; no Supabase credentials or GitHub delivery are needed on the destination domain. The package also ships ready-to-use CSS for tables, callouts, pull quotes, FAQ accordions, citations, YouTube embeds, and a sticky table-of-contents sidebar.
Companion packages
mentionwell-cli— wires Mentionwell into your repo (npx mentionwell-cli init).mentionwell-mcp-docs— Mentionwell docs as an MCP server.mentionwell-mcp-account— manage your Mentionwell account from any AI assistant.
Install
npm install mentionwellUse
// lib/mentionwell-blog.ts
import {
getBlogPostViaApi,
getBlogPostsViaApi,
getAllBlogSlugsViaApi
} from 'mentionwell/api'
const config = {
apiUrl: process.env.MENTIONWELL_API_URL!,
apiKey: process.env.MENTIONWELL_API_KEY!,
siteSlug: process.env.MENTIONWELL_SITE_SLUG!
}
export const fetchBlogPost = (slug: string) => getBlogPostViaApi(config, slug)
export const fetchBlogPosts = (page = 1) => getBlogPostsViaApi(config, page, 12)
export const fetchAllSlugs = () => getAllBlogSlugsViaApi(config)// app/blog/[slug]/page.tsx
import 'mentionwell/styles'
import {
prepareArticleHtml,
extractTocHtml,
stripTocFromArticle,
stripPlatformChrome
} from 'mentionwell/html-utils'
import { fetchBlogPost } from '@/lib/mentionwell-blog'
export default async function ArticlePage({ params }) {
const { slug } = await params
const post = await fetchBlogPost(slug)
if (!post) return <NotFound />
const html = prepareArticleHtml(post.html)
const tocHtml = extractTocHtml(html)
const bodyHtml = stripTocFromArticle(html)
return (
<article className="grid lg:grid-cols-[220px_minmax(0,1fr)_240px] gap-10 max-w-[1200px] mx-auto">
<aside className="hidden lg:block">
{tocHtml && (
<div
className="sticky top-40 wb-toc-sidebar"
dangerouslySetInnerHTML={{ __html: tocHtml }}
/>
)}
</aside>
<div className="wb-article-host" dangerouslySetInnerHTML={{ __html: bodyHtml }} />
<aside className="hidden lg:block">
<div className="sticky top-40 rounded-xl bg-slate-900 text-white p-5">
<h3>Talk to us</h3>
<a href="/contact">Contact</a>
</div>
</aside>
</article>
)
}What you get for free
The Mentionwell pipeline emits a self-contained article with all of these elements; this package's CSS styles them under the wb-* namespace inside .wb-article-host:
- TL;DR key-takeaways aside
- Table of contents (H2-only entries; H3s remain anchor targets)
- Tables with forest-green header and zebra rows
- Pull quotes (
<blockquote class="wb-quote">) - Tip / Watch out / By the numbers callouts (green / amber / purple boxes with emoji)
- Numbered process steps (
<ol class="wb-steps">) with green numbered chips - YouTube embeds as cards with eyebrow + title + channel subtitle + responsive iframe
- Inline / footer CTAs with bronze button
- FAQ accordion (
<details>with custom rotating chevron) - Citations numbered source list
- Author block
Re-theming
Override the CSS variables anywhere in your global stylesheet:
:root {
--wb-ink: #111;
--wb-bronze: #c47a40;
--wb-forest: #1f4a32;
--wb-paper: #fafafa;
--wb-font-display: 'Your Display Font', serif;
--wb-font-body: 'Your Body Font', sans-serif;
}All wb-* classes resolve through these tokens.
API
getBlogPostsViaApi(config, page?, perPage?)
Returns a paginated list of published posts through the Mentionwell hosted API using `Authorization: Bearer ***
getBlogPostViaApi(config, slug)
Returns one published post from the hosted API, or null.
getAllBlogSlugsViaApi(config)
Returns every published slug from the hosted API. Useful for sitemaps.
getBlogPostSummariesViaApi(config)
Returns slug + updated/published timestamps for published posts returned by the hosted API. Useful for sitemap last-modified.
prepareArticleHtml(html)
Strips duplicate chrome blocks (<header class="wb-header">, <figure class="wb-hero">) and any leaked [CTA] placeholder tokens from the article HTML before injection.
Pass { stripPlatformChrome: true } when your template renders its own TL;DR, TOC, FAQ, CTA, or author blocks from structured API fields:
const html = prepareArticleHtml(post.html, { stripPlatformChrome: true })stripPlatformChrome(html)
Removes the platform-rendered TL;DR, TOC, FAQ, CTA, author, header, and hero blocks while preserving body-only blocks such as callouts, tables, quotes, citations, steps, and embeds.
extractTocHtml(html) / stripTocFromArticle(html)
Pull the inline <nav class="wb-toc"> out so you can mount it in a sidebar layout.
getDirAttr(language)
Returns "rtl" for he-* / ar-* BCP-47 codes and "ltr" for everything
else (including undefined). Apply it to .wb-article-host together with
lang — see the "RTL languages" section below.
RTL languages
The reader supports right-to-left languages out of the box. The article
container emits a dir attribute based on the post's language field
(he-* and ar-* resolve to rtl). All layout uses CSS logical
properties, so no extra stylesheet is required.
import { getDirAttr } from 'mentionwell/api'
<div
className="wb-article-host"
dir={getDirAttr(post.language)}
lang={post.language}
dangerouslySetInnerHTML={{ __html: bodyHtml }}
/>If you self-host fonts, add Hebrew (Heebo/Assistant/Rubik) or Arabic (Noto Naskh Arabic/Cairo/Tajawal) webfonts to your destination site. The reader's CSS variables fall back to system fonts gracefully.
License
MIT
