feat(blog): migrate to nextJS
This commit is contained in:
parent
62b34c9c48
commit
badaa09950
38 changed files with 2226 additions and 445 deletions
|
@ -1,14 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
root: true,
|
root: true,
|
||||||
env: { browser: true, es2020: true },
|
extends: ["next/core-web-vitals", "@developomp-site/eslint-config"],
|
||||||
extends: [
|
|
||||||
"plugin:react-hooks/recommended",
|
|
||||||
"@developomp-site/eslint-config",
|
|
||||||
],
|
|
||||||
parserOptions: { ecmaVersion: "latest", sourceType: "module" },
|
|
||||||
plugins: ["react-refresh"],
|
|
||||||
rules: {
|
rules: {
|
||||||
"react-refresh/only-export-components": "warn",
|
|
||||||
"react-hooks/exhaustive-deps": "off",
|
"react-hooks/exhaustive-deps": "off",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
35
apps/blog/.gitignore
vendored
Normal file
35
apps/blog/.gitignore
vendored
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
|
@ -1,36 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" class="dark">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<meta name="theme-color" content="#000000" />
|
|
||||||
<meta name="description" content="developomp's Blog" />
|
|
||||||
<title>pomp's blog</title>
|
|
||||||
|
|
||||||
<!-- OpenGraph -->
|
|
||||||
<meta property="og:title" content="pomp's blog" />
|
|
||||||
<meta property="og:site_name" content="developomp's Blog" />
|
|
||||||
<meta property="og:description" content="developomp's Blog" />
|
|
||||||
<meta property="og:type" content="website" />
|
|
||||||
<meta property="og:url" content="https://blog.developomp.com" />
|
|
||||||
<meta
|
|
||||||
property="og:image"
|
|
||||||
content="https://blog.developomp.com/favicon.svg"
|
|
||||||
/>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body class="overflow-x-hidden overflow-y-scroll">
|
|
||||||
<noscript>
|
|
||||||
<figure>
|
|
||||||
<img src="/img/nojs.avif" alt="No javascript?" />
|
|
||||||
<figcaption>
|
|
||||||
Image compressed down to 4.5kB because you probably have
|
|
||||||
potato internet :D
|
|
||||||
</figcaption>
|
|
||||||
</figure>
|
|
||||||
</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
8
apps/blog/next.config.js
Normal file
8
apps/blog/next.config.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: "export",
|
||||||
|
distDir: "build",
|
||||||
|
images: { unoptimized: true },
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
|
@ -2,49 +2,46 @@
|
||||||
"name": "@developomp-site/blog",
|
"name": "@developomp-site/blog",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "open-cli http://localhost:3000 && next dev",
|
||||||
"build": "vite build",
|
"build": "next build",
|
||||||
"lint": "eslint .",
|
"lint": "next lint",
|
||||||
"preview": "vite preview",
|
|
||||||
"clean": "rm -rf .turbo build node_modules"
|
"clean": "rm -rf .turbo build node_modules"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"devDependencies": {
|
||||||
"@developomp-site/content": "workspace:*",
|
"@developomp-site/content": "workspace:*",
|
||||||
|
"@developomp-site/eslint-config": "workspace:*",
|
||||||
|
"@developomp-site/prettier-config": "workspace:*",
|
||||||
|
"@developomp-site/tailwind-config": "workspace:*",
|
||||||
"@fontsource/noto-sans-kr": "^5.0.5",
|
"@fontsource/noto-sans-kr": "^5.0.5",
|
||||||
"@fontsource/source-code-pro": "^5.0.5",
|
"@fontsource/source-code-pro": "^5.0.5",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
"@fortawesome/free-brands-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
"@fortawesome/free-solid-svg-icons": "^6.4.0",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@kunukn/react-collapse": "^2.2.10",
|
"@kunukn/react-collapse": "^2.2.10",
|
||||||
"highlight.js": "^11.8.0",
|
|
||||||
"hoofd": "^1.7.0",
|
|
||||||
"katex": "^0.16.8",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"wouter": "^2.11.0",
|
|
||||||
"zustand": "^4.3.9"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@developomp-site/eslint-config": "workspace:*",
|
|
||||||
"@developomp-site/prettier-config": "workspace:*",
|
|
||||||
"@developomp-site/tailwind-config": "workspace:*",
|
|
||||||
"@types/highlight.js": "^10.1.0",
|
"@types/highlight.js": "^10.1.0",
|
||||||
"@types/katex": "^0.16.0",
|
"@types/katex": "^0.16.2",
|
||||||
"@types/react": "^18.2.14",
|
"@types/node": "20.4.5",
|
||||||
|
"@types/react": "18.2.17",
|
||||||
"@types/react-collapse": "^5.0.1",
|
"@types/react-collapse": "^5.0.1",
|
||||||
"@types/react-dom": "^18.2.6",
|
"@types/react-dom": "18.2.7",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.61.0",
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
"@typescript-eslint/parser": "^5.61.0",
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
"@vitejs/plugin-react": "^4.0.2",
|
"autoprefixer": "10.4.14",
|
||||||
"eslint": "^8.44.0",
|
"eslint": "8.45.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-config-next": "13.4.12",
|
||||||
"eslint-plugin-react-refresh": "^0.4.2",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
"highlight.js": "^11.8.0",
|
||||||
"tailwindcss": "^3.3.2",
|
"katex": "^0.16.8",
|
||||||
"typescript": "^5.1.6",
|
"next": "13.4.12",
|
||||||
"vite": "^4.4.2",
|
"open-cli": "^7.2.0",
|
||||||
"vite-plugin-dynamic-import": "^1.5.0"
|
"postcss": "8.4.27",
|
||||||
|
"prettier": "^2.8.8",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.4.1",
|
||||||
|
"react": "18.2.0",
|
||||||
|
"react-dom": "18.2.0",
|
||||||
|
"tailwindcss": "3.3.3",
|
||||||
|
"typescript": "5.1.6",
|
||||||
|
"zustand": "^4.3.9"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
export default {
|
module.exports = {
|
||||||
plugins: {
|
plugins: {
|
||||||
tailwindcss: {},
|
tailwindcss: {},
|
||||||
autoprefixer: {},
|
autoprefixer: {},
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { useTitleTemplate } from "hoofd"
|
|
||||||
import { Route, Switch } from "wouter"
|
|
||||||
|
|
||||||
import Footer from "@/components/Footer"
|
|
||||||
import Header from "@/components/Header"
|
|
||||||
import Loading from "@/components/Loading"
|
|
||||||
import Home from "@/pages/Home"
|
|
||||||
import NotFound from "@/pages/NotFound"
|
|
||||||
import Page from "@/pages/Page"
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
useTitleTemplate("pomp's blog | %s")
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Header />
|
|
||||||
<main className="mx-auto mb-8 mt-20 flex w-full max-w-screen-mobile grow flex-col items-center gap-8 px-4">
|
|
||||||
<Switch>
|
|
||||||
<Route path="/">
|
|
||||||
<Home />
|
|
||||||
</Route>
|
|
||||||
{/* <Route path="/search">
|
|
||||||
<Search />
|
|
||||||
</Route> */}
|
|
||||||
<Route path="/404">
|
|
||||||
<NotFound />
|
|
||||||
</Route>
|
|
||||||
<Route path="/loading">
|
|
||||||
<Loading />
|
|
||||||
</Route>
|
|
||||||
<Route>
|
|
||||||
<Page />
|
|
||||||
</Route>
|
|
||||||
</Switch>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App
|
|
|
@ -1,19 +1,17 @@
|
||||||
import { useTitle } from "hoofd"
|
"use client"
|
||||||
|
|
||||||
import { type ReactNode, useEffect, useState } from "react"
|
import { type ReactNode, useEffect, useState } from "react"
|
||||||
|
|
||||||
import PostCard from "@/components/PostCard"
|
import PostCard from "@/components/PostCard"
|
||||||
|
import ShowMoreButton from "@/components/ShowMoreButton"
|
||||||
import contentMap from "@/contentMap"
|
import contentMap from "@/contentMap"
|
||||||
|
|
||||||
import ShowMoreButton from "./ShowMoreButton"
|
|
||||||
|
|
||||||
const totalPosts = Object.keys(contentMap.posts).length
|
const totalPosts = Object.keys(contentMap.posts).length
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [howMany, setHowMany] = useState(5)
|
const [howMany, setHowMany] = useState(5)
|
||||||
const [postCards, setPostCards] = useState<ReactNode[]>([])
|
const [postCards, setPostCards] = useState<ReactNode[]>([])
|
||||||
|
|
||||||
useTitle("Home")
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const postCards: ReactNode[] = []
|
const postCards: ReactNode[] = []
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
faListUl,
|
faListUl,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { Link } from "wouter"
|
import Link from "next/link"
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
seriesHome: string
|
seriesHome: string
|
||||||
|
@ -20,7 +20,7 @@ export default function SeriesControlButtons({
|
||||||
return (
|
return (
|
||||||
<div className="mb-5 flex justify-between">
|
<div className="mb-5 flex justify-between">
|
||||||
{prevURL ? (
|
{prevURL ? (
|
||||||
<Link to={prevURL}>
|
<Link href={prevURL}>
|
||||||
<button className="button">
|
<button className="button">
|
||||||
<FontAwesomeIcon icon={faArrowLeft} />
|
<FontAwesomeIcon icon={faArrowLeft} />
|
||||||
</button>
|
</button>
|
||||||
|
@ -31,14 +31,14 @@ export default function SeriesControlButtons({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Link to={seriesHome}>
|
<Link href={seriesHome}>
|
||||||
<button className="button">
|
<button className="button">
|
||||||
<FontAwesomeIcon icon={faListUl} />
|
<FontAwesomeIcon icon={faListUl} />
|
||||||
</button>
|
</button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
{nextURL ? (
|
{nextURL ? (
|
||||||
<Link to={nextURL}>
|
<Link href={nextURL}>
|
||||||
<button className="button">
|
<button className="button">
|
||||||
<FontAwesomeIcon icon={faArrowRight} />
|
<FontAwesomeIcon icon={faArrowRight} />
|
||||||
</button>
|
</button>
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import "./Toc.scss"
|
import "./Toc.scss"
|
||||||
|
|
||||||
import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
|
import { faCaretDown, faCaretUp } from "@fortawesome/free-solid-svg-icons"
|
||||||
|
@ -10,8 +12,9 @@ interface Props {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Toc({ data }: Props) {
|
export default function Toc({ data }: Props) {
|
||||||
const [isTocOpened, setIsTocOpened] = useState(
|
const [isTocOpened, setIsTocOpened] = useState<boolean>(
|
||||||
localStorage.getItem("isTocOpened") === "true"
|
typeof window !== "undefined" &&
|
||||||
|
localStorage.getItem("isTocOpened") === "true"
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
|
@ -2,27 +2,47 @@ import type { PageData } from "@developomp-site/content/src/types/types"
|
||||||
|
|
||||||
import contentMap from "@/contentMap"
|
import contentMap from "@/contentMap"
|
||||||
|
|
||||||
|
import { Params } from "./page"
|
||||||
|
|
||||||
export enum PageType {
|
export enum PageType {
|
||||||
POST,
|
POST,
|
||||||
SERIES,
|
SERIES,
|
||||||
SERIES_HOME,
|
SERIES_HOME,
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchContent(content_id: string) {
|
export interface Data {
|
||||||
|
pageData: PageData
|
||||||
|
pageType: PageType
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getData(params: Params): Promise<Data> {
|
||||||
|
const contentID = `/${params.category}/${params.slug.join("/")}`
|
||||||
|
|
||||||
|
const content = await fetchContent(contentID)
|
||||||
|
const pageType = categorizePageType(contentID) || PageType.POST
|
||||||
|
const pageData = parsePageData(content, pageType, contentID)
|
||||||
|
|
||||||
|
return {
|
||||||
|
pageData,
|
||||||
|
pageType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchContent(contentID: string) {
|
||||||
try {
|
try {
|
||||||
return await import(
|
return await import(
|
||||||
`@developomp-site/content/dist/content${content_id}.json`
|
`@developomp-site/content/dist/content${contentID}.json`
|
||||||
)
|
)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function categorizePageType(content_id: string): PageType | undefined {
|
export function categorizePageType(contentID: string): PageType | undefined {
|
||||||
if (content_id.startsWith("/post")) return PageType.POST
|
if (contentID.startsWith("/post")) return PageType.POST
|
||||||
if (content_id.startsWith("/series")) {
|
if (contentID.startsWith("/series")) {
|
||||||
// if the URL looks like /series/series-title (if the url has two slashes)
|
// if the URL looks like /series/series-title (if the url has two slashes)
|
||||||
if ([...(content_id.match(/\//g) || [])].length == 2)
|
if ([...(contentID.match(/\//g) || [])].length == 2)
|
||||||
return PageType.SERIES_HOME
|
return PageType.SERIES_HOME
|
||||||
|
|
||||||
// if the URL looks like /series/series-title/post-title (if the url does not have 2 slashes)
|
// if the URL looks like /series/series-title/post-title (if the url does not have 2 slashes)
|
|
@ -1,61 +1,66 @@
|
||||||
import "./Page.scss"
|
import "./Page.scss"
|
||||||
|
|
||||||
import type { PageData } from "@developomp-site/content/src/types/types"
|
import { type Metadata } from "next"
|
||||||
import { useMeta, useTitle } from "hoofd"
|
import { type ParsedUrlQuery } from "querystring"
|
||||||
import { useEffect, useState } from "react"
|
|
||||||
import { useLocation } from "wouter"
|
|
||||||
|
|
||||||
|
import NotFound, { metadata as notFoundMetadata } from "@/app/not-found"
|
||||||
import Card from "@/components/Card"
|
import Card from "@/components/Card"
|
||||||
import Loading from "@/components/Loading"
|
|
||||||
import PostCard from "@/components/PostCard"
|
import PostCard from "@/components/PostCard"
|
||||||
import Tag from "@/components/Tag"
|
import Tag from "@/components/Tag"
|
||||||
import TagList from "@/components/TagList"
|
import TagList from "@/components/TagList"
|
||||||
import contentMap from "@/contentMap"
|
import contentMap from "@/contentMap"
|
||||||
|
|
||||||
import NotFound from "../NotFound"
|
import { getData, PageType } from "./helper"
|
||||||
import {
|
|
||||||
categorizePageType,
|
|
||||||
fetchContent,
|
|
||||||
PageType,
|
|
||||||
parsePageData,
|
|
||||||
} from "./helper"
|
|
||||||
import Meta from "./Meta"
|
import Meta from "./Meta"
|
||||||
import SeriesControlButtons from "./SeriesControlButtons"
|
import SeriesControlButtons from "./SeriesControlButtons"
|
||||||
import Toc from "./Toc"
|
import Toc from "./Toc"
|
||||||
|
|
||||||
export default function Page() {
|
export interface Params extends ParsedUrlQuery {
|
||||||
const [location] = useLocation()
|
category: "posts" | "series"
|
||||||
const [pageData, setPageData] = useState<PageData | undefined>(undefined)
|
slug: string[]
|
||||||
const [pageType, setPageType] = useState<PageType>(PageType.POST)
|
}
|
||||||
const [isLoading, setLoading] = useState(true)
|
|
||||||
|
|
||||||
useTitle(pageData?.title || "Loading")
|
interface Props {
|
||||||
useMeta({ property: "og:title", content: pageData?.title })
|
params: Params
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
export async function generateStaticParams(): Promise<Params[]> {
|
||||||
setPageData(undefined)
|
return Object.keys(contentMap.posts).map((key) => {
|
||||||
setLoading(true)
|
const contentID = key.replace(/\/$/, "") // remove trailing slash
|
||||||
|
const parts = contentID
|
||||||
|
.split("/") // /a/b/c/ => ['', 'a', 'b', 'c', '']
|
||||||
|
.filter((x) => x) // ['', 'a', 'b', 'c', ''] => ['a', 'b', 'c']
|
||||||
|
|
||||||
const content_id = location.replace(/\/$/, "") // remove trailing slash
|
const category = parts[0]
|
||||||
|
if (category !== "posts" && category !== "series")
|
||||||
|
throw "Invalid Page Type"
|
||||||
|
|
||||||
fetchContent(content_id).then((fetched_content) => {
|
const slug = parts.slice(1) // ['a', 'b', 'c'] => ['b', 'c']
|
||||||
const pageType = categorizePageType(content_id)
|
|
||||||
|
|
||||||
// stop loading without setting pageData so 404 page will display
|
return { category, slug }
|
||||||
if (!fetched_content || pageType === undefined) {
|
})
|
||||||
setLoading(false)
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setPageData(parsePageData(fetched_content, pageType, content_id))
|
export async function generateMetadata({ params }: Props): Promise<Metadata> {
|
||||||
setPageType(pageType)
|
if (params.category != "posts" && params.category != "series")
|
||||||
setLoading(false)
|
return notFoundMetadata
|
||||||
})
|
|
||||||
}, [location])
|
|
||||||
|
|
||||||
if (isLoading) return <Loading />
|
const { pageData } = await getData(params)
|
||||||
|
|
||||||
if (!pageData) return <NotFound />
|
return {
|
||||||
|
metadataBase: new URL("https://blog.developomp.com"),
|
||||||
|
title: pageData.title,
|
||||||
|
openGraph: {
|
||||||
|
title: pageData.title,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function Page({ params }: Props) {
|
||||||
|
if (params.category != "posts" && params.category != "series")
|
||||||
|
return <NotFound />
|
||||||
|
|
||||||
|
const { pageData, pageType } = await getData(params)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
84
apps/blog/src/app/layout.tsx
Normal file
84
apps/blog/src/app/layout.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import "@fortawesome/fontawesome-svg-core/styles.css"
|
||||||
|
import "@fontsource/noto-sans-kr/400.css"
|
||||||
|
import "@fontsource/noto-sans-kr/700.css"
|
||||||
|
import "@fontsource/source-code-pro"
|
||||||
|
import "katex/dist/katex.min.css"
|
||||||
|
import "./globals.css"
|
||||||
|
import "../styles/global.scss"
|
||||||
|
import "../styles/anchor.scss"
|
||||||
|
import "../styles/blockQuote.scss"
|
||||||
|
import "../styles/button.scss"
|
||||||
|
import "../styles/callout.scss"
|
||||||
|
import "../styles/checkbox.scss"
|
||||||
|
import "../styles/code.scss"
|
||||||
|
import "../styles/colorChip.scss"
|
||||||
|
import "../styles/heading.scss"
|
||||||
|
import "../styles/hr.scss"
|
||||||
|
import "../styles/img.scss"
|
||||||
|
import "../styles/katex.scss"
|
||||||
|
import "../styles/kbd.scss"
|
||||||
|
import "../styles/list.scss"
|
||||||
|
import "../styles/mark.scss"
|
||||||
|
import "../styles/scrollbar.scss"
|
||||||
|
import "../styles/subSup.scss"
|
||||||
|
import "../styles/table.scss"
|
||||||
|
|
||||||
|
import { type Metadata } from "next"
|
||||||
|
import Image from "next/image"
|
||||||
|
|
||||||
|
import Footer from "@/components/Footer"
|
||||||
|
import Header from "@/components/Header"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL("https://blog.developomp.com"),
|
||||||
|
title: {
|
||||||
|
template: "pomp's blog | %s",
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
description: "developomp's Blog",
|
||||||
|
openGraph: {
|
||||||
|
title: "pomp's blog",
|
||||||
|
siteName: "developomp's Blog",
|
||||||
|
description: "developomp's Blog",
|
||||||
|
type: "website",
|
||||||
|
url: "https://blog.developomp.com",
|
||||||
|
images: "https://blog.developomp.com/favicon.svg",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en" className="dark">
|
||||||
|
<head>
|
||||||
|
<link
|
||||||
|
rel="shortcut icon"
|
||||||
|
type="image/svg+xml"
|
||||||
|
href="favicon.svg"
|
||||||
|
/>
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body className="overflow-x-hidden overflow-y-scroll">
|
||||||
|
<noscript>
|
||||||
|
<figure>
|
||||||
|
<Image src="/img/nojs.avif" alt="No javascript?" />
|
||||||
|
<figcaption>
|
||||||
|
Image compressed down to 4.5kB because you probably
|
||||||
|
have potato internet :D
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<main className="mx-auto mb-8 mt-20 flex w-full max-w-screen-mobile grow flex-col items-center gap-8 px-4">
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,11 +1,16 @@
|
||||||
import { useMeta, useTitle } from "hoofd"
|
import { type Metadata } from "next"
|
||||||
|
|
||||||
import Card from "@/components/Card"
|
import Card from "@/components/Card"
|
||||||
|
|
||||||
export default function NotFound() {
|
export const metadata: Metadata = {
|
||||||
useTitle("404")
|
metadataBase: new URL("https://blog.developomp.com"),
|
||||||
useMeta({ property: "og:title", content: "pomp's blog | Page Not Found" })
|
title: "404",
|
||||||
|
openGraph: {
|
||||||
|
title: "pomp's blog | Page Not Found",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<Card className="items-center gap-4">
|
<Card className="items-center gap-4">
|
||||||
<h1 className="text-7xl">404</h1>
|
<h1 className="text-7xl">404</h1>
|
12
apps/blog/src/app/page.tsx
Normal file
12
apps/blog/src/app/page.tsx
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { Metadata } from "next"
|
||||||
|
|
||||||
|
import Home from "./Home"
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL("https://blog.developomp.com"),
|
||||||
|
title: "pomp's blog | Home",
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
|
return <Home />
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { Link } from "wouter"
|
import Image from "next/image"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
import ReadProgress from "./ReadProgress"
|
import ReadProgress from "./ReadProgress"
|
||||||
import ThemeToggleButton from "./ThemeToggleButton"
|
import ThemeToggleButton from "./ThemeToggleButton"
|
||||||
|
@ -7,18 +8,17 @@ export default function Header() {
|
||||||
return (
|
return (
|
||||||
<header className="fixed z-50 h-16 w-full bg-light-ui shadow-lg dark:bg-dark-ui">
|
<header className="fixed z-50 h-16 w-full bg-light-ui shadow-lg dark:bg-dark-ui">
|
||||||
<div className="mx-auto flex h-[60px] max-w-screen-desktop items-center justify-between">
|
<div className="mx-auto flex h-[60px] max-w-screen-desktop items-center justify-between">
|
||||||
<Link to="/">
|
<Link
|
||||||
<a
|
aria-label="homepage"
|
||||||
aria-label="homepage"
|
href="/"
|
||||||
className="ml-4 h-10 cursor-pointer"
|
className="ml-4 h-10 cursor-pointer"
|
||||||
>
|
>
|
||||||
<img
|
<Image
|
||||||
width="40px"
|
src="/favicon.svg"
|
||||||
height="40px"
|
alt="logo"
|
||||||
src="/favicon.svg"
|
width={40}
|
||||||
alt="logo"
|
height={40}
|
||||||
/>
|
/>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex h-full">
|
<div className="flex h-full">
|
||||||
<ThemeToggleButton />
|
<ThemeToggleButton />
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import { useCallback, useEffect, useState } from "react"
|
"use client"
|
||||||
import { useLocation } from "wouter"
|
|
||||||
|
|
||||||
const st = "scrollTop"
|
import { usePathname } from "next/navigation"
|
||||||
const sh = "scrollHeight"
|
import { useCallback, useEffect, useState } from "react"
|
||||||
const h = document.documentElement
|
|
||||||
const b = document.body
|
|
||||||
|
|
||||||
// https://stackoverflow.com/a/8028584/12979111
|
// https://stackoverflow.com/a/8028584/12979111
|
||||||
function calculateScrollPercent() {
|
function calculateScrollPercent() {
|
||||||
|
const st = "scrollTop"
|
||||||
|
const sh = "scrollHeight"
|
||||||
|
const h = document.documentElement
|
||||||
|
const b = document.body
|
||||||
|
|
||||||
return ((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100
|
return ((h[st] || b[st]) / ((h[sh] || b[sh]) - h.clientHeight)) * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ReadProgress() {
|
export default function ReadProgress() {
|
||||||
const [scroll, setScroll] = useState(0)
|
const [scroll, setScroll] = useState(0)
|
||||||
const [location] = useLocation()
|
const pathname = usePathname()
|
||||||
const scrollHandler = useCallback(() => {
|
const scrollHandler = useCallback(() => {
|
||||||
setScroll(calculateScrollPercent())
|
setScroll(calculateScrollPercent())
|
||||||
}, [])
|
}, [])
|
||||||
|
@ -43,7 +45,7 @@ export default function ReadProgress() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
scrollHandler()
|
scrollHandler()
|
||||||
}, 100)
|
}, 100)
|
||||||
}, [location])
|
}, [pathname])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-1 bg-light-scroll-progress-bg dark:bg-dark-scroll-progress-bg">
|
<div className="h-1 bg-light-scroll-progress-bg dark:bg-dark-scroll-progress-bg">
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
import { faSearch } from "@fortawesome/free-solid-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { Link } from "wouter"
|
import Link from "next/link"
|
||||||
|
|
||||||
export default function SearchButton() {
|
export default function SearchButton() {
|
||||||
return (
|
return (
|
||||||
<Link to="/search" aria-label="go to search page">
|
<Link
|
||||||
<a className="flex w-20 cursor-pointer items-center justify-center text-light-text-default hover:bg-light-ui-hover hover:text-light-text-default dark:text-dark-text-default dark:hover:bg-dark-ui-hover dark:hover:text-dark-text-default">
|
href="/search"
|
||||||
<FontAwesomeIcon icon={faSearch} />
|
aria-label="go to search page"
|
||||||
</a>
|
className="flex w-20 cursor-pointer items-center justify-center text-light-text-default hover:bg-light-ui-hover hover:text-light-text-default dark:text-dark-text-default dark:hover:bg-dark-ui-hover dark:hover:text-dark-text-default"
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={faSearch} />
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import { faMoon, faSun } from "@fortawesome/free-solid-svg-icons"
|
import { faMoon, faSun } from "@fortawesome/free-solid-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
|
|
||||||
|
@ -15,7 +17,7 @@ export default function ThemeToggleButton() {
|
||||||
aria-label="theme toggle"
|
aria-label="theme toggle"
|
||||||
>
|
>
|
||||||
{theme === Theme.Dark ? (
|
{theme === Theme.Dark ? (
|
||||||
<FontAwesomeIcon icon={faMoon} />
|
<FontAwesomeIcon icon={faMoon} size={"1x"} />
|
||||||
) : (
|
) : (
|
||||||
<FontAwesomeIcon icon={faSun} />
|
<FontAwesomeIcon icon={faSun} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import {
|
||||||
faHourglass,
|
faHourglass,
|
||||||
} from "@fortawesome/free-solid-svg-icons"
|
} from "@fortawesome/free-solid-svg-icons"
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||||
import { Link } from "wouter"
|
import Link from "next/link"
|
||||||
|
|
||||||
import Card from "@/components/Card"
|
import Card from "@/components/Card"
|
||||||
import Tag from "@/components/Tag"
|
import Tag from "@/components/Tag"
|
||||||
|
@ -25,42 +25,40 @@ export default function PostCard({ postData, className }: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={content_id} className={`${className} w-full`}>
|
<Link href={content_id} className={`${className} w-full`}>
|
||||||
<a className="w-full">
|
<Card className="w-full cursor-pointer fill-light-text-gray text-light-text-gray hover:shadow-glow dark:fill-dark-text-gray dark:text-dark-text-gray">
|
||||||
<Card className="w-full cursor-pointer fill-light-text-gray text-light-text-gray hover:shadow-glow dark:fill-dark-text-gray dark:text-dark-text-gray">
|
<h2 className="mb-8 text-3xl">
|
||||||
<h2 className="mb-8 text-3xl">
|
{title}
|
||||||
{title}
|
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
||||||
{/* show "(series)" for urls that matches regex "/series/<series-title>" */}
|
{/\/series\/[^/]*$/.test(content_id) && " (series)"}
|
||||||
{/\/series\/[^/]*$/.test(content_id) && " (series)"}
|
</h2>
|
||||||
</h2>
|
<small>
|
||||||
<small>
|
<TagList>
|
||||||
<TagList>
|
{tags &&
|
||||||
{tags &&
|
tags.map((tag) => (
|
||||||
tags.map((tag) => (
|
<Tag key={title + tag} text={tag} />
|
||||||
<Tag key={title + tag} text={tag} />
|
))}
|
||||||
))}
|
</TagList>
|
||||||
</TagList>
|
<hr />
|
||||||
<hr />
|
<div className="flex flex-wrap items-center gap-x-4">
|
||||||
<div className="flex flex-wrap items-center gap-x-4">
|
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
<FontAwesomeIcon icon={faCalendar} />
|
||||||
<FontAwesomeIcon icon={faCalendar} />
|
{date || "Unknown date"}
|
||||||
{date || "Unknown date"}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
|
||||||
<FontAwesomeIcon icon={faBook} />
|
|
||||||
{readTime
|
|
||||||
? readTime + " read"
|
|
||||||
: "unknown read time"}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2 whitespace-nowrap">
|
|
||||||
<FontAwesomeIcon icon={faHourglass} />
|
|
||||||
{typeof wordCount === "number"
|
|
||||||
? wordCount + " words"
|
|
||||||
: "unknown length"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</small>
|
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||||
</Card>
|
<FontAwesomeIcon icon={faBook} />
|
||||||
</a>
|
{readTime
|
||||||
|
? readTime + " read"
|
||||||
|
: "unknown read time"}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2 whitespace-nowrap">
|
||||||
|
<FontAwesomeIcon icon={faHourglass} />
|
||||||
|
{typeof wordCount === "number"
|
||||||
|
? wordCount + " words"
|
||||||
|
: "unknown length"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</small>
|
||||||
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import "@fontsource/noto-sans-kr/400.css"
|
|
||||||
import "@fontsource/noto-sans-kr/700.css"
|
|
||||||
import "@fontsource/source-code-pro"
|
|
||||||
import "katex/dist/katex.min.css"
|
|
||||||
import "./index.css"
|
|
||||||
import "./styles/anchor.scss"
|
|
||||||
import "./styles/blockQuote.scss"
|
|
||||||
import "./styles/button.scss"
|
|
||||||
import "./styles/callout.scss"
|
|
||||||
import "./styles/checkbox.scss"
|
|
||||||
import "./styles/code.scss"
|
|
||||||
import "./styles/colorChip.scss"
|
|
||||||
import "./styles/global.scss"
|
|
||||||
import "./styles/heading.scss"
|
|
||||||
import "./styles/hr.scss"
|
|
||||||
import "./styles/img.scss"
|
|
||||||
import "./styles/katex.scss"
|
|
||||||
import "./styles/kbd.scss"
|
|
||||||
import "./styles/list.scss"
|
|
||||||
import "./styles/mark.scss"
|
|
||||||
import "./styles/scrollbar.scss"
|
|
||||||
import "./styles/subSup.scss"
|
|
||||||
import "./styles/table.scss"
|
|
||||||
|
|
||||||
import ReactDOM from "react-dom/client"
|
|
||||||
|
|
||||||
import App from "@/App.tsx"
|
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
||||||
<App />
|
|
||||||
)
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Home from "./Home"
|
|
||||||
|
|
||||||
export default Home
|
|
|
@ -1,3 +0,0 @@
|
||||||
import NotFound from "./NotFound"
|
|
||||||
|
|
||||||
export default NotFound
|
|
|
@ -1,3 +0,0 @@
|
||||||
import Page from "./Page"
|
|
||||||
|
|
||||||
export default Page
|
|
|
@ -1,6 +1,5 @@
|
||||||
html,
|
html,
|
||||||
body,
|
body {
|
||||||
#root {
|
|
||||||
/* style */
|
/* style */
|
||||||
|
|
||||||
@apply flex flex-col;
|
@apply flex flex-col;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
"use client"
|
||||||
|
|
||||||
import { create } from "zustand"
|
import { create } from "zustand"
|
||||||
|
|
||||||
const themeKey = "theme"
|
const themeKey = "theme"
|
||||||
|
@ -15,6 +17,8 @@ export type ThemeState = {
|
||||||
* Reads site theme setting from local storage
|
* Reads site theme setting from local storage
|
||||||
*/
|
*/
|
||||||
function getStoredThemeSetting(): Theme {
|
function getStoredThemeSetting(): Theme {
|
||||||
|
if (typeof window === "undefined") return Theme.Dark
|
||||||
|
|
||||||
const storedTheme = localStorage.getItem(themeKey)
|
const storedTheme = localStorage.getItem(themeKey)
|
||||||
|
|
||||||
// fix invalid values
|
// fix invalid values
|
||||||
|
@ -40,6 +44,8 @@ function setTheme(targetTheme: Theme) {
|
||||||
* Applies tailwind theme using classes based on current theme setting
|
* Applies tailwind theme using classes based on current theme setting
|
||||||
*/
|
*/
|
||||||
function applyTheme() {
|
function applyTheme() {
|
||||||
|
if (typeof window === "undefined") return
|
||||||
|
|
||||||
if (getStoredThemeSetting() === Theme.Dark) {
|
if (getStoredThemeSetting() === Theme.Dark) {
|
||||||
document.documentElement.classList.add("dark")
|
document.documentElement.classList.add("dark")
|
||||||
} else {
|
} else {
|
||||||
|
@ -48,12 +54,14 @@ function applyTheme() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useTheme = create<ThemeState>()((set) => {
|
export const useTheme = create<ThemeState>()((set) => {
|
||||||
applyTheme()
|
if (typeof window !== "undefined") {
|
||||||
|
|
||||||
addEventListener("storage", () => {
|
|
||||||
setTheme(getStoredThemeSetting())
|
|
||||||
applyTheme()
|
applyTheme()
|
||||||
})
|
|
||||||
|
addEventListener("storage", () => {
|
||||||
|
setTheme(getStoredThemeSetting())
|
||||||
|
applyTheme()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: getStoredThemeSetting(),
|
theme: getStoredThemeSetting(),
|
||||||
|
|
1
apps/blog/src/vite-env.d.ts
vendored
1
apps/blog/src/vite-env.d.ts
vendored
|
@ -1 +0,0 @@
|
||||||
/// <reference types="vite/client" />
|
|
|
@ -1,5 +1,9 @@
|
||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
presets: [require("@developomp-site/tailwind-config/tailwind.config.js")],
|
presets: [require("@developomp-site/tailwind-config/tailwind.config.js")],
|
||||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
content: [
|
||||||
|
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,42 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"target": "es5",
|
||||||
"useDefineForClassFields": true,
|
"lib": [
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
"dom",
|
||||||
"module": "ESNext",
|
"dom.iterable",
|
||||||
"skipLibCheck": true,
|
"esnext"
|
||||||
|
],
|
||||||
/* Bundler mode */
|
"allowJs": true,
|
||||||
"moduleResolution": "bundler",
|
"skipLibCheck": true,
|
||||||
"allowImportingTsExtensions": true,
|
"strict": true,
|
||||||
"resolveJsonModule": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"isolatedModules": true,
|
"noEmit": true,
|
||||||
"noEmit": true,
|
"esModuleInterop": true,
|
||||||
"jsx": "react-jsx",
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
/* Linting */
|
"resolveJsonModule": true,
|
||||||
"strict": true,
|
"isolatedModules": true,
|
||||||
"noUnusedLocals": true,
|
"jsx": "preserve",
|
||||||
"noUnusedParameters": true,
|
"incremental": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"plugins": [
|
||||||
|
{
|
||||||
/* Absolute import */
|
"name": "next"
|
||||||
"baseUrl": "./src",
|
}
|
||||||
"paths": {
|
],
|
||||||
"@/*": ["./*"]
|
"paths": {
|
||||||
}
|
"@/*": [
|
||||||
},
|
"./src/*"
|
||||||
"include": ["src"],
|
]
|
||||||
"references": [{ "path": "./tsconfig.node.json" }]
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"build/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"composite": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowSyntheticDefaultImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
import react from "@vitejs/plugin-react"
|
|
||||||
import path from "path"
|
|
||||||
import { defineConfig } from "vite"
|
|
||||||
import dynamicImport from "vite-plugin-dynamic-import"
|
|
||||||
// https://vitejs.dev/config/
|
|
||||||
export default defineConfig(() => ({
|
|
||||||
plugins: [react(), dynamicImport()],
|
|
||||||
build: {
|
|
||||||
outDir: "build",
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
alias: [{ find: "@", replacement: path.resolve(__dirname, "src") }],
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
port: 3000,
|
|
||||||
open: true,
|
|
||||||
},
|
|
||||||
}))
|
|
|
@ -16,12 +16,6 @@
|
||||||
"target": "blog",
|
"target": "blog",
|
||||||
"cleanUrls": true,
|
"cleanUrls": true,
|
||||||
"public": "apps/blog/build",
|
"public": "apps/blog/build",
|
||||||
"rewrites": [
|
|
||||||
{
|
|
||||||
"source": "**",
|
|
||||||
"destination": "/index.html"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"ignore": ["**/.*"]
|
"ignore": ["**/.*"]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
1935
pnpm-lock.yaml
generated
1935
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue