feat: add toc with accordion animation, add optional toc toggle, lower heti css priority

This commit is contained in:
radishzzz 2025-03-28 00:26:32 +00:00
parent ab74c0abdf
commit dc17f304c1
22 changed files with 280 additions and 167 deletions

View file

@ -30,7 +30,6 @@
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"rehype-slug": "^6.0.0", "rehype-slug": "^6.0.0",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"remark-toc": "^9.0.0",
"sanitize-html": "^2.15.0" "sanitize-html": "^2.15.0"
}, },
"devDependencies": { "devDependencies": {

37
pnpm-lock.yaml generated
View file

@ -59,9 +59,6 @@ importers:
remark-math: remark-math:
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0 version: 6.0.0
remark-toc:
specifier: ^9.0.0
version: 9.0.0
sanitize-html: sanitize-html:
specifier: ^2.15.0 specifier: ^2.15.0
version: 2.15.0 version: 2.15.0
@ -925,9 +922,6 @@ packages:
'@types/sax@1.2.7': '@types/sax@1.2.7':
resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==}
'@types/ungap__structured-clone@1.2.0':
resolution: {integrity: sha512-ZoaihZNLeZSxESbk9PUAPZOlSpcKx81I1+4emtULDVmBLkYutTcMlCj2K9VNlf9EWODxdO6gkAqEaLorXwZQVA==}
'@types/unist@2.0.11': '@types/unist@2.0.11':
resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==}
@ -1691,8 +1685,8 @@ packages:
duplexer@0.1.2: duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
electron-to-chromium@1.5.126: electron-to-chromium@1.5.127:
resolution: {integrity: sha512-AtH1uLcTC72LA4vfYcEJJkrMk/MY/X0ub8Hv7QGAePW2JkeUFHEL/QfS4J77R6M87Sss8O0OcqReSaN1bpyA+Q==} resolution: {integrity: sha512-Ke5OggqOtEqzCzcUyV+9jgO6L6sv1gQVKGtSExXHjD/FK0p4qzPZbrDsrCdy0DptcQprD0V80RCBYSWLMhTTgQ==}
emmet@2.4.11: emmet@2.4.11:
resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==}
@ -2553,9 +2547,6 @@ packages:
mdast-util-to-string@4.0.0: mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
mdast-util-toc@7.1.0:
resolution: {integrity: sha512-2TVKotOQzqdY7THOdn2gGzS9d1Sdd66bvxUyw3aNpWfcPXCLYSJCCgfPy30sEtuzkDraJgqF35dzgmz6xlvH/w==}
mdn-data@2.0.28: mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
@ -3071,9 +3062,6 @@ packages:
remark-stringify@11.0.0: remark-stringify@11.0.0:
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
remark-toc@9.0.0:
resolution: {integrity: sha512-KJ9txbo33GjDAV1baHFze7ij4G8c7SGYoY8Kzsm2gzFpbhL/bSoVpMMzGa3vrNDSWASNd/3ppAqL7cP2zD6JIA==}
request-light@0.5.8: request-light@0.5.8:
resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==}
@ -4610,8 +4598,6 @@ snapshots:
dependencies: dependencies:
'@types/node': 22.13.14 '@types/node': 22.13.14
'@types/ungap__structured-clone@1.2.0': {}
'@types/unist@2.0.11': {} '@types/unist@2.0.11': {}
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
@ -5330,7 +5316,7 @@ snapshots:
browserslist@4.24.4: browserslist@4.24.4:
dependencies: dependencies:
caniuse-lite: 1.0.30001707 caniuse-lite: 1.0.30001707
electron-to-chromium: 1.5.126 electron-to-chromium: 1.5.127
node-releases: 2.0.19 node-releases: 2.0.19
update-browserslist-db: 1.1.3(browserslist@4.24.4) update-browserslist-db: 1.1.3(browserslist@4.24.4)
@ -5589,7 +5575,7 @@ snapshots:
duplexer@0.1.2: {} duplexer@0.1.2: {}
electron-to-chromium@1.5.126: {} electron-to-chromium@1.5.127: {}
emmet@2.4.11: emmet@2.4.11:
dependencies: dependencies:
@ -6754,16 +6740,6 @@ snapshots:
dependencies: dependencies:
'@types/mdast': 4.0.4 '@types/mdast': 4.0.4
mdast-util-toc@7.1.0:
dependencies:
'@types/mdast': 4.0.4
'@types/ungap__structured-clone': 1.2.0
'@ungap/structured-clone': 1.3.0
github-slugger: 2.0.0
mdast-util-to-string: 4.0.0
unist-util-is: 6.0.0
unist-util-visit: 5.0.0
mdn-data@2.0.28: {} mdn-data@2.0.28: {}
mdn-data@2.0.30: {} mdn-data@2.0.30: {}
@ -7518,11 +7494,6 @@ snapshots:
mdast-util-to-markdown: 2.1.2 mdast-util-to-markdown: 2.1.2
unified: 11.0.5 unified: 11.0.5
remark-toc@9.0.0:
dependencies:
'@types/mdast': 4.0.4
mdast-util-toc: 7.1.0
request-light@0.5.8: {} request-light@0.5.8: {}
request-light@0.7.0: {} request-light@0.7.0: {}

