This commit is contained in:
Janis
2023-01-07 10:13:39 +01:00
parent bb1ce95f13
commit fbb82b03e8
8 changed files with 255 additions and 228 deletions

View File

@@ -4,9 +4,6 @@ import Image from "next/image";
import Link from "next/link";
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];
@@ -71,7 +68,16 @@ export default function Nav({ categories }: { categories: Category[] }) {
return (
<nav className={styles.nav}>
<div className={styles.containerLeft}>
<Image src={"/images/logo.svg"} height={40} width={160} alt="Nav bar logo" />
<Image
src={"/images/logo.svg"}
height={40}
width={160}
alt="Nav bar logo"
onClick={() => {
window.open("/", "_self");
}}
className={styles.logo}
/>
<div className={styles.links}>
<div className={styles.dropDown}>
<Link href="/articles">Categories</Link>

View File

@@ -9,17 +9,9 @@ import urlJoin from "url-join";
import { apiUrl } from "../../../global";
import { Prisma } from "@prisma/client";
type ArticleWithContentTableEntries = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true } }>;
type ArticleWithCategory = Prisma.ArticleGetPayload<{ include: { category: true } }>;
// export async function GetContentTableEntries(article: Article): Promise<ContentTableEntry[]> {
// const entries = await prisma.contentTableEntry.findMany({
// where: { articleId: article?.id ?? 1 },
// orderBy: { orderIndex: "asc" },
// });
// return entries;
// }
type ArticleWithIncludes = Prisma.ArticleGetPayload<{
include: { contentTableEntries: true; category: true; image: true };
}>;
export async function GetArticle(articleName: string): Promise<any> {
const result: Response = await fetch(urlJoin(apiUrl, `articles/${articleName ?? ""}`), {
@@ -38,7 +30,10 @@ function ParseMarkdown(markdown: string): string {
//* MAIN
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 article: ArticleWithIncludes = await GetArticle(articleName);
const dateUpdated: Date = new Date(article.dateUpdated);
const dateCreated: Date = new Date(article.dateCreated);
const dateOptions: Intl.DateTimeFormatOptions = { month: "long", day: "numeric", year: "numeric" };
const markdown: string = article?.markdown ?? "";
return (
@@ -46,22 +41,27 @@ export default async function ArticlePage({ params }: { params: { articleName: s
<ContentTable contentTableEntries={article.contentTableEntries} />
<div className={styles.tutorialContent}>
<div className={styles.header}>
<p className="text-muted">Published on January 13, 2022</p>
<p className={`${styles.dates} text-muted`}>
{`Published on ${dateCreated.toLocaleDateString("en-US", dateOptions)}`}
<br />
{dateUpdated > dateCreated ? `Updated on ${dateUpdated.toLocaleDateString("en-US", dateOptions)}` : ""}
</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"}
src={article?.image?.url ?? ""}
height={350}
width={750}
alt="Image"
alt={article?.image?.alt ?? ""}
quality={100}
placeholder="blur"
blurDataURL="/images/blur.png"
loading="lazy"
/>
<p>{article?.introduction}</p>
</div>
<div
className="markdown"
@@ -77,7 +77,7 @@ export default async function ArticlePage({ params }: { params: { articleName: s
}
export async function generateStaticParams() {
const articles: ArticleWithCategory[] = await (
const articles: ArticleWithIncludes[] = await (
await fetch(urlJoin(apiUrl, `articles/`), {
cache: "force-cache",
next: { revalidate: 60 * 10 },

View File

@@ -48,7 +48,7 @@ export default async function CategoryPage({ params }: { params: { 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>

View File

@@ -3,7 +3,7 @@ import prisma from "../../../lib/prisma";
import { Article, Prisma, ContentTableEntry } from '@prisma/client';
import { ResponseError } from "../../../types/responseErrors";
type ArticleWithIncludes = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true, category: true } }>
type ArticleWithIncludes = Prisma.ArticleGetPayload<{ include: { contentTableEntries: true, category: true, image: true } }>
function sortContentTableEntries(entries: ContentTableEntry[]): ContentTableEntry[] {
return entries.sort((a, b) => a.orderIndex - b.orderIndex);
@@ -16,7 +16,7 @@ export default async function handler(req: Request, res: Response) {
const articleName: string = req.query.articleName.toString();
await prisma.article
.findUnique({ where: { name: articleName }, include: { category: true, contentTableEntries: true } })
.findUnique({ where: { name: articleName }, include: { category: true, contentTableEntries: true, image: true } })
.then((result: ArticleWithIncludes) => {
if (result !== null) {
result.contentTableEntries = sortContentTableEntries(result.contentTableEntries);

View File

@@ -11,12 +11,13 @@ model Article {
id Int @id @default(autoincrement())
name String @unique
title String @unique
introduction String @default("")
imageId Int?
image Image? @relation(fields: [imageId], references: [id])
markdown String
contentTableEntries ContentTableEntry[]
categoryId Int?
category Category? @relation(fields: [categoryId], references: [id])
typeId Int?
type ArticleType? @relation(fields: [typeId], references: [id])
dateCreated DateTime @default(now())
dateUpdated DateTime @default(now())
}
@@ -44,20 +45,13 @@ model Category {
dateUpdated DateTime @default(now())
}
model ArticleType {
model Image {
id Int @id @default(autoincrement())
name String @unique
title String @unique
Article Article[]
alt String @default("")
url String @default("")
dateCreated DateTime @default(now())
dateUpdated DateTime @default(now())
}
model Image {
id Int @id @default(autoincrement())
name String @unique
url String @default("")
dateCreated DateTime @default(now())
Article Article[]
}
model Svg {

View File

@@ -22,12 +22,35 @@
padding: 10px 0px 10px 0px;
gap: 10px 0px;
h1 {
.dates {
text-align: center;
}
img {
width: min(100%, 750px);
height: auto;
aspect-ratio: 15/7;
border-radius: 10px;
}
.tags {
display: flex;
flex-direction: row;
gap: 5px 10px;
a {
font-size: 0.8em;
background-color: var(--color-background-article-tag);
opacity: 0.9;
color: var(--color-font-article-tag);
padding: 5px 10px 5px 10px;
border-radius: 5px;
text-decoration: none;
&:hover {
color: var(--color-font-article-tag) !important;
opacity: 1;
}
}
}
}

View File

@@ -1,224 +1,227 @@
@import "../variables.scss";
.nav {
background-color: var(--color-background-nav);
height: $nav-height-inital;
margin-bottom: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
box-shadow: var(--color-shadow-nav) 0px 4px 8px;
background-color: var(--color-background-nav);
height: $nav-height-inital;
margin-bottom: 10px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
align-items: center;
box-shadow: var(--color-shadow-nav) 0px 4px 8px;
@media (max-width: $nav-breakpoint-1) {
grid-template-columns: 1fr 1fr 0.25fr;
}
@media (max-width: $nav-breakpoint-1) {
grid-template-columns: 1fr 1fr 0.25fr;
}
@media (max-width: $nav-breakpoint-2) {
grid-template-columns: 1fr 1fr;
height: 100px;
}
@media (max-width: $nav-breakpoint-2) {
grid-template-columns: 1fr 1fr;
height: 100px;
}
& > div {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
& > div {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
.containerLeft {
@media (max-width: $nav-breakpoint-2) {
grid-column: 1;
grid-row: 1;
}
.containerLeft {
@media (max-width: $nav-breakpoint-2) {
grid-column: 1;
grid-row: 1;
}
@media (max-width: $nav-breakpoint-3) {
column-gap: 20px;
}
@media (max-width: $nav-breakpoint-3) {
column-gap: 20px;
}
.links {
font-size: 0.8em;
font-weight: bold;
.logo {
cursor: pointer;
}
.links {
font-size: 0.8em;
font-weight: bold;
.dropDown {
color: var(--color-font-link);
text-decoration: none;
cursor: pointer;
&:hover {
.dropDownContainer {
display: block;
}
}
.dropDownContainer {
display: none;
position: absolute;
z-index: 1;
.content {
background-color: var(--color-background-dropdown);
min-width: 160px;
box-shadow: 0px 12px 16px 5px rgba(0, 0, 0, 0.2);
margin-top: 21px;
.dropDown {
color: var(--color-font-link);
text-decoration: none;
cursor: pointer;
&:hover {
.dropDownContainer {
display: block;
}
}
.dropDownContainer {
display: none;
position: absolute;
z-index: 1;
.content {
background-color: var(--color-background-dropdown);
min-width: 160px;
box-shadow: 0px 12px 16px 5px rgba(0, 0, 0, 0.2);
margin-top: 21px;
a {
float: none;
color: var(--color-font-link);
padding: 12px 16px;
text-decoration: none;
display: block;
text-align: left;
border-left: 2px solid transparent;
transition: all 50ms linear;
a {
float: none;
color: var(--color-font-link);
padding: 12px 16px;
text-decoration: none;
display: block;
text-align: left;
border-left: 2px solid transparent;
transition: all 50ms linear;
&:hover {
border-color: var(--color-accent);
}
}
}
}
}
}
}
&:hover {
border-color: var(--color-accent);
}
}
}
}
}
}
}
.containerCenter {
width: 100%;
.containerCenter {
width: 100%;
@media (max-width: $nav-breakpoint-2) {
grid-row: 2;
grid-column: span 2;
}
.searchBar {
display: flex;
justify-content: center;
align-items: center;
border: 2px solid rgba(95, 95, 95, 0.5);
border-radius: 5px;
padding-left: 10px;
transition: all 50ms linear;
@media (max-width: $nav-breakpoint-2) {
grid-row: 2;
grid-column: span 2;
}
.searchBar {
display: flex;
justify-content: center;
align-items: center;
border: 2px solid rgba(95, 95, 95, 0.5);
border-radius: 5px;
padding-left: 10px;
transition: all 50ms linear;
&:hover {
border-color: rgba(91, 91, 91, 0.9);
&:hover {
border-color: rgba(91, 91, 91, 0.9);
.icon {
svg {
fill: var(--color-font);
}
}
}
.icon {
svg {
fill: var(--color-font);
}
}
}
&:focus-within {
border-color: rgba(91, 91, 91, 0.9);
.icon {
svg {
fill: var(--color-font);
}
}
}
&:focus-within {
border-color: rgba(91, 91, 91, 0.9);
.icon {
svg {
fill: var(--color-font);
}
}
}
&:focus-within + .searchResults {
display: block;
}
&:focus-within + .searchResults {
display: block;
}
input {
width: 300px;
height: 30px;
background-color: transparent;
border: 0px;
outline: 0;
padding-right: 10px;
color: var(--color-font);
input {
width: 300px;
height: 30px;
background-color: transparent;
border: 0px;
outline: 0;
padding-right: 10px;
color: var(--color-font);
@media (max-width: $nav-breakpoint-4) {
width: 200px;
}
}
@media (max-width: $nav-breakpoint-4) {
width: 200px;
}
}
.icon {
display: block;
padding-right: 10px;
svg {
height: 15px;
}
}
}
.icon {
display: block;
padding-right: 10px;
svg {
height: 15px;
}
}
}
.searchResults {
display: none;
position: absolute;
top: 48px;
.searchResults {
display: none;
position: absolute;
top: 48px;
&:hover {
display: block;
}
&:hover {
display: block;
}
@media (max-width: $nav-breakpoint-2) {
top: 94px;
}
@media (max-width: $nav-breakpoint-2) {
top: 94px;
}
.content {
background-color: rgb(18, 18, 18);
width: 335px;
a {
text-decoration: none;
display: block;
width: 100%;
padding: 5px 10px 5px 10px;
float: none;
border-left: 2px solid transparent;
transition: all 50ms linear;
text-overflow: ellipsis;
font-size: 0.8em;
.content {
background-color: rgb(18, 18, 18);
width: 335px;
a {
text-decoration: none;
display: block;
width: 100%;
padding: 5px 10px 5px 10px;
float: none;
border-left: 2px solid transparent;
transition: all 50ms linear;
text-overflow: ellipsis;
font-size: 0.8em;
&:hover {
border-color: var(--color-accent);
background-color: rgba(41, 41, 41, 0.2);
}
}
@media (max-width: $nav-breakpoint-4) {
width: 235px;
}
}
}
}
&:hover {
border-color: var(--color-accent);
background-color: rgba(41, 41, 41, 0.2);
}
}
@media (max-width: $nav-breakpoint-4) {
width: 235px;
}
}
}
}
.containerRight {
justify-content: flex-end;
padding-right: 50px;
.containerRight {
justify-content: flex-end;
padding-right: 50px;
@media (max-width: $nav-breakpoint-2) {
grid-row: 1;
}
@media (max-width: $nav-breakpoint-2) {
grid-row: 1;
}
@media (max-width: $nav-breakpoint-4) {
padding-right: 20px;
}
@media (max-width: $nav-breakpoint-4) {
padding-right: 20px;
}
.themeSwitch {
svg {
transition: all 50ms linear;
height: 24px;
cursor: pointer;
animation-duration: 150ms;
animation-timing-function: linear;
animation-iteration-count: 1;
.themeSwitch {
svg {
transition: all 50ms linear;
height: 24px;
cursor: pointer;
animation-duration: 150ms;
animation-timing-function: linear;
animation-iteration-count: 1;
animation-direction: normal;
&:hover {
fill: var(--color-font);
}
}
}
}
animation-direction: normal;
&:hover {
fill: var(--color-font);
}
}
}
}
svg {
aspect-ratio: 1;
fill: var(--color-svg-nav);
}
svg {
aspect-ratio: 1;
fill: var(--color-svg-nav);
}
@keyframes spinThemeSwitch {
from {
transform: rotate(0deg);
}
to {
transform: rotate(-180deg);
}
}
@keyframes spinThemeSwitch {
from {
transform: rotate(0deg);
}
to {
transform: rotate(-180deg);
}
}
}

View File

@@ -18,7 +18,8 @@
--color-background-dropdown: var(--color-background-body);
--color-accent: #2294ff;
--color-font-link: var(--color-accent);
--color-font-article-tag: #fff;
--color-background-article-tag: #007aec;
--color-font-link-hover: #5caffc;
/* Colors: Markdown */