import { visit } from 'unist-util-visit' /** * A remark plugin that converts custom directives to embedded media HTML elements * Supports: link cards, Spotify, YouTube, Bilibili, X posts, and GitHub repository cards */ const embedHandlers = { // Link Card link: (node) => { const url = node.attributes?.url if (!url) { return false } // Create the LinkCard HTML structure - all metadata will be fetched by JavaScript return ` ` }, // Spotify spotify: (node) => { const url = node.attributes?.url ?? '' if (!url) { return false } if (!/^https:\/\/open\.spotify\.com\//.test(url)) { return false } let embedUrl = url.replace('open.spotify.com/', 'open.spotify.com/embed/') if (!embedUrl.includes('utm_source=')) { embedUrl += (embedUrl.includes('?') ? '&' : '?') + 'utm_source=generator' } let height = '152' if (url.includes('/album/') || url.includes('/playlist/') || url.includes('/artist/') || url.includes('/show/')) { height = '352' } return `
` }, // Youtube youtube: (node) => { let videoId = node.attributes?.id ?? '' const url = node.attributes?.url ?? '' if (!videoId && url) { const match = url.match(/(?:v=|\/embed\/|youtu\.be\/)([\w-]{11})/) if (match) videoId = match[1] } if (!videoId) { return false } return `
` }, // Bilibili bilibili: (node) => { let bvid = node.attributes?.id ?? '' const url = node.attributes?.url ?? '' if (!bvid && url) { const match = url.match(/\/BV([\w]+)/) if (match) bvid = 'BV' + match[1] } if (!bvid) { return false } return `
` }, // X Post Card x: (node) => { const xUrl = node.attributes?.url ?? '' if (!xUrl) { return false } const twitterUrl = xUrl.replace(/(\w+:\/\/)?x\.com\//g, '$1twitter.com/') const uniqueId = `x-card-${Math.random().toString(36).slice(2, 11)}` return `
` }, // Github Repository Card github: (node) => { const repo = node.attributes?.repo ?? '' if (!repo) { console.warn(`Missing GitHub repository`) return false } const [owner, name] = repo.split('/') if (!owner || !name) { console.warn(`Invalid GitHub repository format: "${repo}"`) return false } return `
${owner}${name}

--

-- -- --
` }, // NeoDB Card neodb: (node) => { const url = node.attributes?.url ?? '' if (!url) { return false } const neodbUrlPattern = /neodb\.social\/(movie|book|music|album|game|tv\/season|tv|podcast)\/([\w-]+)/ const match = url.match(neodbUrlPattern) const category = match ? match[1] : 'other' const isSquare = category === 'music' || category === 'album' || category === 'podcast' const skeletonClass = isSquare ? 'music' : 'other' return `
` } } export default function remarkEmbeddedMedia() { return (tree) => { visit(tree, ['leafDirective', 'containerDirective', 'textDirective'], (node) => { const handler = embedHandlers[node.name] if (!handler) { return } const htmlContent = handler(node) if (!htmlContent) { return } node.type = 'html' node.value = htmlContent delete node.name delete node.attributes delete node.children }) } }