diff --git a/docs/src/content/pages/posts/markdown-style-guide.md b/docs/src/content/pages/posts/markdown-style-guide.md
index 0bbc5cd..68a29f9 100644
--- a/docs/src/content/pages/posts/markdown-style-guide.md
+++ b/docs/src/content/pages/posts/markdown-style-guide.md
@@ -12,17 +12,17 @@ Here is a sample of some basic Markdown syntax that can be used when writing Mar
The following HTML `
`—`` elements represent six levels of section headings. `` is the highest section level while `` is the lowest.
-# H1
+H1
-## H2
+H2
-### H3
+H3
-#### H4
+H4
-##### H5
+H5
-###### H6
+H6
## Paragraph
diff --git a/package.json b/package.json
index 6bfe711..3c8214e 100644
--- a/package.json
+++ b/package.json
@@ -55,14 +55,14 @@
"@astrojs/markdown-remark": "^5.2.0",
"@astrojs/mdx": "^3.1.7",
"@astrojs/sitemap": "^3.2.0",
- "@microflash/rehype-toc": "^1.0.2",
"bcp-47": "^2.1.0",
"dayjs": "^1.11.13",
"fast-glob": "^3.3.2",
- "hastscript": "^9.0.0",
+ "hast-util-find-and-replace": "^5.0.1",
"i18next": "^23.15.2",
"nprogress": "^0.2.0",
- "rehype-autolink-headings": "^7.1.0"
+ "rehype-autolink-headings": "^7.1.0",
+ "unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@antfu/eslint-config": "^3.7.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4a68a33..cb547a3 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -20,9 +20,6 @@ importers:
'@iconify/json':
specifier: ^2.2.256
version: 2.2.256
- '@microflash/rehype-toc':
- specifier: ^1.0.2
- version: 1.0.2
'@unocss/reset':
specifier: ^0.63.2
version: 0.63.3
@@ -35,9 +32,9 @@ importers:
fast-glob:
specifier: ^3.3.2
version: 3.3.2
- hastscript:
- specifier: ^9.0.0
- version: 9.0.0
+ hast-util-find-and-replace:
+ specifier: ^5.0.1
+ version: 5.0.1
i18next:
specifier: ^23.15.2
version: 23.15.2
@@ -47,6 +44,9 @@ importers:
rehype-autolink-headings:
specifier: ^7.1.0
version: 7.1.0
+ unist-util-visit:
+ specifier: ^5.0.0
+ version: 5.0.0
devDependencies:
'@antfu/eslint-config':
specifier: ^3.7.3
@@ -944,9 +944,6 @@ packages:
'@mdx-js/mdx@3.0.1':
resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==}
- '@microflash/rehype-toc@1.0.2':
- resolution: {integrity: sha512-Vx3MAlgjUpj758rYYBRHxePHNadJzCgVT2/FMpPd2vRkgucpXSQ8DhCYEhp8Zrlc1jWjU8bSUsROQvZ1tyA76A==}
-
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1130,9 +1127,6 @@ packages:
'@types/estree@1.0.6':
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
- '@types/hast@2.3.10':
- resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
-
'@types/hast@3.0.4':
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
@@ -2316,8 +2310,8 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
- hast-util-find-and-replace@4.1.3:
- resolution: {integrity: sha512-q8UYl9o5wvg+ZT666rb/xYmCBgTS1wQnoadz1Ugc82XLMx2AGPqIgDVmdf4wxYN7evZFXT5FwmvWeJhSbZ4nIA==}
+ hast-util-find-and-replace@5.0.1:
+ resolution: {integrity: sha512-S12fTskO3Hf2IGCBWXs1UcXT8GEJ3jmvmPZJctkRwfl3a8jnGi8aFYT8kd2zcEH+VE0qcGgKF0ewt5BPAsfIhw==}
hast-util-from-html@2.0.3:
resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
@@ -2325,24 +2319,12 @@ packages:
hast-util-from-parse5@8.0.1:
resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==}
- hast-util-has-property@2.0.1:
- resolution: {integrity: sha512-X2+RwZIMTMKpXUzlotatPzWj8bspCymtXH3cfG3iQKV+wPF53Vgaqxi/eLqGck0wKq1kS9nvoB1wchbCPEL8sg==}
-
- hast-util-heading-rank@2.1.1:
- resolution: {integrity: sha512-iAuRp+ESgJoRFJbSyaqsfvJDY6zzmFoEnL1gtz1+U8gKtGGj1p0CVlysuUAUjq95qlZESHINLThwJzNGmgGZxA==}
-
hast-util-heading-rank@3.0.0:
resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
- hast-util-is-element@2.1.3:
- resolution: {integrity: sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==}
-
hast-util-is-element@3.0.0:
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
- hast-util-parse-selector@3.1.1:
- resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
-
hast-util-parse-selector@4.0.0:
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
@@ -2361,24 +2343,15 @@ packages:
hast-util-to-parse5@8.0.0:
resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
- hast-util-to-string@2.0.0:
- resolution: {integrity: sha512-02AQ3vLhuH3FisaMM+i/9sm4OXGSq1UhOOCpTLLQtHdL3tZt7qil69r8M8iDkZYyC0HCFylcYoP+8IO7ddta1A==}
-
hast-util-to-text@4.0.2:
resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==}
hast-util-whitespace@3.0.0:
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
- hastscript@7.2.0:
- resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==}
-
hastscript@8.0.0:
resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==}
- hastscript@9.0.0:
- resolution: {integrity: sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==}
-
hosted-git-info@2.8.9:
resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==}
@@ -3678,9 +3651,6 @@ packages:
unist-util-find-after@5.0.0:
resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==}
- unist-util-is@5.2.1:
- resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==}
-
unist-util-is@6.0.0:
resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
@@ -3702,15 +3672,9 @@ packages:
unist-util-visit-children@3.0.0:
resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==}
- unist-util-visit-parents@5.1.3:
- resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==}
-
unist-util-visit-parents@6.0.1:
resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
- unist-util-visit@4.1.2:
- resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==}
-
unist-util-visit@5.0.0:
resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
@@ -4880,15 +4844,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@microflash/rehype-toc@1.0.2':
- dependencies:
- hast-util-find-and-replace: 4.1.3
- hast-util-has-property: 2.0.1
- hast-util-heading-rank: 2.1.1
- hast-util-to-string: 2.0.0
- hastscript: 7.2.0
- unist-util-visit: 4.1.2
-
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -5074,10 +5029,6 @@ snapshots:
'@types/estree@1.0.6': {}
- '@types/hast@2.3.10':
- dependencies:
- '@types/unist': 2.0.11
-
'@types/hast@3.0.4':
dependencies:
'@types/unist': 3.0.3
@@ -6700,11 +6651,12 @@ snapshots:
dependencies:
function-bind: 1.1.2
- hast-util-find-and-replace@4.1.3:
+ hast-util-find-and-replace@5.0.1:
dependencies:
+ '@types/hast': 3.0.4
escape-string-regexp: 5.0.0
- hast-util-is-element: 2.1.3
- unist-util-visit-parents: 5.1.3
+ hast-util-is-element: 3.0.0
+ unist-util-visit-parents: 6.0.1
hast-util-from-html@2.0.3:
dependencies:
@@ -6726,29 +6678,14 @@ snapshots:
vfile-location: 5.0.3
web-namespaces: 2.0.1
- hast-util-has-property@2.0.1: {}
-
- hast-util-heading-rank@2.1.1:
- dependencies:
- '@types/hast': 2.3.10
-
hast-util-heading-rank@3.0.0:
dependencies:
'@types/hast': 3.0.4
- hast-util-is-element@2.1.3:
- dependencies:
- '@types/hast': 2.3.10
- '@types/unist': 2.0.11
-
hast-util-is-element@3.0.0:
dependencies:
'@types/hast': 3.0.4
- hast-util-parse-selector@3.1.1:
- dependencies:
- '@types/hast': 2.3.10
-
hast-util-parse-selector@4.0.0:
dependencies:
'@types/hast': 3.0.4
@@ -6834,10 +6771,6 @@ snapshots:
web-namespaces: 2.0.1
zwitch: 2.0.4
- hast-util-to-string@2.0.0:
- dependencies:
- '@types/hast': 2.3.10
-
hast-util-to-text@4.0.2:
dependencies:
'@types/hast': 3.0.4
@@ -6849,14 +6782,6 @@ snapshots:
dependencies:
'@types/hast': 3.0.4
- hastscript@7.2.0:
- dependencies:
- '@types/hast': 2.3.10
- comma-separated-tokens: 2.0.3
- hast-util-parse-selector: 3.1.1
- property-information: 6.5.0
- space-separated-tokens: 2.0.2
-
hastscript@8.0.0:
dependencies:
'@types/hast': 3.0.4
@@ -6865,14 +6790,6 @@ snapshots:
property-information: 6.5.0
space-separated-tokens: 2.0.2
- hastscript@9.0.0:
- dependencies:
- '@types/hast': 3.0.4
- comma-separated-tokens: 2.0.3
- hast-util-parse-selector: 4.0.0
- property-information: 6.5.0
- space-separated-tokens: 2.0.2
-
hosted-git-info@2.8.9: {}
html-escaper@2.0.2: {}
@@ -8482,10 +8399,6 @@ snapshots:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
- unist-util-is@5.2.1:
- dependencies:
- '@types/unist': 2.0.11
-
unist-util-is@6.0.0:
dependencies:
'@types/unist': 3.0.3
@@ -8516,22 +8429,11 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
- unist-util-visit-parents@5.1.3:
- dependencies:
- '@types/unist': 2.0.11
- unist-util-is: 5.2.1
-
unist-util-visit-parents@6.0.1:
dependencies:
'@types/unist': 3.0.3
unist-util-is: 6.0.0
- unist-util-visit@4.1.2:
- dependencies:
- '@types/unist': 2.0.11
- unist-util-is: 5.2.1
- unist-util-visit-parents: 5.1.3
-
unist-util-visit@5.0.0:
dependencies:
'@types/unist': 3.0.3
diff --git a/src/components/Toc.astro b/src/components/Toc.astro
new file mode 100644
index 0000000..3c5d29b
--- /dev/null
+++ b/src/components/Toc.astro
@@ -0,0 +1,19 @@
+---
+import { TocHeading } from '../utils/toc'
+import TocItem from './TocItem.astro'
+
+export interface Props {
+ headings: TocHeading[]
+}
+
+const { headings } = Astro.props
+---
+
+
+
+
+
+
+ {headings.map((toc) => )}
+
+
diff --git a/src/components/TocItem.astro b/src/components/TocItem.astro
new file mode 100644
index 0000000..5a9b71c
--- /dev/null
+++ b/src/components/TocItem.astro
@@ -0,0 +1,22 @@
+---
+import { TocHeading } from '../utils/toc'
+
+export type Props = TocHeading
+
+const { slug, text, children } = Astro.props
+---
+
+
+
+ {text}
+
+ {
+ !!children?.length && (
+
+ {children.map((toc) => (
+
+ ))}
+
+ )
+ }
+
diff --git a/src/components/WrapperPost.astro b/src/components/WrapperPost.astro
index 0c3f6de..73839ff 100644
--- a/src/components/WrapperPost.astro
+++ b/src/components/WrapperPost.astro
@@ -3,12 +3,21 @@ import type { Props } from '../props'
import config from 'virtual:vitesse/user-config'
import { formatDate } from '../logics'
+import { generateToc } from '../utils/toc'
import DraftContentNotice from './DraftContentNotice.astro'
import FallbackContentNotice from './FallbackContentNotice.astro'
+import Toc from './Toc.astro'
const { entry } = Astro.props
const { data: frontmatter } = entry
+const {
+ remarkPluginFrontmatter: { hasToc },
+ headings,
+} = await entry.render()
+
+const toc = generateToc(headings ?? [], 1, 4)
+
let tweetUrl = ''
let elkUrl = ''
@@ -59,6 +68,13 @@ if (config.social?.mastodon) {
>
{Astro.props.entry.data.draft && }
{Astro.props.isFallback && }
+ {
+ hasToc && (
+
+ )
+ }
{
diff --git a/src/index.ts b/src/index.ts
index fa3fe5d..297cf57 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,7 +3,6 @@
* loaded when a user imports the Vitesse integration in their Astro configuration file. These
* directives must be first at the top of the file and can only be preceded by this comment.
*/
-///
///
///
///
@@ -13,12 +12,11 @@ import type { PluginTranslations, VitesseUserConfigWithPlugins } from './utils/p
import { rehypeHeadingIds } from '@astrojs/markdown-remark'
import mdx from '@astrojs/mdx'
-import rehypeToc from '@microflash/rehype-toc'
-import { h } from 'hastscript'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import { vitesseSitemap } from './integrations/sitemap'
import { vitePluginVitesseUserConfig } from './integrations/virtual-user-config'
+import { rehypeToc } from './plugins/rehype-toc'
import { processI18nConfig } from './utils/i18n'
import { injectPluginTranslationsTypes, runPlugins } from './utils/plugins'
import { createTranslationSystemFromFs } from './utils/translations-fs'
@@ -101,26 +99,8 @@ export default function VitesseIntegration({
},
markdown: {
rehypePlugins: [
+ rehypeToc,
rehypeHeadingIds,
- [
- rehypeToc,
- {
- toc: (headings: { id: string, title: string, depth: number }[]) => {
- return h('section', [
- h('div.table-of-contents', [
- h('div.table-of-contents-anchor', [
- h('div.i-ri-menu-2-fill'),
- ]),
- h('ul', [
- ...headings.map(heading => h(`li`, [
- h('a', { href: `#${heading.id}` }, heading.title),
- ])),
- ]),
- ]),
- ])
- },
- },
- ],
[
rehypeAutolinkHeadings,
{
diff --git a/src/plugins/rehype-toc.ts b/src/plugins/rehype-toc.ts
new file mode 100644
index 0000000..d2b69c0
--- /dev/null
+++ b/src/plugins/rehype-toc.ts
@@ -0,0 +1,19 @@
+import type { RehypePlugin } from '@astrojs/markdown-remark'
+import { findAndReplace } from 'hast-util-find-and-replace'
+import { visit } from 'unist-util-visit'
+
+export const rehypeToc: RehypePlugin = () => {
+ return (tree, file: any) => {
+ let replaced = false
+ visit(tree, (node) => {
+ findAndReplace(node, [/\[\[toc\]\]/gi, (text) => {
+ if (!replaced) {
+ file.data.astro.frontmatter.hasToc = true
+ replaced = true
+ return
+ }
+ return text // Return the original text if already replaced
+ }])
+ })
+ }
+}
diff --git a/src/rehype-toc.d.ts b/src/rehype-toc.d.ts
deleted file mode 100644
index 800c85c..0000000
--- a/src/rehype-toc.d.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-declare module '@microflash/rehype-toc' {
- import type { Plugin } from 'unified'
- import type { Node } from 'unist'
-
- interface RehypeTocOptions {
- matcher?: RegExp
- id?: string
- toc?: (headings: { id: string, title: string, depth: number }[]) => Node
- }
-
- const rehypeToc: Plugin<[RehypeTocOptions?]>
-
- export default rehypeToc
-}
diff --git a/src/utils/toc.ts b/src/utils/toc.ts
new file mode 100644
index 0000000..ce3ea5f
--- /dev/null
+++ b/src/utils/toc.ts
@@ -0,0 +1,38 @@
+import type { MarkdownHeading } from 'astro'
+
+export interface TocHeading extends MarkdownHeading {
+ children?: TocHeading[]
+}
+
+export type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6
+
+export function generateToc(
+ headings: readonly TocHeading[],
+ minHeadingLevel: number,
+ maxHeadingLevel: number,
+): TocHeading[] {
+ const filteredItems = headings.filter(item => item.depth >= minHeadingLevel && item.depth <= maxHeadingLevel)
+ const stack: TocHeading[] = []
+ const result: TocHeading[] = []
+
+ for (const item of filteredItems) {
+ item.children = []
+
+ while (stack.length > 0 && stack[stack.length - 1].depth >= item.depth) {
+ stack.pop()
+ }
+
+ if (stack.length === 0) {
+ result.push(item)
+ }
+ else {
+ const parent = stack[stack.length - 1]
+ parent.children = parent.children || []
+ parent.children.push(item)
+ }
+
+ stack.push(item)
+ }
+
+ return result
+}