diff --git a/contentlayer.config.ts b/contentlayer.config.ts
index 1b927a8..0bbbdea 100644
--- a/contentlayer.config.ts
+++ b/contentlayer.config.ts
@@ -1,6 +1,6 @@
import path from "path";
import * as fs from "fs";
-import { createSlug, createText, SpeakerData, TopicsData, unsluggify } from "./src/utils";
+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";
@@ -244,37 +244,47 @@ const createTypesCount = (transcripts: ContentTranscriptType[], sources: Content
fs.writeFileSync("./public/types-data.json", JSON.stringify(nestedTypes));
};
-function organizeContent(transcripts: ContentTranscriptType[]) {
- const tree: ContentTree = {};
+function organizeContent(transcripts: ContentTranscriptType[], sources: ContentSourceType[]) {
+ const tree: any = {};
- 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;
+ 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;
- 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;
+ 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));
}
@@ -316,6 +326,14 @@ export const Transcript = defineDocumentType(() => ({
type: "list",
resolve: (doc) => doc._raw.flattenedPath.split("/"),
},
+ language: {
+ type: "string",
+ resolve: (doc) => {
+ const transcript = doc._raw.flattenedPath.split("/").pop();
+ const lan = transcript?.split(".").length === 2 ? transcript?.split(".")[1] : "en";
+ return lan;
+ },
+ },
},
}));
@@ -374,6 +392,6 @@ export default makeSource({
getTranscriptAliases(allTranscripts);
createSpeakers(allTranscripts);
generateSourcesCount(allTranscripts, allSources);
- organizeContent(allTranscripts);
+ organizeContent(allTranscripts, allSources);
},
});
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 ? (
- {pageDetails.website ?? ""}
+ {metadata.website ?? ""}
) : null}
- {isDirectoryList && pageDetails?.additional_resources ? (
+ {isRoot && metadata?.additional_resources ? (
- {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/components/common/TranscriptDetailsCard.tsx b/src/components/common/TranscriptDetailsCard.tsx
index e11d10a..f6a0ac9 100644
--- a/src/components/common/TranscriptDetailsCard.tsx
+++ b/src/components/common/TranscriptDetailsCard.tsx
@@ -15,38 +15,19 @@ const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: s
return (
-
-
-
- {slug
- .join(" / ")
- .split(" ")
- .map((slg, i) => (
-
- {unsluggify(slg)}
-
- ))}
-
-
- {date && (
-
-
-
{formatDate(date!)}
-
- )}
-
-
+
{title}
+ {date && (
+
+
+
{formatDate(date!)}
+
+ )}
diff --git a/src/components/explore/SourcesBreadCrumbs.tsx b/src/components/explore/SourcesBreadCrumbs.tsx
new file mode 100644
index 0000000..f33cd5c
--- /dev/null
+++ b/src/components/explore/SourcesBreadCrumbs.tsx
@@ -0,0 +1,84 @@
+"use client";
+
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import { ExploreNavigationItems } from "@/utils/data";
+
+export const SourcesBreadCrumbs = ({ slugPaths, current }: { slugPaths: string[]; current: any }) => {
+ const pathname = usePathname();
+
+ const navListWithoutSources = ExploreNavigationItems.filter((item) => item.href !== "/sources").map((item) => item.href.slice(1));
+
+ const language = slugPaths[1];
+ const pathnameArray = pathname.replace(`/${language}`, "").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 breadCrumbData = () => {
+ const _trimPaths = pathnameArray.shift();
+ let currentPathArray = pathnameArray;
+
+ const extractedRoutes: Array<{ [key: string]: string }> = [];
+ const language = slugPaths[1];
+
+ for (let i = 0; i < pathnameArray.length; i++) {
+ const pathChoice = i === 0 ? [slugPaths[0], language] : slugPaths;
+
+ for (const part of pathChoice) {
+ if (typeof current === "object" && !Array.isArray(current) && part in current) {
+ current = current[part];
+ }
+ }
+
+ extractedRoutes.push({ name: current?.metadata?.title as string, link: currentPathArray[i] });
+ }
+
+ return { extractedRoutes };
+ };
+
+ const { extractedRoutes } = breadCrumbData();
+ let newRoutes: Array<{ [key: string]: string }> = [];
+
+ if (extractedRoutes.length > 0) {
+ newRoutes = allRoutes.map((route: any) => {
+ const isPresent = extractedRoutes.find((extractedRoute) => extractedRoute.link === route.name);
+
+ if (isPresent) {
+ return { ...route, name: isPresent.name ?? route.name };
+ }
+ return route;
+ });
+ }
+
+ const breadCrumbRoutes = extractedRoutes.length ? newRoutes : allRoutes;
+ const isActive = breadCrumbRoutes[breadCrumbRoutes.length - 1];
+
+ return (
+
+ {breadCrumbRoutes.map((link, i) => (
+
+
+ {link.name}
+
+ {i !== allRoutes.length - 1 &&
/
}
+
+ ))}
+
+ );
+};
diff --git a/src/utils/index.ts b/src/utils/index.ts
index 1bbb208..fde3a46 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -36,30 +36,6 @@ export type DepreciatedCategories = "tags" | "speakers" | "categories" | "source
export type GroupedData = Record
;
-export function organizeContent(transcripts: Transcript[]): ContentTree {
- const tree: ContentTree = {};
-
- transcripts.forEach((transcript) => {
- const parts = transcript.slugAsParams;
- let current = tree;
-
- for (let i = 0; i < parts.length - 1; i++) {
- if (!(parts[i] in current)) {
- current[parts[i]] = {};
- }
- current = current[parts[i]] as ContentTree;
- }
-
- const lastName = parts[parts.length - 1];
- if (!(lastName in current)) {
- current[lastName] = [];
- }
- (current[lastName] as Transcript[]).push(transcript);
- });
-
- return tree;
-}
-
export function shuffle(data: Transcript[]) {
let currIndex = data.length;
@@ -196,18 +172,17 @@ export const formatDate = (dateString: string): string | null => {
return new Intl.DateTimeFormat("en-GB", options).format(date);
};
-export function filterOutIndexes(arr: {} | ContentTreeArray[]) {
- let filterIndex: ContentTreeArray[] | string[] = [];
+export function loopArrOrObject(arr: {} | ContentTreeArray[]) {
+ let filterIndex: any[] = [];
if (Array.isArray(arr)) {
- const list = arr.filter((item: ContentTreeArray) => {
- const url = item.flattenedPath.split("/");
-
- return !url[url.length - 1].startsWith("_index");
- });
- filterIndex = showOnlyEnglish(list) as ContentTreeArray[];
+ filterIndex = arr;
} else {
- filterIndex = Object.keys(arr).filter((item) => !item.startsWith("_index"));
+ filterIndex = Object.entries(arr).map(([key, values]) => ({
+ route: key,
+ title: (values as unknown as any).metadata.title,
+ count: (values as unknown as any).data.length,
+ }));
}
return filterIndex;
@@ -263,3 +238,48 @@ export const countItemsAndSort = (args: { [category: string]: TagInfo[] }) => {
}, {} as typeof countObject);
return sortObject;
};
+
+export const constructSlugPaths = (slug: string[]) => {
+ const languageCodes = ["zh", "es", "pt"];
+ const isEnglishSlug = slug[0] !== "en" && slug[0].length > 2 && !languageCodes.includes(slug[0]);
+ const englishSlug = ["en", ...slug];
+ const newSlug = isEnglishSlug ? [...englishSlug] : [...slug];
+ [newSlug[0], newSlug[1]] = [newSlug[1], newSlug[0]];
+
+ let slugPaths = newSlug;
+ const addDataKeyToSlug = [...slugPaths.slice(0, 2), "data", ...slugPaths.slice(2)];
+ slugPaths = slugPaths.length >= 3 ? addDataKeyToSlug : slugPaths;
+
+ return { slugPaths };
+};
+
+export const fetchTranscriptDetails = (allTranscripts: Transcript[], paths: string[], isRoot: boolean) => {
+ if (!isRoot || paths.length === 0) return { transcripts: [] };
+
+ const transcripts = allTranscripts.reduce((acc, curr) => {
+ const { url, title, speakers, date, tags, _raw, summary, body } = curr;
+
+ if (paths.includes(url)) {
+ acc.push({
+ title,
+ speakers,
+ date,
+ tags,
+ sourceFilePath: _raw.sourceFilePath,
+ flattenedPath: _raw.flattenedPath,
+ summary,
+ body: createText(body),
+ });
+ }
+ return acc.sort((a, b) => {
+ const sortByTime = new Date(b.date!).getTime() - new Date(a.date!).getTime();
+ const sortByTitle = a.title.localeCompare(b.title);
+
+ return sortByTime || sortByTitle;
+ });
+ }, [] as Array);
+
+ return {
+ transcripts,
+ };
+};