diff --git a/src/components/Navbar.astro b/src/components/Navbar.astro index e643034..905465e 100644 --- a/src/components/Navbar.astro +++ b/src/components/Navbar.astro @@ -168,7 +168,6 @@ const { overlay = false } = Astro.props; .navbar-over-banner { border-color: color-mix(in srgb, var(--color-border) 70%, transparent); background: color-mix(in srgb, var(--color-bg) 82%, transparent); - box-shadow: 0 2px 10px color-mix(in srgb, var(--color-text) 12%, transparent); } .navbar-over-banner [data-navbar-tools] > [data-toolbar-item] > a:hover, diff --git a/src/components/NavigationProgress.astro b/src/components/NavigationProgress.astro index d4a6f17..49c3356 100644 --- a/src/components/NavigationProgress.astro +++ b/src/components/NavigationProgress.astro @@ -114,7 +114,6 @@ min-height: 3px; max-height: 3px; background: var(--color-accent); - box-shadow: 0 3px 8px rgb(0 0 0 / 35%); transform: scaleX(0); transform-origin: left center; transition: transform 240ms linear; diff --git a/src/content/posts/2026/08/eidolon-short-code-preview.md b/src/content/posts/2026/08/eidolon-short-code-preview.md index bc865ef..24733c6 100644 --- a/src/content/posts/2026/08/eidolon-short-code-preview.md +++ b/src/content/posts/2026/08/eidolon-short-code-preview.md @@ -17,6 +17,7 @@ Eidolon 主题支持短代码。除兼容 [Mirages](https://get233.com/archives/ ``` [shortcode param="value"]内容[/shortcode] +亦或是 [shortcode param="value"/] ``` diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 5e945cf..9d80c72 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -14,7 +14,7 @@ interface Props { title?: string; description?: string; image?: string; type?: ' const { title, description, image, type, noindex, math, navbarOverlay = false } = Astro.props; const hasMasthead = Astro.slots.has('masthead'); const appearanceScript = `(function(){var r=document.documentElement,t='mirages-theme',s='mirages-font-scale',f='mirages-font-family',themes=['auto','light','sunset','dark'],fonts=['serif','sans'],stored=Number(localStorage.getItem(s)),scale=stored>=.8&&stored<=1.5?stored*100:stored>=80&&stored<=150?stored:100,theme=localStorage.getItem(t),font=localStorage.getItem(f);scale=Math.round(scale/5)*5;r.dataset.theme=themes.indexOf(theme)>-1?theme:'${siteConfig.appearance.defaultTheme}';r.dataset.font=fonts.indexOf(font)>-1?font:'sans';r.dataset.platform=/Win/.test(navigator.userAgentData?.platform||navigator.platform)?'windows':'other';r.style.fontSize=scale+'%'})();`; - const tabsScript = `document.addEventListener('click',function(event){var button=event.target.closest('[data-shortcode-tab]');if(!button)return;var group=button.closest('[data-shortcode-tabs]');var id=button.dataset.shortcodeTab;group.querySelectorAll('[data-shortcode-tab="'+id+'"]').forEach(function(tab){var active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;var panel=group.querySelector('#'+tab.getAttribute('aria-controls'));if(panel)panel.hidden=!active;});});`; + const tabsScript = `if(!window.__tabsInit){window.__tabsInit=true;document.addEventListener('click',function(event){var button=event.target.closest('[data-shortcode-tab]');if(!button)return;var group=button.closest('[data-shortcode-tabs]');var id=button.dataset.shortcodeTab;group.querySelectorAll('[data-shortcode-tab=\"'+id+'\"]').forEach(function(tab){var active=tab===button;tab.setAttribute('aria-selected',String(active));tab.tabIndex=active?0:-1;var panel=group.querySelector('#'+tab.getAttribute('aria-controls'));if(panel)panel.hidden=!active;});});}`; const collapseScript = `function initCollapse(){document.querySelectorAll('.shortcode-collapse').forEach(function(details){details.dataset.collapseReady='';details.classList.toggle('is-expanded',details.open);var body=details.querySelector(':scope>.shortcode-body');if(body instanceof HTMLElement&&details.open)body.style.height='auto';});}initCollapse();document.addEventListener('astro:page-load',initCollapse);document.addEventListener('click',function(event){var summary=event.target.closest('.shortcode-collapse>summary');if(!summary)return;var details=summary.parentElement;if(!(details instanceof HTMLDetailsElement))return;event.preventDefault();var body=details.querySelector(':scope>.shortcode-body');if(!(body instanceof HTMLElement))return;var expand=!details.classList.contains('is-expanded');details.classList.toggle('is-expanded',expand);if(matchMedia('(prefers-reduced-motion: reduce)').matches){details.open=expand;body.style.height=expand?'auto':'';delete details.dataset.animating;return;}var height=body.getBoundingClientRect().height;details.open=true;body.style.height=height+'px';void body.offsetHeight;details.dataset.animating='true';body.style.height=(expand?body.scrollHeight:0)+'px';});document.addEventListener('transitionend',function(event){var body=event.target;if(!(body instanceof HTMLElement)||!body.classList.contains('shortcode-body')||event.propertyName!=='height')return;var details=body.parentElement;if(!(details instanceof HTMLDetailsElement))return;var expanded=details.classList.contains('is-expanded');details.open=expanded;body.style.height=expanded?'auto':'';delete details.dataset.animating;});`; --- @@ -51,10 +51,35 @@ const hasMasthead = Astro.slots.has('masthead'); diff --git a/src/lib/shortcodes.ts b/src/lib/shortcodes.ts index fed2e27..dcf60c1 100644 --- a/src/lib/shortcodes.ts +++ b/src/lib/shortcodes.ts @@ -69,7 +69,7 @@ function normalizeMarkdownHeadings(source: string): string { }).join('\n'); } -function markdown(source: string, preserveHtml = false, lineNumbers = false): string { +function markdown(source: string, preserveHtml = true, lineNumbers = true): string { const normalized = normalizeMarkdownHeadings(source); const tree = fromMarkdown(normalized, { extensions: [gfm()], mdastExtensions: [gfmFromMarkdown()] }); transform(tree, normalized); @@ -79,23 +79,30 @@ function markdown(source: string, preserveHtml = false, lineNumbers = false): st visit(hast, 'element', (node: any) => { if (node.tagName !== 'pre') return; const code = node.children?.find((child: any) => child.type === 'element' && child.tagName === 'code'); - if (!code || code.children?.some((child: any) => child.type !== 'text')) return; - const value = code.children.map((child: any) => child.value).join(''); - const lines = value.endsWith('\n') ? value.slice(0, -1).split('\n') : value.split('\n'); + if (!code) return; const classes = Array.isArray(node.properties?.className) ? node.properties.className : []; - node.properties = { ...node.properties, className: [...classes, 'shortcode-code'] }; - code.children = lines.flatMap((line: string, index: number) => [ - { + if (!classes.includes('astro-code')) classes.push('astro-code'); + if (!classes.includes('shortcode-code')) classes.push('shortcode-code'); + if (!classes.includes('no-pangu-spacing')) classes.push('no-pangu-spacing'); + node.properties = { ...node.properties, className: classes }; + if (code.children?.every((child: any) => child.type === 'text')) { + const value = code.children.map((child: any) => child.value).join(''); + const text = value.endsWith('\n') ? value.slice(0, -1) : value; + const lines = text.split('\n'); + code.children = lines.map((line: string) => ({ type: 'element', tagName: 'span', properties: { className: ['line'] }, children: [ - { type: 'element', tagName: 'span', properties: { className: ['shortcode-line-number'], ariaHidden: 'true' }, children: [{ type: 'text', value: String(index + 1) }] }, - ...(line ? [{ type: 'text', value: line }] : []), + { + type: 'element', + tagName: 'span', + properties: { className: ['line-content'] }, + children: line ? [{ type: 'text', value: line }] : [{ type: 'text', value: '\n' }], + } ], - }, - ...(index < lines.length - 1 ? [{ type: 'text', value: '\n' }] : []), - ]); + })); + } }); } applyNoticeShortcodes(hast); @@ -131,7 +138,7 @@ function renderBlock(name: string, args: Record, body = ''): str const type = requestedType === 'warn' ? 'warning' : requestedType === 'error' ? 'danger' : HINT_TYPES.has(requestedType ?? '') ? requestedType : 'info'; const icon = ``; const heading = args.title ? `
${icon}

${inline(args.title)}

` : icon; - return ``; + return ``; } if (name === 'collapse') { const open = args.open !== undefined || args.expanded !== undefined ? ' open' : ''; @@ -140,11 +147,11 @@ function renderBlock(name: string, args: Record, body = ''): str if (name === 'tabs') { const parsed = parseBlocks(body); const tabs = parsed.filter((item) => item.name === 'tab'); - if (!tabs.length) return `
${inline(label || '展开内容')}
${markdown(body)}
`; + if (!tabs.length) return `
${inline(label || '展开内容')}
${markdown(body, true, true)}
`; const id = `shortcode-tabs-${tabs.length}-${Math.random().toString(36).slice(2, 8)}`; const selected = Math.max(0, tabs.findIndex((tab) => tab.args.selected !== undefined)); const buttons = tabs.map((tab, index) => ``).join(''); - const panels = tabs.map((tab, index) => `
${markdown(tab.body)}
`).join(''); + const panels = tabs.map((tab, index) => `
${markdown(tab.body, true, true)}
`).join(''); return `
${buttons}
${panels}
`; } if (name === 'tools') { diff --git a/src/styles/content.css b/src/styles/content.css index 4186d7f..17979d9 100644 --- a/src/styles/content.css +++ b/src/styles/content.css @@ -131,54 +131,88 @@ font-family: var(--mirages-font-code); font-size: .875rem; line-height: 1.45; + white-space: pre; +} + +.prose .astro-code { + --code-line-digits: 1; + --code-gutter-background: color-mix(in srgb, var(--color-code) 65%, var(--color-border)); + padding: 0; + overflow: hidden; + position: relative; + background: var(--color-code); } .prose .astro-code code { - --code-line-digits: 1; display: block; + position: relative; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-x: none; + width: 100%; + min-width: 0; padding-block: 1rem; counter-reset: code-line; - font-size: 0; + font-size: .875rem; + line-height: 1.45; + scrollbar-color: color-mix(in srgb, var(--color-muted) 60%, transparent) transparent; + scrollbar-width: thin; + white-space: normal; } -.prose .astro-code code:has(.line:nth-child(10)) { --code-line-digits: 2; } -.prose .astro-code code:has(.line:nth-child(100)) { --code-line-digits: 3; } -.prose .astro-code code:has(.line:nth-child(1000)) { --code-line-digits: 4; } +.prose .astro-code code::-webkit-scrollbar { height: 8px; } +.prose .astro-code code::-webkit-scrollbar-thumb { border-radius: 4px; background: color-mix(in srgb, var(--color-muted) 60%, transparent); } +.prose .astro-code code::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--color-muted) 78%, transparent); } + +.prose .astro-code:has(.line:nth-child(10)) { --code-line-digits: 2; } +.prose .astro-code:has(.line:nth-child(100)) { --code-line-digits: 3; } +.prose .astro-code:has(.line:nth-child(1000)) { --code-line-digits: 4; } + +.prose .astro-code .code-line-numbers { + position: absolute; + left: 0; + top: 0; + bottom: 0; + z-index: 20; + display: block; + width: calc(var(--code-line-digits) * 1ch + 2rem); + padding-block: 1rem; + font-family: var(--mirages-font-code); + font-size: .875rem; + line-height: 1.45; + background: var(--code-gutter-background); + border-right: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent); + box-sizing: border-box; + color: var(--color-muted); + font-variant-numeric: tabular-nums; + text-align: right; + user-select: none; + pointer-events: none; +} + +.prose .astro-code .code-line-numbers > span { + display: block; + height: 1.45em; + padding-inline: 1rem; + box-sizing: border-box; + color: inherit; +} .prose .astro-code .line { - display: flex; + display: block; + width: max-content; + min-width: 100%; min-height: 1.45em; + padding-left: calc(var(--code-line-digits) * 1ch + 3rem); + box-sizing: border-box; counter-increment: code-line; font-size: .875rem; line-height: 1.45; + white-space: pre; } .prose .astro-code .line::before { - display: inline-block; - width: calc(var(--code-line-digits) * 1ch + 2rem); - flex: 0 0 calc(var(--code-line-digits) * 1ch + 2rem); - margin-right: 1rem; - padding-inline: 1rem; - background: var(--color-code-gutter); - box-sizing: border-box; - color: var(--color-muted); - content: counter(code-line); - font-variant-numeric: tabular-nums; - opacity: 0.72; - text-align: right; - user-select: none; -} - -.prose .astro-code .line:first-child::before { - box-shadow: 0 -1rem var(--color-code-gutter); -} - -.prose .astro-code .line:last-child::before { - box-shadow: 0 1rem var(--color-code-gutter); -} - -.prose .astro-code .line:only-child::before { - box-shadow: 0 -1rem var(--color-code-gutter), 0 1rem var(--color-code-gutter); + content: none; } .prose .astro-code span { @@ -186,6 +220,8 @@ } :root[data-theme='dark'] .prose .astro-code { + --code-gutter-background: var(--color-code-gutter); + --shortcode-code-gutter: var(--color-code-gutter); background-color: var(--color-code); color: var(--shiki-dark); } @@ -196,6 +232,8 @@ @media (prefers-color-scheme: dark) { :root[data-theme='auto'] .prose .astro-code { + --code-gutter-background: var(--color-code-gutter); + --shortcode-code-gutter: var(--color-code-gutter); background-color: var(--color-code); color: var(--shiki-dark); } @@ -205,48 +243,49 @@ } } -.prose .astro-code { - padding: 0; - background: var(--color-code); -} - .shortcode-code { - --shortcode-code-gutter: color-mix(in srgb, var(--color-code) 78%, var(--color-accent)); + --shortcode-code-gutter: color-mix(in srgb, var(--color-code) 65%, var(--color-border)); padding: 0; + overflow: hidden; + position: relative; } .shortcode-code code { + display: block; + position: relative; + overflow-x: auto; + overflow-y: hidden; + overscroll-behavior-x: none; + width: 100%; + min-width: 0; padding-block: 1rem; - font-size: 0; -} - -.shortcode-code .line { - display: flex; - min-height: 1.45em; font-size: .875rem; line-height: 1.45; + scrollbar-color: color-mix(in srgb, var(--color-muted) 60%, transparent) transparent; + scrollbar-width: thin; + white-space: normal; +} + +.shortcode-code code::-webkit-scrollbar { height: 8px; } +.shortcode-code code::-webkit-scrollbar-thumb { border-radius: 4px; background: color-mix(in srgb, var(--color-muted) 60%, transparent); } +.shortcode-code code::-webkit-scrollbar-thumb:hover { background: color-mix(in srgb, var(--color-muted) 78%, transparent); } + +.shortcode-code .line { + display: block; + width: max-content; + min-width: 100%; + min-height: 1.45em; + padding-left: 4rem; + box-sizing: border-box; + font-size: .875rem; + line-height: 1.45; + white-space: pre; } .shortcode-code .shortcode-line-number { - display: inline-block; - box-sizing: border-box; - min-width: 3rem; - flex: 0 0 3rem; - margin-right: 1rem; - padding-inline: 1rem; - background: var(--shortcode-code-gutter); - color: var(--color-muted); - font-size: .875rem; - font-variant-numeric: tabular-nums; - opacity: 0.72; - text-align: right; - user-select: none; + display: none; } -.shortcode-code .line:first-of-type .shortcode-line-number { box-shadow: 0 -1rem var(--shortcode-code-gutter); } -.shortcode-code .line:last-of-type .shortcode-line-number { box-shadow: 0 1rem var(--shortcode-code-gutter); } -.shortcode-code .line:only-of-type .shortcode-line-number { box-shadow: 0 -1rem var(--shortcode-code-gutter), 0 1rem var(--shortcode-code-gutter); } - .prose .katex-display { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding-block: .35em; } .prose .mermaid { max-width: 100%; overflow-x: auto; overflow-y: hidden; padding: 1rem 0; } diff --git a/src/styles/shortcodes.css b/src/styles/shortcodes.css index 3881346..896edf4 100644 --- a/src/styles/shortcodes.css +++ b/src/styles/shortcodes.css @@ -6,7 +6,7 @@ border-radius: var(--radius-sm); background: var(--color-accent); color: var(--color-bg); - font-family: var(--mirages-font-ui); + font-family: var(--reading-font-family); font-size: 0.9em; } .prose .shortcode-button:hover { @@ -26,7 +26,7 @@ border-radius: var(--radius-sm); background: transparent; color: var(--color-text); - font-family: var(--mirages-font-ui); + font-family: var(--reading-font-family); text-decoration: none; white-space: nowrap; transition: border-color 0.6s ease; @@ -69,7 +69,7 @@ border-radius: 999px; background: var(--color-accent); color: var(--color-bg); - font: 0.82em var(--mirages-font-ui); + font: 0.82em var(--reading-font-family); } .shortcode-tag-outline { background: transparent; @@ -248,6 +248,7 @@ border-radius: var(--radius-xs); background: transparent; color: var(--color-muted); + font-family: var(--reading-font-family); transition: background-color 150ms ease, color 150ms ease; } .shortcode-tab-list button[aria-selected="true"] { @@ -384,7 +385,7 @@ min-width: 0; margin: 0; overflow-wrap: anywhere; - font: 600 1.08rem/1.35 var(--mirages-font-ui); + font: 600 1.08rem/1.35 var(--reading-font-family); } .shortcode-github-icon { display: inline-block; @@ -412,7 +413,7 @@ justify-content: flex-end; gap: 0.8rem; color: var(--color-muted); - font: 0.75rem var(--mirages-font-ui); + font: 0.75rem var(--reading-font-family); text-align: right; } .shortcode-github-stat { @@ -441,7 +442,7 @@ border: 1px solid var(--color-border); border-radius: var(--radius-sm); color: var(--color-accent); - font: 0.78rem var(--mirages-font-ui); + font: 0.78rem var(--reading-font-family); text-decoration: none; } .shortcode-github-read-more:hover, @@ -459,7 +460,7 @@ padding: 0.6rem 1rem; border-top: 1px solid var(--color-border); color: var(--color-muted); - font: 0.75rem var(--mirages-font-ui); + font: 0.75rem var(--reading-font-family); } .shortcode-github-commit { min-width: 0;