diff --git a/.gitignore b/.gitignore index d55e5e4..7db8e0f 100644 --- a/.gitignore +++ b/.gitignore @@ -21,9 +21,9 @@ /build /public/categories.json /public/types-data.json -/public/tag-data.json +/public/topics-by-category-counts.json /public/aliases.json -/public/topics-data.json +/public/topics-counts.json /public/speaker-data.json /public/source-count-data.json /public/sources-data.json diff --git a/contentlayer.config.ts b/contentlayer.config.ts index 1b927a8..1434acc 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -1,8 +1,16 @@ import path from "path"; import * as fs from "fs"; -import { createSlug, createText, SpeakerData, TopicsData, unsluggify } from "./src/utils"; -import { defineDocumentType, defineNestedType, makeSource } from "contentlayer2/source-files"; -import { Transcript as ContentTranscriptType, Source as ContentSourceType } from "./.contentlayer/generated/types"; +import { createSlug, SpeakerData, TopicsData, unsluggify } from "./src/utils"; +import { + defineDocumentType, + defineNestedType, + makeSource, +} from "contentlayer2/source-files"; +import { + Transcript as ContentTranscriptType, + Source as ContentSourceType, +} from "./.contentlayer/generated/types"; +import { LanguageCodes } from "./src/config"; const Resources = defineNestedType(() => ({ name: "Resources", @@ -11,7 +19,7 @@ const Resources = defineNestedType(() => ({ url: { type: "string" }, }, })); -export interface CategoryInfo { +export interface Topic { title: string; slug: string; optech_url: string; @@ -20,6 +28,14 @@ export interface CategoryInfo { excerpt: string; } +// The full processed topic we use internally +interface ProcessedTopic { + name: string; // Display name (from topic.title or original tag) + slug: string; // Slugified identifier + count: number; // Number of occurrences + categories: string[]; // List of categories it belongs to +} + interface TagInfo { name: string; slug: string; @@ -30,25 +46,6 @@ interface ContentTree { [key: string]: ContentTree | ContentTranscriptType[]; } -/** - * Count the occurrences of all tags across transcripts and write to json file - */ -function createTagCount(allTranscripts: ContentTranscriptType[]): { - tagCounts: Record; -} { - const tagCounts: Record = {}; - - for (const file of allTranscripts) { - if (!file.tags) continue; - - for (const tag of file.tags) { - const formattedTag = createSlug(tag); - tagCounts[formattedTag] = (tagCounts[formattedTag] || 0) + 1; - } - } - - return { tagCounts }; -} const getTranscriptAliases = (allTranscripts: ContentTranscriptType[]) => { const aliases: Record = {}; @@ -66,99 +63,106 @@ const getTranscriptAliases = (allTranscripts: ContentTranscriptType[]) => { fs.writeFileSync("./public/aliases.json", JSON.stringify(aliases)); }; -const getCategories = () => { - const filePath = path.join(process.cwd(), "public", "categories.json"); +const getTopics = () => { + const filePath = path.join(process.cwd(), "public", "topics.json"); const fileContents = fs.readFileSync(filePath, "utf8"); return JSON.parse(fileContents); }; -function organizeTags(transcripts: ContentTranscriptType[]) { - const categories: CategoryInfo[] = getCategories(); - const { tagCounts } = createTagCount(transcripts); +function buildTopicsMap(transcripts: ContentTranscriptType[], topics: Topic[]): Map { + // Create topics lookup map (includes aliases) + const topicsLookup = new Map(); + topics.forEach(topic => { + topicsLookup.set(topic.slug, topic); + topic.aliases?.forEach(alias => topicsLookup.set(alias, topic)); + }); - const tagsByCategory: { [category: string]: TagInfo[] } = {}; - const tagsWithoutCategory = new Set(); - const categorizedTags = new Set(); + // Build the main topics map + const processedTopics = new Map(); - // Create a map for faster category lookup - const categoryMap = new Map(); + // Process all transcripts + transcripts.forEach(transcript => { + transcript.tags?.forEach(tag => { + const slug = createSlug(tag); + const topic = topicsLookup.get(slug); - categories.forEach((cat) => { - cat.categories.forEach((category) => { - if (!tagsByCategory[category]) { - tagsByCategory[category] = []; + if (!processedTopics.has(slug)) { + processedTopics.set(slug, { + name: topic?.title || tag, + slug, + count: 1, + categories: topic?.categories || ["Miscellaneous"], + }); + } else { + const processed = processedTopics.get(slug)!; + processed.count += 1; } }); - categoryMap.set(createSlug(cat.slug), cat); - cat.aliases?.forEach((alias) => categoryMap.set(alias, cat)); }); - // Process all tags at once - const allTags = new Set(transcripts.flatMap((transcript) => transcript.tags?.map((tag) => tag) || [])); - - allTags.forEach((tag) => { - const catInfo = categoryMap.get(tag); - if (catInfo) { - catInfo.categories.forEach((category) => { - if (!tagsByCategory[category].some((t) => t.slug === tag)) { - tagsByCategory[category].push({ - name: catInfo.title, - slug: tag, - count: tagCounts[tag] || 0, - }); - } - }); - categorizedTags.add(tag); - } else { - tagsWithoutCategory.add(tag); - } - }); + return processedTopics; +} - // Add "Miscellaneous" category with remaining uncategorized tags - if (tagsWithoutCategory.size > 0) { - tagsByCategory["Miscellaneous"] = Array.from(tagsWithoutCategory).map((tag) => ({ - name: tag, - slug: tag, - count: tagCounts[tag] || 0, - })); +function generateAlphabeticalList(processedTopics: Map): TopicsData[] { + const result: TopicsData[] = []; + // The categories property is not needed for this list, so we drop it + for (const { name, slug, count } of processedTopics.values()) { + result.push({ name, slug, count }); } + return result.sort((a, b) => a.name.localeCompare(b.name)); +} + +function generateCategorizedList(processedTopics: Map): Record { + const categorizedTopics: Record = {}; + + Array.from(processedTopics.values()).forEach(({ name, slug, count, categories }) => { + categories.forEach(category => { + if (!categorizedTopics[category]) { + categorizedTopics[category] = []; + } + + // Check if topic name contains category name and ends with "(Miscellaneous)" + const modifiedName = name.includes(category) && name.endsWith("(Miscellaneous)") + ? "Miscellaneous" + : name; + + categorizedTopics[category].push({ name: modifiedName, slug, count }); + }); + }); - // Sort tags alphabetically within each category - Object.keys(tagsByCategory).forEach((category) => { - tagsByCategory[category].sort((a, b) => a.name.localeCompare(b.name)); + // Sort topics within each category + Object.values(categorizedTopics).forEach(topics => { + topics.sort((a, b) => { + if (a.name == "Miscellaneous") return 1; + if (b.name == "Miscellaneous") return -1; + return a.name.localeCompare(b.name) + }); }); - fs.writeFileSync("./public/tag-data.json", JSON.stringify(tagsByCategory)); - return { tagsByCategory, tagsWithoutCategory }; + return categorizedTopics; } -function organizeTopics(transcripts: ContentTranscriptType[]) { - const slugTopics: any = {}; - const topicsArray: TopicsData[] = []; +function generateTopicsCounts(transcripts: ContentTranscriptType[]) { + // Get topics + const topics = getTopics(); - transcripts.forEach((transcript) => { - const slugTags = transcript.tags?.map((tag) => ({ - slug: createSlug(tag), - name: tag, - })); + // Build the primary data structure + const processedTopics = buildTopicsMap(transcripts, topics); - slugTags?.forEach(({ slug, name }) => { - if (slugTopics[slug] !== undefined) { - const index = slugTopics[slug]; - topicsArray[index].count += 1; - } else { - const topicsLength = topicsArray.length; - slugTopics[slug] = topicsLength; - topicsArray[topicsLength] = { - slug, - name, - count: 1, - }; - } - }); - }); + // Generate both output formats + const alphabeticalList = generateAlphabeticalList(processedTopics); + const categorizedList = generateCategorizedList(processedTopics); - fs.writeFileSync("./public/topics-data.json", JSON.stringify(topicsArray)); + // Write output files + fs.writeFileSync( + "./public/topics-counts.json", + JSON.stringify(alphabeticalList, null, 2) + ); + + fs.writeFileSync( + "./public/topics-by-category-counts.json", + JSON.stringify(categorizedList, null, 2) + ); } function createSpeakers(transcripts: ContentTranscriptType[]) { @@ -190,7 +194,10 @@ function createSpeakers(transcripts: ContentTranscriptType[]) { fs.writeFileSync("./public/speaker-data.json", JSON.stringify(speakerArray)); } -function generateSourcesCount(transcripts: ContentTranscriptType[], sources: ContentSourceType[]) { +function generateSourcesCount( + transcripts: ContentTranscriptType[], + sources: ContentSourceType[] +) { const sourcesArray: TagInfo[] = []; const slugSources: Record = {}; @@ -204,7 +211,10 @@ function generateSourcesCount(transcripts: ContentTranscriptType[], sources: Con slugSources[slug] = sourcesLength; const getSourceName = (slug: string) => - sources.find((source) => source.language === "en" && source.slugAsParams[0] === slug)?.title ?? unsluggify(slug); + sources.find( + (source) => + source.language === "en" && source.slugAsParams[0] === slug + )?.title ?? unsluggify(slug); sourcesArray[sourcesLength] = { slug, @@ -214,12 +224,21 @@ function generateSourcesCount(transcripts: ContentTranscriptType[], sources: Con } }); - fs.writeFileSync("./public/source-count-data.json", JSON.stringify(sourcesArray)); + fs.writeFileSync( + "./public/source-count-data.json", + JSON.stringify(sourcesArray) + ); return { sourcesArray, slugSources }; } -const createTypesCount = (transcripts: ContentTranscriptType[], sources: ContentSourceType[]) => { - const { sourcesArray, slugSources } = generateSourcesCount(transcripts, sources); +const createTypesCount = ( + transcripts: ContentTranscriptType[], + sources: ContentSourceType[] +) => { + const { sourcesArray, slugSources } = generateSourcesCount( + transcripts, + sources + ); const nestedTypes: any = {}; sources.forEach((transcript) => { @@ -234,7 +253,8 @@ const createTypesCount = (transcripts: ContentTranscriptType[], sources: Content if (!nestedTypes[slugType]) { nestedTypes[slugType] = []; } else { - if (nestedTypes[slugType].includes(getSource) || getSource === null) return; + if (nestedTypes[slugType].includes(getSource) || getSource === null) + return; nestedTypes[slugType].push(getSource); } }); @@ -244,40 +264,75 @@ const createTypesCount = (transcripts: ContentTranscriptType[], sources: Content fs.writeFileSync("./public/types-data.json", JSON.stringify(nestedTypes)); }; -function organizeContent(transcripts: ContentTranscriptType[]) { - const tree: ContentTree = {}; - - transcripts.forEach((transcript) => { - const parts = transcript.slugAsParams; - let current = tree; - - const isNonEnglishDir = /\w+\.[a-z]{2}\b/.test(parts[parts.length - 1]); - if (isNonEnglishDir) return; - - for (let i = 0; i < parts.length - 1; i++) { - if (!current[parts[i]]) { - current[parts[i]] = i === parts.length - 2 ? [] : {}; - } - current = current[parts[i]] as ContentTree; +function organizeContent( + transcripts: ContentTranscriptType[], + sources: ContentSourceType[] +) { + const tree: any = {}; + + sources.forEach((source) => { + const { + _id, + slugAsParams, + language, + _raw, + weight, + body, + hosts, + transcription_coverage, + url, + type, + types, + ...metaData + } = source; + const params = source.slugAsParams; + const topParam = params[0] as string; + const nestedSource = params.length > 1; + + if (!tree[topParam]) { + tree[topParam] = {}; + } + const allTranscriptsForSourceLanguage = transcripts.filter( + (transcript) => + transcript._raw.sourceFileDir === source._raw.sourceFileDir && + transcript.language === language + ); + + const allTranscriptsForSourceLanguageURLs = + allTranscriptsForSourceLanguage.map((transcript) => transcript.url); + + if (!nestedSource) { + tree[topParam] = { + ...tree[topParam], + [language]: { + data: allTranscriptsForSourceLanguageURLs.length + ? allTranscriptsForSourceLanguageURLs + : {}, + metadata: { + ...metaData, + }, + }, + }; + } else { + tree[topParam][language].data = { + ...tree[topParam][language].data, + [params[1]]: { + data: allTranscriptsForSourceLanguageURLs.length + ? allTranscriptsForSourceLanguageURLs + : {}, + metadata: { + ...metaData, + }, + }, + }; } - - (current as unknown as any[]).push({ - title: transcript.title, - speakers: transcript.speakers, - date: transcript.date, - tags: transcript.tags, - sourceFilePath: transcript._raw.sourceFilePath, - flattenedPath: transcript._raw.flattenedPath, - summary: transcript.summary, - body: createText(transcript.body), - source: transcript.source, - }); }); - // Save the result as JSON fs.writeFileSync("./public/sources-data.json", JSON.stringify(tree, null, 2)); } +const getLanCode = /[.]\w{2}$/gi // Removes the last two characters if there's a dot + export const Transcript = defineDocumentType(() => ({ name: "Transcript", filePathPattern: `**/*.md`, @@ -312,9 +367,46 @@ export const Transcript = defineDocumentType(() => ({ type: "string", resolve: (doc) => `/${doc._raw.flattenedPath}`, }, + language: { + type: "string", + resolve: (doc) => { + const transcript = doc._raw.flattenedPath.split("/").pop(); + const lan = transcript?.match(getLanCode); + const languageCode = (lan?.[lan.length - 1] || "").replace(".", ""); + const finalLanguage = LanguageCodes.includes(languageCode) + ? languageCode + : "en"; + return finalLanguage; + }, + }, + languageURL: { + type: "string", + resolve: (doc) => { + const transcript = doc._raw.flattenedPath.split("/").pop(); + const fullPathWithoutDot = doc._raw.flattenedPath.replace( + getLanCode, + "" + ); + + const lan = transcript?.match(getLanCode); + const languageCode = (lan?.[0] || "").replace(".", "") + + if (LanguageCodes.includes(languageCode)) { + return `/${languageCode}/${fullPathWithoutDot}`; + } + + return `/${fullPathWithoutDot}`; + }, + }, slugAsParams: { type: "list", - resolve: (doc) => doc._raw.flattenedPath.split("/"), + resolve: (doc) => { + const pathWithoutDot = doc._raw.flattenedPath.replace( + getLanCode, + "" + ); + return pathWithoutDot.split("/"); + }, }, }, })); @@ -336,13 +428,15 @@ export const Source = defineDocumentType(() => ({ computedFields: { url: { type: "string", - resolve: (doc) => `/${doc._raw.flattenedPath.split("/").slice(0, -1).join("/")}`, + resolve: (doc) => + `/${doc._raw.flattenedPath.split("/").slice(0, -1).join("/")}`, }, language: { type: "string", resolve: (doc) => { const index = doc._raw.flattenedPath.split("/").pop(); - const lan = index?.split(".").length === 2 ? index?.split(".")[1] : "en"; + const lan = + index?.split(".").length === 2 ? index?.split(".")[1] : "en"; return lan; }, }, @@ -364,16 +458,14 @@ export default makeSource({ "STYLE.md", "twitter_handles.json", ".json", - "2018-08-17-richard-bondi-bitcoin-cli-regtest.es.md", ], onSuccess: async (importData) => { const { allTranscripts, allSources } = await importData(); - organizeTags(allTranscripts); + generateTopicsCounts(allTranscripts); createTypesCount(allTranscripts, allSources); - organizeTopics(allTranscripts); getTranscriptAliases(allTranscripts); createSpeakers(allTranscripts); generateSourcesCount(allTranscripts, allSources); - organizeContent(allTranscripts); + organizeContent(allTranscripts, allSources); }, }); diff --git a/next.config.mjs b/next.config.mjs index f3b54d8..dc09efd 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,24 +5,36 @@ const nextConfig = { return { fallback: [ { - source: "/:path*.:ext([^/]+)", // intercept all paths ending with a file extension - destination: "/gh-pages/:path*.:ext", // rewrite to gh-pages/[path_here].ext + source: "/:path*.:ext([a-zA-Z0-9_+]{1,4})", // Match extensions that are 1-4 AlphaNumeric characters long + destination: "/gh-pages/:path*.:ext", // Rewrite to gh-pages/[path_here].ext }, { - source: "/transcripts", - destination: "/gh-pages/index.html", + source: "/tags/:path", + destination: "/gh-pages/tags/:path/index.html", }, { - source: "/types", - destination: "/gh-pages/categories/index.html", + source: "/speakers/:path", + destination: "/gh-pages/speakers/:path/index.html", }, { - source: "/:path*", - destination: "/gh-pages/:path*/index.html", + source: "/es", + destination: "/gh-pages/es/index.html", + }, + { + source: "/zh", + destination: "/gh-pages/zh/index.html", + }, + { + source: "/pt", + destination: "/gh-pages/pt/index.html", + }, + { + source: "/:path((?!.*\\.[a-zA-Z0-9]{1,4}$).*)", // Matches paths without a valid file extension + destination: "/transcript/:path*", // Rewrite to /transcripts/[path...] }, { - source: "/sources/:path((?!.*\\.[^/]+).*)", // Matches /source/[any path without a file extension] - destination: "/[...slug]/:path*", // Replace with your catch-all route + source: "/:path*", + destination: "/gh-pages/:path*/index.html", }, ], }; diff --git a/package-lock.json b/package-lock.json index ff75363..78c4c75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "name": "registry", "version": "0.1.0", "dependencies": { - "@bitcoin-dev-project/bdp-ui": "^1.3.0", + "@bitcoin-dev-project/bdp-ui": "^1.5.1", + "@uiw/react-markdown-preview": "^5.1.3", "contentlayer2": "^0.4.6", + "date-fns": "^4.1.0", "next": "14.2.4", "next-contentlayer2": "^0.4.6", "react": "^18", @@ -50,9 +52,9 @@ } }, "node_modules/@bitcoin-dev-project/bdp-ui": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@bitcoin-dev-project/bdp-ui/-/bdp-ui-1.3.0.tgz", - "integrity": "sha512-wRmnejA6S8AJRGPiVNWI+Eg6PFjLBWKDK6Xg+vrAW/RA7qv/lMQNhOw7KOLt1zHejTBG1FEgfti6Oow4n8qxVQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@bitcoin-dev-project/bdp-ui/-/bdp-ui-1.5.1.tgz", + "integrity": "sha512-bT3MnRJKp918pPHL8aC+ZbrH8Zwon12EWgeToSsoS7nrva+45OR9Qm/C4TDGP96nXBReuMMhAyyLGjtlLBnxeQ==", "dependencies": { "clsx": "^2.1.1", "tailwind-merge": "^2.5.2" @@ -1566,17 +1568,20 @@ "undici-types": "~6.19.2" } }, + "node_modules/@types/prismjs": { + "version": "1.26.5", + "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", + "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==" + }, "node_modules/@types/prop-types": { "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", - "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==" }, "node_modules/@types/react": { "version": "18.3.9", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.9.tgz", "integrity": "sha512-+BpAVyTpJkNWWSSnaLBk6ePpHLOGJKnEQNbINNovPWzvEUyAe3e+/d494QdEh71RekM/qV7lw6jzf1HGrJyAtQ==", - "dev": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -1601,6 +1606,41 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" }, + "node_modules/@uiw/copy-to-clipboard": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.17.tgz", + "integrity": "sha512-O2GUHV90Iw2VrSLVLK0OmNIMdZ5fgEg4NhvtwINsX+eZ/Wf6DWD0TdsK9xwV7dNRnK/UI2mQtl0a2/kRgm1m1A==", + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/@uiw/react-markdown-preview": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@uiw/react-markdown-preview/-/react-markdown-preview-5.1.3.tgz", + "integrity": "sha512-jV02wO4XHWFk54kz7sLqOkdPgJLttSfKLyen47XgjcyGgQXU2I4WJBygmdpV2AT9m/MiQ8qrN1Y+E5Syv9ZDpw==", + "dependencies": { + "@babel/runtime": "^7.17.2", + "@uiw/copy-to-clipboard": "~1.0.12", + "react-markdown": "~9.0.1", + "rehype-attr": "~3.0.1", + "rehype-autolink-headings": "~7.1.0", + "rehype-ignore": "^2.0.0", + "rehype-prism-plus": "2.0.0", + "rehype-raw": "^7.0.0", + "rehype-rewrite": "~4.0.0", + "rehype-slug": "~6.0.0", + "remark-gfm": "~4.0.0", + "remark-github-blockquote-alert": "^1.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -1709,6 +1749,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -1720,6 +1769,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + }, "node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -2062,6 +2116,21 @@ "node": ">= 8" } }, + "node_modules/css-selector-parser": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", + "integrity": "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ] + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2077,8 +2146,16 @@ "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", - "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } }, "node_modules/debug": { "version": "4.3.7", @@ -2134,6 +2211,18 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, + "node_modules/direction": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/direction/-/direction-2.0.1.tgz", + "integrity": "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==", + "bin": { + "direction": "cli.js" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -2152,6 +2241,17 @@ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/esbuild": { "version": "0.20.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", @@ -2417,6 +2517,11 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/github-slugger": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" + }, "node_modules/glob": { "version": "10.4.5", "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", @@ -2491,6 +2596,181 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", + "integrity": "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==", + "dependencies": { + "@types/hast": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/hast-util-raw": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-select": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/hast-util-select/-/hast-util-select-6.0.3.tgz", + "integrity": "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "bcp-47-match": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "css-selector-parser": "^3.0.0", + "devlop": "^1.0.0", + "direction": "^2.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "nth-check": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-estree": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.0.tgz", @@ -2579,6 +2859,36 @@ "inline-style-parser": "0.2.4" } }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -2591,6 +2901,44 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-7.2.0.tgz", + "integrity": "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^3.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/hastscript/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-void-elements": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", @@ -2891,6 +3239,41 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mdast-util-from-markdown": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.1.tgz", @@ -2942,15 +3325,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mdast-util-mdx": { + "node_modules/mdast-util-gfm": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz", + "integrity": "sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==", "dependencies": { "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" }, "funding": { @@ -2958,17 +3343,110 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-mdx-expression": { + "node_modules/mdast-util-gfm-autolink-literal": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", - "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz", + "integrity": "sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", + "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { "type": "opencollective", @@ -3210,6 +3688,120 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.0.tgz", @@ -3890,6 +4482,17 @@ "node": ">=0.10.0" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -3946,6 +4549,22 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, + "node_modules/parse-numeric-range": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", + "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==" + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/pascal-case": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", @@ -4272,6 +4891,31 @@ } } }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -4292,11 +4936,160 @@ "node": ">=8.10.0" } }, + "node_modules/refractor": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-4.8.1.tgz", + "integrity": "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg==", + "dependencies": { + "@types/hast": "^2.0.0", + "@types/prismjs": "^1.0.0", + "hastscript": "^7.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "dependencies": { + "@types/unist": "^2" + } + }, + "node_modules/refractor/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, + "node_modules/rehype-attr": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/rehype-attr/-/rehype-attr-3.0.3.tgz", + "integrity": "sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==", + "dependencies": { + "unified": "~11.0.0", + "unist-util-visit": "~5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-autolink-headings": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", + "integrity": "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-ignore": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rehype-ignore/-/rehype-ignore-2.0.2.tgz", + "integrity": "sha512-BpAT/3lU9DMJ2siYVD/dSR0A/zQgD6Fb+fxkJd4j+wDVy6TYbYpK+FZqu8eM9EuNKGvi4BJR7XTZ/+zF02Dq8w==", + "dependencies": { + "hast-util-select": "^6.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-prism-plus": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz", + "integrity": "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==", + "dependencies": { + "hast-util-to-string": "^3.0.0", + "parse-numeric-range": "^1.3.0", + "refractor": "^4.8.0", + "rehype-parse": "^9.0.0", + "unist-util-filter": "^5.0.0", + "unist-util-visit": "^5.0.0" + } + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-rewrite": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rehype-rewrite/-/rehype-rewrite-4.0.2.tgz", + "integrity": "sha512-rjLJ3z6fIV11phwCqHp/KRo8xuUCO8o9bFJCNw5o6O2wlLk6g8r323aRswdGBQwfXPFYeSuZdAjp4tzo6RGqEg==", + "dependencies": { + "hast-util-select": "^6.0.0", + "unified": "^11.0.3", + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-stringify": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.0.tgz", @@ -4326,6 +5119,37 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-gfm": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.0.tgz", + "integrity": "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-github-blockquote-alert": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/remark-github-blockquote-alert/-/remark-github-blockquote-alert-1.2.1.tgz", + "integrity": "sha512-qNf2mSAoZgh3Cl23/9Y1L7S4Kbf9NsdHvYK398ab/52yEsDPDU5I4cuTcgDRrdIX7Ltc6RK+KCLRtWkbFnL6Dg==", + "dependencies": { + "unist-util-visit": "^5.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://jaywcjlove.github.io/#/sponsor" + } + }, "node_modules/remark-mdx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.0.1.tgz", @@ -4386,6 +5210,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", @@ -4955,6 +5793,16 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-filter": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/unist-util-filter/-/unist-util-filter-5.0.1.tgz", + "integrity": "sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -5061,6 +5909,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", @@ -5074,6 +5935,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index b71d273..f4c4e17 100644 --- a/package.json +++ b/package.json @@ -4,21 +4,23 @@ "private": true, "scripts": { "dev": "next dev", - "fetch-categories": "node scripts/fetchCategories.js", + "fetch-topics": "node scripts/fetchTopics.js", "submodules:update": "git submodule update --init && git submodule update --remote", - "build": "npm run submodules:update && npm run fetch-categories && next build", + "build": "npm run submodules:update && npm run fetch-topics && next build", "start": "next start", "lint": "next lint" }, "dependencies": { - "@bitcoin-dev-project/bdp-ui": "^1.3.0", + "@bitcoin-dev-project/bdp-ui": "^1.5.1", + "@uiw/react-markdown-preview": "^5.1.3", "contentlayer2": "^0.4.6", + "date-fns": "^4.1.0", "next": "14.2.4", "next-contentlayer2": "^0.4.6", "react": "^18", "react-dom": "^18", - "tailwind-merge": "^2.5.2", - "react-intersection-observer": "^9.13.1" + "react-intersection-observer": "^9.13.1", + "tailwind-merge": "^2.5.2" }, "devDependencies": { "@types/node": "^20", diff --git a/public/svgs/eye-close-icon.svg b/public/svgs/eye-close-icon.svg new file mode 100644 index 0000000..3047625 --- /dev/null +++ b/public/svgs/eye-close-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/svgs/eye-open-icon.svg b/public/svgs/eye-open-icon.svg new file mode 100644 index 0000000..bd4c88a --- /dev/null +++ b/public/svgs/eye-open-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/public/svgs/pencil-icon.svg b/public/svgs/pencil-icon.svg new file mode 100644 index 0000000..e665aae --- /dev/null +++ b/public/svgs/pencil-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/scripts/fetchCategories.js b/scripts/fetchTopics.js similarity index 50% rename from scripts/fetchCategories.js rename to scripts/fetchTopics.js index 34edf27..259e9a5 100644 --- a/scripts/fetchCategories.js +++ b/scripts/fetchTopics.js @@ -2,8 +2,8 @@ const fs = require("fs"); const path = require("path"); const https = require("https"); -const url = "https://bitcoinops.org/topics.json"; -const outputPath = path.join(__dirname, "..", "public", "categories.json"); +const url = "https://raw.githubusercontent.com/bitcoinsearch/topics-index/refs/heads/main/topics.json"; +const outputPath = path.join(__dirname, "..", "public", "topics.json"); https .get(url, (res) => { @@ -15,9 +15,9 @@ https res.on("end", () => { fs.writeFileSync(outputPath, data); - console.log("Categories data has been fetched and saved to public folder."); + console.log("Topics data has been fetched and saved to public folder."); }); }) .on("error", (err) => { - console.error("Error fetching categories:", err.message); + console.error("Error fetching topics:", err.message); }); diff --git a/src/app/(explore)/[...slug]/page.tsx b/src/app/(explore)/[...slug]/page.tsx index 90ed7ef..805678c 100644 --- a/src/app/(explore)/[...slug]/page.tsx +++ b/src/app/(explore)/[...slug]/page.tsx @@ -6,75 +6,86 @@ import { ContentTreeArray } from "@/utils/data"; import LinkIcon from "/public/svgs/link-icon.svg"; import WorldIcon from "/public/svgs/world-icon.svg"; import allSources from "@/public/sources-data.json"; -import { ContentTree, filterOutIndexes } from "@/utils"; -import BreadCrumbs from "@/components/common/BreadCrumbs"; +import { constructSlugPaths, fetchTranscriptDetails, loopArrOrObject } from "@/utils"; import { ArrowLinkRight } from "@bitcoin-dev-project/bdp-ui/icons"; -import { allSources as allContentSources } from "contentlayer/generated"; +import { allSources as allContentSources, allTranscripts } from "contentlayer/generated"; import TranscriptDetailsCard from "@/components/common/TranscriptDetailsCard"; +import { SourcesBreadCrumbs } from "@/components/explore/SourcesBreadCrumbs"; // forces 404 for paths not generated from `generateStaticParams` function. export const dynamicParams = false; export function generateStaticParams() { - return allContentSources.map(({ slugAsParams }) => ({ slug: slugAsParams })); + const allSlugs = allContentSources.map(({ language, slugAsParams }) => { + const isEnglish = language === "en"; + if (isEnglish) { + return { + slug: slugAsParams, + }; + } + return { + slug: [language, ...slugAsParams], + }; + }); + + return allSlugs; } const page = ({ params }: { params: { slug: string[] } }) => { const slug = params.slug ?? []; const contentTree = allSources; - let current: any = contentTree; + const { slugPaths } = constructSlugPaths(slug); - for (const part of slug) { + for (const part of slugPaths) { if (typeof current === "object" && !Array.isArray(current) && part in current) { - current = current[part] as ContentTree | ContentTreeArray[]; + current = current[part]; } else { notFound(); } } - const displayCurrent = filterOutIndexes(current); - - const pageDetails = allContentSources.find((source) => { - return source.slugAsParams.join("/") === slug.join("/") && source.language === "en"; - }); - - const isDirectoryList = Array.isArray(current); + const metadata = current.metadata; + const data = loopArrOrObject(current?.data); + const isRoot = Array.isArray(current.data); + const { transcripts } = fetchTranscriptDetails(allTranscripts, data, isRoot); return (
- + <> + +

Back

-

{pageDetails?.title ?? slug[slug.length - 1]}

- {isDirectoryList && pageDetails?.website ? ( +

{metadata?.title ?? slug[slug.length - 1]}

+ {isRoot && metadata?.website ? (
world icon - {pageDetails.website ?? ""} + {metadata.website ?? ""}
) : null} - {isDirectoryList && pageDetails?.additional_resources ? ( + {isRoot && metadata?.additional_resources ? (
link icon
- {pageDetails.additional_resources.map((resource, index) => ( + {metadata.additional_resources.map((resource: any, index: number) => ( {
- {isDirectoryList ? ( + {isRoot ? (
- {(displayCurrent as ContentTreeArray[]) - .sort((a, b) => new Date(b.date!).getTime() - new Date(a.date!).getTime() || a.title.localeCompare(b.title)) - .map((item, i) => ( - - ))} + {(transcripts as ContentTreeArray[]).map((item, i) => ( + + ))}
) : (
-
- {(displayCurrent as string[]).map((key, i) => ( +
+ {(data as any[]).map((value, i) => ( - {key} + {value.title} + {value.count} ))}
diff --git a/src/app/(explore)/categories/page.tsx b/src/app/(explore)/categories/page.tsx index 7e07944..d9bbd67 100644 --- a/src/app/(explore)/categories/page.tsx +++ b/src/app/(explore)/categories/page.tsx @@ -1,6 +1,6 @@ import React from "react"; import TranscriptContentPage from "@/components/explore/TranscriptContentPage"; -import allCategoriesTopic from "@/public/tag-data.json"; +import allCategoriesTopic from "@/public/topics-by-category-counts.json"; const CategoriesPage = () => { diff --git a/src/app/(explore)/topics/page.tsx b/src/app/(explore)/topics/page.tsx index 3422e15..d807f83 100644 --- a/src/app/(explore)/topics/page.tsx +++ b/src/app/(explore)/topics/page.tsx @@ -1,9 +1,8 @@ import React from "react"; import TranscriptContentPage from "@/components/explore/TranscriptContentPage"; -import allTopics from "@/public/topics-data.json"; +import allTopics from "@/public/topics-counts.json"; const TopicsPage = () => { - return (
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 3b8a60c..f20c365 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import "@bitcoin-dev-project/bdp-ui/styles.css" import "./globals.css"; import Script from "next/script"; import Header from "@/components/layout/Header"; +import BossBanner from "@/components/banner/BossBanner"; const manrope = Manrope({ subsets: ["latin"], display: "swap" }); @@ -24,6 +25,7 @@ export default function RootLayout({
+ {children} diff --git a/src/app/transcript/[...slug]/page.tsx b/src/app/transcript/[...slug]/page.tsx new file mode 100644 index 0000000..4cdf66b --- /dev/null +++ b/src/app/transcript/[...slug]/page.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { allTranscripts } from "contentlayer/generated"; +import allSources from "@/public/sources-data.json"; +import { notFound } from "next/navigation"; +import IndividualTranscript from "@/components/individual-transcript/IndividualTranscript"; +import { createSlug } from "@/utils"; +import { BaseCrumbType } from "@/components/common/BaseCrumbLists"; + +// forces 404 for paths not generated from `generateStaticParams` function. +export const dynamicParams = false; + +export function generateStaticParams() { + const allSingleTranscriptPaths = allTranscripts.map((transcript) => { + const slugForLanguage = transcript.languageURL + .split("/") + .filter((path) => Boolean(path.trim())); + return { + slug: slugForLanguage, + }; + }); + return allSingleTranscriptPaths; +} + +const Page = ({ params }: { params: { slug: string[] } }) => { + const slugArray = params.slug; + let transcriptUrl = `/${slugArray.join("/")}`; + + const transcript = allTranscripts.find( + (transcript) => transcript.languageURL === transcriptUrl + ); + + if (!transcript) { + return notFound(); + } + + let sourcesData: any = allSources; + + // This function returns an array of breadcrumb which we use to display navigation + const breadcrumbRoutes:BaseCrumbType[] = transcript.slugAsParams.map( + (crumb: string, index: number) => { + let title = ""; + const languageNumber = transcript.slugAsParams.length - (index + 1); + const link = transcript.languageURL + .split("/") + .slice(0, languageNumber === 0 ? undefined : -languageNumber) + .join("/"); + + // Needed to update the object when we go down the tree + sourcesData = sourcesData[crumb as keyof typeof allSources]; + // Checks if the index is 0 so we can update the title and the data to the new sourcesData + if (index === 0) { + title = sourcesData[transcript.language]?.metadata.title; + sourcesData = sourcesData[transcript.language]?.data; + } + // Checks if it's the last item in the map and updates the title to the transcripts title + else if (index === transcript.slugAsParams.length - 1) { + title = transcript.title; + } + // if it's neither the last or the first update the title and sourcesData + else { + title = sourcesData?.metadata.title; + sourcesData = sourcesData?.data; + } + + return { + name: title, + link, + isActive: index === transcript.slugAsParams.length - 1, + }; + } + ); + + return ( + + ); +}; + +export default Page; diff --git a/src/components/banner/BossBanner.tsx b/src/components/banner/BossBanner.tsx new file mode 100644 index 0000000..b3256a5 --- /dev/null +++ b/src/components/banner/BossBanner.tsx @@ -0,0 +1,22 @@ +"use client"; + +import { Banner } from "@bitcoin-dev-project/bdp-ui"; +import { usePathname } from "next/navigation"; + + +const BossBanner = () => { + const pathname = usePathname() + const isHome = pathname === "/" + return ( +
+ +
+ ); +}; + +export default BossBanner; \ No newline at end of file diff --git a/src/components/common/BaseCrumbLists.tsx b/src/components/common/BaseCrumbLists.tsx new file mode 100644 index 0000000..35b235b --- /dev/null +++ b/src/components/common/BaseCrumbLists.tsx @@ -0,0 +1,33 @@ +import Link from "next/link"; + +export type BaseCrumbType = { + name: string; + link: string; + isActive: boolean; +}; + +const BaseCrumbLists = ({crumbsArray}:{crumbsArray:BaseCrumbType[]}) => { + return ( +
+ {crumbsArray.map((link, i) => ( +
+ + {link.name} + + {i !== crumbsArray.length - 1 && ( +

/

+ )} +
+ ))} +
+ ); +}; + +export default BaseCrumbLists; diff --git a/src/components/common/BreadCrumbs.tsx b/src/components/common/BreadCrumbs.tsx deleted file mode 100644 index cd0645c..0000000 --- a/src/components/common/BreadCrumbs.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { usePathname } from "next/navigation"; -import React from "react"; -import { ExploreNavigationItems } from "@/utils/data"; - -const BreadCrumbs = () => { - const pathname = usePathname(); - - const navListWithoutSources = ExploreNavigationItems.filter((item) => item.href !== "/sources").map((item) => item.href.slice(1)); - - const pathnameArray = pathname.split("/"); - const isNotSourcesPage = navListWithoutSources.includes(pathnameArray[1]); - - const allRoutes = pathnameArray.map((path, idx) => { - const route = pathname - .split("/") - .slice(0, idx + 1) - .join("/"); - return { name: path || "home", link: route || "/" }; - }); - - if (!isNotSourcesPage && pathnameArray[1] !== "sources") { - allRoutes.splice(1, 0, { name: "Sources", link: "/sources" }); - } - - const isActive = allRoutes[allRoutes.length - 1]; - - return ( -
- {allRoutes.map((link, i) => ( -
- - {link.name} - - {i !== allRoutes.length - 1 &&

/

} -
- ))} -
- ); -}; - -export default BreadCrumbs; diff --git a/src/components/common/Pill.tsx b/src/components/common/Pill.tsx new file mode 100644 index 0000000..64d3960 --- /dev/null +++ b/src/components/common/Pill.tsx @@ -0,0 +1,15 @@ +import Link from "next/link"; + +const Pill = ({ name, slug }: { name: string; slug: string }) => { + return ( + + {name} + + ); +}; + +export default Pill; diff --git a/src/components/common/Tabs.tsx b/src/components/common/Tabs.tsx new file mode 100644 index 0000000..812abe6 --- /dev/null +++ b/src/components/common/Tabs.tsx @@ -0,0 +1,132 @@ +"use client"; + +import { Resources } from "contentlayer/generated"; +import React, { SetStateAction, useState } from "react"; +import TranscriptTabContent from "../individual-transcript/TranscriptTabContent"; +import ContentGrouping from "../explore/ContentGrouping"; +import { ContentData, GroupedData, TopicsData } from "@/utils"; + +const Tabs = ({ + summary, + markdown, + extraInfo, + currentHeading, + groupedHeading, + setCurrentHeading, +}: { + summary?: string; + markdown: string; + extraInfo?: Resources[]; + currentHeading?: string; + groupedHeading?: Record; + setCurrentHeading?: React.Dispatch>; +}) => { + const [openTabs, setOpenTabs] = useState< + "transcript" | "summary" | "extraInfo" + >("transcript"); + + return ( +
+
+ setOpenTabs("transcript")} + /> + {summary && ( + setOpenTabs("summary")} + /> + )} + + {extraInfo && ( + setOpenTabs("extraInfo")} + /> + )} +
+ + {/* This is needed since the layout for the mobile design changes and goes under the tabs */} + {Object.keys({ ...groupedHeading }).length > 0 && ( +
+ +
+ )} + +
+ {openTabs === "transcript" && ( +
+
+ +
+
+ )} + {openTabs === "summary" && ( +
+

{summary}

+
+ )} + {openTabs === "extraInfo" && ( +
+ {extraInfo?.map((info) => ( +

+ {" "} + {info.title}: + + {info.url} + +

+ ))} +
+ )} +
+
+ ); +}; + +const Tab = ({ + title, + isOpen, + onClick, +}: { + title: string; + isOpen: boolean; + onClick: () => void; +}) => { + return ( + + ); +}; + +export default Tabs; diff --git a/src/components/common/TranscriptDetailsCard.tsx b/src/components/common/TranscriptDetailsCard.tsx index e11d10a..89b3669 100644 --- a/src/components/common/TranscriptDetailsCard.tsx +++ b/src/components/common/TranscriptDetailsCard.tsx @@ -6,47 +6,29 @@ import DateIcon from "/public/svgs/date-icon.svg"; import TagsIcon from "/public/svgs/tags-icon.svg"; import { createSlug, formatDate, unsluggify } from "@/utils"; import { MicIcon } from "@bitcoin-dev-project/bdp-ui/icons"; +import Pill from "./Pill"; const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: string[] }) => { - const { speakers, tags, summary, date, title, body, flattenedPath: url } = data; + const { speakers, tags, summary, date, title, body, languageURL } = data; const calculateRemaining = (data: string[]) => (data?.length && data.length > 3 ? data.length - 3 : 0); return (
-
-
-
- {slug - .join(" / ") - .split(" ") - .map((slg, i) => ( -

- {unsluggify(slg)} -

- ))} -
- - {date && ( -
- date icon -

{formatDate(date!)}

-
- )} -
- +
{title} + {date && ( +
+ date icon +

{formatDate(date!)}

+
+ )}
@@ -60,13 +42,7 @@ const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: s
{speakers.slice(0, 3).map((speaker, idx) => ( - - {speaker} - + ))} {calculateRemaining(speakers) === 0 ? null : ( @@ -89,13 +65,7 @@ const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: s
{tags.slice(0, 3).map((tag, idx) => ( - - {unsluggify(tag)} - + ))} {calculateRemaining(tags) === 0 ? null : ( diff --git a/src/components/common/TranscriptMetadataCard.tsx b/src/components/common/TranscriptMetadataCard.tsx new file mode 100644 index 0000000..b4792e0 --- /dev/null +++ b/src/components/common/TranscriptMetadataCard.tsx @@ -0,0 +1,194 @@ +"use client"; + +import React, { useState } from "react"; +import Image from "next/image"; +import { + BookmarkIcon, + CalendarIcon, + MicIcon, +} from "@bitcoin-dev-project/bdp-ui/icons"; +import Link from "next/link"; +import { createSlug } from "@/utils"; +import AiGeneratedIcon from "../svgs/AIGeneratedIcon"; +import { format, isDate } from "date-fns"; +import Pill from "./Pill"; + +interface ITranscriptMetadataComponent { + title: string; + date: string | Date; + topics: string[]; + speakers: string[] | null; + transcriptBy: string | string[]; +} + +const TranscriptMetadataComponent = ({ + title, + speakers, + topics, + date, + transcriptBy, +}: ITranscriptMetadataComponent) => { + const [showDetail, setShowDetail] = useState(true); + const isAiGenerated = transcriptBy.includes("needs-review") ? true : false; + const handleShowDetail = () => { + setShowDetail((prev) => !prev); + }; + + const convertedDate = date ? new Date(date) : false + + const formattedDate = isDate(convertedDate) ? format(convertedDate, "d MMMM, yyyy") : ""; + + return ( +
+
+

+ {title} +

+ +
+ {/* Depends on the Show and Hide Button */} + {showDetail && ( +
+ + +

Date

+ + } + footer={ +
+ {formattedDate ? +

+ {formattedDate} +

: +

Not available

+ } +
+ } + /> + {/* render only 3 tags*/} + + +

Topics

+ + } + footer={ +
+ {(topics && topics.length > 0) ? + topics.map((topic) => ( + + )): +

Not available

+ } +
+ } + /> + + + +

Speakers

+ + } + footer={ +
+ {speakers && speakers.length > 0 ? + speakers.map((speaker) => ( + + )): +

Not available

+ } +
+ } + /> + + + pencil icon +

+ Transcript by +

+ + } + footer={ +
+ {isAiGenerated ? ( + <> + + + + AI Generated (Review for sats) + + + {" "} + + ) : ( +

+ {transcriptBy} +

+ )} +
+ } + /> +
+ )} +
+ ); +}; + +const MetadataBlock = ({ + header, + footer, +}: { + header: React.ReactNode; + footer: React.ReactNode; +}) => { + return ( +
+
{header}
+
{footer}
+
+ ); +}; + +export default TranscriptMetadataComponent; diff --git a/src/components/explore/ContentGrouping.tsx b/src/components/explore/ContentGrouping.tsx index 09d0a76..85bcce9 100644 --- a/src/components/explore/ContentGrouping.tsx +++ b/src/components/explore/ContentGrouping.tsx @@ -1,18 +1,21 @@ -"use client" +"use client"; -import { createSlug, GroupedData } from "@/utils"; +import { createContentSlug, createSlug, GroupedData } from "@/utils"; import Link from "next/link"; -import { useEffect, useRef } from "react"; +import { SetStateAction, useEffect, useRef } from "react"; +import { twMerge } from "tailwind-merge"; export interface IContentGrouping { currentGroup: string; groupedData: GroupedData | never[]; screen?: "mobile" | "desktop"; + className?: string; } const ContentGrouping = ({ currentGroup, groupedData, screen, + className, }: IContentGrouping) => { const selectRef = useRef(null); const linkRef = useRef(null); @@ -23,11 +26,11 @@ const ContentGrouping = ({ linkRef.current.click(); } }; - + useEffect(() => { if (currentGroup) { if (selectRef.current) { - selectRef.current.value = createSlug(currentGroup); + selectRef.current.value = `${currentGroup}`; } } }, [currentGroup]); @@ -36,16 +39,17 @@ const ContentGrouping = ({ <> {screen === "desktop" && (