This commit is contained in:
Janis
2023-01-07 08:52:59 +01:00
parent 6ed3cc22f3
commit dd121030e4
30 changed files with 7745 additions and 8665 deletions

View File

@@ -2,146 +2,127 @@
import styles from "../styles/modules/Nav.module.scss";
import Image from "next/image";
import Link from "next/link";
import { MutableRefObject, useEffect, useRef, useState } from "react";
import async from "./articles/[categoryName]/[articleName]/head";
import ContentTable from "./articles/[categoryName]/[articleName]/ContentTable";
export type NavCategory = {
name: string;
title: string;
};
import { useEffect, useState } from "react";
import { Category } from "@prisma/client";
import urlJoin from "url-join";
import { apiUrl } from "./global";
import { GetStaticProps } from "next";
function switchTheme(theme) {
const bodyElement = document.getElementsByTagName("body")[0];
const bodyElement = document.getElementsByTagName("body")[0];
if (theme == "dark") {
bodyElement.classList.remove("theme-light");
} else {
bodyElement.classList.add("theme-light");
}
if (theme == "dark") {
bodyElement.classList.remove("theme-light");
} else {
bodyElement.classList.add("theme-light");
}
}
function toggleTheme() {
const svgElement = document.getElementById("themeSwitchSvg");
const svgElement = document.getElementById("themeSwitchSvg");
if (localStorage.getItem("theme") == "light") {
svgElement.style.animationDirection = "normal";
svgElement.style.animationName = styles.spinThemeSwitch;
} else {
svgElement.style.animationDirection = "reverse";
svgElement.style.animationName = styles.spinThemeSwitch;
}
if (localStorage.getItem("theme") == "light") {
svgElement.style.animationDirection = "normal";
svgElement.style.animationName = styles.spinThemeSwitch;
} else {
svgElement.style.animationDirection = "reverse";
svgElement.style.animationName = styles.spinThemeSwitch;
}
setTimeout(() => {
if (localStorage.getItem("theme") == "light") {
localStorage.setItem("theme", "dark");
switchTheme("dark");
} else {
localStorage.setItem("theme", "light");
switchTheme("light");
}
svgElement.style.animationName = "";
}, 150);
setTimeout(() => {
if (localStorage.getItem("theme") == "light") {
localStorage.setItem("theme", "dark");
switchTheme("dark");
} else {
localStorage.setItem("theme", "light");
switchTheme("light");
}
svgElement.style.animationName = "";
}, 150);
}
export default function Nav({ categories }: { categories: NavCategory[] }) {
const [searchResults, setSearchResults] = useState([]);
export default function Nav({ categories }: { categories: Category[] }) {
const [searchResults, setSearchResults] = useState([]);
async function handleSearchInput(event) {
const query = event.target.value;
let result = await fetch(`/api/search?q=${query}`);
let json = await result.json();
async function handleSearchInput(event) {
const query = event.target.value;
let result = await fetch(`/api/search?q=${query}`);
let json = await result.json();
if (json.length == 0 && query.length > 0) {
setSearchResults([{ name: "", title: "No article found..." }]);
} else {
setSearchResults(json);
}
}
if (json.length == 0 && query.length > 0) {
setSearchResults([{ name: "", title: "No article found..." }]);
} else {
setSearchResults(json);
}
}
useEffect(() => {
if (localStorage.getItem("theme") == "dark") {
switchTheme("dark");
} else {
switchTheme("light");
}
}, []);
useEffect(() => {
if (localStorage.getItem("theme") == "dark") {
switchTheme("dark");
} else {
switchTheme("light");
}
}, []);
useEffect(() => {
console.log(searchResults);
}, [searchResults]);
useEffect(() => {
console.log(searchResults);
}, [searchResults]);
return (
<nav className={styles.nav}>
<div className={styles.containerLeft}>
<Image
src={"/images/logo.svg"}
height={40}
width={160}
alt="Nav bar logo"
/>
<div className={styles.links}>
<div className={styles.dropDown}>
<Link href="/articles">Categories</Link>
<div className={styles.dropDownContainer}>
<div className={styles.content}>
<Link href={"/articles"}>All</Link>
{categories?.map((cat, i) => {
{
return (
<Link
key={i}
href={`/articles/${cat.name.toLowerCase()}`}
>
{cat.title}
</Link>
);
}
})}
</div>
</div>
</div>
</div>
</div>
<div className={styles.containerCenter}>
<div className={styles.searchBar}>
<div className={styles.icon}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z" />
</svg>
</div>
<input onInput={handleSearchInput} type="text" name="" id="" />
</div>
<div className={styles.searchResults}>
<div className={styles.content}>
{searchResults.map((s) => {
{
return (
<Link href={`/articles/${s.name.toLowerCase()}`}>
{s.title}
</Link>
);
}
})}
</div>
</div>
</div>
<div className={styles.containerRight}>
<div
className={styles.themeSwitch}
onClick={() => {
toggleTheme();
}}
>
<svg
id="themeSwitchSvg"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
>
<path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zm64 0c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256z" />
</svg>
</div>
</div>
</nav>
);
return (
<nav className={styles.nav}>
<div className={styles.containerLeft}>
<Image src={"/images/logo.svg"} height={40} width={160} alt="Nav bar logo" />
<div className={styles.links}>
<div className={styles.dropDown}>
<Link href="/articles">Categories</Link>
<div className={styles.dropDownContainer}>
<div className={styles.content}>
<Link href={"/articles"}>All</Link>
{categories?.map((cat, i) => {
{
return (
<Link key={i} href={`/articles/${cat.name.toLowerCase()}`}>
{cat.title}
</Link>
);
}
})}
</div>
</div>
</div>
</div>
</div>
<div className={styles.containerCenter}>
<div className={styles.searchBar}>
<div className={styles.icon}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352c79.5 0 144-64.5 144-144s-64.5-144-144-144S64 128.5 64 208s64.5 144 144 144z" />
</svg>
</div>
<input onInput={handleSearchInput} type="text" name="" id="" />
</div>
<div className={styles.searchResults}>
<div className={styles.content}>
{searchResults.map((s) => {
{
return <Link href={`/articles/${s.name.toLowerCase()}`}>{s.title}</Link>;
}
})}
</div>
</div>
</div>
<div className={styles.containerRight}>
<div
className={styles.themeSwitch}
onClick={() => {
toggleTheme();
}}
>
<svg id="themeSwitchSvg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
<path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zm64 0c0 141.4-114.6 256-256 256S0 397.4 0 256S114.6 0 256 0S512 114.6 512 256z" />
</svg>
</div>
</div>
</nav>
);
}

View File

@@ -1,32 +1,23 @@
import React from "react";
import prisma from "../../../../lib/prisma";
import styles from "../../../../styles/modules/TutorialContentTable.module.scss";
import styles from "../../../../styles/modules/ArticleContentTable.module.scss";
import { Article, ContentTableEntry } from "@prisma/client";
export default function ContentTable({
contentTableEntries,
}: {
contentTableEntries: ContentTableEntry[];
}) {
return (
<div className={styles.tutorialContentTable}>
<div className={styles.stickyContainer}>
<div className={styles.list}>
<h2>Contents</h2>
{contentTableEntries?.map((e, i) => {
return (
<a key={i} href={"#" + e.anchor}>
{e.title}
</a>
);
})}
</div>
{contentTableEntries?.length < 15 ? (
<div className={styles.adContainer}>Future advertisement</div>
) : (
""
)}
</div>
</div>
);
export default function ContentTable({ contentTableEntries }: { contentTableEntries: ContentTableEntry[] }) {
return (
<div className={styles.articleContentTable}>
<div className={styles.stickyContainer}>
<div className={styles.list}>
<h2>Contents</h2>
{contentTableEntries?.map((e, i) => {
return (
<a key={i} href={"#" + e.anchor}>
{e.title}
</a>
);
})}
</div>
{contentTableEntries?.length < 15 ? <div className={styles.adContainer}>Future advertisement</div> : ""}
</div>
</div>
);
}

View File

@@ -1,82 +1,93 @@
import { marked } from "marked";
import ContentTable from "./ContentTable";
import Sidebar from "./Sidebar";
import styles from "../../../../styles/modules/Tutorial.module.scss";
import styles from "../../../../styles/modules/Article.module.scss";
import LoadMarkdown from "./LoadMarkdown";
import prisma from "../../../../lib/prisma";
import { Article, Category, ContentTableEntry } from "@prisma/client";
import Image from "next/image";
import urlJoin from "url-join";
import { apiUrl } from "../../../global";
import { Prisma } from "@prisma/client";
export async function GetContentTableEntries(
article: Article
): Promise<ContentTableEntry[]> {
const entries = await prisma.contentTableEntry.findMany({
where: { articleId: article?.id ?? 1 },
orderBy: { orderIndex: "asc" },
});
type ArticleWithContentTableEntries = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true } }>;
type ArticleWithCategory = Prisma.ArticleGetPayload<{ include: { category: true } }>;
return entries;
}
// export async function GetContentTableEntries(article: Article): Promise<ContentTableEntry[]> {
// const entries = await prisma.contentTableEntry.findMany({
// where: { articleId: article?.id ?? 1 },
// orderBy: { orderIndex: "asc" },
// });
export async function GetArticle(articleName: string) {
const article = await prisma.article.findUnique({
where: { name: articleName.toLowerCase() ?? "" },
});
// return entries;
// }
return article;
export async function GetArticle(articleName: string): Promise<any> {
const result: Response = await fetch(urlJoin(apiUrl, `articles/${articleName ?? ""}`), {
cache: "force-cache",
next: { revalidate: 60 * 10 },
});
return result.json();
}
function ParseMarkdown(markdown: string): string {
let result = marked.parse(markdown);
return result;
let result = marked.parse(markdown);
return result;
}
//* MAIN
export default async function ArticlePage({
params,
}: {
params: { articleName: string; categoryName: string };
}) {
const articleName: string = params.articleName
.toLowerCase()
.replaceAll("%20", " ");
const article: Article = await GetArticle(articleName);
const markdown: string = article?.markdown ?? "";
const contentTableEntries: ContentTableEntry[] = await GetContentTableEntries(
article
);
export default async function ArticlePage({ params }: { params: { articleName: string; categoryName: string } }) {
const articleName: string = params.articleName.toLowerCase().replaceAll("%20", " ");
const article: ArticleWithContentTableEntries = await GetArticle(articleName);
const markdown: string = article?.markdown ?? "";
return (
<div className={styles.tutorial}>
<ContentTable contentTableEntries={contentTableEntries} />
<div className={styles.tutorialContent}>
<div className={styles.head}>
<h1>{article?.title}</h1>
</div>
<div
className="markdown"
dangerouslySetInnerHTML={{
__html: ParseMarkdown(markdown),
}}
></div>
<LoadMarkdown />
</div>
<Sidebar />
</div>
);
return (
<div className={styles.article}>
<ContentTable contentTableEntries={article.contentTableEntries} />
<div className={styles.tutorialContent}>
<div className={styles.header}>
<p className="text-muted">Published on January 13, 2022</p>
<h1>{article?.title}</h1>
<div className={styles.tags}>
<a href="#">Docker</a> <a href="#">Setup</a> <a href="#">Ubuntu</a>
</div>
<Image
src={"/images/test.jpg"}
height={350}
width={750}
alt="Image"
quality={100}
placeholder="blur"
blurDataURL="/images/blur.png"
loading="lazy"
/>
</div>
<div
className="markdown"
dangerouslySetInnerHTML={{
__html: ParseMarkdown(markdown),
}}
></div>
<LoadMarkdown />
</div>
<Sidebar />
</div>
);
}
export async function generateStaticParams() {
const articles = await prisma.article.findMany();
const articles: ArticleWithCategory[] = await (
await fetch(urlJoin(apiUrl, `articles/`), {
cache: "force-cache",
next: { revalidate: 60 * 10 },
})
).json();
async function GetCategory(categoryId: number): Promise<Category> {
return await prisma.category.findUnique({ where: { id: categoryId } });
}
return await Promise.all(
articles.map(async (article) => ({
categoryName: (await GetCategory(article.categoryId)).name ?? "",
articleName: article.name ?? "",
}))
);
return await Promise.all(
articles.map(async (article) => ({
categoryName: article.category?.name ?? "",
articleName: article.name ?? "",
}))
);
}

View File

@@ -1,63 +1,72 @@
import styles from "../../../styles/modules/Category.module.scss";
import Link from "next/link";
import prisma from "../../../lib/prisma";
import { apiUrl } from "../../global";
import { Article, Category } from "@prisma/client";
import urlJoin from "url-join";
export async function GetAllArticles(category: Category): Promise<Article[]> {
return await prisma.article.findMany({ where: { category: category } });
async function GetAllArticles(categoryName: string): Promise<any> {
const result: Response = await fetch(urlJoin(apiUrl, `articles?categoryName=${categoryName}`), {
cache: "force-cache",
next: { revalidate: 3600 },
});
return result.json();
}
export async function GetPopularArticles(
category: Category
): Promise<Article[]> {
return await prisma.article.findMany({
where: { category: category },
take: 6,
});
async function GetPopularArticles(categoryName: string): Promise<any> {
const result: Response = await fetch(
urlJoin(apiUrl, `articles?categoryName=${categoryName}&limit=6&orderBy=popularity`),
{
cache: "force-cache",
next: { revalidate: 3600 },
}
);
return result.json();
}
export async function GetRecentArticles(
category: Category
): Promise<Article[]> {
return await prisma.article.findMany({
where: { category: category },
take: 6,
orderBy: { dateCreated: "desc" },
});
async function GetRecentArticles(categoryName: string): Promise<any> {
const result: Response = await fetch(
urlJoin(apiUrl, `articles?categoryName=${categoryName}&limit=6&orderBy=recent`),
{
cache: "force-cache",
next: { revalidate: 3600 },
}
);
return result.json();
}
export async function GetCategory(categoryName: string): Promise<Category> {
return await prisma.category.findUnique({ where: { name: categoryName } });
async function GetCategory(categoryName: string): Promise<any> {
const result: Response = await fetch(urlJoin(apiUrl, `categories/${categoryName}`), {
cache: "force-cache",
next: { revalidate: 3600 },
});
return result.json();
}
export default async function CategoryPage({
params,
}: {
params: { categoryName: string };
}) {
const categoryName = params.categoryName.toLowerCase().replaceAll("%20", " ");
const category: Category = await GetCategory(categoryName);
const allArticles: Article[] = await GetAllArticles(category);
const popularArticles: Article[] = await GetPopularArticles(category);
const recentArticles: Article[] = await GetRecentArticles(category);
return (
<div className={styles.category}>
<h1>{category?.title}</h1>
<div className={styles.content}>
<div className={`${styles.showcase} ${styles.smallShowcase}`}>
<h2>Most popular articles</h2>
{popularArticles?.map((a, i) => {
{
return (
<Link key={i} href={`/articles/${category.name}/${a.name}`}>
{a.name}
</Link>
);
}
})}
</div>
export default async function CategoryPage({ params }: { params: { categoryName: string } }) {
const categoryName = params.categoryName.toLowerCase().replaceAll("%20", " ");
const category: Category = await GetCategory(categoryName);
const allArticles: Article[] = await GetAllArticles(categoryName);
const popularArticles: Article[] = await GetPopularArticles(categoryName);
const recentArticles: Article[] = await GetRecentArticles(categoryName);
console.log(popularArticles);
return (
<div className={styles.category}>
<h1>{category?.title}</h1>
<div className={styles.content}>
<div className={`${styles.showcase} ${styles.smallShowcase}`}>
<h2>Most popular articles</h2>
{popularArticles?.map((a, i) => {
{
return (
<Link key={i} href={`/articles/${category.name}/${a.name}`}>
{a.name}
</Link>
);
}
})}
</div>
{/* <div className={`${styles.showcase} ${styles.smallShowcase}`}>
{/* <div className={`${styles.showcase} ${styles.smallShowcase}`}>
<h2>Most recent articles</h2>
{recentArticles?.map((a, i) => {
{
@@ -70,19 +79,19 @@ export default async function CategoryPage({
})}
</div> */}
<div className={styles.showcase}>
<h2>All articles</h2>
{allArticles?.map((a, i) => {
{
return (
<Link key={i} href={`/articles/${category.name}/${a.name}`}>
{a.name}
</Link>
);
}
})}
</div>
</div>
</div>
);
<div className={styles.showcase}>
<h2>All articles</h2>
{allArticles?.map((a, i) => {
{
return (
<Link key={i} href={`/articles/${category.name}/${a.name}`}>
{a.name}
</Link>
);
}
})}
</div>
</div>
</div>
);
}

View File

@@ -1,47 +1,45 @@
import styles from "../../styles/modules/CategoryList.module.scss";
import Link from "next/link";
import prisma from "../../lib/prisma";
import { Category, Svg, Prisma } from "@prisma/client";
import { Suspense } from "react";
import dynamic from "next/dynamic";
import urlJoin from "url-join";
import { apiUrl } from "../global";
type CategoryWithSvg = Prisma.CategoryGetPayload<{ include: { svg: true } }>;
export async function GetCategories(): Promise<CategoryWithSvg[]> {
return await prisma.category.findMany({ include: { svg: true } });
export async function GetCategories(): Promise<any> {
const result: Response = await fetch(urlJoin(apiUrl, `categories`), {
cache: "force-cache",
next: { revalidate: 3600 },
});
return result.json();
}
export default async function CategoryList() {
const categories = await GetCategories();
return (
<div className={styles.categoryList}>
<h1>Overview</h1>
<div className={styles.content}>
<div className={styles.grid}>
{categories?.length > 0
? categories.map((cat, i) => {
return (
<div key={i} className={styles.linkContainer}>
<Link
href={`/articles/${cat.name.toLowerCase()}`}
style={{ backgroundColor: cat.color }}
>
<div className={styles.svgContainer}>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox={cat?.svg?.viewbox}
>
<path d={cat?.svg?.path} />
</svg>
</div>
{cat.title}
</Link>
</div>
);
})
: "We did not find any categories"}
</div>
</div>
</div>
);
const categories = await GetCategories();
return (
<div className={styles.categoryList}>
<h1>Overview</h1>
<div className={styles.content}>
<div className={styles.grid}>
{categories?.length > 0
? categories.map((cat, i) => {
return (
<div key={i} className={styles.linkContainer}>
<Link href={`/articles/${cat.name.toLowerCase()}`} style={{ backgroundColor: cat.color }}>
<div className={styles.svgContainer}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox={cat?.svg?.viewbox}>
<path d={cat?.svg?.path} />
</svg>
</div>
{cat.title}
</Link>
</div>
);
})
: "We did not find any categories"}
</div>
</div>
</div>
);
}

3
app/global.ts Normal file
View File

@@ -0,0 +1,3 @@
//! Using this because publicRuntimeConfig is not implemented in appDir yet
export const apiUrl: string = "http://localhost:3000/api/";

View File

@@ -4,32 +4,30 @@ import "../styles/variables.scss";
import Nav from "./Nav";
import Footer from "./Footer";
import { Category } from "@prisma/client";
import prisma from "../lib/prisma";
import { NavCategory } from "./Nav";
import urlJoin from "url-join";
import { apiUrl } from "./global";
export async function GetNavCategories(): Promise<NavCategory[]> {
const result: NavCategory[] = await prisma.category.findMany({
select: { name: true, title: true },
});
return result;
async function getCategories(): Promise<Category[]> {
const result: Response = await fetch(urlJoin(apiUrl, `categories`), {
cache: "force-cache",
next: { revalidate: 3600 },
});
return await result.json();
}
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html style={{ scrollBehavior: "smooth" }}>
<head></head>
export default async function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html style={{ scrollBehavior: "smooth" }}>
<head></head>
<body className="body">
<header>
<Nav categories={await GetNavCategories()} />
</header>
<main>{children}</main>
<Footer />
</body>
</html>
);
<body className="body">
<header>
<Nav categories={await getCategories()} />
</header>
<main>{children}</main>
<Footer />
</body>
</html>
);
}