mirror of
https://github.com/DerTyp7/explainegy-nextjs.git
synced 2025-10-29 21:02:13 +01:00
cherry
This commit is contained in:
22
components/ContentTable.tsx
Normal file
22
components/ContentTable.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import styles from "../styles/modules/ArticleContentTable.module.scss";
|
||||
|
||||
export default function ContentTable({ contentTableData }: { contentTableData: any }) {
|
||||
return (
|
||||
<div className={styles.articleContentTable}>
|
||||
<div className={styles.stickyContainer}>
|
||||
<div className={styles.list}>
|
||||
<h2>Contents</h2>
|
||||
{contentTableData?.map((e, i) => {
|
||||
return (
|
||||
<a key={i} href={"#" + e.anchor}>
|
||||
{e.title}
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{contentTableData?.length < 15 ? <div className={styles.adContainer}>Future advertisement</div> : ""}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
40
components/Footer.tsx
Normal file
40
components/Footer.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
import styles from "../styles/modules/Footer.module.scss";
|
||||
import Image from "next/image";
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className={styles.footer}>
|
||||
<div className={styles.adContainer}>Future advertisement</div>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.company}>
|
||||
<Image
|
||||
src={"/images/logo.svg"}
|
||||
width={190}
|
||||
height={52}
|
||||
alt={"Logo"}
|
||||
/>
|
||||
<h1>Simple tutorials for everyone!</h1>
|
||||
</div>
|
||||
<div className={styles.links}>
|
||||
<div className={styles.grid}>
|
||||
<a href="#">Tutorials</a>
|
||||
<a href="#">Contact</a>
|
||||
<a href="#">About</a>
|
||||
|
||||
<a></a>
|
||||
<a href="#">Report Bug</a>
|
||||
<a href="#">Legal</a>
|
||||
|
||||
<a></a>
|
||||
<a href="#">Feedback</a>
|
||||
<a href="#">Privacy</a>
|
||||
|
||||
<a></a>
|
||||
<a></a>
|
||||
<a href="#">Cookies</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
76
components/Markdown.tsx
Normal file
76
components/Markdown.tsx
Normal file
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
import PropTypes from "prop-types";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import oneDark from "react-syntax-highlighter/dist/esm/styles/prism/one-dark";
|
||||
import oneLight from "react-syntax-highlighter/dist/esm/styles/prism/one-light";
|
||||
import styles from "../styles/modules/markdown.module.scss";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import remarkGemoji from "remark-gemoji";
|
||||
import remarkStringify from "remark-stringify";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { formatTextToUrlName } from "../utils";
|
||||
|
||||
function flatten(text, child) {
|
||||
return typeof child === "string" ? text + child : React.Children.toArray(child.props.children).reduce(flatten, text);
|
||||
}
|
||||
|
||||
function HeadingRenderer({ children, level }) {
|
||||
children = React.Children.toArray(children);
|
||||
const text = children.reduce(flatten, "");
|
||||
return React.createElement("h" + level, { id: formatTextToUrlName(text) }, children);
|
||||
}
|
||||
|
||||
export default function Markdown({ value }: { value: any }) {
|
||||
return (
|
||||
<div>
|
||||
<ReactMarkdown
|
||||
className={styles.markdown}
|
||||
//@ts-ignore
|
||||
remarkPlugins={[remarkGfm, remarkGemoji, remarkStringify]}
|
||||
//@ts-ignore
|
||||
components={{
|
||||
h1: HeadingRenderer,
|
||||
h2: HeadingRenderer,
|
||||
h3: HeadingRenderer,
|
||||
h4: HeadingRenderer,
|
||||
h5: HeadingRenderer,
|
||||
h6: HeadingRenderer,
|
||||
code({ node, inline, className, children, ...props }) {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
return !inline ? (
|
||||
<>
|
||||
<div className={styles.toolbar}>
|
||||
<div
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(String(children).replace(/\n$/, ""));
|
||||
}}
|
||||
className={styles.copyBtn}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
|
||||
<path d="M502.6 70.63l-61.25-61.25C435.4 3.371 427.2 0 418.7 0H255.1c-35.35 0-64 28.66-64 64l.0195 256C192 355.4 220.7 384 256 384h192c35.2 0 64-28.8 64-64V93.25C512 84.77 508.6 76.63 502.6 70.63zM464 320c0 8.836-7.164 16-16 16H255.1c-8.838 0-16-7.164-16-16L239.1 64.13c0-8.836 7.164-16 16-16h128L384 96c0 17.67 14.33 32 32 32h47.1V320zM272 448c0 8.836-7.164 16-16 16H63.1c-8.838 0-16-7.164-16-16L47.98 192.1c0-8.836 7.164-16 16-16H160V128H63.99c-35.35 0-64 28.65-64 64l.0098 256C.002 483.3 28.66 512 64 512h192c35.2 0 64-28.8 64-64v-32h-47.1L272 448z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SyntaxHighlighter style={oneDark} language={match ? match[1] : ""} PreTag="div" {...props}>
|
||||
{String(children).replace(/\n$/, "")}
|
||||
</SyntaxHighlighter>
|
||||
</>
|
||||
) : (
|
||||
<code>{children}</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{value}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Markdown.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
135
components/Nav.tsx
Normal file
135
components/Nav.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
"use client";
|
||||
import styles from "../styles/modules/Nav.module.scss";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Category } from "@prisma/client";
|
||||
|
||||
function switchTheme(theme) {
|
||||
const bodyElement = document.getElementsByTagName("body")[0];
|
||||
|
||||
if (theme == "dark") {
|
||||
bodyElement.classList.remove("theme-light");
|
||||
} else {
|
||||
bodyElement.classList.add("theme-light");
|
||||
}
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
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;
|
||||
}
|
||||
|
||||
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: 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();
|
||||
|
||||
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(() => {
|
||||
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"
|
||||
onClick={() => {
|
||||
window.open("/", "_self");
|
||||
}}
|
||||
className={styles.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>
|
||||
);
|
||||
}
|
||||
28
components/Sidebar.tsx
Normal file
28
components/Sidebar.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import React from "react";
|
||||
import styles from "../styles/modules/Sidebar.module.scss";
|
||||
export default function Sidebar() {
|
||||
return (
|
||||
<div className={styles.sidebar}>
|
||||
<div className={styles.stickyContainer}>
|
||||
<div className={styles.sidebarContainer}>
|
||||
<h3>Popular</h3>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
</div>
|
||||
|
||||
<div className={styles.sidebarContainer}>
|
||||
<h3>Related</h3>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
<a href="#"> Set up Docker</a>
|
||||
</div>
|
||||
<div className={styles.adContainer}>Future advertisement</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user