Skip to content

Commit

Permalink
Refactor: source data structure to account for languages and metadata (
Browse files Browse the repository at this point in the history
…#57)

* refactor setup algorithm

* chore: remove slug from transcript card
  • Loading branch information
IgboPharaoh authored and Emmanuel-Develops committed Nov 29, 2024
1 parent fc00be8 commit 4ad3973
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 120 deletions.
74 changes: 46 additions & 28 deletions contentlayer.config.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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));
}

Expand Down Expand Up @@ -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;
},
},
},
}));

Expand Down Expand Up @@ -374,6 +392,6 @@ export default makeSource({
getTranscriptAliases(allTranscripts);
createSpeakers(allTranscripts);
generateSourcesCount(allTranscripts, allSources);
organizeContent(allTranscripts);
organizeContent(allTranscripts, allSources);
},
});
76 changes: 43 additions & 33 deletions src/app/(explore)/[...slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<div className='flex items-start lg:gap-[50px]'>
<div className='flex flex-col w-full gap-6 md:gap-8 2xl:gap-10 no-scrollbar'>
<div
className={`flex flex-col ${
isDirectoryList ? "border-b border-b-[#9B9B9B] pb-6 md:border-b-0 md:pb-0" : "border-b border-b-[#9B9B9B] pb-6 lg:pb-10"
isRoot ? "border-b border-b-[#9B9B9B] pb-6 md:border-b-0 md:pb-0" : "border-b border-b-[#9B9B9B] pb-6 lg:pb-10"
} gap-5 2xl:gap-6`}
>
<BreadCrumbs />
<>
<SourcesBreadCrumbs slugPaths={slugPaths} current={contentTree} />
</>
<div className='flex flex-col'>
<Link href={slug.slice(0, -1).join("/") === "" ? `/sources` : `/${slug.slice(0, -1).join("/")}`} className='flex gap-1 items-center'>
<ArrowLinkRight className='rotate-180 w-5 md:w-6' />
<p>Back</p>
</Link>

<h3 className='text-xl 2xl:text-2xl font-medium pt-6 md:pt-3'>{pageDetails?.title ?? slug[slug.length - 1]}</h3>
{isDirectoryList && pageDetails?.website ? (
<h3 className='text-xl 2xl:text-2xl font-medium pt-6 md:pt-3'>{metadata?.title ?? slug[slug.length - 1]}</h3>
{isRoot && metadata?.website ? (
<div className='flex gap-1 items-center pt-3 md:pt-6'>
<Image src={WorldIcon} alt='world icon' className='w-[18px] md:w-[20px]' />
<Link
href={pageDetails?.website ?? ""}
href={metadata?.website ?? ""}
target='_blank'
className='text-xs md:text-sm xl:text-base leading-[17.6px] font-medium text-black underline text-wrap break-words line-clamp-1'
>
{pageDetails.website ?? ""}
{metadata.website ?? ""}
</Link>
</div>
) : null}

{isDirectoryList && pageDetails?.additional_resources ? (
{isRoot && metadata?.additional_resources ? (
<div className='flex gap-1 items-center pt-3 md:pt-6'>
<Image src={LinkIcon} alt='link icon' className='w-[18px] md:w-[20px]' />
<div className='flex gap-1 flex-wrap'>
{pageDetails.additional_resources.map((resource, index) => (
{metadata.additional_resources.map((resource: any, index: number) => (
<Link
href={resource.url ?? ""}
key={`${resource.title}-${index}`}
Expand All @@ -90,24 +101,23 @@ const page = ({ params }: { params: { slug: string[] } }) => {
</div>
</div>

{isDirectoryList ? (
{isRoot ? (
<div className='flex flex-col gap-6 h-full pb-8 overflow-scroll'>
{(displayCurrent as ContentTreeArray[])
.sort((a, b) => new Date(b.date!).getTime() - new Date(a.date!).getTime() || a.title.localeCompare(b.title))
.map((item, i) => (
<TranscriptDetailsCard key={i} slug={slug} data={item} />
))}
{(transcripts as ContentTreeArray[]).map((item, i) => (
<TranscriptDetailsCard key={i} slug={slug} data={item} />
))}
</div>
) : (
<div className='flex-col flex gap-10 overflow-scroll pb-8'>
<div className='grid grid-cols-1 sm:grid-cols-2 gap-2.5'>
{(displayCurrent as string[]).map((key, i) => (
<div className='grid grid-cols-1 sm:grid-cols-2 gap-2.5 '>
{(data as any[]).map((value, i) => (
<Link
key={`${key}-${i}}`}
href={`/${[...slug, key].join("/")}`}
key={`${value.route}-${i}}`}
href={`/${[...slug, value.route].join("/")}`}
className='flex capitalize cursor-pointer border max-w-[100%] border-gray-custom-1200 rounded-[5px] justify-between items-center text-sm py-5 px-4 lg:py-7 2xl:px-6 2xl:text-lg font-semibold text-gray-custom-1100'
>
<span className='text-wrap break-words max-w-[80%]'>{key}</span>
<span className='text-wrap break-words max-w-[80%]'>{value.title}</span>
<span>{value.count}</span>
</Link>
))}
</div>
Expand Down
33 changes: 7 additions & 26 deletions src/components/common/TranscriptDetailsCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,19 @@ const TranscriptDetailsCard = ({ data, slug }: { data: ContentTreeArray; slug: s
return (
<div className='border border-gray-custom-1200 rounded-lg p-4 md:p-5 2xl:p-6 flex flex-col gap-3 md:gap-4'>
<section className='flex justify-between'>
<div className='flex flex-col gap-2 w-full'>
<section className='flex flex-col md:flex-row gap-3 md:justify-between md:items-center w-full'>
<div className='flex gap-2 flex-wrap'>
{slug
.join(" / ")
.split(" ")
.map((slg, i) => (
<p
key={`${slg}-${i}`}
className={`text-xs md:text-sm 2xl:text-base leading-[20.64px] ${
slg === "/" ? "text-custom-black-custom-200" : "text-gray-custom-800"
} font-medium capitalize`}
>
{unsluggify(slg)}
</p>
))}
</div>

{date && (
<div className='flex gap-2 items-center h-fit'>
<Image src={DateIcon} alt='date icon' className='w-[18px] md:w-[20px]' />
<p className='text-xs md:text-sm 2xl:text-base leading-[17.6px] font-medium text-gray-custom-800'>{formatDate(date!)}</p>
</div>
)}
</section>

<div className='flex md:flex-row flex-col justify-between gap-2 w-full'>
<Link
href={`/${url}`}
className='font-bold text-base leading-[21.86px] md:text-xl 2xl:text-[22.5px] md:leading-[30px] text-orange-custom-100 md:text-black'
>
{title}
</Link>
{date && (
<div className='flex gap-2 items-center h-fit'>
<Image src={DateIcon} alt='date icon' className='w-[18px] md:w-[20px]' />
<p className='text-xs md:text-sm 2xl:text-base leading-[17.6px] font-medium text-gray-custom-800'>{formatDate(date!)}</p>
</div>
)}
</div>
</section>

Expand Down
84 changes: 84 additions & 0 deletions src/components/explore/SourcesBreadCrumbs.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex gap-1 flex-wrap'>
{breadCrumbRoutes.map((link, i) => (
<div key={link.name} className='flex gap-1 items-center'>
<Link
className={`capitalize hover:underline font-medium text-sm 2xl:text-base text-nowrap ${
isActive.name.toLowerCase() === link.name.toLowerCase() ? "text-orange-custom-100" : "text-black md:text-gray-custom-800"
}`}
href={link.link}
>
{link.name}
</Link>
{i !== allRoutes.length - 1 && <p className='text-custom-black-custom-200'>/</p>}
</div>
))}
</div>
);
};
Loading

0 comments on commit 4ad3973

Please sign in to comment.