diff --git a/contentlayer.config.ts b/contentlayer.config.ts index 592db08..0921604 100644 --- a/contentlayer.config.ts +++ b/contentlayer.config.ts @@ -1,8 +1,16 @@ import path from "path"; import * as fs from "fs"; 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 { + 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", @@ -94,7 +102,11 @@ function organizeTags(transcripts: ContentTranscriptType[]) { }); // Process all tags at once - const allTags = new Set(transcripts.flatMap((transcript) => transcript.tags?.map((tag) => tag) || [])); + const allTags = new Set( + transcripts.flatMap( + (transcript) => transcript.tags?.map((tag) => tag) || [] + ) + ); allTags.forEach((tag) => { const catInfo = categoryMap.get(tag); @@ -116,11 +128,13 @@ function organizeTags(transcripts: ContentTranscriptType[]) { // 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, - })); + tagsByCategory["Miscellaneous"] = Array.from(tagsWithoutCategory).map( + (tag) => ({ + name: tag, + slug: tag, + count: tagCounts[tag] || 0, + }) + ); } // Sort tags alphabetically within each category @@ -190,7 +204,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 +221,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 +234,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 +263,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,11 +274,27 @@ const createTypesCount = (transcripts: ContentTranscriptType[], sources: Content fs.writeFileSync("./public/types-data.json", JSON.stringify(nestedTypes)); }; -function organizeContent(transcripts: ContentTranscriptType[], sources: ContentSourceType[]) { +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 { + _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; @@ -257,16 +303,21 @@ function organizeContent(transcripts: ContentTranscriptType[], sources: ContentS tree[topParam] = {}; } const allTranscriptsForSourceLanguage = transcripts.filter( - (transcript) => transcript._raw.sourceFileDir === source._raw.sourceFileDir && transcript.language === language + (transcript) => + transcript._raw.sourceFileDir === source._raw.sourceFileDir && + transcript.language === language ); - const allTranscriptsForSourceLanguageURLs = allTranscriptsForSourceLanguage.map((transcript) => transcript.url); + const allTranscriptsForSourceLanguageURLs = + allTranscriptsForSourceLanguage.map((transcript) => transcript.url); if (!nestedSource) { tree[topParam] = { ...tree[topParam], [language]: { - data: allTranscriptsForSourceLanguageURLs.length ? allTranscriptsForSourceLanguageURLs : {}, + data: allTranscriptsForSourceLanguageURLs.length + ? allTranscriptsForSourceLanguageURLs + : {}, metadata: { ...metaData, }, @@ -276,7 +327,9 @@ function organizeContent(transcripts: ContentTranscriptType[], sources: ContentS tree[topParam][language].data = { ...tree[topParam][language].data, [params[1]]: { - data: allTranscriptsForSourceLanguageURLs.length ? allTranscriptsForSourceLanguageURLs : {}, + data: allTranscriptsForSourceLanguageURLs.length + ? allTranscriptsForSourceLanguageURLs + : {}, metadata: { ...metaData, }, @@ -326,22 +379,42 @@ export const Transcript = defineDocumentType(() => ({ type: "string", resolve: (doc) => { const transcript = doc._raw.flattenedPath.split("/").pop(); - const lan = transcript?.split(".").length === 2 ? transcript?.split(".")[1] : "en"; - return lan; + const lan = transcript?.match(/[.]\w+/gi); + const lanWithoutDot = (lan?.[lan.length - 1] || "").replace(".", ""); + const finalLanguage = LanguageCodes.includes(lanWithoutDot) + ? lanWithoutDot + : "en"; + return finalLanguage; }, }, - languageURL:{ - type:"string", - resolve:(doc)=> { + languageURL: { + type: "string", + resolve: (doc) => { const transcript = doc._raw.flattenedPath.split("/").pop(); - const pathWithoutDot = doc._raw.flattenedPath.replace(/[.]\w+/gi,"") - const lan = transcript?.split(".").length === 2 ? `/${transcript?.split(".")[1]}` : ""; - return `${lan}/${pathWithoutDot}` - } + const fullPathWithoutDot = doc._raw.flattenedPath.replace( + /[.]\w{2}$/gi, // Removes the last two characters if there's a dot + "" + ); + + const stringAfterDot = transcript?.match(/\.(\w{2})$/gi); // Removes the last two characters if there's a dot + const languageWithoutDot = (stringAfterDot?.[1] || "") + + if (LanguageCodes.includes(languageWithoutDot)) { + return `/${languageWithoutDot}/${fullPathWithoutDot}`; + } + + return `/${fullPathWithoutDot}`; + }, }, slugAsParams: { type: "list", - resolve: (doc) => doc._raw.flattenedPath.split("/").slice(0, -1), + resolve: (doc) => { + const pathWithoutDot = doc._raw.flattenedPath.replace( + /[.]\w{2}$/gi, // Removes the last two characters if there's a dot + "" + ); + return pathWithoutDot.split("/"); + }, }, }, })); @@ -363,13 +436,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; }, }, @@ -403,4 +478,4 @@ export default makeSource({ generateSourcesCount(allTranscripts, allSources); organizeContent(allTranscripts, allSources); }, -}); \ No newline at end of file +}); diff --git a/next.config.mjs b/next.config.mjs index fce3664..dc09efd 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,8 +5,8 @@ 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: "/tags/:path", @@ -29,17 +29,9 @@ const nextConfig = { destination: "/gh-pages/pt/index.html", }, { - source: "/:path((?!.*\\.[^/]+).*)", // Matches paths without a file extension + source: "/:path((?!.*\\.[a-zA-Z0-9]{1,4}$).*)", // Matches paths without a valid file extension destination: "/transcript/:path*", // Rewrite to /transcripts/[path...] }, - { - source: "/transcripts", - destination: "/gh-pages/index.html", - }, - { - source: "/types", - destination: "/gh-pages/categories/index.html", - }, { source: "/:path*", destination: "/gh-pages/:path*/index.html", diff --git a/src/app/transcript/[...slug]/page.tsx b/src/app/transcript/[...slug]/page.tsx index 05902eb..5b72bf4 100644 --- a/src/app/transcript/[...slug]/page.tsx +++ b/src/app/transcript/[...slug]/page.tsx @@ -1,42 +1,70 @@ import React from "react"; import { allTranscripts } from "contentlayer/generated"; -import { LanguageCodes } from "@/config"; +import allSources from "@/public/sources-data.json"; import { notFound } from "next/navigation"; import IndividualTranscript from "@/components/individual-transcript/IndividualTranscript"; +import { createSlug } from "@/utils"; // 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())); + const slugForLanguage = transcript.languageURL + .split("/") + .filter((path) => Boolean(path.trim())); return { - slug: slugForLanguage - } + slug: slugForLanguage, + }; }); - return allSingleTranscriptPaths; } const Page = ({ params }: { params: { slug: string[] } }) => { const slugArray = params.slug; - const isNonEnglishLanguage = LanguageCodes.includes(slugArray[0]); - let transcriptUrl = ""; - if (isNonEnglishLanguage) { - const languageCode = slugArray.shift(); - slugArray[slugArray.length - 1] = slugArray[slugArray.length - 1] + `.${languageCode}`; - transcriptUrl = `/${slugArray.join("/")}`; - } else { - transcriptUrl = `/${slugArray.join("/")}`; - } + let transcriptUrl = `/${slugArray.join("/")}`; - const transcript = allTranscripts.find(transcript => transcript.url === transcriptUrl) + const transcript = allTranscripts.find( + (transcript) => transcript.languageURL === transcriptUrl + ); - if(!transcript) { + if (!transcript) { return notFound(); } + + let data: any = allSources; + + const breadCrumbRoutes = transcript.slugAsParams.map( + (crumb: string, index: number) => { + let title = ""; + const languageNumber = transcript.slugAsParams.length - (index + 1); + data = data[crumb as keyof typeof allSources]; + if (index === 0) { + title = data[transcript.language]?.metadata.title; + data = data[transcript.language]?.data; + } else if (index === transcript.slugAsParams.length - 1) { + title = transcript.title; + } else { + title = data?.metadata.title; + data = data?.data; + } + + return { + name: title, + link: transcript.languageURL + .split("/") + .slice(0, languageNumber === 0 ? undefined : -languageNumber) + .join("/"), + isActive: index === transcript.slugAsParams.length - 1, + }; + } + ); + return ( - + ); }; diff --git a/src/components/common/BaseBreadCrumbs.tsx b/src/components/common/BaseBreadCrumbs.tsx new file mode 100644 index 0000000..68ddd9f --- /dev/null +++ b/src/components/common/BaseBreadCrumbs.tsx @@ -0,0 +1,34 @@ +import Link from "next/link"; +import { FC } from "react"; + +export type BaseBreadCrumbsType = { + name: string; + link: string; + isActive: boolean; +}; + +const BaseBreadCrumbs = ({crumbsArray}:{crumbsArray:BaseBreadCrumbsType[]}) => { + return ( +
+ {crumbsArray.map((link, i) => ( +
+ + {link.name} + + {i !== crumbsArray.length - 1 && ( +

/

+ )} +
+ ))} +
+ ); +}; + +export default BaseBreadCrumbs; diff --git a/src/components/common/ContentSwitch.tsx b/src/components/common/ContentSwitch.tsx index 68efaa7..8f70eeb 100644 --- a/src/components/common/ContentSwitch.tsx +++ b/src/components/common/ContentSwitch.tsx @@ -72,7 +72,7 @@ const ContentSwitch = ({ /> )} -
+
{ + return ( + + {name} + + ); +}; + +export default Pill; diff --git a/src/components/common/TranscriptDetailsCard.tsx b/src/components/common/TranscriptDetailsCard.tsx index f6a0ac9..89b3669 100644 --- a/src/components/common/TranscriptDetailsCard.tsx +++ b/src/components/common/TranscriptDetailsCard.tsx @@ -6,9 +6,10 @@ 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); @@ -17,7 +18,7 @@ const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: s
{title} @@ -41,13 +42,7 @@ const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: s
{speakers.slice(0, 3).map((speaker, idx) => ( - - {speaker} - + ))} {calculateRemaining(speakers) === 0 ? null : ( @@ -70,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 index 593277b..57353a5 100644 --- a/src/components/common/TranscriptMetadataCard.tsx +++ b/src/components/common/TranscriptMetadataCard.tsx @@ -11,6 +11,7 @@ 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; @@ -85,7 +86,7 @@ const TranscriptMetadataComponent = ({ } footer={ -
+

{formattedDate}

@@ -102,16 +103,12 @@ const TranscriptMetadataComponent = ({ } footer={
- {topics && + {(topics && topics.length > 0) ? topics.map((topic) => ( - - {topic} - - ))} + + )): +

Not available

+ }
} /> @@ -125,16 +122,12 @@ const TranscriptMetadataComponent = ({ } footer={
- {speakers && + {speakers && speakers.length > 0 ? speakers.map((speaker) => ( - - {speaker} - - ))} + + )): +

Not available

+ }
} /> @@ -149,16 +142,16 @@ const TranscriptMetadataComponent = ({ height={24} className="w-5" /> -

+

Transcript by

} footer={ -
+
{isAiGenerated ? ( <> - + AI Generated (Review for sats) diff --git a/src/components/explore/ContentGrouping.tsx b/src/components/explore/ContentGrouping.tsx index 30d2886..85bcce9 100644 --- a/src/components/explore/ContentGrouping.tsx +++ b/src/components/explore/ContentGrouping.tsx @@ -2,7 +2,7 @@ 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 { @@ -26,8 +26,7 @@ const ContentGrouping = ({ linkRef.current.click(); } }; - -console.log(createContentSlug("Fee Management"), "test") + useEffect(() => { if (currentGroup) { if (selectRef.current) { @@ -41,7 +40,7 @@ console.log(createContentSlug("Fee Management"), "test") {screen === "desktop" && (