From a4576db4386d954998f73e2a6a0a799be75aca73 Mon Sep 17 00:00:00 2001 From: sergeyyarkov Date: Sun, 23 Nov 2025 15:37:29 +0300 Subject: [PATCH] fix articles list component --- package-lock.json | 89 ++++++++----------- src/components/ArticleItem/ArticleItem.tsx | 2 +- src/components/ArticlesList/ArticlesList.tsx | 6 +- .../ArticlesList/SearchInput/SearchInput.tsx | 2 - src/layouts/article.astro | 10 +-- src/utils/article.ts | 68 +------------- src/utils/markdown.ts | 65 ++++++++++++++ 7 files changed, 112 insertions(+), 130 deletions(-) create mode 100644 src/utils/markdown.ts diff --git a/package-lock.json b/package-lock.json index 97bda29..5ed0404 100644 --- a/package-lock.json +++ b/package-lock.json @@ -338,17 +338,6 @@ } } }, - "node_modules/@astrojs/solid-js/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, "node_modules/@astrojs/solid-js/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -371,6 +360,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -383,6 +373,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -562,6 +553,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3562,6 +3554,7 @@ "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.8.tgz", "integrity": "sha512-QiiRnNPdxCcAGO2UlO07o+QeGgRfEC5Dlm0x35WPB/ixFK1T2bsNB6KaXri70cVkYY1GYgoRtrWv3HCR26o9aw==", "license": "MIT", + "peer": true, "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.4", @@ -3653,17 +3646,6 @@ "@iconify/utils": "^2.1.30" } }, - "node_modules/astro/node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "undici-types": "~7.16.0" - } - }, "node_modules/astro/node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3723,6 +3705,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3889,6 +3872,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -4201,6 +4185,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -8645,6 +8630,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", "devOptional": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -8742,6 +8728,7 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=10" } @@ -8953,6 +8940,7 @@ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", @@ -9291,6 +9279,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -9418,6 +9407,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9458,8 +9448,7 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/unicode-properties": { "version": "1.4.1", @@ -10427,6 +10416,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -10661,16 +10651,6 @@ "vite-plugin-solid": "^2.11.8" }, "dependencies": { - "@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "optional": true, - "peer": true, - "requires": { - "undici-types": "~7.16.0" - } - }, "fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -10680,12 +10660,14 @@ "picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true }, "vite": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "peer": true, "requires": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -10765,6 +10747,7 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", + "peer": true, "requires": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -12509,6 +12492,7 @@ "version": "5.15.8", "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.8.tgz", "integrity": "sha512-QiiRnNPdxCcAGO2UlO07o+QeGgRfEC5Dlm0x35WPB/ixFK1T2bsNB6KaXri70cVkYY1GYgoRtrWv3HCR26o9aw==", + "peer": true, "requires": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.4", @@ -12575,16 +12559,6 @@ "zod-to-ts": "^1.2.0" }, "dependencies": { - "@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", - "optional": true, - "peer": true, - "requires": { - "undici-types": "~7.16.0" - } - }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -12620,7 +12594,8 @@ "picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true }, "readdirp": { "version": "4.1.2", @@ -12681,6 +12656,7 @@ "version": "6.4.1", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "peer": true, "requires": { "esbuild": "^0.25.0", "fdir": "^6.4.4", @@ -12847,6 +12823,7 @@ "version": "4.28.0", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "peer": true, "requires": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", @@ -15745,6 +15722,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.68.0.tgz", "integrity": "sha512-Lmj9lM/fef0nQswm1J2HJcEsBUba4wgNx2fea6yJHODREoMFnwRpZydBnX/RjyXw2REIwdkbqE4hrTo4qfDBUA==", "devOptional": true, + "peer": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -15813,7 +15791,8 @@ "seroval": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", - "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==" + "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", + "peer": true }, "seroval-plugins": { "version": "1.3.3", @@ -15960,6 +15939,7 @@ "version": "1.9.10", "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", + "peer": true, "requires": { "csstype": "^3.1.0", "seroval": "~1.3.0", @@ -16182,7 +16162,8 @@ "picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==" + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "peer": true } } }, @@ -16255,7 +16236,8 @@ "typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==" + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "peer": true }, "typescript-auto-import-cache": { "version": "0.3.6", @@ -16281,11 +16263,9 @@ "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==" }, "undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "version": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "optional": true, - "peer": true + "optional": true }, "unicode-properties": { "version": "1.4.1", @@ -16923,7 +16903,8 @@ "zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==" + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "peer": true }, "zod-to-json-schema": { "version": "3.24.6", diff --git a/src/components/ArticleItem/ArticleItem.tsx b/src/components/ArticleItem/ArticleItem.tsx index 47cfc5f..9e09be3 100644 --- a/src/components/ArticleItem/ArticleItem.tsx +++ b/src/components/ArticleItem/ArticleItem.tsx @@ -1,5 +1,5 @@ import type { Component } from "solid-js"; -import { createDateFormatter, createRelativeArticleUrl } from "@root/utils"; +import { createRelativeArticleUrl, createDateFormatter } from "@root/utils"; import { DEFAULT_LANGUAGE, URL_BLOG_PREFIX } from "@root/constants"; import "./ArticleItem.scss"; import type { ArticleTranslation } from "@/directus-schema"; diff --git a/src/components/ArticlesList/ArticlesList.tsx b/src/components/ArticlesList/ArticlesList.tsx index 333c895..e6d4f54 100644 --- a/src/components/ArticlesList/ArticlesList.tsx +++ b/src/components/ArticlesList/ArticlesList.tsx @@ -47,7 +47,11 @@ const ArticlesList: Component = (props) => { a.title.toLocaleLowerCase().includes(search.trim().toLocaleLowerCase()) ); if (tags && tags.length > 0) { - filtered = filtered.filter((a) => tags.some((t) => a.tags.includes(t))); + filtered = filtered.filter((a) => + tags.some((t) => { + return a.tags.map((t) => t.tag_id.title).includes(t); + }) + ); } return filtered; }); diff --git a/src/components/ArticlesList/SearchInput/SearchInput.tsx b/src/components/ArticlesList/SearchInput/SearchInput.tsx index b4e78b5..1763b8c 100644 --- a/src/components/ArticlesList/SearchInput/SearchInput.tsx +++ b/src/components/ArticlesList/SearchInput/SearchInput.tsx @@ -23,8 +23,6 @@ const SearchInput: Component = (props) => { const onSearch: JSX.EventHandler = (e) => { const value = e.currentTarget.value; - console.log(value); - if (value.trim().length === 0) { resetSearch(); return; diff --git a/src/layouts/article.astro b/src/layouts/article.astro index e978e26..5213b97 100644 --- a/src/layouts/article.astro +++ b/src/layouts/article.astro @@ -8,7 +8,7 @@ import ArticlePost from "@components/ArticlePost.astro"; import OpenGraph from "@components/OpenGraph.astro"; // import TableOfContents from "@root/components/TableOfContents.astro"; import type { ArticleTranslation } from "@/directus-schema"; -import * as utils from "@utils/article"; +import { markdownToHTML } from "@utils/markdown"; export interface Props { lang?: string | undefined; @@ -19,7 +19,7 @@ export interface Props { const canonicalUrl = new URL(Astro.url.pathname, Astro.site); const { lang = DEFAULT_LANGUAGE, article } = Astro.props as Props; const { title, description, cover_image, content } = article; -const htmlContent = await utils.markdownToHTML(content); +const htmlContent = await markdownToHTML(content); --- @@ -28,17 +28,17 @@ const htmlContent = await utils.markdownToHTML(content); title={title} siteName={SITE_METADATA.name} description={description} - type="article" + type='article' url={canonicalUrl.href} image={cover_image ? { path: cover_image.filename_disk, width: "700", height: "400", type: cover_image.type } : undefined} /> - + -
+
diff --git a/src/utils/article.ts b/src/utils/article.ts index d78c8b8..4f16716 100644 --- a/src/utils/article.ts +++ b/src/utils/article.ts @@ -1,19 +1,6 @@ import type { MarkdownHeading } from "astro"; -import type { CollectionEntry } from "astro:content"; import { DEFAULT_LANGUAGE } from "@root/constants"; import type { ArticleTranslation } from "@/directus-schema"; -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkPrism from "remark-prism"; -import remarkToc from "remark-toc"; -import remarkRehype from "remark-rehype"; -import rehypeAutolinkHeadings from "rehype-autolink-headings"; -import rehypeSlug from "rehype-slug"; -import rehypeStringify from "rehype-stringify"; -import { escape } from "html-escaper"; -import { h } from "hastscript"; -import { toString } from "hast-util-to-string"; -// import { selectAll } from "hast-util-select"; /** * If there is a translation of an article in the current language of the page, @@ -59,7 +46,7 @@ export function formatToArticleBlocks(articles: ArticleTranslation[]) { blocks[year].push({ id: a.id, title: a.title, - // tags: a.tags, + tags: a.tags, slug: a.slug, pub_date: a.pub_date, }); @@ -84,56 +71,3 @@ export function buildToc(headings: MarkdownHeading[], maxDepth: number = 2) { }); return toc; } - -export async function markdownToHTML(content: string): Promise { - const createAnchorLabel = (heading: string) => { - const node = h("span.anchor-label", escape(heading)); - if (node.properties) node.properties["is:raw"] = true; - return node; - }; - - const file = await unified() - .use(remarkParse) - .use(remarkToc, { heading: "(table[ -]of[ -])?contents?|toc|содержание", maxDepth: 2 }) - .use(remarkPrism as any) - .use(remarkRehype) - .use(rehypeSlug) - .use(rehypeAutolinkHeadings, { - properties: { - class: "anchor-link", - }, - behavior: "after", - group: ({ tagName }) => - h(`div.heading-wrapper.level-${tagName}`, { - tabIndex: -1, - }), - content: (heading) => [ - h( - `span.anchor-icon`, - undefined, - h( - "svg", - { - width: 18, - height: 18, - version: 1.1, - viewBox: "0 0 16 16", - xlmns: "http://www.w3.org/2000/svg", - ariaHidden: "true", - "data-filter": "none", - }, - h("path", { - fillRule: "evenodd", - fill: "currentcolor", - d: "M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z", - }) - ) - ), - createAnchorLabel(toString(heading)), - ], - }) - .use(rehypeStringify) - .process(content); - - return String(file); -} diff --git a/src/utils/markdown.ts b/src/utils/markdown.ts new file mode 100644 index 0000000..e54dd67 --- /dev/null +++ b/src/utils/markdown.ts @@ -0,0 +1,65 @@ +import { unified } from "unified"; +import remarkParse from "remark-parse"; +import remarkPrism from "remark-prism"; +import remarkToc from "remark-toc"; +import remarkRehype from "remark-rehype"; +import rehypeAutolinkHeadings from "rehype-autolink-headings"; +import rehypeSlug from "rehype-slug"; +import rehypeStringify from "rehype-stringify"; +import { escape } from "html-escaper"; +import { h } from "hastscript"; +import { toString } from "hast-util-to-string"; +// import { selectAll } from "hast-util-select"; + +export async function markdownToHTML(content: string): Promise { + const createAnchorLabel = (heading: string) => { + const node = h("span.anchor-label", escape(heading)); + if (node.properties) node.properties["is:raw"] = true; + return node; + }; + + const file = await unified() + .use(remarkParse) + .use(remarkToc, { heading: "(table[ -]of[ -])?contents?|toc|содержание", maxDepth: 2 }) + .use(remarkPrism as any) + .use(remarkRehype) + .use(rehypeSlug) + .use(rehypeAutolinkHeadings, { + properties: { + class: "anchor-link", + }, + behavior: "after", + group: ({ tagName }) => + h(`div.heading-wrapper.level-${tagName}`, { + tabIndex: -1, + }), + content: (heading) => [ + h( + `span.anchor-icon`, + undefined, + h( + "svg", + { + width: 18, + height: 18, + version: 1.1, + viewBox: "0 0 16 16", + xlmns: "http://www.w3.org/2000/svg", + ariaHidden: "true", + "data-filter": "none", + }, + h("path", { + fillRule: "evenodd", + fill: "currentcolor", + d: "M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z", + }) + ) + ), + createAnchorLabel(toString(heading)), + ], + }) + .use(rehypeStringify) + .process(content); + + return String(file); +}