View file

@ -33,6 +33,7 @@ const navItems = [
--- ---
<nav <nav
aria-label="Site Navigation"
class:list={[ class:list={[
isPost ? 'hidden lg:block' : '', isPost ? 'hidden lg:block' : '',
'mb-10.5 text-3.6 font-semibold leading-8.75 font-navbar', 'mb-10.5 text-3.6 font-semibold leading-8.75 font-navbar',

View file

@ -2,6 +2,7 @@
import { OverlayScrollbars } from 'overlayscrollbars' import { OverlayScrollbars } from 'overlayscrollbars'
function setupScrollbar() { function setupScrollbar() {
// Add scrollbar to body
const bodyElement = document.body const bodyElement = document.body
if (!bodyElement.hasAttribute('data-scrollbar-initialized')) { if (!bodyElement.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({ OverlayScrollbars({
@ -20,6 +21,7 @@ function setupScrollbar() {
bodyElement.setAttribute('data-scrollbar-initialized', 'true') bodyElement.setAttribute('data-scrollbar-initialized', 'true')
} }
// Add scrollbar to code blocks
const preElements = document.querySelectorAll('pre') const preElements = document.querySelectorAll('pre')
preElements.forEach((pre) => { preElements.forEach((pre) => {
if (!pre.hasAttribute('data-scrollbar-initialized')) { if (!pre.hasAttribute('data-scrollbar-initialized')) {
@ -30,7 +32,7 @@ function setupScrollbar() {
}, },
}, { }, {
scrollbars: { scrollbars: {
theme: 'scrollbar-code', theme: 'scrollbar-widget',
autoHide: 'leave', autoHide: 'leave',
autoHideDelay: 500, autoHideDelay: 500,
}, },
@ -39,6 +41,25 @@ function setupScrollbar() {
pre.setAttribute('data-scrollbar-initialized', 'true') pre.setAttribute('data-scrollbar-initialized', 'true')
} }
}) })
// Add scrollbar to TOC content
const tocElement = document.getElementById('toc-content')
if (tocElement && !tocElement.hasAttribute('data-scrollbar-initialized')) {
OverlayScrollbars({
target: tocElement,
cancel: {
nativeScrollbarsOverlaid: true,
},
}, {
scrollbars: {
theme: 'scrollbar-widget',
autoHide: 'leave',
autoHideDelay: 500,
},
})
tocElement.setAttribute('data-scrollbar-initialized', 'true')
}
} }
setupScrollbar() setupScrollbar()
@ -64,7 +85,7 @@ document.addEventListener('astro:after-swap', setupScrollbar)
--os-handle-min-size: 12%; --os-handle-min-size: 12%;
} }
.scrollbar-code { .scrollbar-widget {
--os-size: 0.6rem; --os-size: 0.6rem;
--os-padding-perpendicular: 0.1rem; --os-padding-perpendicular: 0.1rem;
--os-padding-axis: 0.2rem; --os-padding-axis: 0.2rem;
@ -80,7 +101,7 @@ document.addEventListener('astro:after-swap', setupScrollbar)
} }
@media (max-width: 1023px) { @media (max-width: 1023px) {
.os-scrollbar { .os-scrollbar.scrollbar-body {
display: none !important; display: none !important;
} }
} }

View file

@ -0,0 +1,112 @@
---
import type { MarkdownHeading } from 'astro'
import { ui } from '@/i18n/ui'
import { getPageInfo } from '@/utils/page'
interface Props {
headings: MarkdownHeading[]
}
const { currentLang } = getPageInfo(Astro.url.pathname)
const currentUI = ui[currentLang as keyof typeof ui]
const { headings = [] } = Astro.props
const filteredHeadings = headings.filter(heading =>
heading.depth >= 2
&& heading.depth <= 4
&& heading.text !== 'Footnotes',
)
---
{filteredHeadings.length > 0 && (
<div
class="relative mb-6 bg-secondary/5"
border="~ secondary/5 rounded solid"
transition="~ duration-300 ease-in-out"
>
{/* Accordion toggle for expandable TOC */}
<input
type="checkbox"
id="toc-toggle"
class="accordion-toggle"
hidden
/>
<label
for="toc-toggle"
class="absolute inset-0 z-99 cursor-pointer"
></label>
{/* TOC title bar */}
<div class="h-12 w-full flex items-center bg-secondary/0">
<span class="toc-title">
{currentUI.toc}
</span>
</div>
{/* Expandable content wrapper */}
<div class="accordion-wrapper">
<nav
id="toc-content"
class="accordion-content"
aria-label="Table of Contents"
>
<ul class="toc-list">
{filteredHeadings.map(heading => (
<li
class:list={{
'ml-0': heading.depth === 2,
'ml-4': heading.depth === 3,
'ml-8': heading.depth === 4,
}}
>
<a
href={`#${heading.slug}`}
class:list={[
{ 'toc-link-h2': heading.depth === 2 },
{ 'toc-link-h3': heading.depth === 3 },
{ 'toc-link-h4': heading.depth === 4 },
]}
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
</div>
</div>
)}
<!-- Override heti default styles -->
<style>
.toc-title {
--at-apply: 'font-semibold pl-4 px-4 py-3';
}
.toc-list {
--at-apply: 'list-none pl-0 space-y-2 mt-1 mb-4';
}
.toc-link-h2 {
--at-apply: 'text-sm no-underline font-semibold';
}
.toc-link-h3 {
--at-apply: 'text-sm no-underline font-normal';
}
.toc-link-h4 {
--at-apply: 'text-sm no-underline font-normal';
}
/* Initial collapsed state with zero height grid row */
.accordion-wrapper {
--at-apply: 'grid rows-[0fr] duration-300 ease-in-out';
}
.accordion-content {
--at-apply: 'overflow-hidden max-h-66 lg:max-h-82 z-99 pl-4';
}
/* When toggle is checked, expand the wrapper to show content */
.accordion-toggle:checked ~ .accordion-wrapper {
grid-template-rows: 1fr;
}
.accordion-toggle:checked ~ .accordion-wrapper .accordion-content {
--at-apply: 'overflow-y-auto';
}
</style>

View file

@ -14,7 +14,7 @@ const postsCollection = defineCollection({
// Advanced // Advanced
draft: z.boolean().optional().default(false), draft: z.boolean().optional().default(false),
pin: z.number().int().min(0).max(99).optional().default(0), pin: z.number().int().min(0).max(99).optional().default(0),
toc: z.boolean().optional().default(false), toc: z.boolean().optional().default(true),
lang: z.enum(['', ...allLocales]).optional().default(''), lang: z.enum(['', ...allLocales]).optional().default(''),
abbrlink: z.string().optional().default('').refine( abbrlink: z.string().optional().default('').refine(
abbrlink => !abbrlink || /^[a-z0-9\-]*$/.test(abbrlink), abbrlink => !abbrlink || /^[a-z0-9\-]*$/.test(abbrlink),

View file

@ -231,7 +231,7 @@ Pins the article to the top. The higher the number, the higher the priority of t
#### toc #### toc
Enables the table of contents. This feature is not yet implemented. Generate table of contents. Set to false to disable automatic generation. Default is true.
#### lang #### lang
@ -243,15 +243,15 @@ Specifies the article language. Only one language can be specified. If not speci
# moreLocales: ['es', 'ru'] # moreLocales: ['es', 'ru']
# lang: '' # lang: ''
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
-> example.com/es/posts/apple/ -> example.com/es/posts/apple/
-> example.com/ru/posts/apple/ -> example.com/ru/posts/apple/
# lang: en # lang: en
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
# lang: es # lang: es
src/content/posts/banana.md -> example.com/es/posts/banana/ src/content/posts/apple.md -> example.com/es/posts/apple/
# lang: ru # lang: ru
src/content/posts/orange.md -> example.com/ru/posts/orange/ src/content/posts/apple.md -> example.com/ru/posts/apple/
``` ```
#### abbrlink #### abbrlink

View file

@ -231,7 +231,7 @@ Fija el artículo en la parte superior. Cuanto mayor sea el número, mayor será
#### toc #### toc
Habilita la tabla de contenidos. Esta función aún no está implementada. ¿Generar índice? Si se establece en false, se desactiva la generación automática. Valor predeterminado: true.
#### lang #### lang
@ -243,15 +243,15 @@ Especifica el idioma del artículo. Solo se puede especificar un idioma. Si no s
# moreLocales: ['es', 'ru'] # moreLocales: ['es', 'ru']
# lang: '' # lang: ''
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
-> example.com/es/posts/apple/ -> example.com/es/posts/apple/
-> example.com/ru/posts/apple/ -> example.com/ru/posts/apple/
# lang: en # lang: en
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
# lang: es # lang: es
src/content/posts/banana.md -> example.com/es/posts/banana/ src/content/posts/apple.md -> example.com/es/posts/apple/
# lang: ru # lang: ru
src/content/posts/orange.md -> example.com/ru/posts/orange/ src/content/posts/apple.md -> example.com/ru/posts/apple/
``` ```
#### abbrlink #### abbrlink

View file

@ -231,7 +231,7 @@ abbrlink: theme-guide
#### toc #### toc
目次を有効にします。この機能はまだ実装されていません 目次を自動生成するかどうか。false に設定すると自動生成が無効になり、デフォルトは true
#### lang #### lang
@ -243,15 +243,15 @@ abbrlink: theme-guide
# moreLocales: ['es', 'ru'] # moreLocales: ['es', 'ru']
# lang: '' # lang: ''
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
-> example.com/es/posts/apple/ -> example.com/es/posts/apple/
-> example.com/ru/posts/apple/ -> example.com/ru/posts/apple/
# lang: en # lang: en
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
# lang: es # lang: es
src/content/posts/banana.md -> example.com/es/posts/banana/ src/content/posts/apple.md -> example.com/es/posts/apple/
# lang: ru # lang: ru
src/content/posts/orange.md -> example.com/ru/posts/orange/ src/content/posts/apple.md -> example.com/ru/posts/apple/
``` ```
#### abbrlink #### abbrlink

View file

@ -231,7 +231,7 @@ abbrlink: theme-guide
#### toc #### toc
Включает оглавление. Эта функция еще не реализована. Генерировать оглавление. При значении false автоматическая генерация отключается. По умолчанию: true.
#### lang #### lang
@ -243,15 +243,15 @@ abbrlink: theme-guide
# moreLocales: ['es', 'ru'] # moreLocales: ['es', 'ru']
# lang: '' # lang: ''
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
-> example.com/es/posts/apple/ -> example.com/es/posts/apple/
-> example.com/ru/posts/apple/ -> example.com/ru/posts/apple/
# lang: en # lang: en
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
# lang: es # lang: es
src/content/posts/banana.md -> example.com/es/posts/banana/ src/content/posts/apple.md -> example.com/es/posts/apple/
# lang: ru # lang: ru
src/content/posts/orange.md -> example.com/ru/posts/orange/ src/content/posts/apple.md -> example.com/ru/posts/apple/
``` ```
#### abbrlink #### abbrlink

View file

@ -231,7 +231,7 @@ abbrlink: theme-guide
#### toc #### toc
是否開啟目錄。尚未實現該功能 是否生成目錄。設為 false 時停用自動生成,預設為 true
#### lang #### lang
@ -243,15 +243,15 @@ abbrlink: theme-guide
# moreLocales: ['es', 'ru'] # moreLocales: ['es', 'ru']
# lang: '' # lang: ''
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
-> example.com/es/posts/apple/ -> example.com/es/posts/apple/
-> example.com/ru/posts/apple/ -> example.com/ru/posts/apple/
# lang: en # lang: en
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
# lang: es # lang: es
src/content/posts/banana.md -> example.com/es/posts/banana/ src/content/posts/apple.md -> example.com/es/posts/apple/
# lang: ru # lang: ru
src/content/posts/orange.md -> example.com/ru/posts/orange/ src/content/posts/apple.md -> example.com/ru/posts/apple/
``` ```
#### abbrlink #### abbrlink

View file

@ -231,7 +231,7 @@ abbrlink: theme-guide
#### toc #### toc
是否开启目录。尚未实现该功能 是否生成目录。设为 false 时关闭自动生成,默认为 true
#### lang #### lang
@ -243,15 +243,15 @@ abbrlink: theme-guide
# moreLocales: ['es', 'ru'] # moreLocales: ['es', 'ru']
# lang: '' # lang: ''
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
-> example.com/es/posts/apple/ -> example.com/es/posts/apple/
-> example.com/ru/posts/apple/ -> example.com/ru/posts/apple/
# lang: en # lang: en
src/content/posts/apple.md -> example.com/posts/apple/ src/content/posts/apple.md -> example.com/posts/apple/
# lang: es # lang: es
src/content/posts/banana.md -> example.com/es/posts/banana/ src/content/posts/apple.md -> example.com/es/posts/apple/
# lang: ru # lang: ru
src/content/posts/orange.md -> example.com/ru/posts/orange/ src/content/posts/apple.md -> example.com/ru/posts/apple/
``` ```
#### abbrlink #### abbrlink

View file

@ -6,6 +6,7 @@ export const ui = {
posts: '文章', posts: '文章',
tags: '标签', tags: '标签',
about: '关于', about: '关于',
toc: '目录',
}, },
'zh-tw': { 'zh-tw': {
title: '重新編排', title: '重新編排',
@ -14,6 +15,7 @@ export const ui = {
posts: '文章', posts: '文章',
tags: '標籤', tags: '標籤',
about: '關於', about: '關於',
toc: '目錄',
}, },
'ja': { 'ja': {
title: '再組版', title: '再組版',
@ -22,6 +24,7 @@ export const ui = {
posts: '記事', posts: '記事',
tags: 'タグ', tags: 'タグ',
about: '概要', about: '概要',
toc: '目次',
}, },
'en': { 'en': {
title: 'Retypeset', title: 'Retypeset',
@ -30,6 +33,7 @@ export const ui = {
posts: 'Posts', posts: 'Posts',
tags: 'Tags', tags: 'Tags',
about: 'About', about: 'About',
toc: 'Table of Contents',
}, },
'es': { 'es': {
title: 'Retipografía', title: 'Retipografía',
@ -38,6 +42,7 @@ export const ui = {
posts: 'Artículos', posts: 'Artículos',
tags: 'Etiquetas', tags: 'Etiquetas',
about: 'Sobre', about: 'Sobre',
toc: 'Índice',
}, },
'ru': { 'ru': {
title: 'Переверстка', title: 'Переверстка',
@ -46,5 +51,6 @@ export const ui = {
posts: 'Посты', posts: 'Посты',
tags: 'Теги', tags: 'Теги',
about: 'О себе', about: 'О себе',
toc: 'Оглавление',
}, },
} }

View file

@ -3,6 +3,7 @@ import type { CollectionEntry } from 'astro:content'
import Comments from '@/components/Comments/index.astro' import Comments from '@/components/Comments/index.astro'
import PostDate from '@/components/PostDate.astro' import PostDate from '@/components/PostDate.astro'
import GoBack from '@/components/Widgets/GoBack.astro' import GoBack from '@/components/Widgets/GoBack.astro'
import TOC from '@/components/Widgets/TOC.astro'
import { allLocales, defaultLocale, moreLocales } from '@/config' import { allLocales, defaultLocale, moreLocales } from '@/config'
import { getTagPath } from '@/i18n/path' import { getTagPath } from '@/i18n/path'
import Layout from '@/layouts/Layout.astro' import Layout from '@/layouts/Layout.astro'
@ -96,7 +97,7 @@ export async function getStaticPaths() {
const { post, lang, supportedLangs } = Astro.props const { post, lang, supportedLangs } = Astro.props
const description = generateDescription(post, 'meta') const description = generateDescription(post, 'meta')
const { Content, remarkPluginFrontmatter } = await post.render() const { Content, headings, remarkPluginFrontmatter } = await post.render()
--- ---
<Layout <Layout
@ -132,6 +133,8 @@ const { Content, remarkPluginFrontmatter } = await post.render()
minutes={remarkPluginFrontmatter.minutes} minutes={remarkPluginFrontmatter.minutes}
/> />
</div> </div>
<!-- TOC -->
{post.data.toc && <TOC headings={headings} />}
<!-- Content --> <!-- Content -->
<Content /> <Content />
</article> </article>

View file

@ -5,7 +5,7 @@
} }
html { html {
--at-apply: 'bg-background c-secondary antialiased scrollbar-hidden'; --at-apply: 'bg-background c-secondary antialiased scrollbar-hidden scroll-smooth';
} }
html::-webkit-scrollbar { html::-webkit-scrollbar {

View file

@ -18,165 +18,165 @@
} }
/* Links */ /* Links */
.heti a { .heti :where(a) {
--at-apply: 'underline decoration-secondary/40 underline-0.075em underline-offset-0.2em lg:underline-0.1em'; --at-apply: 'underline decoration-secondary/40 underline-0.075em underline-offset-0.2em lg:underline-0.1em';
--at-apply: 'font-medium transition-colors tracking-0 hover:(c-primary decoration-secondary/80) '; --at-apply: 'font-medium transition-colors tracking-0 hover:(c-primary decoration-secondary/80) ';
} }
/* Paragraphs */ /* Paragraphs */
.heti p { .heti :where(p) {
--at-apply: 'mt-3 mb-4 text-justify'; --at-apply: 'mt-3 mb-4 text-justify';
} }
.heti p:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) { .heti :where(p:not(:lang(zh)):not(:lang(ja)):not(:lang(ko))) {
--at-apply: 'text-start'; --at-apply: 'text-start';
} }
/* Headings */ /* Headings */
.heti h1, .heti :where(h1),
.heti h2, .heti :where(h2),
.heti h3, .heti :where(h3),
.heti h4, .heti :where(h4),
.heti h5, .heti :where(h5),
.heti h6 { .heti :where(h6) {
--at-apply: 'mt-6 mb-3 font-semibold'; --at-apply: 'mt-6 mb-3 font-semibold';
} }
.heti h1 { .heti :where(h1) {
--at-apply: 'text-8 leading-12'; --at-apply: 'text-8 leading-12';
} }
.heti h2 { .heti :where(h2) {
--at-apply: 'text-6 leading-9'; --at-apply: 'text-6 leading-9';
} }
.heti h3 { .heti :where(h3) {
--at-apply: 'text-5 leading-9'; --at-apply: 'text-5 leading-9';
} }
.heti h4 { .heti :where(h4) {
--at-apply: 'text-4.5 leading-6'; --at-apply: 'text-4.5 leading-6';
} }
.heti h5 { .heti :where(h5) {
--at-apply: 'text-4 leading-6'; --at-apply: 'text-4 leading-6';
} }
.heti h6 { .heti :where(h6) {
--at-apply: 'text-3.5 leading-6'; --at-apply: 'text-3.5 leading-6';
} }
.heti h1, .heti :where(h1),
.heti h2, .heti :where(h2),
.heti h3 { .heti :where(h3) {
--at-apply: 'tracking-0.05em'; --at-apply: 'tracking-0.05em';
} }
.heti h1:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti :where(h1:not(:lang(zh)):not(:lang(ja)):not(:lang(ko))),
.heti h2:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)), .heti :where(h2:not(:lang(zh)):not(:lang(ja)):not(:lang(ko))),
.heti h3:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) { .heti :where(h3:not(:lang(zh)):not(:lang(ja)):not(:lang(ko))) {
--at-apply: 'tracking-0'; --at-apply: 'tracking-0';
} }
.heti h1 + h2, .heti :where(h1 + h2),
.heti h2 + h3, .heti :where(h2 + h3),
.heti h3 + h4, .heti :where(h3 + h4),
.heti h4 + h5, .heti :where(h4 + h5),
.heti h5 + h6 { .heti :where(h5 + h6) {
--at-apply: 'mt-3'; --at-apply: 'mt-3';
} }
/* Blockquotes */ /* Blockquotes */
.heti blockquote { .heti :where(blockquote) {
--at-apply: 'mt-3 mb-6 px-4 py-3 rounded'; --at-apply: 'mt-3 mb-6 px-4 py-3 rounded';
--at-apply: 'border-l-4 border-solid border-secondary/25 bg-secondary/5'; --at-apply: 'border-l-4 border-solid border-secondary/25 bg-secondary/5';
} }
.heti blockquote blockquote { .heti blockquote :where(blockquote) {
--at-apply: 'my-3'; --at-apply: 'my-3';
} }
.heti blockquote p { .heti blockquote :where(p) {
--at-apply: 'my-2'; --at-apply: 'my-2';
} }
/* Code Blocks */ /* Code Blocks */
.heti pre { .heti :where(pre) {
--at-apply: 'mt-3 mb-4 px-4 py-3 rounded bg-secondary/5! border border-solid border-secondary/7'; --at-apply: 'mt-3 mb-4 px-4 py-3 rounded bg-secondary/5! border border-solid border-secondary/5';
--at-apply: 'overflow-auto whitespace-pre scrollbar-hidden'; --at-apply: 'overflow-auto whitespace-pre scrollbar-hidden';
} }
.heti pre code { .heti pre :where(code) {
--at-apply: 'p-0 bg-secondary/0 tracking-0 border-none'; --at-apply: 'p-0 bg-secondary/0 tracking-0 border-none';
} }
html.dark .heti pre span { html.dark .heti pre :where(span) {
--at-apply: 'text-[var(--shiki-dark)]!'; --at-apply: 'text-[var(--shiki-dark)]!';
} }
/* Inline Code */ /* Inline Code */
.heti code { .heti :where(code) {
--at-apply: 'p-0.5 bg-secondary/5 rounded text-0.85em border border-solid border-secondary/7'; --at-apply: 'p-0.5 bg-secondary/5 rounded text-0.85em border border-solid border-secondary/5';
} }
/* Horizontal Rules */ /* Horizontal Rules */
.heti hr { .heti :where(hr) {
--at-apply: 'border-secondary/25'; --at-apply: 'border-secondary/25';
} }
/* Lists */ /* Lists */
.heti ul, .heti :where(ul),
.heti ol, .heti :where(ol),
.heti dl { .heti :where(dl) {
--at-apply: 'mt-3 mb-6'; --at-apply: 'mt-3 mb-6';
} }
.heti ul, .heti :where(ul),
.heti ol { .heti :where(ol) {
--at-apply: 'pl-8'; --at-apply: 'pl-8';
} }
.heti ul ul, .heti ul :where(ul),
.heti ul ol, .heti ul :where(ol),
.heti ol ul, .heti ol :where(ul),
.heti ol ol { .heti ol :where(ol) {
--at-apply: 'my-0'; --at-apply: 'my-0';
} }
.heti ul { .heti :where(ul) {
--at-apply: 'list-disc'; --at-apply: 'list-disc';
} }
.heti ol { .heti :where(ol) {
--at-apply: 'list-decimal'; --at-apply: 'list-decimal';
} }
.heti ul ul, .heti ul :where(ul),
.heti ol ul { .heti ol :where(ul) {
--at-apply: 'list-circle'; --at-apply: 'list-circle';
} }
.heti ul ul ul, .heti ul ul :where(ul),
.heti ul ol ul, .heti ul ol :where(ul),
.heti ol ul ul, .heti ol ul :where(ul),
.heti ol ol ul { .heti ol ol :where(ul) {
--at-apply: 'list-square'; --at-apply: 'list-square';
} }
.heti li { .heti :where(li) {
--at-apply: 'list-unset'; --at-apply: 'list-unset';
} }
/* Tables */ /* Tables */
.heti table { .heti :where(table) {
--at-apply: 'box-border table-fixed mt-3 mb-6 rounded-md break-words'; --at-apply: 'box-border table-fixed mt-3 mb-6 rounded-md break-words';
--at-apply: 'border border-solid border-secondary/25 border-collapse'; --at-apply: 'border border-solid border-secondary/25 border-collapse';
} }
.heti th, .heti :where(th),
.heti td { .heti :where(td) {
--at-apply: 'px-3 py-1.5 border border-solid border-secondary/40'; --at-apply: 'px-3 py-1.5 border border-solid border-secondary/40';
} }
/* Abbreviations */ /* Abbreviations */
.heti abbr { .heti :where(abbr) {
--at-apply: 'tracking-0'; --at-apply: 'tracking-0';
} }
.heti abbr[title] { .heti :where(abbr[title]) {
--at-apply: 'mx-0.25 pb-0.25 border-dotted border-secondary border-b-1 no-underline cursor-help'; --at-apply: 'mx-0.25 pb-0.25 border-dotted border-secondary border-b-1 no-underline cursor-help';
} }
/* Superscript and Subscript */ /* Superscript and Subscript */
.heti sub, .heti :where(sub),
.heti sup { .heti :where(sup) {
--at-apply: 'mx-0.15em relative text-0.75em leading-1 align-baseline'; --at-apply: 'mx-0.15em relative text-0.75em leading-1 align-baseline';
} }
.heti sub { .heti :where(sub) {
--at-apply: 'bottom--0.25em'; --at-apply: 'bottom--0.25em';
} }
.heti sup { .heti :where(sup) {
--at-apply: 'top--0.5em'; --at-apply: 'top--0.5em';
} }
.heti sub a, .heti sub :where(a),
.heti sup a { .heti sup :where(a) {
--at-apply: 'no-underline'; --at-apply: 'no-underline';
} }
.heti sup:target, .heti sup:target,
@ -189,16 +189,16 @@ html.dark .heti sup a:target {
} }
/* Keyboard Input */ /* Keyboard Input */
.heti kbd { .heti :where(kbd) {
--at-apply: 'rounded border border-solid border-secondary/40 text-secondary'; --at-apply: 'rounded border border-solid border-secondary/40 text-secondary';
--at-apply: 'inline-block text-0.85em font-bold leading-none px-1 py-0.75 whitespace-nowrap'; --at-apply: 'inline-block text-0.85em font-bold leading-none px-1 py-0.75 whitespace-nowrap';
} }
/* Highlighted Text */ /* Highlighted Text */
.heti mark { .heti :where(mark) {
--at-apply: 'mx-0.25 px-0.25 py-0.5 text-inherit bg-#ff0'; --at-apply: 'mx-0.25 px-0.25 py-0.5 text-inherit bg-#ff0';
} }
html.dark .heti mark { html.dark .heti :where(mark) {
--at-apply: 'bg-#4d4a00e0'; --at-apply: 'bg-#4d4a00e0';
} }
@ -231,68 +231,68 @@ html.dark .heti mark {
content: "Сноски"; content: "Сноски";
--at-apply: 'block text-5 font-semibold mt-6 mb-3'; --at-apply: 'block text-5 font-semibold mt-6 mb-3';
} }
.heti .data-footnote-backref { .heti :where(.data-footnote-backref) {
--at-apply: 'no-underline'; --at-apply: 'no-underline';
} }
/* Bold */ /* Bold */
.heti b, .heti :where(b),
.heti strong { .heti :where(strong) {
--at-apply: 'font-semibold'; --at-apply: 'font-semibold';
} }
/* Italic */ /* Italic */
.heti i { .heti :where(i) {
--at-apply: 'font-italic'; --at-apply: 'font-italic';
} }
/* Emphasized */ /* Emphasized */
.heti em { .heti :where(em) {
--at-apply: 'italic'; --at-apply: 'italic';
} }
/* Quotes */ /* Quotes */
.heti q { .heti :where(q) {
quotes: "「" "」" "『" "』"; quotes: "「" "」" "『" "』";
} }
.heti q:not(:lang(zh)):not(:lang(ja)):not(:lang(ko)) { .heti :where(q:not(:lang(zh)):not(:lang(ja)):not(:lang(ko))) {
quotes: initial; quotes: initial;
quotes: auto; quotes: auto;
} }
/* Wavy Underline */ /* Wavy Underline */
.heti u { .heti :where(u) {
--at-apply: 'mx-0.25 underline decoration-wavy decoration-red underline-offset-4'; --at-apply: 'mx-0.25 underline decoration-wavy decoration-red underline-offset-4';
} }
html.dark .heti u { html.dark .heti :where(u) {
--at-apply: 'decoration-#A14F50'; --at-apply: 'decoration-#A14F50';
} }
/* Strikethrough */ /* Strikethrough */
.heti del, .heti :where(del),
.heti s { .heti :where(s) {
--at-apply: 'mx-0.25'; --at-apply: 'mx-0.25';
} }
/* Images */ /* Images */
.heti figure { .heti :where(figure) {
--at-apply: 'my-6'; --at-apply: 'my-6';
} }
.heti figure > figcaption { .heti figure > :where(figcaption) {
--at-apply: 'text-center text-sm mt-3 text-secondary/75'; --at-apply: 'text-center text-sm mt-3 text-secondary/75';
} }
/* Markdown Extensions Style >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */ /* Markdown Extensions Style >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> */
.heti details { .heti :where(details) {
--at-apply: 'my-4 px-4 py-3 border border-solid border-secondary/25 rounded cursor-pointer'; --at-apply: 'my-4 px-4 py-3 border border-solid border-secondary/25 rounded cursor-pointer';
} }
.heti details summary { .heti details :where(summary) {
--at-apply: 'cursor-pointer'; --at-apply: 'cursor-pointer';
} }
.heti details[open] summary { .heti details[open] :where(summary) {
--at-apply: 'border-b border-solid border-secondary/25 mb-3 pb-3'; --at-apply: 'border-b border-solid border-secondary/25 mb-3 pb-3';
} }
/* .heti details summary { /* .heti details :where(summary) {
list-style: none; list-style: none;
} }
.heti details summary::-webkit-details-marker { .heti details summary::-webkit-details-marker {