This post was written during the old blog (Next.js + MDX) era. After migrating to the current static site, all of these features happen at build time: TradingView widgets became ticker cards that only load the live chart when you click "Live chart"; ECharts blocks became static SVG charts with hover values; source embeds are pinned commit snippets with line numbers; and language icons became corner language labels on code blocks.
This update pushes the blog's writing experience another step forward: code blocks are easier to scan, code references link straight to GitHub, and financial posts can embed TradingView charts with a tiny shortcode.
What's New
Language Icons on Code Blocks
A language logo and copy button float in the top-right corner of every code block—no title bar, no badge border, and no extra vertical space. When a block has a title or comes from GitHub, a small path line appears above the code and the GitHub link sits tucked into a small icon in the top-right corner.
const feature = 'language icons'
const theme = 'catppuccin'
console.log(feature, theme)tsRestyled Inline Code
Inline code in prose like git log --follow, $AAPL, and ::tv AAPL no longer sticks out like a bright patch. In light mode it blends closer to Catppuccin Latte's paper tone, and in dark mode it settles into a low-luminance Mocha surface.
Quoting GitHub Code Directly
When writing a post, enter:
::github-code repo="mizorewww/blog" ref="c067042276d4b4b384c66c0c61fcd4f6716eb599" path="contentlayer.config.ts" lines="251-253" lang="ts" title="contentlayer.config.ts"mdIt renders as a code block with Shiki syntax highlighting, a language icon, and a direct link to GitHub:
251const githubEmbedPattern = /^::github-(code|diff)\s+(.+)$/
252const tradingViewMiniPattern = /^\$([A-Za-z0-9._:-]+)$/
253const tradingViewAdvancedPattern = /^::(?:tv|tv-advanced|tradingview)\s+(.+)$/Line-by-Line Diff Highlighting Like GitHub
When writing a post, enter:
::github-diff repo="mizorewww/blog" ref="c067042276d4b4b384c66c0c61fcd4f6716eb599" path="css/prism.css" lines="1-15"mdRendered output:
Changes to css/prism.css in c067042
@@ -8,6 +8,44 @@ figure[data-rehype-pretty-code-figure] [data-rehype-pretty-code-title] {
@apply flex items-center justify-between gap-3 border-b border-slate-200 bg-white/70 px-4 py-2 text-xs text-slate-600 dark:border-[#405064] dark:bg-white/[0.035] dark:text-white/65;
}
+figure[data-rehype-pretty-code-figure] .code-title-main {
+ @apply inline-flex min-w-0 items-center gap-2;
+}
+
+figure[data-rehype-pretty-code-figure] .code-language-icon {
+ @apply inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-[5px] border border-slate-300 bg-slate-100 px-1 font-sans text-[0.62rem] leading-none font-semibold text-slate-600 dark:border-white/15 dark:bg-white/10 dark:text-white/70;
+}
+
+figure[data-rehype-pretty-code-figure] .code-language-icon[data-code-language='ts'],
+figure[data-rehype-pretty-code-figure] .code-language-icon[data-code-language='tsx'] {
+ @apply border-sky-200 bg-sky-50 text-sky-700 dark:border-sky-400/25 dark:bg-sky-400/10 dark:text-sky-200;diffSingle-Line Tickers Become Mini Charts
Put a ticker on its own line:
$AAPL
$BINANCE:BTCUSDT.PmdOn the old site, each of these lines turned into a Mini Chart. The current static site doesn't load TradingView scripts upfront—instead, it renders a ticker card linking to TradingView:
Shortcode for Advanced Charts
When writing a post, enter:
::tv AAPL interval=60 height=460mdOn the old site this rendered an interactive Advanced Chart; here it is likewise a ticker card.
Rendering ECharts Directly from Code Blocks
Use an ```echarts code block whose body is the chart's option JSON, with title and height supported in the meta string:
```echarts title="Weekly Visits" height=320
{
"tooltip": { "trigger": "axis" },
"xAxis": {
"type": "category",
"data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
},
"yAxis": { "type": "value" },
"series": [
{
"name": "Visits",
"type": "line",
"smooth": true,
"areaStyle": {},
"data": [120, 200, 150, 80, 70, 110, 130]
}
]
}
```mdOn the old site these were two ECharts instances. The current site draws the same data as static SVG at build time; since there is no pie chart type, the second example is represented as a data table:
| Category | Posts | Share |
|---|---|---|
| Tinkering | 40 | 40% |
| Dev | 32 | 32% |
| Notes | 18 | 18% |
| Other | 10 | 10% |
If the JSON is invalid, it gracefully falls back to a regular code block instead of breaking the build. When you need functions or other options that plain JSON cannot express, you can also pass a JS object directly to the <ECharts> component. Charts follow the site's light/dark theme, and the echarts library is lazy-loaded on demand so it stays out of the initial bundle.
How It Works Under the Hood
Turning Shortcodes into Components in Remark
These regular expressions match the ticker chart syntax. $AAPL becomes a Mini Chart and ::tv AAPL becomes an Advanced Chart.
251const githubEmbedPattern = /^::github-(code|diff)\s+(.+)$/
252const tradingViewMiniPattern = /^\$([A-Za-z0-9._:-]+)$/
253const tradingViewAdvancedPattern = /^::(?:tv|tv-advanced|tradingview)\s+(.+)$/The Mini Chart transform is intentionally conservative: it only triggers when an entire paragraph consists of a single ticker, so mentioning $AAPL in the middle of a sentence won't drop a giant chart into the paragraph.
288function createTradingViewMiniNode(symbol: string): MdastNode | null {
289 if (!isTradingViewTicker(symbol)) {
290 return null
291 }
292
293 return createMdxFlowNode('TradingViewMiniChart', {
294 symbol: normalizeTradingViewSymbol(symbol),
295 })Advanced Chart adds parameter parsing so attributes like height=460 and interval=60 are forwarded as JSX props.
298function createTradingViewAdvancedNode(source: string): MdastNode | null {
299 const [rawSymbol, ...attrParts] = source.trim().split(/\s+/)
300
301 if (!rawSymbol || !isTradingViewTicker(rawSymbol)) {
302 return null
303 }
304
305 const attrs = parseEmbedAttributes(attrParts.join(' ')) as TradingViewAttrs
306
307 return createMdxFlowNode('TradingViewAdvancedChart', {
308 height: attrs.height,
309 interval: attrs.interval,
310 locale: attrs.locale,
311 symbol: normalizeTradingViewSymbol(rawSymbol),
312 timezone: attrs.timezone,
313 })Finally, the plugins are wired into Contentlayer's remark pipeline. Because shortcodes are transformed before MDX compilation, no string scanning happens in the browser.
1063export default makeSource({
1064 contentDirPath: 'data',
1065 documentTypes: [Blog, Authors],
1066 mdx: {
1067 cwd: process.cwd(),
1068 remarkPlugins: [remarkGfm, remarkTradingViewWidgets, remarkIconShortcodes, remarkGitHubEmbeds],
1069 rehypePlugins: [Loading TradingView Only on the Client
The official TradingView widget requires injecting an external <script>, so the component stays a client component. On render, it clears the container and inserts the widget mount node and config script; whenever the theme or props change, it rebuilds the widget.
83useEffect(() => {
84 const container = containerRef.current
85
86 if (!container) {
87 return
88 }
89
90 container.innerHTML = '<div class="tradingview-widget-container__widget"></div>'
91
92 const script = document.createElement('script')
93 script.async = true
94 script.src = scriptSrc
95 script.text = JSON.stringify(config)
96 container.appendChild(script)
97
98 return () => {
99 container.innerHTML = ''
100 }
101}, [config, scriptSrc])Ticker symbols are normalized first. Bare AAPL defaults to NASDAQ:AAPL, while an explicit symbol like NYSE:IBM is left untouched.
4export function normalizeTradingViewSymbol(value: string, defaultExchange = 'NASDAQ') {
5 const symbol = value.trim().replace(/^\$/, '').toUpperCase()
6
7 if (!symbol) {
8 return ''
9 }
10
11 if (explicitTradingViewSymbolPattern.test(symbol)) {
12 return symbol
13 }
14
15 if (plainTickerPattern.test(symbol)) {
16 return `${defaultExchange}:${symbol}`
17 }
18
19 return symbol
20}Enhancing Code Block Title Bars in Rehype
Language icons aren't written by hand in each post; they're injected across the board during the rehype pass. It locates the figure generated by Shiki and ensures a title bar exists.
850function enhanceCodeTitle(node: HastNode) {
851 if (
852 node.tagName !== 'figure' ||
853 node.properties?.['data-rehype-pretty-code-figure'] === undefined
854 ) {
855 return
856 }
857
858 const title = ensureCodeTitle(node)
859 const code = findDescendantElement(node, 'code')
860 const sourceUrl = typeof code?.data?.githubSourceUrl === 'string' ? code.data.githubSourceUrl : ''
861 const language = getCodeLanguage(code)
862 const titleText = getNodeText(title) || getLanguageName(language)
863
864 title.children = [createCodeTitleNode(titleText, language)]
865
866 if (sourceUrl) {
867 title.children.push(createCodeSourceLinkNode(sourceUrl, language))
868 }The left side of the title bar pairs the language icon with the title text. When no explicit title is provided, it falls back to the language display name—such as TypeScript, Diff, or Markdown.
787function createCodeTitleNode(titleText: string, language: string): HastNode {
788 return {
789 type: 'element',
790 tagName: 'span',
791 properties: { className: ['code-title-main'] },
792 children: [
793 createCodeLanguageIconNode(language),
794 {
795 type: 'element',
796 tagName: 'span',
797 properties: { className: ['code-title-text'] },
798 children: [{ type: 'text', value: titleText || getLanguageName(language) }],
799 },Visually, the logos come straight from simple-icons brand icons, tree-shaken on demand so they don't bloat first-load JavaScript; languages without a brand icon fall back to a short text badge.
11figure[data-rehype-pretty-code-figure] .code-title-main {
12 @apply inline-flex min-w-0 items-center gap-2;
13}
14
15figure[data-rehype-pretty-code-figure] .code-language-icon {
16 @apply inline-flex h-5 min-w-5 shrink-0 items-center justify-center rounded-[5px] border border-slate-300 bg-slate-100 px-1 font-sans text-[0.62rem] leading-none font-semibold text-slate-600 dark:border-white/15 dark:bg-white/10 dark:text-white/70;
17}Keeping Inline Code from Stealing Attention
Inline code now uses a low-contrast, subtly bordered token style. It's still unmistakably code, without popping out of the paragraph like a button.
110& :where(code):not(pre code) {
111 border: 1px solid color-mix(in oklab, var(--color-slate-300) 72%, transparent);
112 border-radius: 6px;
113 background: linear-gradient(
114 to bottom,
115 color-mix(in oklab, white 92%, var(--color-slate-100)),
116 color-mix(in oklab, var(--color-slate-100) 88%, white)
117 );
118 box-shadow: inset 0 -1px 0 color-mix(in oklab, var(--color-slate-300) 45%, transparent);
119 color: #4c4f69;
120 padding: 0.08rem 0.34rem;
121 font-size: 0.86em;
122 font-weight: 500;
123 line-height: 1.75;
124 word-break: break-word;GitHub Code and Diffs Still Go Through Shiki
At build time, ::github-code and ::github-diff fetch the real source file or diff and emit a standard code AST node. That means they don't need a custom renderer—Shiki highlights them just like any other code block.
543if (kind === 'code') {
544 const value = selectCodeLines(await getGitHubFile(attrs), attrs.lines)
545 const title =
546 attrs.title ||
547 `${normalizeGitHubRepo(attrs.repo)}:${attrs.path}${attrs.lines ? `#L${attrs.lines}` : ''}`
548
549 return createCodeNode(value, attrs.lang || 'text', title, {
550 showLineNumbers: true,
551 sourceUrl: getGitHubCodeUrl(attrs),
552 })Diffs follow the exact same path, except the data source is a patch and the language defaults to diff.
555const value = selectCodeLines(await getGitHubDiff(attrs), attrs.lines)
556const title =
557 attrs.title ||
558 `${normalizeGitHubRepo(attrs.repo)}:${attrs.path || `${attrs.base || attrs.ref}...${attrs.head || ''}`}`
559
560return createCodeNode(value || 'No diff matched this query.', attrs.lang || 'diff', title, {
561 showLineNumbers: true,
562 sourceUrl: getGitHubDiffUrl(attrs),
563})That's why GitHub embeds get the Shiki color theme, language icon, copy button, GitHub link, and diff line backgrounds all at once.
The Mental Model for Writing Posts
For normal posts, you just write Markdown. When you want an icon, write :icon-code:; for a market ticker, write $AAPL or ::tv AAPL; for a data chart, write an ```echarts block; to quote source code, write ::github-code; and to quote a commit diff, write ::github-diff.
The build step turns these shortcodes into stable MDX components or Shiki code blocks, while the runtime only handles interactivity and external widgets. The authoring syntax stays brief, and the rendered page stays predictable.