From 79a8f498c59ad0d8b0de456574c4a7ce4b4e8ce7 Mon Sep 17 00:00:00 2001 From: Anton <62949848+icepaq@users.noreply.github.com> Date: Tue, 15 Mar 2022 22:49:41 -0400 Subject: [PATCH 01/63] render posts server side --- client/package.json | 2 ++ client/pages/_app.tsx | 6 ++++++ client/pages/post/[id].tsx | 42 ++++++++++++++++++++++++++++++++++++-- client/yarn.lock | 10 +++++++++ 4 files changed, 58 insertions(+), 2 deletions(-) diff --git a/client/package.json b/client/package.json index 59fa0b18..0d194d17 100644 --- a/client/package.json +++ b/client/package.json @@ -12,9 +12,11 @@ "@fec/remark-a11y-emoji": "^3.1.0", "@geist-ui/core": "^2.3.5", "@geist-ui/icons": "^1.0.1", + "@types/cookie": "^0.4.1", "@types/js-cookie": "^3.0.1", "client-zip": "^2.0.0", "comlink": "^4.3.1", + "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", "next": "12.1.0", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 91888263..d3bf55d3 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -13,6 +13,12 @@ export type ThemeProps = { changeTheme: () => void } +export type PostProps = { + renderedPost: any | null, // Still don't have an official data type for posts + theme: "light" | "dark" | string, + changeTheme: () => void +} + type AppProps

= { pageProps: P; } & Omit, "pageProps">; diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 115b69fb..edfebf5c 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -6,13 +6,16 @@ import { useEffect, useState } from "react"; import Document from '../../components/document' import Header from "../../components/header"; import VisibilityBadge from "../../components/visibility-badge"; -import { ThemeProps } from "../_app"; +import { PostProps } from "../_app"; import PageSeo from "components/page-seo"; import Head from "next/head"; import styles from './styles.module.css'; import Cookies from "js-cookie"; +import cookie from "cookie"; +import { GetServerSideProps } from "next"; -const Post = ({ theme, changeTheme }: ThemeProps) => { + +const Post = ({renderedPost, theme, changeTheme}: PostProps) => { const [post, setPost] = useState() const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState() @@ -21,6 +24,14 @@ const Post = ({ theme, changeTheme }: ThemeProps) => { useEffect(() => { async function fetchPost() { setIsLoading(true); + + if (renderedPost) { + console.log('Using Server Side Post'); + setPost(renderedPost) + setIsLoading(false) + return; + } + if (router.query.id) { const post = await fetch(`/server-api/posts/${router.query.id}`, { method: "GET", @@ -108,4 +119,31 @@ const Post = ({ theme, changeTheme }: ThemeProps) => { ) } +export const getServerSideProps: GetServerSideProps = async (context) => { + + const headers = context.req.headers; + const host = headers.host; + const driftToken = cookie.parse(headers.cookie || '')[`drift-token`]; + + let post; + + if (context.query.id) { + post = await fetch('http://' + host + `/server-api/posts/${context.query.id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${driftToken}` + } + }).then(res => res.json()); + + console.log(post); + } + + return { + props: { + renderedPost: post + } + } +} + export default Post diff --git a/client/yarn.lock b/client/yarn.lock index 28d7f557..1286cb15 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -161,6 +161,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== +"@types/cookie@^0.4.1": + version "0.4.1" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" + integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== + "@types/debug@^4.0.0": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -532,6 +537,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +cookie@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" + integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== + core-js-pure@^3.20.2: version "3.21.1" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" From 7364eb668bbe6f5ff8642b8f03394fa854f013a0 Mon Sep 17 00:00:00 2001 From: Anton <62949848+icepaq@users.noreply.github.com> Date: Wed, 16 Mar 2022 18:54:04 -0400 Subject: [PATCH 02/63] remove client side post fetch --- client/pages/post/[id].tsx | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index edfebf5c..f3a2da33 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -26,35 +26,16 @@ const Post = ({renderedPost, theme, changeTheme}: PostProps) => { setIsLoading(true); if (renderedPost) { - console.log('Using Server Side Post'); setPost(renderedPost) setIsLoading(false) + return; } - if (router.query.id) { - const post = await fetch(`/server-api/posts/${router.query.id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${Cookies.get("drift-token")}` - } - }) - - if (post.ok) { - const res = await post.json() - if (res) - setPost(res) - else - setError("Post not found") - } else { - if (post.status.toString().startsWith("4")) { - router.push("/signin") - } else { - setError(post.statusText) - } - } - setIsLoading(false) + if (post.status.toString().startsWith("4")) { + router.push("/signin") + } else { + setError(post.statusText) } } fetchPost() From ac9027c522d4ac3f23a9bf69a51148cda3ac5c08 Mon Sep 17 00:00:00 2001 From: Anton <62949848+icepaq@users.noreply.github.com> Date: Wed, 16 Mar 2022 18:57:40 -0400 Subject: [PATCH 03/63] fix wrong post check --- client/pages/post/[id].tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index f3a2da33..2e5d69ad 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -25,17 +25,17 @@ const Post = ({renderedPost, theme, changeTheme}: PostProps) => { async function fetchPost() { setIsLoading(true); - if (renderedPost) { + if (renderedPost.ok) { setPost(renderedPost) setIsLoading(false) return; } - if (post.status.toString().startsWith("4")) { + if (renderedPost.status.toString().startsWith("4")) { router.push("/signin") } else { - setError(post.statusText) + setError(renderedPost.statusText) } } fetchPost() From 3ac9cbcf4e57696442c2af2f63dc3b285810f4a8 Mon Sep 17 00:00:00 2001 From: Anton <62949848+icepaq@users.noreply.github.com> Date: Wed, 16 Mar 2022 18:58:43 -0400 Subject: [PATCH 04/63] remove console.log --- client/pages/post/[id].tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 2e5d69ad..b523d688 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -116,8 +116,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { "Authorization": `Bearer ${driftToken}` } }).then(res => res.json()); - - console.log(post); } return { From e646df43f2c0c2791da1c49d34812887af80dd3b Mon Sep 17 00:00:00 2001 From: Anton <62949848+icepaq@users.noreply.github.com> Date: Wed, 16 Mar 2022 19:21:22 -0400 Subject: [PATCH 05/63] clean up post check --- client/pages/post/[id].tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index b523d688..374130f7 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -25,17 +25,17 @@ const Post = ({renderedPost, theme, changeTheme}: PostProps) => { async function fetchPost() { setIsLoading(true); - if (renderedPost.ok) { + if (renderedPost) { setPost(renderedPost) setIsLoading(false) return; } - if (renderedPost.status.toString().startsWith("4")) { - router.push("/signin") + if (!Cookies.get('drift-token')) { + router.push('/signin'); } else { - setError(renderedPost.statusText) + setError('Post Error'); } } fetchPost() From c720b929ce1b36f7cd68dd0e0d7b98c2109872d9 Mon Sep 17 00:00:00 2001 From: Anton <62949848+icepaq@users.noreply.github.com> Date: Sat, 19 Mar 2022 20:15:17 -0400 Subject: [PATCH 06/63] specify renderPost, new error message, try await --- client/pages/post/[id].tsx | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 374130f7..b471dcd5 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -16,7 +16,7 @@ import { GetServerSideProps } from "next"; const Post = ({renderedPost, theme, changeTheme}: PostProps) => { - const [post, setPost] = useState() + const [post, setPost] = useState(renderedPost); const [isLoading, setIsLoading] = useState(true) const [error, setError] = useState() const router = useRouter(); @@ -35,7 +35,7 @@ const Post = ({renderedPost, theme, changeTheme}: PostProps) => { if (!Cookies.get('drift-token')) { router.push('/signin'); } else { - setError('Post Error'); + setError('Something went wrong fetching the post'); } } fetchPost() @@ -115,7 +115,14 @@ export const getServerSideProps: GetServerSideProps = async (context) => { "Content-Type": "application/json", "Authorization": `Bearer ${driftToken}` } - }).then(res => res.json()); + }); + + try { + post = await post.json(); + } catch (e) { + console.log(e); + post = null; + } } return { From 9ba17db6f9f75ed8c386e5615213ae74f16594df Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 20 Mar 2022 20:45:37 -0700 Subject: [PATCH 07/63] client: improve responsiveness --- client/components/document/index.tsx | 1 + client/components/post-list/list-item.tsx | 10 +++---- client/components/preview/preview.module.css | 2 -- .../preview/react-markdown-preview.tsx | 2 +- client/pages/post/[id].tsx | 9 ++++--- client/pages/post/styles.module.css | 26 ++++++++++++++----- 6 files changed, 32 insertions(+), 18 deletions(-) diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index 4b1a08ff..a1040541 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -114,6 +114,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init label="Filename" disabled={!editable} width={"100%"} + id={title} /> {remove && editable && diff --git a/client/pages/post/styles.module.css b/client/pages/post/styles.module.css index 5bcfe224..9691f87f 100644 --- a/client/pages/post/styles.module.css +++ b/client/pages/post/styles.module.css @@ -1,11 +1,23 @@ .header { - display: flex; - justify-content: space-between; - align-items: center; + display: flex; + justify-content: space-between; + align-items: center; +} + +.header .titleAndBadge { + display: flex; + text-align: center; + justify-content: space-between; + align-items: center; } @media screen and (max-width: 650px) { - .header { - flex-direction: column; - } -} \ No newline at end of file + .header { + flex-direction: column; + } + + .header .titleAndBadge { + flex-direction: column; + padding-bottom: var(--gap-double); + } +} From 921f219c5a3e39a1dc68fde5bf0be42795dad9ff Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 20 Mar 2022 20:54:31 -0700 Subject: [PATCH 08/63] client: add focus styling for file upload area --- .../new-post/drag-and-drop/drag-and-drop.module.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/client/components/new-post/drag-and-drop/drag-and-drop.module.css b/client/components/new-post/drag-and-drop/drag-and-drop.module.css index 1277720d..bacc5ff7 100644 --- a/client/components/new-post/drag-and-drop/drag-and-drop.module.css +++ b/client/components/new-post/drag-and-drop/drag-and-drop.module.css @@ -18,10 +18,14 @@ border-radius: 2px; border-style: dashed; outline: none; - transition: border 0.24s ease-in-out; + transition: all 0.24s ease-in-out; cursor: pointer; } +.dropzone:focus { + box-shadow: 0 0 4px 1px rgba(124, 124, 124, 0.5); +} + .error { color: red; font-size: 0.8rem; From 2fbcb41cdd34cced35c68d070df3c5e0dc984a11 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Sun, 20 Mar 2022 21:18:38 -0700 Subject: [PATCH 09/63] client: clean-up drag and drop code and set post title if unset --- .../new-post/drag-and-drop/index.tsx | 21 +++++++------------ client/components/new-post/index.tsx | 21 +++++++++++++++++-- client/components/new-post/title/index.tsx | 11 +++++----- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/client/components/new-post/drag-and-drop/index.tsx b/client/components/new-post/drag-and-drop/index.tsx index eaaab5b6..08973db7 100644 --- a/client/components/new-post/drag-and-drop/index.tsx +++ b/client/components/new-post/drag-and-drop/index.tsx @@ -94,12 +94,12 @@ const allowedFileExtensions = [ 'webmanifest', ] -function FileDropzone({ setDocs, docs }: { setDocs: React.Dispatch>, docs: Document[] }) { +function FileDropzone({ setDocs }: { setDocs: ((docs: Document[]) => void) }) { const { palette } = useTheme() const { setToast } = useToasts() - const onDrop = useCallback(async (acceptedFiles) => { - const newDocs = await Promise.all(acceptedFiles.map((file: File) => { - return new Promise((resolve, reject) => { + const onDrop = async (acceptedFiles: File[]) => { + const newDocs = await Promise.all(acceptedFiles.map((file) => { + return new Promise((resolve, reject) => { const reader = new FileReader() reader.onabort = () => setToast({ text: 'File reading was aborted', type: 'error' }) @@ -116,15 +116,8 @@ function FileDropzone({ setDocs, docs }: { setDocs: React.Dispatch [...oldDocs, ...newDocs]) - }, [setDocs, setToast, docs]) + setDocs(newDocs) + } const validator = (file: File) => { // TODO: make this configurable @@ -170,7 +163,7 @@ function FileDropzone({ setDocs, docs }: { setDocs: React.Dispatch {fileRejections.length > 0 &&

} diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 12c8a30b..6e41a42f 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -63,10 +63,27 @@ const Post = () => { setDocs(docs.map((doc) => doc.id === id ? { ...doc, content } : doc)) }, [docs]) + const uploadDocs = (files: Document[]) => { + // if no title is set and the only document is empty, + const isFirstDocEmpty = docs.length === 1 && docs[0].title === '' && docs[0].content === '' + const shouldSetTitle = !title && isFirstDocEmpty + console.log(shouldSetTitle, title, isFirstDocEmpty) + if (shouldSetTitle) { + if (files.length === 1) { + setTitle(files[0].title) + } else if (files.length > 1) { + setTitle('Uploaded files') + } + } + + if (isFirstDocEmpty) setDocs(files) + else setDocs([...docs, ...files]) + } + return (
- - <FileDropzone docs={docs} setDocs={setDocs} /> + <Title title={title} handleChange={(e) => setTitle(e.target.value)} /> + <FileDropzone setDocs={uploadDocs} /> { docs.map(({ id }) => { const doc = docs.find((doc) => doc.id === id) diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index 3ad765ab..dae79ac5 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,5 +1,5 @@ +import { ChangeEvent, memo } from 'react' import { Text, Input } from '@geist-ui/core' -import { memo } from 'react' import ShiftBy from '@components/shift-by' import styles from '../post.module.css' @@ -14,18 +14,19 @@ const titlePlaceholders = [ ] type props = { - setTitle: (title: string) => void + handleChange?: (event: ChangeEvent<HTMLInputElement>) => void title?: string } -const Title = ({ setTitle, title }: props) => { +const Title = ({ handleChange, title }: props) => { + return (<div className={styles.title}> <Text h1 width={"150px"} className={styles.drift}>Drift</Text> <ShiftBy y={-3}> <Input placeholder={titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]} value={title || ""} - onChange={(event) => setTitle(event.target.value)} + onChange={handleChange} height={"55px"} font={1.5} label="Post title" @@ -35,4 +36,4 @@ const Title = ({ setTitle, title }: props) => { </div>) } -export default memo(Title) \ No newline at end of file +export default Title \ No newline at end of file From 3f8511e0c1a9d610ceff977786f15a5c0c14afaf Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Sun, 20 Mar 2022 21:43:04 -0700 Subject: [PATCH 10/63] client: stop unnecessary title re-renders in /new --- .../components/new-post/drag-and-drop/index.tsx | 4 ++-- client/components/new-post/index.tsx | 16 ++++++++-------- client/components/new-post/title/index.tsx | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/client/components/new-post/drag-and-drop/index.tsx b/client/components/new-post/drag-and-drop/index.tsx index 08973db7..dfae57ce 100644 --- a/client/components/new-post/drag-and-drop/index.tsx +++ b/client/components/new-post/drag-and-drop/index.tsx @@ -1,5 +1,5 @@ import { Button, Text, useTheme, useToasts } from '@geist-ui/core' -import { useCallback, useEffect } from 'react' +import { memo, useCallback, useEffect } from 'react' import { useDropzone } from 'react-dropzone' import styles from './drag-and-drop.module.css' import { Document } from '../' @@ -170,4 +170,4 @@ function FileDropzone({ setDocs }: { setDocs: ((docs: Document[]) => void) }) { ) } -export default FileDropzone \ No newline at end of file +export default memo(FileDropzone) \ No newline at end of file diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 6e41a42f..373c62f3 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -24,6 +24,7 @@ const Post = () => { content: '', id: generateUUID() }]) + const [isSubmitting, setSubmitting] = useState(false) const remove = (id: string) => { @@ -63,11 +64,10 @@ const Post = () => { setDocs(docs.map((doc) => doc.id === id ? { ...doc, content } : doc)) }, [docs]) - const uploadDocs = (files: Document[]) => { + const uploadDocs = useCallback((files: Document[]) => { // if no title is set and the only document is empty, const isFirstDocEmpty = docs.length === 1 && docs[0].title === '' && docs[0].content === '' const shouldSetTitle = !title && isFirstDocEmpty - console.log(shouldSetTitle, title, isFirstDocEmpty) if (shouldSetTitle) { if (files.length === 1) { setTitle(files[0].title) @@ -78,15 +78,15 @@ const Post = () => { if (isFirstDocEmpty) setDocs(files) else setDocs([...docs, ...files]) - } + }, [docs, title]) + return ( <div> - <Title title={title} handleChange={(e) => setTitle(e.target.value)} /> + <Title title={title} setTitle={setTitle} /> <FileDropzone setDocs={uploadDocs} /> { - docs.map(({ id }) => { - const doc = docs.find((doc) => doc.id === id) + docs.map(({ content, id, title }) => { return ( <Document remove={() => remove(id)} @@ -94,8 +94,8 @@ const Post = () => { editable={true} setContent={(content) => updateContent(content, id)} setTitle={(title) => updateTitle(title, id)} - content={doc?.content} - title={doc?.title} + content={content} + title={title} /> ) }) diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index dae79ac5..d9b2302a 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -14,11 +14,11 @@ const titlePlaceholders = [ ] type props = { - handleChange?: (event: ChangeEvent<HTMLInputElement>) => void + setTitle: (title: string) => void title?: string } -const Title = ({ handleChange, title }: props) => { +const Title = ({ setTitle, title }: props) => { return (<div className={styles.title}> <Text h1 width={"150px"} className={styles.drift}>Drift</Text> @@ -26,7 +26,7 @@ const Title = ({ handleChange, title }: props) => { <Input placeholder={titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]} value={title || ""} - onChange={handleChange} + onChange={(event) => setTitle(event.target.value)} height={"55px"} font={1.5} label="Post title" @@ -36,4 +36,4 @@ const Title = ({ handleChange, title }: props) => { </div>) } -export default Title \ No newline at end of file +export default memo(Title) \ No newline at end of file From c57e0d669252e0de7ddf4d88a1b506a1cb5a1aa9 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Sun, 20 Mar 2022 22:34:42 -0700 Subject: [PATCH 11/63] client: fix logging out with new cookie auth --- client/components/new-post/index.tsx | 1 - client/lib/hooks/use-signed-in.ts | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 373c62f3..65b9f5a1 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -80,7 +80,6 @@ const Post = () => { else setDocs([...docs, ...files]) }, [docs, title]) - return ( <div> <Title title={title} setTitle={setTitle} /> diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index 2ad7f7cf..b8cedc94 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -6,7 +6,10 @@ import Cookies from 'js-cookie' const useSignedIn = ({ redirectIfNotAuthed = false }: { redirectIfNotAuthed?: boolean }) => { const [isSignedIn, setSignedIn] = useSharedState('isSignedIn', false) const [isLoading, setLoading] = useSharedState('isLoading', true) - const signout = useCallback(() => setSignedIn(false), [setSignedIn]) + const signout = useCallback(() => { + Cookies.remove('drift-token') + setSignedIn(false) + }, [setSignedIn]) const router = useRouter(); if (redirectIfNotAuthed && !isLoading && isSignedIn === false) { From e2c5e2dac9be34b197a219356dae0e3af4d70133 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Sun, 20 Mar 2022 23:09:38 -0700 Subject: [PATCH 12/63] server: enable sqlite3 database and document env vars --- README.md | 19 ++++++++++++++++++- server/.gitignore | 3 ++- server/lib/sequelize.ts | 9 +++++---- server/src/server.ts | 3 +-- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index c28b727b..3d7a4438 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,23 @@ You can run `yarn dev` in either / both folders to start the server and client w If you're deploying the front-end to something like Vercel, you'll need to set the root folder to `client/`. +### Environment Variables + +You can change these to your liking. + +`client/.env`: + +- `API_URL`: defaults to localhost:3001, but allows you to host the front-end separately from the backend on a service like Vercel or Netlify +- `WELCOME_CONTENT`: a markdown string (with \n newlines) that's rendered on the home page +- `WELCOME_TITLE`: the file title for the post on the homepage. + +`server/.env`: + +- `PORT`: the default port to start the server on (3000 by default) +- `ENV`: can be `production` or `debug`, toggles logging +- `JWT_SECRET`: a secure token for JWT tokens. You can generate one [here](https://www.grc.com/passwords.htm). +- `MEMORY_DB`: if "true", a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. + ## Current status Drift is a major work in progress. Below is a (rough) list of completed and envisioned features. If you want to help address any of them, please let me know regardless of your experience and I'll be happy to assist. @@ -34,7 +51,7 @@ Drift is a major work in progress. Below is a (rough) list of completed and envi - [ ] SSO via HTTP header (Issue: [#11](https://github.com/MaxLeiter/Drift/issues/11)) - [x] downloading files (individually and entire posts) - [ ] password protected posts -- [ ] sqlite database (should be very easy to set-up; the ORM is just currently set to memory for ease of development) +- [x] sqlite database - [ ] non-node backend - [ ] administrator account / settings - [ ] docker-compose (PR: [#13](https://github.com/MaxLeiter/Drift/pull/13)) diff --git a/server/.gitignore b/server/.gitignore index cfbfa63b..2a5caafa 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,3 +1,4 @@ .env node_modules/ -dist/ \ No newline at end of file +dist/ +drift.sqlite \ No newline at end of file diff --git a/server/lib/sequelize.ts b/server/lib/sequelize.ts index 7d53dd05..f364c3bf 100644 --- a/server/lib/sequelize.ts +++ b/server/lib/sequelize.ts @@ -1,8 +1,9 @@ -import {Sequelize} from 'sequelize-typescript'; +import { Sequelize } from 'sequelize-typescript'; export const sequelize = new Sequelize({ dialect: 'sqlite', - database: 'movies', - storage: ':memory:', - models: [__dirname + '/models'] + database: 'drift', + storage: process.env.MEMORY_DB === "true" ? ":memory:" : __dirname + './../drift.sqlite', + models: [__dirname + '/models'], + host: 'localhost', }); diff --git a/server/src/server.ts b/server/src/server.ts index 503dcdba..2e96ea8d 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,8 +4,7 @@ import config from '../lib/config'; import { sequelize } from '../lib/sequelize'; (async () => { - await sequelize.sync({ force: true }); - + await sequelize.sync(); createServer(app) .listen( config.port, From c4cd55f4e6d247dff96e0c86a50d3898c89ace3e Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Sun, 20 Mar 2022 23:27:09 -0700 Subject: [PATCH 13/63] server/client: add registration password and env vars --- README.md | 3 ++- client/components/auth/index.tsx | 40 +++++++++++++++++++++++++++----- server/src/routes/auth.ts | 21 +++++++++++++++-- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 3d7a4438..e5b42585 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,8 @@ You can change these to your liking. - `PORT`: the default port to start the server on (3000 by default) - `ENV`: can be `production` or `debug`, toggles logging - `JWT_SECRET`: a secure token for JWT tokens. You can generate one [here](https://www.grc.com/passwords.htm). -- `MEMORY_DB`: if "true", a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. +- `MEMORY_DB`: if `true`, a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. +- `REGISTRATION_PASSWORD`: if MEMORY_DB is not `true`, the user will be required to provide this password to sign-up, in addition to their username and account password. If it's not set, no password will be required. ## Current status diff --git a/client/components/auth/index.tsx b/client/components/auth/index.tsx index 1db9c549..c43111d8 100644 --- a/client/components/auth/index.tsx +++ b/client/components/auth/index.tsx @@ -1,4 +1,4 @@ -import { FormEvent, useState } from 'react' +import { FormEvent, useEffect, useState } from 'react' import { Button, Input, Text, Note } from '@geist-ui/core' import styles from './auth.module.css' import { useRouter } from 'next/router' @@ -13,10 +13,29 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); + const [serverPassword, setServerPassword] = useState(''); const [errorMsg, setErrorMsg] = useState(''); - + const [requiresServerPassword, setRequiresServerPassword] = useState(false); const signingIn = page === 'signin' + useEffect(() => { + async function fetchRequiresPass() { + if (!signingIn) { + const resp = await fetch("/server-api/auth/requires-passcode", { + method: "GET", + }) + if (resp.ok) { + const res = await resp.json() + setRequiresServerPassword(res) + } else { + setErrorMsg("Something went wrong.") + } + } + } + fetchRequiresPass() + }, [page, signingIn]) + + const handleJson = (json: any) => { Cookies.set('drift-token', json.token); Cookies.set('drift-userid', json.userId); @@ -24,10 +43,10 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { router.push('/') } - const handleSubmit = async (e: FormEvent<HTMLFormElement>) => { e.preventDefault() - if (page === "signup" && (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)) return setErrorMsg(ERROR_MESSAGE) + if (!signingIn && (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6)) return setErrorMsg(ERROR_MESSAGE) + if (!signingIn && requiresServerPassword && !NO_EMPTY_SPACE_REGEX.test(serverPassword)) return setErrorMsg(ERROR_MESSAGE) else setErrorMsg(''); const reqOpts = { @@ -35,14 +54,13 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }) + body: JSON.stringify({ username, password, serverPassword }) } try { const signUrl = signingIn ? '/server-api/auth/signin' : '/server-api/auth/signup'; const resp = await fetch(signUrl, reqOpts); const json = await resp.json(); - console.log(json) if (!resp.ok) throw new Error(json.error.message); handleJson(json) @@ -78,6 +96,16 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { required scale={4 / 3} /> + {requiresServerPassword && <Input + htmlType='password' + id="server-password" + value={serverPassword} + onChange={(event) => setServerPassword(event.target.value)} + placeholder="Server Password" + required + scale={4 / 3} + />} + <Button type="success" htmlType="submit">{signingIn ? 'Sign In' : 'Sign Up'}</Button> </div> <div className={styles.formContentSpace}> diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 7a3aa7e7..049445c0 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -7,17 +7,26 @@ import jwt from '../../lib/middleware/jwt' const NO_EMPTY_SPACE_REGEX = /^\S*$/ +export const requiresServerPassword = (process.env.MEMORY_DB || process.env.ENV === 'production') && !!process.env.REGISTRATION_PASSWORD +console.log(`Registration password required: ${requiresServerPassword}`) + export const auth = Router() -const validateAuthPayload = (username: string, password: string): void => { +const validateAuthPayload = (username: string, password: string, serverPassword?: string): void => { if (!NO_EMPTY_SPACE_REGEX.test(username) || password.length < 6) { throw new Error("Authentication data does not fulfill requirements") } + + if (requiresServerPassword) { + if (!serverPassword || process.env.REGISTRATION_PASSWORD !== serverPassword) { + throw new Error("Server password is incorrect. Please contact the server administrator.") + } + } } auth.post('/signup', async (req, res, next) => { try { - validateAuthPayload(req.body.username, req.body.password) + validateAuthPayload(req.body.username, req.body.password, req.body.serverPassword) const username = req.body.username.toLowerCase(); @@ -69,6 +78,14 @@ auth.post('/signin', async (req, res, next) => { } }); +auth.get('/requires-passcode', async (req, res, next) => { + if (requiresServerPassword) { + res.status(200).json({ requiresPasscode: true }); + } else { + res.status(200).json({ requiresPasscode: false }); + } +}) + function generateAccessToken(id: string) { return sign({ id: id }, config.jwt_secret, { expiresIn: '2d' }); } From 594e903fe4835d1595d353eacb5874046c8ac317 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Sun, 20 Mar 2022 23:30:50 -0700 Subject: [PATCH 14/63] README: update disclaimer on current status for production use --- README.md | 2 +- client/components/auth/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e5b42585..74a7f5cd 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ You can run `yarn dev` in either / both folders to start the server and client w ### Production -**Note: Drift is not yet ready for production usage and should not be used seriously until the database has been setup, which I'll get to when the server API is semi stable.** +**Note: Drift is not yet ready for production usage and should not be used too seriously. I'll make every effort to not lose data, but I won't make any guarantees until the project is further along.** `yarn build` in both `client/` and `server/` will produce production code for the client and server respectively. The client and server each also have Dockerfiles which you can use with a docker-compose (an example compose will be provided in the near future). diff --git a/client/components/auth/index.tsx b/client/components/auth/index.tsx index c43111d8..43b1d28f 100644 --- a/client/components/auth/index.tsx +++ b/client/components/auth/index.tsx @@ -26,7 +26,7 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { }) if (resp.ok) { const res = await resp.json() - setRequiresServerPassword(res) + setRequiresServerPassword(res.requiresPasscode) } else { setErrorMsg("Something went wrong.") } From a5e4c0ef7503625ba130153ad8e51d706074c3e4 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 00:46:15 -0700 Subject: [PATCH 15/63] client: use next middleware for redirects/rewrites based on auth; make preview 100% height always --- client/components/document/index.tsx | 3 ++- client/pages/_middleware.tsx | 31 ++++++++++++++++++++++++++++ client/pages/index.tsx | 1 - 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 client/pages/_middleware.tsx diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index a1040541..35a4b3ef 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -49,7 +49,8 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => { const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit', skeleton, id }: Props) => { const codeEditorRef = useRef<HTMLTextAreaElement>(null) const [tab, setTab] = useState(initialTab) - const height = editable ? "500px" : '100%' + // const height = editable ? "500px" : '100%' + const height = "100%"; const handleTabChange = (newTab: string) => { if (newTab === 'edit') { diff --git a/client/pages/_middleware.tsx b/client/pages/_middleware.tsx new file mode 100644 index 00000000..85b29366 --- /dev/null +++ b/client/pages/_middleware.tsx @@ -0,0 +1,31 @@ +import { NextFetchEvent, NextRequest, NextResponse } from 'next/server' + +const PUBLIC_FILE = /.(.*)$/ + +export function middleware(req: NextRequest, ev: NextFetchEvent) { + const pathname = req.nextUrl.pathname + // const isPageRequest = + // !PUBLIC_FILE.test(req.nextUrl.pathname) && + // !req.nextUrl.pathname.startsWith('/api') && + // // header added when next/link pre-fetches a route + // !req.headers.get('x-middleware-preflight') + + // If you're signed in we replace the home page with the new post page + if (pathname === '/') { + if (req.cookies['drift-token']) { + return NextResponse.rewrite(new URL(`/new`, req.url).href) + } + // If you're not signed in we redirect the new post page to the home page + } else if (pathname === '/new') { + if (!req.cookies['drift-token']) { + return NextResponse.redirect(new URL(`/`, req.url).href) + } + // If you're signed in we redirect the sign in page to the home page (which is the new page) + } else if (pathname === '/signin') { + if (req.cookies['drift-token']) { + return NextResponse.redirect(new URL(`/`, req.url).href) + } + } + + return NextResponse.next() +} diff --git a/client/pages/index.tsx b/client/pages/index.tsx index 9d59334f..771d175b 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -27,7 +27,6 @@ const Home = ({ theme, changeTheme, introContent }: Props) => { <Page className={styles.container} width="100%"> <PageSeo /> - <Page.Header> <Header theme={theme} changeTheme={changeTheme} /> </Page.Header> From abe419dabac3875955b20da691839aad08e85cfd Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 01:15:37 -0700 Subject: [PATCH 16/63] client: add signout route --- client/pages/_middleware.tsx | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/client/pages/_middleware.tsx b/client/pages/_middleware.tsx index 85b29366..d397e5af 100644 --- a/client/pages/_middleware.tsx +++ b/client/pages/_middleware.tsx @@ -4,26 +4,35 @@ const PUBLIC_FILE = /.(.*)$/ export function middleware(req: NextRequest, ev: NextFetchEvent) { const pathname = req.nextUrl.pathname + const signedIn = req.cookies['drift-token'] + const getURL = (pageName: string) => new URL(`/${pageName}`, req.url).href // const isPageRequest = // !PUBLIC_FILE.test(req.nextUrl.pathname) && // !req.nextUrl.pathname.startsWith('/api') && // // header added when next/link pre-fetches a route // !req.headers.get('x-middleware-preflight') - // If you're signed in we replace the home page with the new post page - if (pathname === '/') { - if (req.cookies['drift-token']) { - return NextResponse.rewrite(new URL(`/new`, req.url).href) + if (pathname === '/signout') { + // If you're signed in we remove the cookie and redirect to the home page + // If you're not signed in we redirect to the home page + if (signedIn) { + const resp = NextResponse.redirect(getURL('')); + resp.clearCookie('drift-token'); + return resp + } + } else if (pathname === '/') { + if (signedIn) { + return NextResponse.rewrite(getURL('new')) } // If you're not signed in we redirect the new post page to the home page } else if (pathname === '/new') { - if (!req.cookies['drift-token']) { - return NextResponse.redirect(new URL(`/`, req.url).href) + if (!signedIn) { + return NextResponse.redirect(getURL('')) } // If you're signed in we redirect the sign in page to the home page (which is the new page) } else if (pathname === '/signin') { - if (req.cookies['drift-token']) { - return NextResponse.redirect(new URL(`/`, req.url).href) + if (signedIn) { + return NextResponse.redirect(getURL('')) } } From bf878473af50066dcbcb9cf2141eb518e729ec23 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 01:36:31 -0700 Subject: [PATCH 17/63] client: change all auth redirects to middleware --- client/components/header/index.tsx | 40 +++-------------------- client/lib/hooks/use-signed-in.ts | 52 +++++++----------------------- client/pages/_middleware.tsx | 8 ++++- client/pages/new.tsx | 10 +++--- 4 files changed, 27 insertions(+), 83 deletions(-) diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx index e5a69135..d8feabce 100644 --- a/client/components/header/index.tsx +++ b/client/components/header/index.tsx @@ -5,7 +5,6 @@ import { useEffect, useMemo, useState } from "react"; import styles from './header.module.css'; import { useRouter } from "next/router"; import useSignedIn from "../../lib/hooks/use-signed-in"; -import Cookies from 'js-cookie' type Tab = { name: string @@ -23,7 +22,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => { const [expanded, setExpanded] = useState<boolean>(false) const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) const isMobile = useMediaQuery('xs', { match: 'down' }) - const { isLoading, isSignedIn, signout } = useSignedIn({ redirectIfNotAuthed: false }) + const isSignedIn = useSignedIn() const [pages, setPages] = useState<Tab[]>([]) useEffect(() => { @@ -42,7 +41,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => { name: "Home", href: "/", icon: <HomeIcon />, - condition: true, + condition: !isSignedIn, value: "home" }, { @@ -59,34 +58,9 @@ const Header = ({ changeTheme, theme }: DriftProps) => { condition: isSignedIn, value: "mine" }, - // { - // name: "Settings", - // href: "/settings", - // icon: <SettingsIcon />, - // condition: isSignedIn - // }, { name: "Sign out", - onClick: () => { - if (typeof window !== 'undefined') { - localStorage.clear(); - - // // send token to API blacklist - // fetch('/api/auth/signout', { - // method: 'POST', - // headers: { - // 'Content-Type': 'application/json' - // }, - // body: JSON.stringify({ - // token: Cookies.get("drift-token") - // }) - // }) - - signout(); - router.push("/signin"); - } - }, - href: "#signout", + href: "/signout", icon: <SignoutIcon />, condition: isSignedIn, value: "signout" @@ -126,12 +100,8 @@ const Header = ({ changeTheme, theme }: DriftProps) => { } ] - if (isLoading) { - return setPages([]) - } - setPages(pageList.filter(page => page.condition)) - }, [changeTheme, isLoading, isMobile, isSignedIn, router, signout, theme]) + }, [changeTheme, isMobile, isSignedIn, theme]) // useEffect(() => { // setSelectedTab(pages.find((page) => { @@ -161,7 +131,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => { hideDivider hideBorder onChange={onTabChange}> - {!isLoading && pages.map((tab) => { + {pages.map((tab) => { return <Tabs.Item font="14px" label={<>{tab.icon} {tab.name}</>} diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index b8cedc94..c19faeb4 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -1,48 +1,18 @@ -import { useRouter } from "next/router"; -import { useCallback, useEffect } from "react" -import useSharedState from "./use-shared-state"; -import Cookies from 'js-cookie' +import Cookies from "js-cookie"; +import { useEffect, useState } from "react"; -const useSignedIn = ({ redirectIfNotAuthed = false }: { redirectIfNotAuthed?: boolean }) => { - const [isSignedIn, setSignedIn] = useSharedState('isSignedIn', false) - const [isLoading, setLoading] = useSharedState('isLoading', true) - const signout = useCallback(() => { - Cookies.remove('drift-token') - setSignedIn(false) - }, [setSignedIn]) - - const router = useRouter(); - if (redirectIfNotAuthed && !isLoading && isSignedIn === false) { - router.push('/signin') - } +const useSignedIn = () => { + const [signedIn, setSignedIn] = useState(typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); useEffect(() => { - async function checkToken() { - const token = Cookies.get('drift-token') - if (token) { - const response = await fetch('/server-api/auth/verify-token', { - method: 'GET', - headers: { - 'Authorization': `Bearer ${token}` - } - }) - if (response.ok) { - setSignedIn(true) - } - } - setLoading(false) + if (Cookies.get("drift-token")) { + setSignedIn(true); + } else { + setSignedIn(false); } - setLoading(true) - checkToken() + }, []); - const interval = setInterval(() => { - checkToken() - }, 10000); - - return () => clearInterval(interval); - }, [setLoading, setSignedIn]) - - return { isSignedIn, isLoading, signout } + return signedIn; } -export default useSignedIn +export default useSignedIn; diff --git a/client/pages/_middleware.tsx b/client/pages/_middleware.tsx index d397e5af..a7909c91 100644 --- a/client/pages/_middleware.tsx +++ b/client/pages/_middleware.tsx @@ -18,6 +18,8 @@ export function middleware(req: NextRequest, ev: NextFetchEvent) { if (signedIn) { const resp = NextResponse.redirect(getURL('')); resp.clearCookie('drift-token'); + resp.clearCookie('drift-userid'); + return resp } } else if (pathname === '/') { @@ -30,10 +32,14 @@ export function middleware(req: NextRequest, ev: NextFetchEvent) { return NextResponse.redirect(getURL('')) } // If you're signed in we redirect the sign in page to the home page (which is the new page) - } else if (pathname === '/signin') { + } else if (pathname === '/signin' || pathname === '/signup') { if (signedIn) { return NextResponse.redirect(getURL('')) } + } else if (pathname === '/new') { + if (!signedIn) { + return NextResponse.redirect(getURL('')) + } } return NextResponse.next() diff --git a/client/pages/new.tsx b/client/pages/new.tsx index 9c66928b..b8966fc6 100644 --- a/client/pages/new.tsx +++ b/client/pages/new.tsx @@ -7,12 +7,10 @@ import { ThemeProps } from './_app' import { useRouter } from 'next/router' import PageSeo from '@components/page-seo' -const Home = ({ theme, changeTheme }: ThemeProps) => { +const New = ({ theme, changeTheme }: ThemeProps) => { const router = useRouter() - const { isSignedIn, isLoading } = useSignedIn({ redirectIfNotAuthed: true }) - if (!isSignedIn && !isLoading) { - router.push("/signin") - } + const isSignedIn = useSignedIn() + return ( <Page className={styles.container} width="100%"> <PageSeo title="Drift - New" /> @@ -28,4 +26,4 @@ const Home = ({ theme, changeTheme }: ThemeProps) => { ) } -export default Home +export default New From 65b0c8f7f39def271f0112cf31439074bc216bb2 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 02:15:36 -0700 Subject: [PATCH 18/63] client: distinguish current page in header --- client/components/header/index.tsx | 4 ++-- client/pages/_middleware.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx index d8feabce..d5905f22 100644 --- a/client/components/header/index.tsx +++ b/client/components/header/index.tsx @@ -18,7 +18,7 @@ type Tab = { const Header = ({ changeTheme, theme }: DriftProps) => { const router = useRouter(); - const [selectedTab, setSelectedTab] = useState<string>(); + const [selectedTab, setSelectedTab] = useState<string>(router.pathname === '/' ? 'home' : router.pathname.split('/')[1]); const [expanded, setExpanded] = useState<boolean>(false) const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) const isMobile = useMediaQuery('xs', { match: 'down' }) @@ -91,7 +91,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => { onClick: function () { if (typeof window !== 'undefined') { changeTheme(); - setSelectedTab(undefined); + setSelectedTab(''); } }, icon: theme === 'light' ? <Moon /> : <Sun />, diff --git a/client/pages/_middleware.tsx b/client/pages/_middleware.tsx index a7909c91..e31452e4 100644 --- a/client/pages/_middleware.tsx +++ b/client/pages/_middleware.tsx @@ -38,7 +38,7 @@ export function middleware(req: NextRequest, ev: NextFetchEvent) { } } else if (pathname === '/new') { if (!signedIn) { - return NextResponse.redirect(getURL('')) + return NextResponse.redirect(getURL('/signin')) } } From 3f0212c5c6a63d3a4a096fff31ffdb719c9816e2 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 03:28:06 -0700 Subject: [PATCH 19/63] begin work on protected posts --- client/components/header/controls.tsx | 2 +- client/components/new-post/index.tsx | 94 ++++++++++++------- client/components/new-post/password/index.tsx | 50 ++++++++++ client/components/visibility-badge/index.tsx | 3 +- client/lib/types.d.ts | 12 +++ client/pages/_app.tsx | 6 +- client/pages/index.tsx | 2 +- client/pages/new.tsx | 4 +- client/pages/post/[id].tsx | 2 +- client/pages/signin.tsx | 2 +- client/pages/signup.tsx | 2 +- server/lib/models/Post.ts | 3 + server/src/routes/posts.ts | 2 - server/src/server.ts | 2 +- 14 files changed, 136 insertions(+), 50 deletions(-) create mode 100644 client/components/new-post/password/index.tsx create mode 100644 client/lib/types.d.ts diff --git a/client/components/header/controls.tsx b/client/components/header/controls.tsx index f621c806..ee763129 100644 --- a/client/components/header/controls.tsx +++ b/client/components/header/controls.tsx @@ -2,9 +2,9 @@ import React from 'react' import MoonIcon from '@geist-ui/icons/moon' import SunIcon from '@geist-ui/icons/sun' import { Select } from '@geist-ui/core' -import { ThemeProps } from '../../pages/_app' // import { useAllThemes, useTheme } from '@geist-ui/core' import styles from './header.module.css' +import { ThemeProps } from '@lib/types' const Controls = ({ changeTheme, theme }: ThemeProps) => { const switchThemes = (type: string | string[]) => { diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 65b9f5a1..6b401b4d 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -1,29 +1,56 @@ -import { Button, ButtonDropdown, useToasts } from '@geist-ui/core' +import { Button, ButtonDropdown, Input, Modal, Note, useModal, useToasts } from '@geist-ui/core' import { useRouter } from 'next/router'; import { useCallback, useState } from 'react' import generateUUID from '@lib/generate-uuid'; -import Document from '../document'; +import DocumentComponent from '../document'; import FileDropzone from './drag-and-drop'; import styles from './post.module.css' import Title from './title'; import Cookies from 'js-cookie' - -export type Document = { - title: string - content: string - id: string -} +import type { PostVisibility, Document as DocumentType } from '@lib/types'; +import PasswordModal from './password'; const Post = () => { const { setToast } = useToasts() - const router = useRouter(); const [title, setTitle] = useState<string>() - const [docs, setDocs] = useState<Document[]>([{ + const [docs, setDocs] = useState<DocumentType[]>([{ title: '', content: '', id: generateUUID() }]) + const [passwordModalVisible, setPasswordModalVisible] = useState(false) + const sendRequest = useCallback(async (url: string, data: { visibility?: PostVisibility, title?: string, files?: DocumentType[], password?: string, userId: string }) => { + const res = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${Cookies.get('drift-token')}` + }, + body: JSON.stringify({ + title, + files: docs, + ...data, + }) + }) + + if (res.ok) { + const json = await res.json() + router.push(`/post/${json.id}`) + } else { + const json = await res.json() + setToast({ + text: json.message, + type: 'error' + }) + } + + }, [docs, router, setToast, title]) + + const closePasswordModel = () => { + setPasswordModalVisible(false) + setSubmitting(false) + } const [isSubmitting, setSubmitting] = useState(false) @@ -31,29 +58,30 @@ const Post = () => { setDocs(docs.filter((doc) => doc.id !== id)) } - const onSubmit = async (visibility: string) => { + const onSubmit = async (visibility: PostVisibility, password?: string) => { setSubmitting(true) - const response = await fetch('/server-api/posts/create', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${Cookies.get("drift-token")}` - }, - body: JSON.stringify({ - title, - files: docs, - visibility, - userId: Cookies.get("drift-userid"), - }) + + if (visibility === 'protected' && !password) { + setPasswordModalVisible(true) + return + } + + await sendRequest('/server-api/posts/create', { + title, + files: docs, + visibility, + password, + userId: Cookies.get('drift-userid') || '' }) - const json = await response.json() + + + setSubmitting(false) + } + + const onClosePasswordModal = () => { + setPasswordModalVisible(false) setSubmitting(false) - if (json.id) - router.push(`/post/${json.id}`) - else { - setToast({ text: json.error.message, type: "error" }) - } } const updateTitle = useCallback((title: string, id: string) => { @@ -64,7 +92,7 @@ const Post = () => { setDocs(docs.map((doc) => doc.id === id ? { ...doc, content } : doc)) }, [docs]) - const uploadDocs = useCallback((files: Document[]) => { + const uploadDocs = useCallback((files: DocumentType[]) => { // if no title is set and the only document is empty, const isFirstDocEmpty = docs.length === 1 && docs[0].title === '' && docs[0].content === '' const shouldSetTitle = !title && isFirstDocEmpty @@ -87,7 +115,7 @@ const Post = () => { { docs.map(({ content, id, title }) => { return ( - <Document + <DocumentComponent remove={() => remove(id)} key={id} editable={true} @@ -120,10 +148,12 @@ const Post = () => { <ButtonDropdown.Item main onClick={() => onSubmit('private')}>Create Private</ButtonDropdown.Item> <ButtonDropdown.Item onClick={() => onSubmit('public')} >Create Public</ButtonDropdown.Item> <ButtonDropdown.Item onClick={() => onSubmit('unlisted')} >Create Unlisted</ButtonDropdown.Item> + <ButtonDropdown.Item onClick={() => onSubmit('protected')} >Create with Password</ButtonDropdown.Item> </ButtonDropdown> + <PasswordModal isOpen={passwordModalVisible} onClose={onClosePasswordModal} onSubmit={(password) => onSubmit('protected', password)} /> </div> </div > ) } -export default Post \ No newline at end of file +export default Post diff --git a/client/components/new-post/password/index.tsx b/client/components/new-post/password/index.tsx new file mode 100644 index 00000000..2305e945 --- /dev/null +++ b/client/components/new-post/password/index.tsx @@ -0,0 +1,50 @@ +import { Input, Modal, Note, Spacer } from "@geist-ui/core" +import { useState } from "react" + +type Props = { + isOpen: boolean + onClose: () => void + onSubmit: (password: string) => void +} + +const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify }: Props) => { + const [password, setPassword] = useState<string>() + const [confirmPassword, setConfirmPassword] = useState<string>() + const [error, setError] = useState<string>() + + const onSubmit = () => { + if (!password || !confirmPassword) { + setError('Please enter a password') + return + } + + if (password !== confirmPassword) { + setError("Passwords do not match") + return + } + + onSubmitAfterVerify(password) + } + + return (<> + {<Modal visible={isOpen} > + <Modal.Title>Enter a password</Modal.Title> + <Modal.Content> + {!error && <Note type="warning" label='Warning'> + This doesn't protect your post from the server administrator. + </Note>} + {error && <Note type="error" label='Error'> + {error} + </Note>} + <Spacer /> + <Input width={"100%"} label="Password" marginBottom={1} htmlType="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} /> + <Input width={"100%"} label="Confirm" htmlType="password" placeholder="Confirm Password" onChange={(e) => setConfirmPassword(e.target.value)} /> + </Modal.Content> + <Modal.Action passive onClick={onClose}>Cancel</Modal.Action> + <Modal.Action onClick={onSubmit}>Submit</Modal.Action> + </Modal>} + </>) +} + + +export default PasswordModal \ No newline at end of file diff --git a/client/components/visibility-badge/index.tsx b/client/components/visibility-badge/index.tsx index 0e9fa908..fa0dfb22 100644 --- a/client/components/visibility-badge/index.tsx +++ b/client/components/visibility-badge/index.tsx @@ -1,6 +1,5 @@ import { Badge } from "@geist-ui/core" - -type Visibility = "unlisted" | "private" | "public" +import { Visibility } from "@lib/types" type Props = { visibility: Visibility diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts new file mode 100644 index 00000000..c10b748a --- /dev/null +++ b/client/lib/types.d.ts @@ -0,0 +1,12 @@ +export type PostVisibility = "unlisted" | "private" | "public" | "protected" + +export type ThemeProps = { + theme: "light" | "dark" | string, + changeTheme: () => void +} + +export type Document = { + title: string + content: string + id: string +} diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 91888263..f0292219 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -7,11 +7,7 @@ import useSharedState from '@lib/hooks/use-shared-state'; import 'react-loading-skeleton/dist/skeleton.css' import { SkeletonTheme } from 'react-loading-skeleton'; import Head from 'next/head'; - -export type ThemeProps = { - theme: "light" | "dark" | string, - changeTheme: () => void -} +import { ThemeProps } from '@lib/types'; type AppProps<P = any> = { pageProps: P; diff --git a/client/pages/index.tsx b/client/pages/index.tsx index 771d175b..c8303d83 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -2,11 +2,11 @@ import styles from '@styles/Home.module.css' import { Page, Spacer, Text } from '@geist-ui/core' import Header from '@components/header' -import { ThemeProps } from './_app' import Document from '@components/document' import Image from 'next/image' import ShiftBy from '@components/shift-by' import PageSeo from '@components/page-seo' +import { ThemeProps } from '@lib/types' export function getStaticProps() { const introDoc = process.env.WELCOME_CONTENT diff --git a/client/pages/new.tsx b/client/pages/new.tsx index b8966fc6..7a6f5948 100644 --- a/client/pages/new.tsx +++ b/client/pages/new.tsx @@ -3,12 +3,10 @@ import NewPost from '@components/new-post' import { Page } from '@geist-ui/core' import useSignedIn from '@lib/hooks/use-signed-in' import Header from '@components/header' -import { ThemeProps } from './_app' -import { useRouter } from 'next/router' import PageSeo from '@components/page-seo' +import { ThemeProps } from '@lib/types' const New = ({ theme, changeTheme }: ThemeProps) => { - const router = useRouter() const isSignedIn = useSignedIn() return ( diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 0bd32738..b4f6da19 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -6,10 +6,10 @@ import { useEffect, useState } from "react"; import Document from '../../components/document' import Header from "../../components/header"; import VisibilityBadge from "../../components/visibility-badge"; -import { ThemeProps } from "../_app"; import PageSeo from "components/page-seo"; import styles from './styles.module.css'; import Cookies from "js-cookie"; +import { ThemeProps } from "@lib/types"; const Post = ({ theme, changeTheme }: ThemeProps) => { const [post, setPost] = useState<any>() diff --git a/client/pages/signin.tsx b/client/pages/signin.tsx index defc7d5b..0b98aabc 100644 --- a/client/pages/signin.tsx +++ b/client/pages/signin.tsx @@ -2,7 +2,7 @@ import { Page } from "@geist-ui/core"; import PageSeo from "@components/page-seo"; import Auth from "@components/auth"; import Header from "@components/header"; -import { ThemeProps } from "./_app"; +import { ThemeProps } from "@lib/types"; const SignIn = ({ theme, changeTheme }: ThemeProps) => ( <Page width={"100%"}> diff --git a/client/pages/signup.tsx b/client/pages/signup.tsx index 3628ef6d..9aa7b734 100644 --- a/client/pages/signup.tsx +++ b/client/pages/signup.tsx @@ -2,7 +2,7 @@ import { Page } from "@geist-ui/core"; import Auth from "@components/auth"; import Header from "@components/header"; import PageSeo from '@components/page-seo'; -import { ThemeProps } from "./_app"; +import { ThemeProps } from "@lib/types"; const SignUp = ({ theme, changeTheme }: ThemeProps) => ( <Page width="100%"> diff --git a/server/lib/models/Post.ts b/server/lib/models/Post.ts index 7dab48dd..4bc6dc4b 100644 --- a/server/lib/models/Post.ts +++ b/server/lib/models/Post.ts @@ -48,6 +48,9 @@ export class Post extends Model { @Column visibility!: string; + @Column + password?: string; + @UpdatedAt @Column updatedAt!: Date; diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index a528f310..6456458e 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -26,7 +26,6 @@ posts.post('/create', jwt, async (req, res, next) => { throw new Error("Please provide a visibility.") } - // Create the "post" object const newPost = new Post({ title: req.body.title, visibility: req.body.visibility, @@ -35,7 +34,6 @@ posts.post('/create', jwt, async (req, res, next) => { await newPost.save() await newPost.$add('users', req.body.userId); const newFiles = await Promise.all(req.body.files.map(async (file) => { - // Establish a "file" for each file in the request const newFile = new File({ title: file.title, content: file.content, diff --git a/server/src/server.ts b/server/src/server.ts index 2e96ea8d..134ad9af 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,7 +4,7 @@ import config from '../lib/config'; import { sequelize } from '../lib/sequelize'; (async () => { - await sequelize.sync(); + await sequelize.sync({ force: true }); createServer(app) .listen( config.port, From dc64972188344e1f62a5d9f01ba3da1784d58100 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 12:45:35 -0700 Subject: [PATCH 20/63] client: tsconfig and next settings --- client/next.config.js | 1 + client/package.json | 3 +- client/pages/_middleware.tsx | 2 +- client/pages/post/[id].tsx | 2 +- client/tsconfig.json | 12 +- client/yarn.lock | 248 ++++++++++++++++++++++++----------- 6 files changed, 191 insertions(+), 77 deletions(-) diff --git a/client/next.config.js b/client/next.config.js index 16eb5923..316e44b2 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -6,6 +6,7 @@ const nextConfig = { reactStrictMode: true, experimental: { outputStandalone: true, + optimizeCss: true, }, async rewrites() { return [ diff --git a/client/package.json b/client/package.json index 0a61aa98..62c01622 100644 --- a/client/package.json +++ b/client/package.json @@ -15,9 +15,10 @@ "@types/js-cookie": "^3.0.1", "client-zip": "^2.0.0", "comlink": "^4.3.1", + "critters": "^0.0.16", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", - "next": "12.1.0", + "next": "^12.1.1-canary.15", "prismjs": "^1.27.0", "react": "17.0.2", "react-debounce-render": "^8.0.2", diff --git a/client/pages/_middleware.tsx b/client/pages/_middleware.tsx index e31452e4..a6c34478 100644 --- a/client/pages/_middleware.tsx +++ b/client/pages/_middleware.tsx @@ -2,7 +2,7 @@ import { NextFetchEvent, NextRequest, NextResponse } from 'next/server' const PUBLIC_FILE = /.(.*)$/ -export function middleware(req: NextRequest, ev: NextFetchEvent) { +export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname const signedIn = req.cookies['drift-token'] const getURL = (pageName: string) => new URL(`/${pageName}`, req.url).href diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index b4f6da19..0bd32738 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -6,10 +6,10 @@ import { useEffect, useState } from "react"; import Document from '../../components/document' import Header from "../../components/header"; import VisibilityBadge from "../../components/visibility-badge"; +import { ThemeProps } from "../_app"; import PageSeo from "components/page-seo"; import styles from './styles.module.css'; import Cookies from "js-cookie"; -import { ThemeProps } from "@lib/types"; const Post = ({ theme, changeTheme }: ThemeProps) => { const [post, setPost] = useState<any>() diff --git a/client/tsconfig.json b/client/tsconfig.json index 759858c9..768f8569 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,15 +1,25 @@ { "compilerOptions": { - "target": "es5", + "target": "es2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "node", + "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", diff --git a/client/yarn.lock b/client/yarn.lock index 5c3bd26b..668b5ec7 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -68,10 +68,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== -"@next/env@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.0.tgz#73713399399b34aa5a01771fb73272b55b22c314" - integrity sha512-nrIgY6t17FQ9xxwH3jj0a6EOiQ/WDHUos35Hghtr+SWN/ntHIQ7UpuvSi0vaLzZVHQWaDupKI+liO5vANcDeTQ== +"@next/env@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.1-canary.15.tgz#d1c210df31c8865042f2b81ffb37660b9cc70045" + integrity sha512-2r7r5r/+hSgCTeGTMErGwlxiawNX3RGCKrgWGyyIYKGcJ2xunxN3ScDKIAjGa77eOWGm4JccB4T6xXwmPUut5g== "@next/eslint-plugin-next@12.1.0": version "12.1.0" @@ -80,60 +80,65 @@ dependencies: glob "7.1.7" -"@next/swc-android-arm64@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.0.tgz#865ba3a9afc204ff2bdeea49dd64d58705007a39" - integrity sha512-/280MLdZe0W03stA69iL+v6I+J1ascrQ6FrXBlXGCsGzrfMaGr7fskMa0T5AhQIVQD4nA/46QQWxG//DYuFBcA== +"@next/swc-android-arm-eabi@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.1.1-canary.15.tgz#4539e24cc64fe8f76ce48c7e37c4419f9f436566" + integrity sha512-I1ghAtx23GcVi7modLdXLZZgfngcLzgzx/mWVXhWa2Pl8NPippIDMNF10Kf75RnOgkrPAEbVJByXAAEY7OkG6g== -"@next/swc-darwin-arm64@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.0.tgz#08e8b411b8accd095009ed12efbc2f1d4d547135" - integrity sha512-R8vcXE2/iONJ1Unf5Ptqjk6LRW3bggH+8drNkkzH4FLEQkHtELhvcmJwkXcuipyQCsIakldAXhRbZmm3YN1vXg== +"@next/swc-android-arm64@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.1.1-canary.15.tgz#929ca296b7bfb7fdac77645d53199792d631048f" + integrity sha512-qa8M33AUfCW+P8NqGYd77+qhcM+QAP/KD7DBzCoIB08Djf/IQ3xBshM8dvCgWTYYz1ydnlHbMmJk+xiApoEfbQ== -"@next/swc-darwin-x64@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.0.tgz#fcd684497a76e8feaca88db3c394480ff0b007cd" - integrity sha512-ieAz0/J0PhmbZBB8+EA/JGdhRHBogF8BWaeqR7hwveb6SYEIJaDNQy0I+ZN8gF8hLj63bEDxJAs/cEhdnTq+ug== +"@next/swc-darwin-arm64@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.1.1-canary.15.tgz#99764be54f9f9ba9866de18bc23f0fcfc98ac217" + integrity sha512-9L8X49wug00GpM4iIakq1MmsXKvWykwYjQRt++brGA4/rCJlG6WUiM4WDBQr2QqLCTMrfbd5umQ3qbL0F23VkQ== -"@next/swc-linux-arm-gnueabihf@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.0.tgz#9ec6380a27938a5799aaa6035c205b3c478468a7" - integrity sha512-njUd9hpl6o6A5d08dC0cKAgXKCzm5fFtgGe6i0eko8IAdtAPbtHxtpre3VeSxdZvuGFh+hb0REySQP9T1ttkog== +"@next/swc-darwin-x64@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.1.1-canary.15.tgz#4bdda400c5e542ff3355078a175fa6f74a20e39d" + integrity sha512-SMldDPyrN1GVqLYcXEUs7rwxbRP7ra8OxmRre3MJcUq1TB+mVkasNCDCO6WX1pJ9vDxu5Z5WuiBlp8bioCCp1w== -"@next/swc-linux-arm64-gnu@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.0.tgz#7f4196dff1049cea479607c75b81033ae2dbd093" - integrity sha512-OqangJLkRxVxMhDtcb7Qn1xjzFA3s50EIxY7mljbSCLybU+sByPaWAHY4px97ieOlr2y4S0xdPKkQ3BCAwyo6Q== +"@next/swc-linux-arm-gnueabihf@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.1.1-canary.15.tgz#f066f295cefe4206093614e2db292ef435ff7734" + integrity sha512-f8YBO686RvqfjIc8gv9gRJFPOSLEdtuJ1Q8FassJ2Jy4GMZdCGJaVFbcavsnd1ksw/KVxC79GaVigSVRmmgArA== -"@next/swc-linux-arm64-musl@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.0.tgz#b445f767569cdc2dddee785ca495e1a88c025566" - integrity sha512-hB8cLSt4GdmOpcwRe2UzI5UWn6HHO/vLkr5OTuNvCJ5xGDwpPXelVkYW/0+C3g5axbDW2Tym4S+MQCkkH9QfWA== +"@next/swc-linux-arm64-gnu@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.1.1-canary.15.tgz#a176237eedbdf8895c254f207d7e39a64b29c6b2" + integrity sha512-2J8LWInvvm7iNlx/mmH+BqUwLQndHKaFxgoLPqpVnn3XaIsHgSl+d9jU0ECJrp9OtzVgFmFgJYIfRMq9dNRDFw== -"@next/swc-linux-x64-gnu@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.0.tgz#67610e9be4fbc987de7535f1bcb17e45fe12f90e" - integrity sha512-OKO4R/digvrVuweSw/uBM4nSdyzsBV5EwkUeeG4KVpkIZEe64ZwRpnFB65bC6hGwxIBnTv5NMSnJ+0K/WmG78A== +"@next/swc-linux-arm64-musl@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.1.1-canary.15.tgz#235e44bf445e3e2b9dd01e757396520e7f57addf" + integrity sha512-SGEuapk5eheIsCE9tdLL5hxomUSQvETT9KCss8/x/+44009iBXsuKFP6DdF+0GFJzv/rpO+6ijPfnwQnvzTxeQ== -"@next/swc-linux-x64-musl@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.0.tgz#ea19a23db08a9f2e34ac30401f774cf7d1669d31" - integrity sha512-JohhgAHZvOD3rQY7tlp7NlmvtvYHBYgY0x5ZCecUT6eCCcl9lv6iV3nfu82ErkxNk1H893fqH0FUpznZ/H3pSw== +"@next/swc-linux-x64-gnu@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.1.1-canary.15.tgz#a89ca271a575335b6ed385d718792409ae85783c" + integrity sha512-9cgczb1Mr1BNZBxH5a02ermKGLihpBlp1ZVeIYwSGv+WFGCBIirIbZfvWDXpLG2fdA1YbPk1122o2h8ItVLlJw== -"@next/swc-win32-arm64-msvc@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.0.tgz#eadf054fc412085659b98e145435bbba200b5283" - integrity sha512-T/3gIE6QEfKIJ4dmJk75v9hhNiYZhQYAoYm4iVo1TgcsuaKLFa+zMPh4056AHiG6n9tn2UQ1CFE8EoybEsqsSw== +"@next/swc-linux-x64-musl@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.1.1-canary.15.tgz#b8ee5f2cbefedd305da286a2d10633656b85092a" + integrity sha512-nCqi/WVjp0PwQLBE7QPe4wNm6Hontuwh5p8ZIOmiSIGkxvbHdPbXWomKQFCwm2387B1OLGzv9VFJ0cBcfEbjWA== -"@next/swc-win32-ia32-msvc@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.0.tgz#68faeae10c89f698bf9d28759172b74c9c21bda1" - integrity sha512-iwnKgHJdqhIW19H9PRPM9j55V6RdcOo6rX+5imx832BCWzkDbyomWnlzBfr6ByUYfhohb8QuH4hSGEikpPqI0Q== +"@next/swc-win32-arm64-msvc@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.1.1-canary.15.tgz#094dab65bba62d516077539e7fc8edf6dd57ebc2" + integrity sha512-HO7Pcgky002h7xyjFdwbondKUXbDdxDcPQqxH4hUYg3umZr9dW2fTwKh6BTFDODeb9PtST+KaW4erFJPzRPYJw== -"@next/swc-win32-x64-msvc@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.0.tgz#d27e7e76c87a460a4da99c5bfdb1618dcd6cd064" - integrity sha512-aBvcbMwuanDH4EMrL2TthNJy+4nP59Bimn8egqv6GHMVj0a44cU6Au4PjOhLNqEh9l+IpRGBqMTzec94UdC5xg== +"@next/swc-win32-ia32-msvc@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.1.1-canary.15.tgz#2015910672229c11b11e3f10941fcee7c3dd2e10" + integrity sha512-llK5rpggUbe2OseHRQ32q8EjQHk8remoKrqlbXTJrEz9Fa6BNx8uI/IAT6tr9oX6VgTBeIFu1kX20yXJFxsL3Q== + +"@next/swc-win32-x64-msvc@12.1.1-canary.15": + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.1.1-canary.15.tgz#d1b08175419161cc0e4435333f5f7ca3b617708f" + integrity sha512-VcbXco9q82qHqBebHfPZqAgfs6OLek7EJJt28/OaSBnCJGzCl0hkWo3NkHEI4SQwYzTAgMufU3a9WjkEz3Oeew== "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -409,6 +414,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -447,7 +457,7 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -542,6 +552,18 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== +critters@^0.0.16: + version "0.0.16" + resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.16.tgz#ffa2c5561a65b43c53b940036237ce72dcebfe93" + integrity sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A== + dependencies: + chalk "^4.1.0" + css-select "^4.2.0" + parse5 "^6.0.1" + parse5-htmlparser2-tree-adapter "^6.0.1" + postcss "^8.3.7" + pretty-bytes "^5.3.0" + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -551,6 +573,22 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +css-select@^4.2.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" + integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== + dependencies: + boolbase "^1.0.0" + css-what "^5.1.0" + domhandler "^4.3.0" + domutils "^2.8.0" + nth-check "^2.0.1" + +css-what@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" + integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== + csstype@^3.0.2: version "3.0.11" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" @@ -640,6 +678,36 @@ dom-helpers@^5.1.3: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dom-serializer@^1.0.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" + integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== + dependencies: + domelementtype "^2.0.1" + domhandler "^4.2.0" + entities "^2.0.0" + +domelementtype@^2.0.1, domelementtype@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" + integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== + +domhandler@^4.2.0, domhandler@^4.3.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" + integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== + dependencies: + domelementtype "^2.2.0" + +domutils@^2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" + integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== + dependencies: + dom-serializer "^1.0.1" + domelementtype "^2.2.0" + domhandler "^4.2.0" + dotenv@^16.0.0: version "16.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" @@ -650,6 +718,11 @@ emoji-regex@^9.2.0, emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== +entities@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" + integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== + es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.1.tgz#d4885796876916959de78edaa0df456627115ec3" @@ -2047,7 +2120,7 @@ ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -nanoid@^3.1.30: +nanoid@^3.1.30, nanoid@^3.3.1: version "3.3.1" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35" integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw== @@ -2057,28 +2130,36 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= -next@12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/next/-/next-12.1.0.tgz#c33d753b644be92fc58e06e5a214f143da61dd5d" - integrity sha512-s885kWvnIlxsUFHq9UGyIyLiuD0G3BUC/xrH0CEnH5lHEWkwQcHOORgbDF0hbrW9vr/7am4ETfX4A7M6DjrE7Q== +next@^12.1.1-canary.15: + version "12.1.1-canary.15" + resolved "https://registry.yarnpkg.com/next/-/next-12.1.1-canary.15.tgz#a31c99a512b29d98aa0f6d837931d5c7f2aa2c35" + integrity sha512-WjD8zIDkLTiGYdnLbmeLmoNZvf7+UZ6oNxDDDNtddUVvej11AB8d+FmcmEuK7L3P8+5HKp1nZEg6oMxmpX/BzQ== dependencies: - "@next/env" "12.1.0" + "@next/env" "12.1.1-canary.15" caniuse-lite "^1.0.30001283" postcss "8.4.5" - styled-jsx "5.0.0" + styled-jsx "5.0.1" use-subscription "1.5.1" optionalDependencies: - "@next/swc-android-arm64" "12.1.0" - "@next/swc-darwin-arm64" "12.1.0" - "@next/swc-darwin-x64" "12.1.0" - "@next/swc-linux-arm-gnueabihf" "12.1.0" - "@next/swc-linux-arm64-gnu" "12.1.0" - "@next/swc-linux-arm64-musl" "12.1.0" - "@next/swc-linux-x64-gnu" "12.1.0" - "@next/swc-linux-x64-musl" "12.1.0" - "@next/swc-win32-arm64-msvc" "12.1.0" - "@next/swc-win32-ia32-msvc" "12.1.0" - "@next/swc-win32-x64-msvc" "12.1.0" + "@next/swc-android-arm-eabi" "12.1.1-canary.15" + "@next/swc-android-arm64" "12.1.1-canary.15" + "@next/swc-darwin-arm64" "12.1.1-canary.15" + "@next/swc-darwin-x64" "12.1.1-canary.15" + "@next/swc-linux-arm-gnueabihf" "12.1.1-canary.15" + "@next/swc-linux-arm64-gnu" "12.1.1-canary.15" + "@next/swc-linux-arm64-musl" "12.1.1-canary.15" + "@next/swc-linux-x64-gnu" "12.1.1-canary.15" + "@next/swc-linux-x64-musl" "12.1.1-canary.15" + "@next/swc-win32-arm64-msvc" "12.1.1-canary.15" + "@next/swc-win32-ia32-msvc" "12.1.1-canary.15" + "@next/swc-win32-x64-msvc" "12.1.1-canary.15" + +nth-check@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" + integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== + dependencies: + boolbase "^1.0.0" object-assign@^4.1.1: version "4.1.1" @@ -2197,7 +2278,14 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse5@^6.0.0: +parse5-htmlparser2-tree-adapter@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" + integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== + dependencies: + parse5 "^6.0.1" + +parse5@^6.0.0, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -2246,11 +2334,25 @@ postcss@8.4.5: picocolors "^1.0.0" source-map-js "^1.0.1" +postcss@^8.3.7: + version "8.4.12" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" + integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== + dependencies: + nanoid "^3.3.1" + picocolors "^1.0.0" + source-map-js "^1.0.2" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +pretty-bytes@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" + integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== + prismjs@^1.25.0, prismjs@^1.27.0, prismjs@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" @@ -2625,7 +2727,7 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map-js@^1.0.1: +source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== @@ -2702,10 +2804,10 @@ style-to-object@^0.3.0: dependencies: inline-style-parser "0.1.1" -styled-jsx@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.0.tgz#816b4b92e07b1786c6b7111821750e0ba4d26e77" - integrity sha512-qUqsWoBquEdERe10EW8vLp3jT25s/ssG1/qX5gZ4wu15OZpmSMFI2v+fWlRhLfykA5rFtlJ1ME8A8pm/peV4WA== +styled-jsx@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.1.tgz#78fecbbad2bf95ce6cd981a08918ce4696f5fc80" + integrity sha512-+PIZ/6Uk40mphiQJJI1202b+/dYeTVd9ZnMPR80pgiWbjIwvN2zIp4r9et0BgqBuShh48I0gttPlAXA7WVvBxw== supports-color@^7.1.0: version "7.2.0" From 1c68aa9765422c4214625236803addd1ee4cf17d Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 14:20:20 -0700 Subject: [PATCH 21/63] client: use cookie for theme, redirect post view in server side props --- .../document/formatting-icons/index.tsx | 10 +- client/components/header/controls.tsx | 4 +- .../new-post/drag-and-drop/index.tsx | 9 +- client/components/new-post/index.tsx | 2 +- client/components/new-post/title/index.tsx | 2 +- client/components/visibility-badge/index.tsx | 4 +- client/next.config.js | 1 - client/package.json | 8 +- client/pages/_app.tsx | 14 +- client/pages/post/[id].tsx | 145 ++-- client/tsconfig.json | 1 + client/yarn.lock | 677 ++++++++++++++---- 12 files changed, 641 insertions(+), 236 deletions(-) diff --git a/client/components/document/formatting-icons/index.tsx b/client/components/document/formatting-icons/index.tsx index d5e15fe1..913c126e 100644 --- a/client/components/document/formatting-icons/index.tsx +++ b/client/components/document/formatting-icons/index.tsx @@ -20,7 +20,7 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM // return { textBefore: '', textAfter: '' } // }, [textareaRef,]) - const handleBoldClick = useCallback((e) => { + const handleBoldClick = useCallback(() => { if (textareaRef?.current && setText) { const selectionStart = textareaRef.current.selectionStart const selectionEnd = textareaRef.current.selectionEnd @@ -37,7 +37,7 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM } }, [setText, textareaRef]) - const handleItalicClick = useCallback((e) => { + const handleItalicClick = useCallback(() => { if (textareaRef?.current && setText) { const selectionStart = textareaRef.current.selectionStart const selectionEnd = textareaRef.current.selectionEnd @@ -52,7 +52,7 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM } }, [setText, textareaRef]) - const handleLinkClick = useCallback((e) => { + const handleLinkClick = useCallback(() => { if (textareaRef?.current && setText) { const selectionStart = textareaRef.current.selectionStart const selectionEnd = textareaRef.current.selectionEnd @@ -73,7 +73,7 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM } }, [setText, textareaRef]) - const handleImageClick = useCallback((e) => { + const handleImageClick = useCallback(() => { if (textareaRef?.current && setText) { const selectionStart = textareaRef.current.selectionStart const selectionEnd = textareaRef.current.selectionEnd @@ -134,4 +134,4 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM } -export default FormattingIcons \ No newline at end of file +export default FormattingIcons diff --git a/client/components/header/controls.tsx b/client/components/header/controls.tsx index ee763129..333453a7 100644 --- a/client/components/header/controls.tsx +++ b/client/components/header/controls.tsx @@ -5,12 +5,12 @@ import { Select } from '@geist-ui/core' // import { useAllThemes, useTheme } from '@geist-ui/core' import styles from './header.module.css' import { ThemeProps } from '@lib/types' +import Cookies from 'js-cookie' const Controls = ({ changeTheme, theme }: ThemeProps) => { const switchThemes = (type: string | string[]) => { changeTheme() - if (typeof window === 'undefined' || !window.localStorage) return - window.localStorage.setItem('drift-theme', Array.isArray(type) ? type[0] : type) + Cookies.set('drift-theme', Array.isArray(type) ? type[0] : type) } diff --git a/client/components/new-post/drag-and-drop/index.tsx b/client/components/new-post/drag-and-drop/index.tsx index dfae57ce..25de2bd7 100644 --- a/client/components/new-post/drag-and-drop/index.tsx +++ b/client/components/new-post/drag-and-drop/index.tsx @@ -1,10 +1,9 @@ -import { Button, Text, useTheme, useToasts } from '@geist-ui/core' -import { memo, useCallback, useEffect } from 'react' +import { Text, useTheme, useToasts } from '@geist-ui/core' +import { memo } from 'react' import { useDropzone } from 'react-dropzone' import styles from './drag-and-drop.module.css' -import { Document } from '../' +import type { Document } from '@lib/types' import generateUUID from '@lib/generate-uuid' -import { XCircle } from '@geist-ui/icons' const allowedFileTypes = [ 'application/json', 'application/x-javascript', @@ -99,7 +98,7 @@ function FileDropzone({ setDocs }: { setDocs: ((docs: Document[]) => void) }) { const { setToast } = useToasts() const onDrop = async (acceptedFiles: File[]) => { const newDocs = await Promise.all(acceptedFiles.map((file) => { - return new Promise<Document>((resolve, reject) => { + return new Promise<Document>((resolve) => { const reader = new FileReader() reader.onabort = () => setToast({ text: 'File reading was aborted', type: 'error' }) diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 6b401b4d..a12cc8fc 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -1,4 +1,4 @@ -import { Button, ButtonDropdown, Input, Modal, Note, useModal, useToasts } from '@geist-ui/core' +import { Button, ButtonDropdown, useToasts } from '@geist-ui/core' import { useRouter } from 'next/router'; import { useCallback, useState } from 'react' import generateUUID from '@lib/generate-uuid'; diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index d9b2302a..3865c858 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, memo } from 'react' +import { memo } from 'react' import { Text, Input } from '@geist-ui/core' import ShiftBy from '@components/shift-by' import styles from '../post.module.css' diff --git a/client/components/visibility-badge/index.tsx b/client/components/visibility-badge/index.tsx index fa0dfb22..aac799d4 100644 --- a/client/components/visibility-badge/index.tsx +++ b/client/components/visibility-badge/index.tsx @@ -1,8 +1,8 @@ import { Badge } from "@geist-ui/core" -import { Visibility } from "@lib/types" +import { PostVisibility } from "@lib/types" type Props = { - visibility: Visibility + visibility: PostVisibility } const VisibilityBadge = ({ visibility }: Props) => { diff --git a/client/next.config.js b/client/next.config.js index 316e44b2..16eb5923 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -6,7 +6,6 @@ const nextConfig = { reactStrictMode: true, experimental: { outputStandalone: true, - optimizeCss: true, }, async rewrites() { return [ diff --git a/client/package.json b/client/package.json index 2018adea..f2d66652 100644 --- a/client/package.json +++ b/client/package.json @@ -16,11 +16,11 @@ "@types/js-cookie": "^3.0.1", "client-zip": "^2.0.0", "comlink": "^4.3.1", - "critters": "^0.0.16", "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", "next": "^12.1.1-canary.15", + "prism-react-renderer": "^1.3.1", "prismjs": "^1.27.0", "react": "17.0.2", "react-debounce-render": "^8.0.2", @@ -32,8 +32,8 @@ "react-syntax-highlighter-virtualized-renderer": "^1.1.0", "rehype-autolink-headings": "^6.1.1", "rehype-katex": "^6.0.2", + "rehype-remark": "^9.1.2", "rehype-slug": "^5.0.1", - "rehype-stringify": "^9.0.3", "remark-gfm": "^3.0.1", "remark-math": "^5.1.1", "swr": "^1.2.2" @@ -41,9 +41,11 @@ "devDependencies": { "@types/node": "17.0.21", "@types/react": "17.0.39", + "@types/react-dom": "^17.0.14", "@types/react-syntax-highlighter": "^13.5.2", "eslint": "8.10.0", "eslint-config-next": "12.1.0", - "typescript": "4.6.2" + "typescript": "4.6.2", + "typescript-plugin-css-modules": "^3.4.0" } } diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index c0499056..809a8834 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -8,12 +8,7 @@ import 'react-loading-skeleton/dist/skeleton.css' import { SkeletonTheme } from 'react-loading-skeleton'; import Head from 'next/head'; import { ThemeProps } from '@lib/types'; - -export type PostProps = { - renderedPost: any | null, // Still don't have an official data type for posts - theme: "light" | "dark" | string, - changeTheme: () => void -} +import Cookies from 'js-cookie'; type AppProps<P = any> = { pageProps: P; @@ -22,11 +17,10 @@ type AppProps<P = any> = { export type DriftProps = ThemeProps function MyApp({ Component, pageProps }: AppProps<ThemeProps>) { - const [themeType, setThemeType] = useSharedState<string>('theme', 'light') - const theme = useTheme(); + const [themeType, setThemeType] = useSharedState<string>('theme', Cookies.get('drift-theme') || 'light') + useEffect(() => { - if (typeof window === 'undefined' || !window.localStorage) return - const storedTheme = window.localStorage.getItem('drift-theme') + const storedTheme = Cookies.get('drift-theme') if (storedTheme) setThemeType(storedTheme) // TODO: useReducer? }, [setThemeType, themeType]) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index cddf7947..0289cb00 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -1,45 +1,36 @@ import { Button, Page, Text } from "@geist-ui/core"; -import Skeleton from 'react-loading-skeleton'; import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; import Document from '../../components/document' import Header from "../../components/header"; import VisibilityBadge from "../../components/visibility-badge"; -import { PostProps } from "../_app"; import PageSeo from "components/page-seo"; import styles from './styles.module.css'; -import Cookies from "js-cookie"; import cookie from "cookie"; import { GetServerSideProps } from "next"; +import { PostVisibility, ThemeProps } from "@lib/types"; +type File = { + id: string + title: string + content: string +} -const Post = ({renderedPost, theme, changeTheme}: PostProps) => { - const [post, setPost] = useState(renderedPost); - const [isLoading, setIsLoading] = useState(true) - const [error, setError] = useState<string>() +type Files = File[] + +export type PostProps = ThemeProps & { + post: { + id: string + title: string + description: string + visibility: PostVisibility + files: Files + } +} + +const Post = ({ post, theme, changeTheme }: PostProps) => { const router = useRouter(); - useEffect(() => { - async function fetchPost() { - setIsLoading(true); - - if (renderedPost) { - setPost(renderedPost) - setIsLoading(false) - - return; - } - - if (!Cookies.get('drift-token')) { - router.push('/signin'); - } else { - setError('Something went wrong fetching the post'); - } - } - fetchPost() - }, [router, router.query.id]) - const download = async () => { const clientZip = require("client-zip") @@ -59,78 +50,88 @@ const Post = ({renderedPost, theme, changeTheme}: PostProps) => { return ( <Page width={"100%"}> - {!isLoading && ( - <PageSeo - title={`${post.title} - Drift`} - description={post.description} - isPrivate={post.visibility === 'private'} - /> - )} + <PageSeo + title={`${post.title} - Drift`} + description={post.description} + isPrivate={post.visibility !== 'public'} + /> <Page.Header> <Header theme={theme} changeTheme={changeTheme} /> </Page.Header> <Page.Content width={"var(--main-content-width)"} margin="auto"> {/* {!isLoading && <PostFileExplorer files={post.files} />} */} - - {error && <Text type="error">{error}</Text>} - {!error && isLoading && <><Text h2><Skeleton width={400} /></Text> - <Document skeleton={true} /> - </>} - {!isLoading && post && <> - <div className={styles.header}> - <div className={styles.titleAndBadge}> - <Text h2>{post.title}</Text> - <span><VisibilityBadge visibility={post.visibility} /></span> - </div> - <Button auto onClick={download}> - Download as ZIP archive - </Button> + <div className={styles.header}> + <div className={styles.titleAndBadge}> + <Text h2>{post.title}</Text> + <span><VisibilityBadge visibility={post.visibility} /></span> </div> - {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( - <Document - key={id} - id={id} - content={content} - title={title} - editable={false} - initialTab={'preview'} - /> - ))} - </>} + <Button auto onClick={download}> + Download as ZIP archive + </Button> + </div> + {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( + <Document + key={id} + id={id} + content={content} + title={title} + editable={false} + initialTab={'preview'} + /> + ))} </Page.Content> </Page > ) } export const getServerSideProps: GetServerSideProps = async (context) => { + const headers = context.req.headers + const host = headers.host + const driftToken = cookie.parse(headers.cookie || '')[`drift-token`] + let driftTheme = cookie.parse(headers.cookie || '')[`drift-theme`] + if (driftTheme !== "light" && driftTheme !== "dark") { + driftTheme = "light" + } + - const headers = context.req.headers; - const host = headers.host; - const driftToken = cookie.parse(headers.cookie || '')[`drift-token`]; - - let post; - if (context.query.id) { - post = await fetch('http://' + host + `/server-api/posts/${context.query.id}`, { + const post = await fetch('http://' + host + `/server-api/posts/${context.query.id}`, { method: "GET", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${driftToken}` } - }); + }) + if (!post.ok || post.status !== 200) { + return { + redirect: { + destination: '/', + permanent: false, + }, + } + } try { - post = await post.json(); + const json = await post.json(); + const maxAge = 60 * 60 * 24 * 365; + context.res.setHeader( + 'Cache-Control', + `${json.visibility === "public" ? "public" : "private"}, s-maxage=${maxAge}, max-age=${maxAge}` + ) + return { + props: { + post: json + } + } } catch (e) { - console.log(e); - post = null; + console.log(e) } } return { props: { - renderedPost: post + post: null } } } diff --git a/client/tsconfig.json b/client/tsconfig.json index 768f8569..2088465b 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "plugins": [{ "name": "typescript-plugin-css-modules" }], "target": "es2020", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, diff --git a/client/yarn.lock b/client/yarn.lock index ad5256e3..2d58786e 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -178,6 +178,11 @@ dependencies: "@types/ms" "*" +"@types/extend@^3.0.0": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@types/extend/-/extend-3.0.1.tgz#923dc2d707d944382433e01d6cc0c69030ab2c75" + integrity sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw== + "@types/hast@^2.0.0": version "2.3.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" @@ -232,6 +237,13 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== +"@types/react-dom@^17.0.14": + version "17.0.14" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f" + integrity sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ== + dependencies: + "@types/react" "*" + "@types/react-syntax-highlighter@^13.5.2": version "13.5.2" resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-13.5.2.tgz#357cc03581dc434c57c3b31f70e0eecdbf7b3ab0" @@ -327,6 +339,13 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -334,6 +353,14 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -386,6 +413,11 @@ ast-types-flow@^0.0.7: resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" integrity sha1-9wtzXGvKGlycItmCw+Oef+ujva0= +atob@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + attr-accept@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" @@ -419,10 +451,15 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -boolbase@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" - integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +big.js@^3.1.3: + version "3.2.0" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" + integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== brace-expansion@^1.1.7: version "1.1.11" @@ -432,7 +469,7 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.1: +braces@^3.0.1, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -462,7 +499,16 @@ ccount@^2.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@^4.0.0, chalk@^4.1.0: +chalk@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +chalk@^4.0.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -470,21 +516,11 @@ chalk@^4.0.0, chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -character-entities-html4@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" - integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== - character-entities-legacy@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz#94bc1845dce70a5bb9d2ecc748725661293d8fc1" integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA== -character-entities-legacy@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" - integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== - character-entities@^1.0.0: version "1.2.4" resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-1.2.4.tgz#e12c3939b7eaf4e5b15e7ad4c5e28e1d48c5b16b" @@ -500,6 +536,21 @@ character-reference-invalid@^1.0.0: resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz#083329cda0eae272ab3dbbf37e9a382c13af1560" integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg== +"chokidar@>=3.0.0 <4.0.0": + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + client-zip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/client-zip/-/client-zip-2.0.0.tgz#c93676c92ddb40c858da83517c27297a53874f8d" @@ -510,6 +561,13 @@ clsx@^1.0.4: resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -517,6 +575,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= + color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" @@ -552,6 +615,13 @@ cookie@^0.4.2: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== +copy-anything@^2.0.1: + version "2.0.6" + resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-2.0.6.tgz#092454ea9584a7b7ad5573062b2a87f5900fc480" + integrity sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw== + dependencies: + is-what "^3.14.1" + core-js-pure@^3.20.2: version "3.21.1" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" @@ -562,18 +632,6 @@ core-js@^2.4.0: resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== -critters@^0.0.16: - version "0.0.16" - resolved "https://registry.yarnpkg.com/critters/-/critters-0.0.16.tgz#ffa2c5561a65b43c53b940036237ce72dcebfe93" - integrity sha512-JwjgmO6i3y6RWtLYmXwO5jMd+maZt8Tnfu7VVISmEWyQqfLpB8soBswf8/2bu6SBXxtKA68Al3c+qIG1ApT68A== - dependencies: - chalk "^4.1.0" - css-select "^4.2.0" - parse5 "^6.0.1" - parse5-htmlparser2-tree-adapter "^6.0.1" - postcss "^8.3.7" - pretty-bytes "^5.3.0" - cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -583,21 +641,35 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" -css-select@^4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.2.1.tgz#9e665d6ae4c7f9d65dbe69d0316e3221fb274cdd" - integrity sha512-/aUslKhzkTNCQUB2qTX84lVmfia9NyjP3WpDGtj/WxhwBzWBYUV3DgUpurHTme8UTPcPlAD1DJ+b0nN/t50zDQ== +css-parse@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" + integrity sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q= dependencies: - boolbase "^1.0.0" - css-what "^5.1.0" - domhandler "^4.3.0" - domutils "^2.8.0" - nth-check "^2.0.1" + css "^2.0.0" -css-what@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-5.1.0.tgz#3f7b707aadf633baf62c2ceb8579b545bb40f7fe" - integrity sha512-arSMRWIIFY0hV8pIxZMEfmMI47Wj3R/aWpZDDxWYCPEiOMv6tfOrnpDtgxBYPEQD4V0Y/958+1TdC3iWTFcUPw== +css-selector-tokenizer@^0.7.0: + version "0.7.3" + resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" + integrity sha512-jWQv3oCEL5kMErj4wRnK/OPoBi0D+P1FR2cDCKYPaMeD2eW3/mttav8HT4hT1CKopiJI/psEULjkClhvJo4Lvg== + dependencies: + cssesc "^3.0.0" + fastparse "^1.1.2" + +css@^2.0.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + +cssesc@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" + integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== csstype@^3.0.2: version "3.0.11" @@ -616,7 +688,7 @@ debug@^2.6.9: dependencies: ms "2.0.0" -debug@^3.2.7: +debug@^3.2.6, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -630,6 +702,13 @@ debug@^4.0.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: dependencies: ms "2.1.2" +debug@~3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + decode-named-character-reference@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.0.1.tgz#57b2bd9112659cacbc449d3577d7dadb8e1f3d1b" @@ -637,6 +716,11 @@ decode-named-character-reference@^1.0.0: dependencies: character-entities "^2.0.0" +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -688,35 +772,10 @@ dom-helpers@^5.1.3: "@babel/runtime" "^7.8.7" csstype "^3.0.2" -dom-serializer@^1.0.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.3.2.tgz#6206437d32ceefaec7161803230c7a20bc1b4d91" - integrity sha512-5c54Bk5Dw4qAxNOI1pFEizPSjVsx5+bpJKmL2kPn8JhBUq2q09tTCa3mjijun2NfK78NMouDYNMBkOrPZiS+ig== - dependencies: - domelementtype "^2.0.1" - domhandler "^4.2.0" - entities "^2.0.0" - -domelementtype@^2.0.1, domelementtype@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" - integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== - -domhandler@^4.2.0, domhandler@^4.3.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" - integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ== - dependencies: - domelementtype "^2.2.0" - -domutils@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" - integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== - dependencies: - dom-serializer "^1.0.1" - domelementtype "^2.2.0" - domhandler "^4.2.0" +dotenv@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" + integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== dotenv@^16.0.0: version "16.0.0" @@ -728,10 +787,17 @@ emoji-regex@^9.2.0, emoji-regex@^9.2.2: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -entities@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" - integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + +errno@^0.1.1: + version "0.1.8" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" + integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== + dependencies: + prr "~1.0.1" es-abstract@^1.19.0, es-abstract@^1.19.1: version "1.19.1" @@ -768,6 +834,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" @@ -1012,6 +1083,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fastparse@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fastparse/-/fastparse-1.1.2.tgz#91728c5a5942eced8531283c79441ee4122c35a9" + integrity sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ== + fastq@^1.6.0: version "1.13.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" @@ -1077,6 +1153,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -1092,6 +1173,13 @@ gemoji@^6.1.0: resolved "https://registry.yarnpkg.com/gemoji/-/gemoji-6.1.0.tgz#268fbb0c81d1a8c32a4bcc39bdfdd66080ba7ce9" integrity sha512-MOlX3doQ1fsfzxQX8Y+u6bC5Ssc1pBUBIPVyrS69EzKt+5LIZAOm0G5XGVNhwXFgkBF3r+Yk88ONyrFHo8iNFA== +generic-names@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917" + integrity sha1-LXhqEhruUIh2eWk56OO/+DbCCRc= + dependencies: + loader-utils "^0.2.16" + get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -1114,7 +1202,7 @@ github-slugger@^1.1.1: resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.4.0.tgz#206eb96cdb22ee56fdc53a28d5a302338463444e" integrity sha512-w0dzqw/nt51xMVmlaV1+JRzN+oCa1KfcgGEWhxUG16wbdA+Xnt/yoFO8Z8x/V82ZcZ0wy6ln9QDup5avbhiDhQ== -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -1140,7 +1228,7 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.7: +glob@^7.1.3, glob@^7.1.6, glob@^7.1.7: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -1171,11 +1259,21 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" +graceful-fs@^4.1.2: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= + has-flag@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" @@ -1200,6 +1298,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hast-util-embedded@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hast-util-embedded/-/hast-util-embedded-2.0.0.tgz#877f4261044854743fc2621f728930ca61c8376f" + integrity sha512-vEr54rDu2CheBM4nLkWbW8Rycf8HhkA/KsrDnlyKnvBTyhyO+vAG6twHnfUbiRGo56YeUBNCI4HFfHg3Wu+tig== + dependencies: + hast-util-is-element "^2.0.0" + hast-util-from-parse5@^7.0.0: version "7.1.0" resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-7.1.0.tgz#c129dd3a24dd8a867ab8a029ca47e27aa54864b7" @@ -1246,21 +1351,25 @@ hast-util-parse-selector@^3.0.0: dependencies: "@types/hast" "^2.0.0" -hast-util-to-html@^8.0.0: - version "8.0.3" - resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-8.0.3.tgz#4e37580872e143ea9ce0dba87918b19e4ea997e3" - integrity sha512-/D/E5ymdPYhHpPkuTHOUkSatxr4w1ZKrZsG0Zv/3C2SRVT0JFJG53VS45AMrBtYk0wp5A7ksEhiC8QaOZM95+A== +hast-util-to-mdast@^8.3.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/hast-util-to-mdast/-/hast-util-to-mdast-8.3.1.tgz#b2e3b666968d3ab96a0e6ec991cca0ca5c79e3b2" + integrity sha512-nxLcom1oW5y/1CyaV24K16LOfYbAIS74BDbCPW6WduzAYTAVDp3g/DM1CY6Ngo+Dx5itLzvmCm7SnUHBZd3NVQ== dependencies: + "@types/extend" "^3.0.0" "@types/hast" "^2.0.0" - ccount "^2.0.0" - comma-separated-tokens "^2.0.0" + "@types/mdast" "^3.0.0" + "@types/unist" "^2.0.0" + extend "^3.0.0" + hast-util-has-property "^2.0.0" hast-util-is-element "^2.0.0" - hast-util-whitespace "^2.0.0" - html-void-elements "^2.0.0" - property-information "^6.0.0" - space-separated-tokens "^2.0.0" - stringify-entities "^4.0.2" + hast-util-to-text "^3.0.0" + mdast-util-phrasing "^3.0.0" + mdast-util-to-string "^3.0.0" + rehype-minify-whitespace "^5.0.0" + trim-trailing-lines "^2.0.0" unist-util-is "^5.0.0" + unist-util-visit "^4.0.0" hast-util-to-string@^2.0.0: version "2.0.0" @@ -1269,7 +1378,7 @@ hast-util-to-string@^2.0.0: dependencies: "@types/hast" "^2.0.0" -hast-util-to-text@^3.1.0: +hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.1.tgz#b7699a75f7a61af6e0befb67660cd78460d96dc6" integrity sha512-7S3mOBxACy8syL45hCn3J7rHqYaXkxRfsX6LXEU5Shz4nt4GxdjtMUtG+T6G/ZLUHd7kslFAf14kAN71bz30xA== @@ -1322,10 +1431,24 @@ hoist-non-react-statics@^3.3.2: dependencies: react-is "^16.7.0" -html-void-elements@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" - integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +icss-utils@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-3.0.1.tgz#ee70d3ae8cac38c6be5ed91e851b27eed343ad0f" + integrity sha1-7nDTroysOMa+XtkehRsn7tNDrQ8= + dependencies: + postcss "^6.0.2" + +icss-utils@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" + integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== ignore@^4.0.6: version "4.0.6" @@ -1337,6 +1460,16 @@ ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +image-size@~0.5.0: + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" + integrity sha1-Cd/Uq50g4p6xw+gLiZA3jfnjy5w= + +immutable@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23" + integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw== + import-fresh@^3.0.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -1358,7 +1491,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1397,6 +1530,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -1439,7 +1579,7 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1507,6 +1647,11 @@ is-weakref@^1.0.1: dependencies: call-bind "^1.0.2" +is-what@^3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" + integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1539,6 +1684,11 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json5@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + json5@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" @@ -1585,6 +1735,23 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" +less@^4.1.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/less/-/less-4.1.2.tgz#6099ee584999750c2624b65f80145f8674e4b4b0" + integrity sha512-EoQp/Et7OSOVu0aJknJOtlXZsnr8XE8KwuzTHOLeVSEx8pVWUICc8Q0VYRHgzyjX78nMEyC/oztWFbgyhtNfDA== + dependencies: + copy-anything "^2.0.1" + parse-node-version "^1.0.1" + tslib "^2.3.0" + optionalDependencies: + errno "^0.1.1" + graceful-fs "^4.1.2" + image-size "~0.5.0" + make-dir "^2.1.0" + mime "^1.4.1" + needle "^2.5.2" + source-map "~0.6.0" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -1593,6 +1760,21 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lilconfig@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" + integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== + +loader-utils@^0.2.16: + version "0.2.17" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-0.2.17.tgz#f86e6374d43205a6e6c60e9196f17c0299bfb348" + integrity sha1-+G5jdNQyBabmxg6RlvF8Apm/s0g= + dependencies: + big.js "^3.1.3" + emojis-list "^2.0.0" + json5 "^0.5.0" + object-assign "^4.0.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -1601,6 +1783,11 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash.camelcase@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" + integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= + lodash.debounce@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" @@ -1611,6 +1798,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@^4.17.4: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== + longest-streak@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.0.1.tgz#c97315b7afa0e7d9525db9a5a2953651432bdc5d" @@ -1646,6 +1838,14 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-dir@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" + integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA== + dependencies: + pify "^4.0.1" + semver "^5.6.0" + markdown-table@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" @@ -1759,6 +1959,14 @@ mdast-util-math@^2.0.0: longest-streak "^3.0.0" mdast-util-to-markdown "^1.3.0" +mdast-util-phrasing@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-3.0.0.tgz#c44fcf6be61a3cb8da54ab2df22320e61d4537ce" + integrity sha512-S+QYsDRLkGi8U7o5JF1agKa/sdP+CNGXXLqC17pdTVL8FHHgQEiwFGa9yE5aYtUxNiFGYoaDy9V1kC85Sz86Gg== + dependencies: + "@types/mdast" "^3.0.0" + unist-util-is "^5.0.0" + mdast-util-to-hast@^12.1.0: version "12.1.1" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.1.1.tgz#89a2bb405eaf3b05eb8bf45157678f35eef5dbca" @@ -2098,6 +2306,11 @@ micromatch@^4.0.4: braces "^3.0.1" picomatch "^2.2.3" +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2110,6 +2323,11 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mkdirp@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + mri@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" @@ -2140,6 +2358,15 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +needle@^2.5.2: + version "2.9.1" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.9.1.tgz#22d1dffbe3490c2b83e301f7709b6736cd8f2684" + integrity sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ== + dependencies: + debug "^3.2.6" + iconv-lite "^0.4.4" + sax "^1.2.4" + next@^12.1.1-canary.15: version "12.1.1-canary.15" resolved "https://registry.yarnpkg.com/next/-/next-12.1.1-canary.15.tgz#a31c99a512b29d98aa0f6d837931d5c7f2aa2c35" @@ -2164,14 +2391,12 @@ next@^12.1.1-canary.15: "@next/swc-win32-ia32-msvc" "12.1.1-canary.15" "@next/swc-win32-x64-msvc" "12.1.1-canary.15" -nth-check@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.0.1.tgz#2efe162f5c3da06a28959fbd3db75dbeea9f0fc2" - integrity sha512-it1vE95zF6dTT9lBsYbxvqh0Soy4SPowchj0UBGj/V6cTPnXXtQOPUbhZ6CmGzAD/rW22LQK6E96pcdJXk4A4w== - dependencies: - boolbase "^1.0.0" +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -2288,14 +2513,12 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse5-htmlparser2-tree-adapter@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz#2cdf9ad823321140370d4dbf5d3e92c7c8ddc6e6" - integrity sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA== - dependencies: - parse5 "^6.0.1" +parse-node-version@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== -parse5@^6.0.0, parse5@^6.0.1: +parse5@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -2330,11 +2553,56 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.2.3: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pify@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" + integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== + +postcss-filter-plugins@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-3.0.1.tgz#9d226e946d56542ab7c26123053459a331df545d" + integrity sha512-tRKbW4wWBEkSSFuJtamV2wkiV9rj6Yy7P3Y13+zaynlPEEZt8EgYKn3y/RBpMeIhNmHXFlSdzofml65hD5OafA== + dependencies: + postcss "^6.0.14" + +postcss-icss-keyframes@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/postcss-icss-keyframes/-/postcss-icss-keyframes-0.2.1.tgz#80c4455e0112b0f2f9c3c05ac7515062bb9ff295" + integrity sha1-gMRFXgESsPL5w8Bax1FQYruf8pU= + dependencies: + icss-utils "^3.0.1" + postcss "^6.0.2" + postcss-value-parser "^3.3.0" + +postcss-icss-selectors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/postcss-icss-selectors/-/postcss-icss-selectors-2.0.3.tgz#27fa1afcaab6c602c866cbb298f3218e9bc1c9b3" + integrity sha1-J/oa/Kq2xgLIZsuymPMhjpvBybM= + dependencies: + css-selector-tokenizer "^0.7.0" + generic-names "^1.0.2" + icss-utils "^3.0.1" + lodash "^4.17.4" + postcss "^6.0.2" + +postcss-load-config@^3.0.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.3.tgz#21935b2c43b9a86e6581a576ca7ee1bde2bd1d23" + integrity sha512-5EYgaM9auHGtO//ljHH+v/aC/TQ5LHXtL7bQajNAUBKUVKiYE8rYpFms7+V26D9FncaGe2zwCoPQsFKb5zF/Hw== + dependencies: + lilconfig "^2.0.4" + yaml "^1.10.2" + +postcss-value-parser@^3.3.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" + integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== + postcss@8.4.5: version "8.4.5" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" @@ -2344,7 +2612,16 @@ postcss@8.4.5: picocolors "^1.0.0" source-map-js "^1.0.1" -postcss@^8.3.7: +postcss@^6.0.14, postcss@^6.0.2: + version "6.0.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" + integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== + dependencies: + chalk "^2.4.1" + source-map "^0.6.1" + supports-color "^5.4.0" + +postcss@^8.3.0: version "8.4.12" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== @@ -2358,10 +2635,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -pretty-bytes@^5.3.0: - version "5.6.0" - resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" - integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== prismjs@^1.25.0, prismjs@^1.27.0, prismjs@~1.27.0: version "1.27.0" @@ -2389,6 +2666,11 @@ property-information@^6.0.0: resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.1.1.tgz#5ca85510a3019726cb9afed4197b7b8ac5926a22" integrity sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w== +prr@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= + punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -2513,6 +2795,13 @@ react@17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + refractor@^3.2.0: version "3.6.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-3.6.0.tgz#ac318f5a0715ead790fcfb0c71f4dd83d977935a" @@ -2572,6 +2861,18 @@ rehype-katex@^6.0.2: unist-util-remove-position "^4.0.0" unist-util-visit "^4.0.0" +rehype-minify-whitespace@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/rehype-minify-whitespace/-/rehype-minify-whitespace-5.0.0.tgz#f3eec0ac94e78adbb868133e64864343ba9fff1f" + integrity sha512-cUkQldYx8jpWM6FZmCkOF/+mev09+5NAtBzUFZgnfXsIoglxo3h6t3S3JbNouiaqIDE6vbpI+GQpOg0xD3Z7kg== + dependencies: + "@types/hast" "^2.0.0" + hast-util-embedded "^2.0.0" + hast-util-is-element "^2.0.0" + hast-util-whitespace "^2.0.0" + unified "^10.0.0" + unist-util-is "^5.0.0" + rehype-parse@^8.0.0: version "8.0.4" resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688" @@ -2582,6 +2883,16 @@ rehype-parse@^8.0.0: parse5 "^6.0.0" unified "^10.0.0" +rehype-remark@^9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/rehype-remark/-/rehype-remark-9.1.2.tgz#b4ed84d7e692426c3269e72ec477906cec659c05" + integrity sha512-c0fG3/CrJ95zAQ07xqHSkdpZybwdsY7X5dNWvgL2XqLKZuqmG3+vk6kP/4miCnp+R+x/0uKKRSpfXb9aGR8Z5w== + dependencies: + "@types/hast" "^2.0.0" + "@types/mdast" "^3.0.0" + hast-util-to-mdast "^8.3.0" + unified "^10.0.0" + rehype-slug@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/rehype-slug/-/rehype-slug-5.0.1.tgz#6e732d0c55b3b1e34187e74b7363fb53229e5f52" @@ -2595,15 +2906,6 @@ rehype-slug@^5.0.1: unified "^10.0.0" unist-util-visit "^4.0.0" -rehype-stringify@^9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/rehype-stringify/-/rehype-stringify-9.0.3.tgz#70e3bd6d4d29e7acf36b802deed350305d2c3c17" - integrity sha512-kWiZ1bgyWlgOxpqD5HnxShKAdXtb2IUljn3hQAhySeak6IOQPPt6DeGnsIh4ixm7yKJWzm8TXFuC/lPfcWHJqw== - dependencies: - "@types/hast" "^2.0.0" - hast-util-to-html "^8.0.0" - unified "^10.0.0" - remark-gfm@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-3.0.1.tgz#0b180f095e3036545e9dddac0e8df3fa5cfee54f" @@ -2643,11 +2945,21 @@ remark-rehype@^10.0.0: mdast-util-to-hast "^12.1.0" unified "^10.0.0" +reserved-words@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1" + integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE= + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -2691,6 +3003,25 @@ sade@^1.7.3: dependencies: mri "^1.1.0" +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sass@^1.32.13: + version "1.49.9" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9" + integrity sha512-YlYWkkHP9fbwaFRZQRXgDi3mXZShslVmmo+FVK3kHLUELHHEYrCmL1x6IUjC7wLS6VuJSAFXRQS/DxdsC4xL1A== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +sax@^1.2.4, sax@~1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + scheduler@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" @@ -2699,6 +3030,11 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" +semver@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -2737,11 +3073,37 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -source-map-js@^1.0.1, source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-resolve@^0.5.2: + version "0.5.3" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" + integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== + dependencies: + atob "^2.1.2" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-url@^0.4.0: + version "0.4.1" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" + integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== + +source-map@^0.6.1, source-map@~0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.3: + version "0.7.3" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" + integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== + space-separated-tokens@^1.0.0: version "1.1.5" resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz#85f32c3d10d9682007e917414ddc5c26d1aa6899" @@ -2782,14 +3144,6 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -stringify-entities@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.2.tgz#13d113dc7449dc8ae4cb22c28883ee3fff8753e3" - integrity sha512-MTxTVcEkorNtBbNpoFJPEh0kKdM6+QbMjLbaxmvaPMmayOXdr/AIVIIJX7FReUVweRBFJfZepK4A4AKgwuFpMQ== - dependencies: - character-entities-html4 "^2.0.0" - character-entities-legacy "^3.0.0" - strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2819,6 +3173,27 @@ styled-jsx@5.0.1: resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.1.tgz#78fecbbad2bf95ce6cd981a08918ce4696f5fc80" integrity sha512-+PIZ/6Uk40mphiQJJI1202b+/dYeTVd9ZnMPR80pgiWbjIwvN2zIp4r9et0BgqBuShh48I0gttPlAXA7WVvBxw== +stylus@^0.54.8: + version "0.54.8" + resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" + integrity sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg== + dependencies: + css-parse "~2.0.0" + debug "~3.1.0" + glob "^7.1.6" + mkdirp "~1.0.4" + safer-buffer "^2.1.2" + sax "~1.2.4" + semver "^6.3.0" + source-map "^0.7.3" + +supports-color@^5.3.0, supports-color@^5.4.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2848,6 +3223,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +trim-trailing-lines@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-2.0.0.tgz#1fdfbc20db6651bed117bc5c736f6cf052bcf421" + integrity sha512-lyJ/STqmYO8k9aWZbodPTrVkRh/50yFwNZ/HxKp/30eM9zf898MgQCG/NxfMnx7fZSKbaEcuZ1V+QgWrXFH6rg== + trough@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" @@ -2868,7 +3248,7 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.3: +tslib@^2.0.3, tslib@^2.3.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== @@ -2892,6 +3272,25 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +typescript-plugin-css-modules@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/typescript-plugin-css-modules/-/typescript-plugin-css-modules-3.4.0.tgz#4ff6905d88028684d1608c05c62cb6346e5548cc" + integrity sha512-2MdjfSg4MGex1csCWRUwKD+MpgnvcvLLr9bSAMemU/QYGqBsXdez0cc06H/fFhLtRoKJjXg6PSTur3Gy1Umhpw== + dependencies: + dotenv "^10.0.0" + icss-utils "^5.1.0" + less "^4.1.1" + lodash.camelcase "^4.3.0" + postcss "^8.3.0" + postcss-filter-plugins "^3.0.1" + postcss-icss-keyframes "^0.2.1" + postcss-icss-selectors "^2.0.3" + postcss-load-config "^3.0.1" + reserved-words "^0.1.2" + sass "^1.32.13" + stylus "^0.54.8" + tsconfig-paths "^3.9.0" + typescript@4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" @@ -3028,6 +3427,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + use-subscription@1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" @@ -3119,6 +3523,11 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yaml@^1.10.2: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + zwitch@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.2.tgz#91f8d0e901ffa3d66599756dde7f57b17c95dce1" From d06d0ffea295aeb5f0ddf9cfd4a2e14ff4a8b6a4 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 14:54:36 -0700 Subject: [PATCH 22/63] client: remove cache control --- client/pages/post/[id].tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 0289cb00..4a6cf0d9 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -29,8 +29,6 @@ export type PostProps = ThemeProps & { } const Post = ({ post, theme, changeTheme }: PostProps) => { - const router = useRouter(); - const download = async () => { const clientZip = require("client-zip") @@ -117,7 +115,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const maxAge = 60 * 60 * 24 * 365; context.res.setHeader( 'Cache-Control', - `${json.visibility === "public" ? "public" : "private"}, s-maxage=${maxAge}, max-age=${maxAge}` + `${json.visibility === "public" ? "public" : "private"}, s-maxage=${maxAge}` ) return { props: { From 2ec4019ea2636b507c1dffe90883be0f4977bbe9 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 14:56:45 -0700 Subject: [PATCH 23/63] client: fix submitting status on new post --- client/components/new-post/index.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index a12cc8fc..64d45cd0 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -73,15 +73,10 @@ const Post = () => { password, userId: Cookies.get('drift-userid') || '' }) - - - - setSubmitting(false) } const onClosePasswordModal = () => { setPasswordModalVisible(false) - setSubmitting(false) } const updateTitle = useCallback((title: string, id: string) => { From 3efbeb726fc77784e129f246402258352be3f9a1 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 15:55:21 -0700 Subject: [PATCH 24/63] client: tree-shaking improvements --- .../document/formatting-icons/index.tsx | 5 +- client/components/document/index.tsx | 4 +- client/components/header/index.tsx | 18 ++- client/components/new-post/index.tsx | 5 +- .../preview/react-markdown-preview.tsx | 7 +- client/next.config.js | 6 +- client/package.json | 5 +- client/pages/post/[id].tsx | 4 +- client/yarn.lock | 132 +++++++++++------- server/src/server.ts | 2 +- 10 files changed, 120 insertions(+), 68 deletions(-) diff --git a/client/components/document/formatting-icons/index.tsx b/client/components/document/formatting-icons/index.tsx index 913c126e..288e7daa 100644 --- a/client/components/document/formatting-icons/index.tsx +++ b/client/components/document/formatting-icons/index.tsx @@ -1,5 +1,8 @@ import { ButtonGroup, Button } from "@geist-ui/core" -import { Bold, Italic, Link, Image as ImageIcon } from '@geist-ui/icons' +import Bold from '@geist-ui/icons/bold' +import Italic from '@geist-ui/icons/italic' +import Link from '@geist-ui/icons/link' +import ImageIcon from '@geist-ui/icons/image' import { RefObject, useCallback, useMemo } from "react" import styles from '../document.module.css' diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index 35a4b3ef..55f31066 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -2,7 +2,9 @@ import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } fro import { ChangeEvent, memo, useCallback, useMemo, useRef, useState } from "react" import styles from './document.module.css' import MarkdownPreview from '../preview' -import { Trash, Download, ExternalLink } from '@geist-ui/icons' +import Trash from '@geist-ui/icons/trash' +import Download from '@geist-ui/icons/download' +import ExternalLink from '@geist-ui/icons/externalLink' import FormattingIcons from "./formatting-icons" import Skeleton from "react-loading-skeleton" // import Link from "next/link" diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx index d5905f22..c90291dc 100644 --- a/client/components/header/index.tsx +++ b/client/components/header/index.tsx @@ -1,11 +1,21 @@ import { Page, ButtonGroup, Button, useBodyScroll, useMediaQuery, Tabs, Spacer } from "@geist-ui/core"; -import { Github as GitHubIcon, UserPlus as SignUpIcon, User as SignInIcon, Home as HomeIcon, Menu as MenuIcon, Tool as SettingsIcon, UserX as SignoutIcon, PlusCircle as NewIcon, List as YourIcon, Moon, Sun } from "@geist-ui/icons"; import { DriftProps } from "../../pages/_app"; -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useState } from "react"; import styles from './header.module.css'; import { useRouter } from "next/router"; import useSignedIn from "../../lib/hooks/use-signed-in"; +import HomeIcon from '@geist-ui/icons/home'; +import MenuIcon from '@geist-ui/icons/menu'; +import GitHubIcon from '@geist-ui/icons/github'; +import SignOutIcon from '@geist-ui/icons/userX'; +import SignInIcon from '@geist-ui/icons/user'; +import SignUpIcon from '@geist-ui/icons/userPlus'; +import NewIcon from '@geist-ui/icons/plusCircle'; +import YourIcon from '@geist-ui/icons/list' +import MoonIcon from '@geist-ui/icons/moon'; +import SunIcon from '@geist-ui/icons/sun'; + type Tab = { name: string icon: JSX.Element @@ -61,7 +71,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => { { name: "Sign out", href: "/signout", - icon: <SignoutIcon />, + icon: <SignOutIcon />, condition: isSignedIn, value: "signout" }, @@ -94,7 +104,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => { setSelectedTab(''); } }, - icon: theme === 'light' ? <Moon /> : <Sun />, + icon: theme === 'light' ? <MoonIcon /> : <SunIcon />, condition: true, value: "theme", } diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 64d45cd0..cd77a43e 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -49,7 +49,6 @@ const Post = () => { const closePasswordModel = () => { setPasswordModalVisible(false) - setSubmitting(false) } const [isSubmitting, setSubmitting] = useState(false) @@ -89,7 +88,7 @@ const Post = () => { const uploadDocs = useCallback((files: DocumentType[]) => { // if no title is set and the only document is empty, - const isFirstDocEmpty = docs.length === 1 && docs[0].title === '' && docs[0].content === '' + const isFirstDocEmpty = docs.length <= 1 && docs[0].title === '' && docs[0].content === '' const shouldSetTitle = !title && isFirstDocEmpty if (shouldSetTitle) { if (files.length === 1) { @@ -147,7 +146,7 @@ const Post = () => { </ButtonDropdown> <PasswordModal isOpen={passwordModalVisible} onClose={onClosePasswordModal} onSubmit={(password) => onSubmit('protected', password)} /> </div> - </div > + </div> ) } diff --git a/client/components/preview/react-markdown-preview.tsx b/client/components/preview/react-markdown-preview.tsx index 92d9963d..b02ac3e1 100644 --- a/client/components/preview/react-markdown-preview.tsx +++ b/client/components/preview/react-markdown-preview.tsx @@ -5,10 +5,11 @@ import rehypeSlug from 'rehype-slug' import rehypeAutolinkHeadings from 'rehype-autolink-headings' // @ts-ignore because of no types in remark-a11y-emoji -import a11yEmoji from '@fec/remark-a11y-emoji'; +// import a11yEmoji from '@fec/remark-a11y-emoji'; import styles from './preview.module.css' -import { vscDarkPlus as dark, vs as light } from 'react-syntax-highlighter/dist/cjs/styles/prism' +import dark from 'react-syntax-highlighter/dist/cjs/styles/prism/vsc-dark-plus' +import light from 'react-syntax-highlighter/dist/cjs/styles/prism/vs' import useSharedState from "@lib/hooks/use-shared-state"; type Props = { @@ -20,7 +21,7 @@ const ReactMarkdownPreview = ({ content, height }: Props) => { const [themeType] = useSharedState<string>('theme') return (<div style={{ height }}> <ReactMarkdown className={styles.markdownPreview} - remarkPlugins={[remarkGfm, a11yEmoji]} + remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]]} components={{ code({ node, inline, className, children, ...props }) { diff --git a/client/next.config.js b/client/next.config.js index 16eb5923..d802e769 100644 --- a/client/next.config.js +++ b/client/next.config.js @@ -1,6 +1,10 @@ const dotenv = require("dotenv"); dotenv.config(); +const withBundleAnalyzer = require("@next/bundle-analyzer")({ + enabled: process.env.ANALYZE === "true", +}); + /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, @@ -21,4 +25,4 @@ const nextConfig = { }, }; -module.exports = nextConfig; +module.exports = withBundleAnalyzer(nextConfig); diff --git a/client/package.json b/client/package.json index f2d66652..3e73c530 100644 --- a/client/package.json +++ b/client/package.json @@ -6,10 +6,10 @@ "dev": "next dev --port 3001", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "analyze": "ANALYZE=true next build" }, "dependencies": { - "@fec/remark-a11y-emoji": "^3.1.0", "@geist-ui/core": "^2.3.5", "@geist-ui/icons": "^1.0.1", "@types/cookie": "^0.4.1", @@ -39,6 +39,7 @@ "swr": "^1.2.2" }, "devDependencies": { + "@next/bundle-analyzer": "^12.1.0", "@types/node": "17.0.21", "@types/react": "17.0.39", "@types/react-dom": "^17.0.14", diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 4a6cf0d9..5ddeb3f8 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -7,7 +7,7 @@ import VisibilityBadge from "../../components/visibility-badge"; import PageSeo from "components/page-seo"; import styles from './styles.module.css'; import cookie from "cookie"; -import { GetServerSideProps } from "next"; +import { GetServerSideProps, GetStaticPaths, GetStaticProps } from "next"; import { PostVisibility, ThemeProps } from "@lib/types"; type File = { @@ -92,7 +92,6 @@ export const getServerSideProps: GetServerSideProps = async (context) => { driftTheme = "light" } - if (context.query.id) { const post = await fetch('http://' + host + `/server-api/posts/${context.query.id}`, { method: "GET", @@ -135,3 +134,4 @@ export const getServerSideProps: GetServerSideProps = async (context) => { } export default Post + diff --git a/client/yarn.lock b/client/yarn.lock index 2d58786e..7dbfe7ad 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -32,16 +32,6 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" -"@fec/remark-a11y-emoji@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@fec/remark-a11y-emoji/-/remark-a11y-emoji-3.1.0.tgz#765ead0e26b6f06878f246c6e920a6edf0a5dc08" - integrity sha512-cYjCutvrValbQ2dykKhEE6OBTsEGpAJzeJnDtGKXhk2JYkMNSlKPnUk/kRFHH+PHmrAF+FlNIU28XKD+nYy7jQ== - dependencies: - emoji-regex "^9.2.0" - gemoji "^6.1.0" - mdast-util-find-and-replace "^1.0.0" - unist-util-visit "^2.0.3" - "@geist-ui/core@^2.3.5": version "2.3.5" resolved "https://registry.yarnpkg.com/@geist-ui/core/-/core-2.3.5.tgz#9b9f64f1c26e9fa68eb7e2bc4a2c1022ee353f66" @@ -68,6 +58,13 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@next/bundle-analyzer@^12.1.0": + version "12.1.0" + resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-12.1.0.tgz#9f6d6cda2a26220c936805be407243e22790f4b7" + integrity sha512-pOtWRWaKQXff8A80Ex3E67EH8XuERHxBPn8cQgKzfhRKQwoTEareHe2nWJO1uXTQm6m7ZRhmhb4+uwp+UvmITQ== + dependencies: + webpack-bundle-analyzer "4.3.0" + "@next/env@12.1.1-canary.15": version "12.1.1-canary.15" resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.1-canary.15.tgz#d1c210df31c8865042f2b81ffb37660b9cc70045" @@ -161,6 +158,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@polka/url@^1.0.0-next.20": + version "1.0.0-next.21" + resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" + integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== + "@rushstack/eslint-patch@^1.0.8": version "1.1.0" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" @@ -319,7 +321,12 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.7.0: +acorn-walk@^8.0.0: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.0.4, acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== @@ -508,7 +515,7 @@ chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.1.0: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -600,6 +607,11 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98" integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg== +commander@^6.2.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + commander@^8.0.0: version "8.3.0" resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" @@ -782,7 +794,12 @@ dotenv@^16.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.0.tgz#c619001253be89ebb638d027b609c75c26e47411" integrity sha512-qD9WU0MPM4SWLPJy/r2Be+2WgQj8plChsyrCNQzW/0WjvcJQiKQJ9mH3ZgB3fxbUUxgc/11ZJ0Fi5KiimWGz2Q== -emoji-regex@^9.2.0, emoji-regex@^9.2.2: +duplexer@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" + integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== + +emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== @@ -1168,11 +1185,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -gemoji@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/gemoji/-/gemoji-6.1.0.tgz#268fbb0c81d1a8c32a4bcc39bdfdd66080ba7ce9" - integrity sha512-MOlX3doQ1fsfzxQX8Y+u6bC5Ssc1pBUBIPVyrS69EzKt+5LIZAOm0G5XGVNhwXFgkBF3r+Yk88ONyrFHo8iNFA== - generic-names@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/generic-names/-/generic-names-1.0.3.tgz#2d786a121aee508876796939e8e3bff836c20917" @@ -1264,6 +1276,13 @@ graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +gzip-size@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" + integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== + dependencies: + duplexer "^0.1.2" + has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -1798,7 +1817,7 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== -lodash@^4.17.4: +lodash@^4.17.20, lodash@^4.17.4: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -1860,15 +1879,6 @@ mdast-util-definitions@^5.0.0: "@types/unist" "^2.0.0" unist-util-visit "^3.0.0" -mdast-util-find-and-replace@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-1.1.1.tgz#b7db1e873f96f66588c321f1363069abf607d1b5" - integrity sha512-9cKl33Y21lyckGzpSmEQnIDjEfeeWelN5s1kUW1LwdB0Fkuq2u+4GdqcGEygYxJE8GVqCl0741bYXHgamfWAZA== - dependencies: - escape-string-regexp "^4.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" - mdast-util-find-and-replace@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-2.1.0.tgz#69728acd250749f8aac6e150e07d1fd15619e829" @@ -2333,6 +2343,11 @@ mri@^1.1.0: resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== +mrmime@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.0.tgz#14d387f0585a5233d291baba339b063752a2398b" + integrity sha512-a70zx7zFfVO7XpnQ2IX1Myh9yY4UYvfld/dikWRnsXxbyvMcfz+u6UfgNAtH+k2QqtJuzVpv6eLTx1G2+WKZbQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -2463,6 +2478,11 @@ once@^1.3.0: dependencies: wrappy "1" +opener@^1.5.2: + version "1.5.2" + resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" + integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -3068,6 +3088,15 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +sirv@^1.0.7: + version "1.0.19" + resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" + integrity sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ== + dependencies: + "@polka/url" "^1.0.0-next.20" + mrmime "^1.0.0" + totalist "^1.0.0" + slash@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" @@ -3223,6 +3252,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +totalist@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" + integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== + trim-trailing-lines@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-2.0.0.tgz#1fdfbc20db6651bed117bc5c736f6cf052bcf421" @@ -3339,11 +3373,6 @@ unist-util-generated@^2.0.0: resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113" integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw== -unist-util-is@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-4.1.0.tgz#976e5f462a7a5de73d94b706bac1b90671b57797" - integrity sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg== - unist-util-is@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-5.1.1.tgz#e8aece0b102fa9bc097b0fef8f870c496d4a6236" @@ -3369,14 +3398,6 @@ unist-util-stringify-position@^3.0.0: dependencies: "@types/unist" "^2.0.0" -unist-util-visit-parents@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-3.1.1.tgz#65a6ce698f78a6b0f56aa0e88f13801886cdaef6" - integrity sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-4.1.1.tgz#e83559a4ad7e6048a46b1bdb22614f2f3f4724f2" @@ -3393,15 +3414,6 @@ unist-util-visit-parents@^5.0.0: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" -unist-util-visit@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-2.0.3.tgz#c3703893146df47203bb8a9795af47d7b971208c" - integrity sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^4.0.0" - unist-util-visit-parents "^3.0.0" - unist-util-visit@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-3.1.0.tgz#9420d285e1aee938c7d9acbafc8e160186dbaf7b" @@ -3485,6 +3497,21 @@ web-namespaces@^2.0.0: resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" integrity sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ== +webpack-bundle-analyzer@4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz#2f3c0ca9041d5ee47fa418693cf56b4a518b578b" + integrity sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA== + dependencies: + acorn "^8.0.4" + acorn-walk "^8.0.0" + chalk "^4.1.0" + commander "^6.2.0" + gzip-size "^6.0.0" + lodash "^4.17.20" + opener "^1.5.2" + sirv "^1.0.7" + ws "^7.3.1" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -3513,6 +3540,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^7.3.1: + version "7.5.7" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" + integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" diff --git a/server/src/server.ts b/server/src/server.ts index 134ad9af..2e96ea8d 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,7 +4,7 @@ import config from '../lib/config'; import { sequelize } from '../lib/sequelize'; (async () => { - await sequelize.sync({ force: true }); + await sequelize.sync(); createServer(app) .listen( config.port, From 90fa28ad6521454b279c501a2493d9d6e74f46c9 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 17:20:41 -0700 Subject: [PATCH 25/63] post generation rework with static paths/props --- README.md | 2 + client/.env.local | 1 + client/components/my-posts/index.tsx | 14 +- client/components/new-post/index.tsx | 3 +- client/components/post-list/list-item.tsx | 3 +- .../preview/react-markdown-preview.tsx | 2 +- client/lib/get-post-path.ts | 13 ++ client/pages/api/raw/[id].ts | 13 +- client/pages/api/revalidate.ts | 24 ++++ client/pages/mine.tsx | 34 ++++- client/pages/post/[id].tsx | 80 +++++------ client/pages/post/private/[id].tsx | 128 ++++++++++++++++++ server/{ => src}/lib/config.ts | 0 server/{ => src}/lib/middleware/jwt.ts | 0 server/src/lib/middleware/secret-key.ts | 14 ++ server/{ => src}/lib/models/File.ts | 0 server/{ => src}/lib/models/Post.ts | 0 server/{ => src}/lib/models/PostAuthor.ts | 0 server/{ => src}/lib/models/User.ts | 0 server/{ => src}/lib/sequelize.ts | 0 server/src/routes/auth.ts | 6 +- server/src/routes/files.ts | 14 +- server/src/routes/posts.ts | 39 ++++-- server/src/routes/users.ts | 8 +- server/src/server.ts | 4 +- server/tsconfig.json | 2 +- 26 files changed, 302 insertions(+), 102 deletions(-) create mode 100644 client/lib/get-post-path.ts create mode 100644 client/pages/api/revalidate.ts create mode 100644 client/pages/post/private/[id].tsx rename server/{ => src}/lib/config.ts (100%) rename server/{ => src}/lib/middleware/jwt.ts (100%) create mode 100644 server/src/lib/middleware/secret-key.ts rename server/{ => src}/lib/models/File.ts (100%) rename server/{ => src}/lib/models/Post.ts (100%) rename server/{ => src}/lib/models/PostAuthor.ts (100%) rename server/{ => src}/lib/models/User.ts (100%) rename server/{ => src}/lib/sequelize.ts (100%) diff --git a/README.md b/README.md index 74a7f5cd..cb12ef79 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ You can change these to your liking. - `API_URL`: defaults to localhost:3001, but allows you to host the front-end separately from the backend on a service like Vercel or Netlify - `WELCOME_CONTENT`: a markdown string (with \n newlines) that's rendered on the home page - `WELCOME_TITLE`: the file title for the post on the homepage. +- `SECRET_KEY`: a secret key used for validating API requests that is never exposed to the browser `server/.env`: @@ -38,6 +39,7 @@ You can change these to your liking. - `JWT_SECRET`: a secure token for JWT tokens. You can generate one [here](https://www.grc.com/passwords.htm). - `MEMORY_DB`: if `true`, a sqlite database will not be created and changes will only exist in memory. Mainly for the demo. - `REGISTRATION_PASSWORD`: if MEMORY_DB is not `true`, the user will be required to provide this password to sign-up, in addition to their username and account password. If it's not set, no password will be required. +- `SECRET_KEY`: the same secret key as the client ## Current status diff --git a/client/.env.local b/client/.env.local index 3bcbece7..a4e28f86 100644 --- a/client/.env.local +++ b/client/.env.local @@ -1,3 +1,4 @@ API_URL=http://localhost:3000 WELCOME_TITLE="Welcome to Drift" WELCOME_CONTENT="### Drift is a self-hostable clone of GitHub Gist. \nIt is a simple way to share code and text snippets with your friends, with support for the following:\n \n - Render GitHub Extended Markdown (including images)\n - User authentication\n - Private, public, and secret posts\n \n If you want to signup, you can join at [/signup](/signup) as long as you have a passcode provided by the administrator (which you don\'t need for this demo).\n **This demo is on a memory-only database, so accounts and pastes can be deleted at any time.**\n You can find the source code on [GitHub](https://github.com/MaxLeiter/drift).\n \n Drift was inspired by [this tweet](https://twitter.com/emilyst/status/1499858264346935297):\n > What is the absolute closest thing to GitHub Gist that can be self-hosted?\n In terms of design and functionality. Hosts images and markdown, rendered. Creates links that can be private or public. Uses/requires registration. I have looked at dozens of pastebin-like things." +SECRET_KEY=secret \ No newline at end of file diff --git a/client/components/my-posts/index.tsx b/client/components/my-posts/index.tsx index 15ea5892..80fbdba3 100644 --- a/client/components/my-posts/index.tsx +++ b/client/components/my-posts/index.tsx @@ -1,17 +1,7 @@ -import useSWR from "swr" import PostList from "../post-list" -import Cookies from "js-cookie" -const fetcher = (url: string) => fetch(url, { - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${Cookies.get("drift-token")}` - }, -}).then(r => r.json()) - -const MyPosts = () => { - const { data, error } = useSWR('/server-api/users/mine', fetcher) - return <PostList posts={data} error={error} /> +const MyPosts = ({ posts, error }: { posts: any, error: any }) => { + return <PostList posts={posts} error={error} /> } export default MyPosts diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index cd77a43e..a962bb8e 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -9,6 +9,7 @@ import Title from './title'; import Cookies from 'js-cookie' import type { PostVisibility, Document as DocumentType } from '@lib/types'; import PasswordModal from './password'; +import getPostPath from '@lib/get-post-path'; const Post = () => { const { setToast } = useToasts() @@ -36,7 +37,7 @@ const Post = () => { if (res.ok) { const json = await res.json() - router.push(`/post/${json.id}`) + router.push(getPostPath(json.visibility, json.id)) } else { const json = await res.json() setToast({ diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index 30c37ecd..c034117f 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from "react" import timeAgo from "@lib/time-ago" import ShiftBy from "../shift-by" import VisibilityBadge from "../visibility-badge" +import getPostPath from "@lib/get-post-path" const FilenameInput = ({ title }: { title: string }) => <Input value={title} @@ -33,7 +34,7 @@ const ListItem = ({ post }: { post: any }) => { <Grid.Container> <Grid md={14} xs={14}> <Text h3 paddingLeft={1 / 2} > - <NextLink passHref={true} href={`/post/${post.id}`}> + <NextLink passHref={true} href={getPostPath(post.visibility, post.id)}> <Link color>{post.title} <ShiftBy y={-1}><VisibilityBadge visibility={post.visibility} /></ShiftBy> </Link> diff --git a/client/components/preview/react-markdown-preview.tsx b/client/components/preview/react-markdown-preview.tsx index b02ac3e1..5507aae2 100644 --- a/client/components/preview/react-markdown-preview.tsx +++ b/client/components/preview/react-markdown-preview.tsx @@ -1,6 +1,6 @@ import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" -import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'; +import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/prism-async-light'; import rehypeSlug from 'rehype-slug' import rehypeAutolinkHeadings from 'rehype-autolink-headings' diff --git a/client/lib/get-post-path.ts b/client/lib/get-post-path.ts new file mode 100644 index 00000000..47745319 --- /dev/null +++ b/client/lib/get-post-path.ts @@ -0,0 +1,13 @@ +import type { PostVisibility } from "./types" + +export default function getPostPath(visibility: PostVisibility, id: string) { + switch (visibility) { + case "private": + return `/post/private/${id}` + case "protected": + return `/post/protected/${id}` + case "unlisted": + case "public": + return `/post/${id}` + } +} diff --git a/client/pages/api/raw/[id].ts b/client/pages/api/raw/[id].ts index 62ef2745..f8f4d512 100644 --- a/client/pages/api/raw/[id].ts +++ b/client/pages/api/raw/[id].ts @@ -2,13 +2,20 @@ import { NextApiRequest, NextApiResponse } from "next" const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { const { id, download } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`) + const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { + headers: { + 'Accept': 'text/plain', + 'x-secret-key': process.env.SECRET_KEY || '' + } + }) + res.setHeader("Content-Type", "text/plain") + res.setHeader('Cache-Control', 's-maxage=86400'); + if (file.ok) { const data = await file.json() const { title, content } = data // serve the file raw as plain text - res.setHeader("Content-Type", "text/plain") - res.setHeader('Cache-Control', 's-maxage=86400'); + if (download) { res.setHeader("Content-Disposition", `attachment; filename="${title}"`) } else { diff --git a/client/pages/api/revalidate.ts b/client/pages/api/revalidate.ts new file mode 100644 index 00000000..07c62794 --- /dev/null +++ b/client/pages/api/revalidate.ts @@ -0,0 +1,24 @@ +import { NextApiRequest, NextApiResponse } from "next" + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + if (req.headers['x-secret-key'] !== process.env.SECRET_KEY) { + return res.status(401).send('Unauthorized') + } + + const { path } = req.query + + if (!path || typeof path !== 'string') { + return res.status(400).json({ + error: "Missing path" + }) + } + + try { + await res.unstable_revalidate(path) + return res.json({ revalidated: true }) + } catch (err) { + // If there was an error, Next.js will continue + // to show the last successfully generated page + return res.status(500).send('Error revalidating') + } +} diff --git a/client/pages/mine.tsx b/client/pages/mine.tsx index b273d548..9e502c20 100644 --- a/client/pages/mine.tsx +++ b/client/pages/mine.tsx @@ -3,18 +3,48 @@ import { Page } from '@geist-ui/core' import Header from '@components/header' import MyPosts from '@components/my-posts' +import cookie from "cookie"; +import { GetServerSideProps } from 'next'; +import { ThemeProps } from '@lib/types'; -const Home = ({ theme, changeTheme }: { theme: "light" | "dark", changeTheme: () => void }) => { +const Home = ({ posts, error, theme, changeTheme }: ThemeProps & { posts: any; error: any; }) => { return ( <Page className={styles.container} width="100%"> <Page.Header> <Header theme={theme} changeTheme={changeTheme} /> </Page.Header> <Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}> - <MyPosts /> + <MyPosts error={error} posts={posts} /> </Page.Content> </Page > ) } +// get server side props +export const getServerSideProps: GetServerSideProps = async ({ req }) => { + const driftToken = cookie.parse(req.headers.cookie || '')[`drift-token`] + if (!driftToken) { + return { + redirect: { + destination: '/', + permanent: false, + } + } + } + + const posts = await fetch('http://localhost:3000/server-api/users/mine', { + method: "GET", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${driftToken}` + } + }) + + return { + props: { + posts: await posts.json(), + error: posts.status !== 200, + } + } +} export default Home diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 5ddeb3f8..20c91bc8 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -1,13 +1,11 @@ import { Button, Page, Text } from "@geist-ui/core"; -import { useRouter } from "next/router"; -import Document from '../../components/document' -import Header from "../../components/header"; -import VisibilityBadge from "../../components/visibility-badge"; +import Document from '@components/document' +import Header from "@components/header"; +import VisibilityBadge from "@components/visibility-badge"; import PageSeo from "components/page-seo"; import styles from './styles.module.css'; -import cookie from "cookie"; -import { GetServerSideProps, GetStaticPaths, GetStaticProps } from "next"; +import type { GetStaticPaths, GetStaticProps } from "next"; import { PostVisibility, ThemeProps } from "@lib/types"; type File = { @@ -51,7 +49,7 @@ const Post = ({ post, theme, changeTheme }: PostProps) => { <PageSeo title={`${post.title} - Drift`} description={post.description} - isPrivate={post.visibility !== 'public'} + isPrivate={false} /> <Page.Header> @@ -83,53 +81,37 @@ const Post = ({ post, theme, changeTheme }: PostProps) => { ) } -export const getServerSideProps: GetServerSideProps = async (context) => { - const headers = context.req.headers - const host = headers.host - const driftToken = cookie.parse(headers.cookie || '')[`drift-token`] - let driftTheme = cookie.parse(headers.cookie || '')[`drift-theme`] - if (driftTheme !== "light" && driftTheme !== "dark") { - driftTheme = "light" - } - - if (context.query.id) { - const post = await fetch('http://' + host + `/server-api/posts/${context.query.id}`, { - method: "GET", - headers: { - "Content-Type": "application/json", - "Authorization": `Bearer ${driftToken}` - } - }) - - if (!post.ok || post.status !== 200) { - return { - redirect: { - destination: '/', - permanent: false, - }, - } +export const getStaticPaths: GetStaticPaths = async () => { + const posts = await fetch(process.env.API_URL + `/posts/`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-secret-key": process.env.SECRET_KEY || "", } - try { - const json = await post.json(); - const maxAge = 60 * 60 * 24 * 365; - context.res.setHeader( - 'Cache-Control', - `${json.visibility === "public" ? "public" : "private"}, s-maxage=${maxAge}` - ) - return { - props: { - post: json - } - } - } catch (e) { - console.log(e) + }) + + const json = await posts.json() + const filtered = json.filter((post: any) => post.visibility === "public" || post.visibility === "unlisted") + const paths = filtered.map((post: any) => ({ + params: { id: post.id } + })) + + return { paths, fallback: 'blocking' } +} + +export const getStaticProps: GetStaticProps = async ({ params }) => { + const post = await fetch(process.env.API_URL + `/posts/${params?.id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "x-secret-key": process.env.SECRET_KEY || "", } - } + }) return { props: { - post: null - } + post: await post.json() + }, } } diff --git a/client/pages/post/private/[id].tsx b/client/pages/post/private/[id].tsx new file mode 100644 index 00000000..2dfdb3c0 --- /dev/null +++ b/client/pages/post/private/[id].tsx @@ -0,0 +1,128 @@ +import { Button, Page, Text } from "@geist-ui/core"; + +import Document from '@components/document' +import Header from "@components/header"; +import VisibilityBadge from "@components/visibility-badge"; +import PageSeo from "components/page-seo"; +import styles from '../styles.module.css'; +import cookie from "cookie"; +import type { GetServerSideProps } from "next"; +import { PostVisibility, ThemeProps } from "@lib/types"; + +type File = { + id: string + title: string + content: string +} + +type Files = File[] + +export type PostProps = ThemeProps & { + post: { + id: string + title: string + description: string + visibility: PostVisibility + files: Files + } +} + +const Post = ({ post, theme, changeTheme }: PostProps) => { + const download = async () => { + const clientZip = require("client-zip") + + const blob = await clientZip.downloadZip(post.files.map((file: any) => { + return { + name: file.title, + input: file.content, + lastModified: new Date(file.updatedAt) + } + })).blob() + const link = document.createElement("a") + link.href = URL.createObjectURL(blob) + link.download = `${post.title}.zip` + link.click() + link.remove() + } + + return ( + <Page width={"100%"}> + <PageSeo + title={`${post.title} - Drift`} + description={post.description} + isPrivate={true} + /> + + <Page.Header> + <Header theme={theme} changeTheme={changeTheme} /> + </Page.Header> + <Page.Content width={"var(--main-content-width)"} margin="auto"> + {/* {!isLoading && <PostFileExplorer files={post.files} />} */} + <div className={styles.header}> + <div className={styles.titleAndBadge}> + <Text h2>{post.title}</Text> + <span><VisibilityBadge visibility={post.visibility} /></span> + </div> + <Button auto onClick={download}> + Download as ZIP archive + </Button> + </div> + {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( + <Document + key={id} + id={id} + content={content} + title={title} + editable={false} + initialTab={'preview'} + /> + ))} + </Page.Content> + </Page > + ) +} + +export const getServerSideProps: GetServerSideProps = async (context) => { + const headers = context.req.headers + const host = headers.host + const driftToken = cookie.parse(headers.cookie || '')[`drift-token`] + + if (context.query.id) { + const post = await fetch('http://' + host + `/server-api/posts/${context.query.id}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${driftToken}` + } + }) + + if (!post.ok || post.status !== 200) { + return { + redirect: { + destination: '/', + permanent: false, + }, + } + } + try { + const json = await post.json(); + + return { + props: { + post: json + } + } + } catch (e) { + console.log(e) + } + } + + return { + props: { + post: null + } + } +} + +export default Post + diff --git a/server/lib/config.ts b/server/src/lib/config.ts similarity index 100% rename from server/lib/config.ts rename to server/src/lib/config.ts diff --git a/server/lib/middleware/jwt.ts b/server/src/lib/middleware/jwt.ts similarity index 100% rename from server/lib/middleware/jwt.ts rename to server/src/lib/middleware/jwt.ts diff --git a/server/src/lib/middleware/secret-key.ts b/server/src/lib/middleware/secret-key.ts new file mode 100644 index 00000000..241fe647 --- /dev/null +++ b/server/src/lib/middleware/secret-key.ts @@ -0,0 +1,14 @@ +import { NextFunction, Request, Response } from 'express'; + +const key = process.env.SECRET_KEY; +if (!key) { + throw new Error('SECRET_KEY is not set.'); +} + +export default function authenticateToken(req: Request, res: Response, next: NextFunction) { + const requestKey = req.headers['x-secret-key'] + if (requestKey !== key) { + return res.sendStatus(401) + } + next() +} diff --git a/server/lib/models/File.ts b/server/src/lib/models/File.ts similarity index 100% rename from server/lib/models/File.ts rename to server/src/lib/models/File.ts diff --git a/server/lib/models/Post.ts b/server/src/lib/models/Post.ts similarity index 100% rename from server/lib/models/Post.ts rename to server/src/lib/models/Post.ts diff --git a/server/lib/models/PostAuthor.ts b/server/src/lib/models/PostAuthor.ts similarity index 100% rename from server/lib/models/PostAuthor.ts rename to server/src/lib/models/PostAuthor.ts diff --git a/server/lib/models/User.ts b/server/src/lib/models/User.ts similarity index 100% rename from server/lib/models/User.ts rename to server/src/lib/models/User.ts diff --git a/server/lib/sequelize.ts b/server/src/lib/sequelize.ts similarity index 100% rename from server/lib/sequelize.ts rename to server/src/lib/sequelize.ts diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index 049445c0..9e77a93a 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -1,9 +1,9 @@ import { Router } from 'express' import { genSalt, hash, compare } from "bcrypt" -import { User } from '../../lib/models/User' +import { User } from '../lib/models/User' import { sign } from 'jsonwebtoken' -import config from '../../lib/config' -import jwt from '../../lib/middleware/jwt' +import config from '../lib/config' +import jwt from '../lib/middleware/jwt' const NO_EMPTY_SPACE_REGEX = /^\S*$/ diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index 7a23751a..13efe551 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -1,10 +1,11 @@ import { Router } from 'express' +import secretKey from '../lib/middleware/secret-key'; // import { Movie } from '../models/Post' -import { File } from '../../lib/models/File' +import { File } from '../lib/models/File' export const files = Router() -files.get("/raw/:id", async (req, res, next) => { +files.get("/raw/:id", secretKey, async (req, res, next) => { try { const file = await File.findOne({ where: { @@ -12,18 +13,9 @@ files.get("/raw/:id", async (req, res, next) => { }, attributes: ["title", "content"], }) - // TODO: fix post inclusion - // if (file?.post.visibility === 'public' || file?.post.visibility === 'unlisted') { - res.setHeader("Cache-Control", "public, max-age=86400"); res.json(file); - // } else { - // TODO: should this be `private, `? - // res.setHeader("Cache-Control", "max-age=86400"); - // res.json(file); - // } } catch (e) { next(e); } }); - diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index 6456458e..70f54bf6 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -1,10 +1,11 @@ import { Router } from 'express' // import { Movie } from '../models/Post' -import { File } from '../../lib/models/File' -import { Post } from '../../lib/models/Post'; -import jwt, { UserJwtRequest } from '../../lib/middleware/jwt'; +import { File } from '../lib/models/File' +import { Post } from '../lib/models/Post'; +import jwt, { UserJwtRequest } from '../lib/middleware/jwt'; import * as crypto from "crypto"; -import { User } from '../../lib/models/User'; +import { User } from '../lib/models/User'; +import secretKey from '../lib/middleware/secret-key'; export const posts = Router() @@ -57,7 +58,18 @@ posts.post('/create', jwt, async (req, res, next) => { } }); -posts.get("/:id", async (req: UserJwtRequest, res, next) => { +posts.get("/", secretKey, async (req, res, next) => { + try { + const posts = await Post.findAll({ + attributes: ["id", "title", "visibility", "createdAt"], + }) + res.json(posts); + } catch (e) { + next(e); + } +}); + +posts.get("/:id", secretKey, async (req, res, next) => { try { const post = await Post.findOne({ where: { @@ -76,16 +88,19 @@ posts.get("/:id", async (req: UserJwtRequest, res, next) => { }, ] }) + if (!post) { + throw new Error("Post not found.") + } - if (post?.visibility === 'public' || post?.visibility === 'unlisted') { - res.setHeader("Cache-Control", "public, max-age=86400"); + if (post.visibility === 'public' || post?.visibility === 'unlisted') { res.json(post); - } else { - // TODO: should this be `private, `? - res.setHeader("Cache-Control", "max-age=86400"); - jwt(req, res, () => { + } else if (post.visibility === 'private') { + console.log("here") + jwt(req as UserJwtRequest, res, () => { res.json(post); - }); + }) + } else if (post.visibility === 'protected') { + } } catch (e) { diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts index 3ffefd01..67c793f5 100644 --- a/server/src/routes/users.ts +++ b/server/src/routes/users.ts @@ -1,9 +1,9 @@ import { Router } from 'express' // import { Movie } from '../models/Post' -import { User } from '../../lib/models/User' -import { File } from '../../lib/models/File' -import jwt, { UserJwtRequest } from '../../lib/middleware/jwt' -import { Post } from '../../lib/models/Post' +import { User } from '../lib/models/User' +import { File } from '../lib/models/File' +import jwt, { UserJwtRequest } from '../lib/middleware/jwt' +import { Post } from '../lib/models/Post' export const users = Router() diff --git a/server/src/server.ts b/server/src/server.ts index 2e96ea8d..b3670024 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -1,7 +1,7 @@ import { createServer } from 'http'; import { app } from './app'; -import config from '../lib/config'; -import { sequelize } from '../lib/sequelize'; +import config from './lib/config'; +import { sequelize } from './lib/sequelize'; (async () => { await sequelize.sync(); diff --git a/server/tsconfig.json b/server/tsconfig.json index e5fbe651..765d8a8c 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -15,6 +15,6 @@ "strictPropertyInitialization": true, "outDir": "dist" }, - "include": ["lib/**/*.ts", "index.ts", "src/**/*.ts"], + "include": ["index.ts", "src/**/*.ts"], "exclude": ["node_modules"] } From d30c34deec50f9648ce993743b5481df1f6faf47 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 17:42:37 -0700 Subject: [PATCH 26/63] Add usage of SECRET_KEY to secure API routes --- client/pages/api/raw/[id].ts | 4 +++- client/pages/api/revalidate.ts | 24 ---------------------- client/pages/mine.tsx | 14 +++++++++++-- client/pages/post/private/[id].tsx | 3 ++- server/src/lib/models/Post.ts | 2 +- server/src/routes/files.ts | 12 +++++++++-- server/src/routes/posts.ts | 31 +++++++++++++++++++++++++++-- server/src/routes/users.ts | 32 +----------------------------- 8 files changed, 58 insertions(+), 64 deletions(-) delete mode 100644 client/pages/api/revalidate.ts diff --git a/client/pages/api/raw/[id].ts b/client/pages/api/raw/[id].ts index f8f4d512..4cfe4e52 100644 --- a/client/pages/api/raw/[id].ts +++ b/client/pages/api/raw/[id].ts @@ -5,9 +5,11 @@ const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { headers: { 'Accept': 'text/plain', - 'x-secret-key': process.env.SECRET_KEY || '' + 'x-secret-key': process.env.SECRET_KEY || '', + 'Authorization': `Bearer ${req.cookies['drift-token']}`, } }) + res.setHeader("Content-Type", "text/plain") res.setHeader('Cache-Control', 's-maxage=86400'); diff --git a/client/pages/api/revalidate.ts b/client/pages/api/revalidate.ts deleted file mode 100644 index 07c62794..00000000 --- a/client/pages/api/revalidate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { NextApiRequest, NextApiResponse } from "next" - -export default async function handler(req: NextApiRequest, res: NextApiResponse) { - if (req.headers['x-secret-key'] !== process.env.SECRET_KEY) { - return res.status(401).send('Unauthorized') - } - - const { path } = req.query - - if (!path || typeof path !== 'string') { - return res.status(400).json({ - error: "Missing path" - }) - } - - try { - await res.unstable_revalidate(path) - return res.json({ revalidated: true }) - } catch (err) { - // If there was an error, Next.js will continue - // to show the last successfully generated page - return res.status(500).send('Error revalidating') - } -} diff --git a/client/pages/mine.tsx b/client/pages/mine.tsx index 9e502c20..429c9dc6 100644 --- a/client/pages/mine.tsx +++ b/client/pages/mine.tsx @@ -31,14 +31,24 @@ export const getServerSideProps: GetServerSideProps = async ({ req }) => { } } - const posts = await fetch('http://localhost:3000/server-api/users/mine', { + const posts = await fetch(process.env.API_URL + `/posts/mine`, { method: "GET", headers: { "Content-Type": "application/json", - "Authorization": `Bearer ${driftToken}` + "Authorization": `Bearer ${driftToken}`, + "x-secret-key": process.env.SECRET_KEY || '' } }) + if (!posts.ok || posts.status !== 200) { + return { + redirect: { + destination: '/', + permanent: false, + } + } + } + return { props: { posts: await posts.json(), diff --git a/client/pages/post/private/[id].tsx b/client/pages/post/private/[id].tsx index 2dfdb3c0..c9b43583 100644 --- a/client/pages/post/private/[id].tsx +++ b/client/pages/post/private/[id].tsx @@ -92,7 +92,8 @@ export const getServerSideProps: GetServerSideProps = async (context) => { method: "GET", headers: { "Content-Type": "application/json", - "Authorization": `Bearer ${driftToken}` + "Authorization": `Bearer ${driftToken}`, + "x-secret-key": process.env.SECRET_KEY || "", } }) diff --git a/server/src/lib/models/Post.ts b/server/src/lib/models/Post.ts index 4bc6dc4b..2be1b024 100644 --- a/server/src/lib/models/Post.ts +++ b/server/src/lib/models/Post.ts @@ -38,7 +38,7 @@ export class Post extends Model { @BelongsToMany(() => User, () => PostAuthor) users?: User[]; - @HasMany(() => File) + @HasMany(() => File, { constraints: false }) files?: File[]; @CreatedAt diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index 13efe551..70a43cde 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -1,6 +1,5 @@ import { Router } from 'express' import secretKey from '../lib/middleware/secret-key'; -// import { Movie } from '../models/Post' import { File } from '../lib/models/File' export const files = Router() @@ -13,7 +12,16 @@ files.get("/raw/:id", secretKey, async (req, res, next) => { }, attributes: ["title", "content"], }) - res.json(file); + + // TODO: JWT-checkraw files + if (file?.post?.visibility === "private") { + // jwt(req as UserJwtRequest, res, () => { + // res.json(file); + // }) + res.json(file); + } else { + res.json(file); + } } catch (e) { next(e); diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index 70f54bf6..fde52b6b 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -69,6 +69,35 @@ posts.get("/", secretKey, async (req, res, next) => { } }); +posts.get("/mine", jwt, secretKey, async (req: UserJwtRequest, res, next) => { + if (!req.user) { + return res.status(401).json({ error: "Unauthorized" }) + } + + try { + const user = await User.findByPk(req.user.id, { + include: [ + { + model: Post, + as: "posts", + include: [ + { + model: File, + as: "files" + } + ] + }, + ], + }) + if (!user) { + return res.status(404).json({ error: "User not found" }) + } + return res.json(user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())) + } catch (error) { + next(error) + } +}) + posts.get("/:id", secretKey, async (req, res, next) => { try { const post = await Post.findOne({ @@ -95,7 +124,6 @@ posts.get("/:id", secretKey, async (req, res, next) => { if (post.visibility === 'public' || post?.visibility === 'unlisted') { res.json(post); } else if (post.visibility === 'private') { - console.log("here") jwt(req as UserJwtRequest, res, () => { res.json(post); }) @@ -107,4 +135,3 @@ posts.get("/:id", secretKey, async (req, res, next) => { next(e); } }); - diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts index 67c793f5..aedcd1e1 100644 --- a/server/src/routes/users.ts +++ b/server/src/routes/users.ts @@ -1,9 +1,7 @@ import { Router } from 'express' // import { Movie } from '../models/Post' import { User } from '../lib/models/User' -import { File } from '../lib/models/File' -import jwt, { UserJwtRequest } from '../lib/middleware/jwt' -import { Post } from '../lib/models/Post' +import jwt from '../lib/middleware/jwt' export const users = Router() @@ -16,31 +14,3 @@ users.get('/', jwt, async (req, res, next) => { } }) -users.get("/mine", jwt, async (req: UserJwtRequest, res, next) => { - if (!req.user) { - return res.status(401).json({ error: "Unauthorized" }) - } - - try { - const user = await User.findByPk(req.user.id, { - include: [ - { - model: Post, - as: "posts", - include: [ - { - model: File, - as: "files" - } - ] - }, - ], - }) - if (!user) { - return res.status(404).json({ error: "User not found" }) - } - return res.json(user.posts?.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())) - } catch (error) { - next(error) - } -}) From a3130e6d2a21ffd6d64275235f3d9d948e832eb7 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 17:58:04 -0700 Subject: [PATCH 27/63] server: fix sqlite database location --- server/src/lib/sequelize.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/lib/sequelize.ts b/server/src/lib/sequelize.ts index f364c3bf..edc6bd19 100644 --- a/server/src/lib/sequelize.ts +++ b/server/src/lib/sequelize.ts @@ -3,7 +3,7 @@ import { Sequelize } from 'sequelize-typescript'; export const sequelize = new Sequelize({ dialect: 'sqlite', database: 'drift', - storage: process.env.MEMORY_DB === "true" ? ":memory:" : __dirname + './../drift.sqlite', + storage: process.env.MEMORY_DB === "true" ? ":memory:" : __dirname + '/../../drift.sqlite', models: [__dirname + '/models'], host: 'localhost', }); From ecd06a2258af28a95960d6f1c2da25de205f8020 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 18:51:19 -0700 Subject: [PATCH 28/63] client: finish protected posts --- client/components/header/index.tsx | 6 +- client/components/new-post/index.tsx | 13 +- client/components/new-post/password/index.tsx | 5 +- client/lib/hooks/use-signed-in.ts | 7 +- client/lib/types.d.ts | 16 +++ client/pages/_app.tsx | 1 - client/pages/post/[id].tsx | 18 +-- client/pages/post/protected/[id].tsx | 134 ++++++++++++++++++ server/src/routes/posts.ts | 29 +++- 9 files changed, 195 insertions(+), 34 deletions(-) create mode 100644 client/pages/post/protected/[id].tsx diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx index c90291dc..cf1f37af 100644 --- a/client/components/header/index.tsx +++ b/client/components/header/index.tsx @@ -1,5 +1,4 @@ import { Page, ButtonGroup, Button, useBodyScroll, useMediaQuery, Tabs, Spacer } from "@geist-ui/core"; -import { DriftProps } from "../../pages/_app"; import { useEffect, useState } from "react"; import styles from './header.module.css'; import { useRouter } from "next/router"; @@ -15,6 +14,7 @@ import NewIcon from '@geist-ui/icons/plusCircle'; import YourIcon from '@geist-ui/icons/list' import MoonIcon from '@geist-ui/icons/moon'; import SunIcon from '@geist-ui/icons/sun'; +import type { ThemeProps } from "@lib/types"; type Tab = { name: string @@ -26,13 +26,13 @@ type Tab = { } -const Header = ({ changeTheme, theme }: DriftProps) => { +const Header = ({ changeTheme, theme }: ThemeProps) => { const router = useRouter(); const [selectedTab, setSelectedTab] = useState<string>(router.pathname === '/' ? 'home' : router.pathname.split('/')[1]); const [expanded, setExpanded] = useState<boolean>(false) const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) const isMobile = useMediaQuery('xs', { match: 'down' }) - const isSignedIn = useSignedIn() + const { signedIn: isSignedIn } = useSignedIn() const [pages, setPages] = useState<Tab[]>([]) useEffect(() => { diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index a962bb8e..4c68c504 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -41,17 +41,15 @@ const Post = () => { } else { const json = await res.json() setToast({ - text: json.message, + text: json.error.message, type: 'error' }) + setPasswordModalVisible(false) + setSubmitting(false) } }, [docs, router, setToast, title]) - const closePasswordModel = () => { - setPasswordModalVisible(false) - } - const [isSubmitting, setSubmitting] = useState(false) const remove = (id: string) => { @@ -59,12 +57,12 @@ const Post = () => { } const onSubmit = async (visibility: PostVisibility, password?: string) => { - setSubmitting(true) - + console.log(visibility, password, passwordModalVisible) if (visibility === 'protected' && !password) { setPasswordModalVisible(true) return } + setSubmitting(true) await sendRequest('/server-api/posts/create', { title, @@ -77,6 +75,7 @@ const Post = () => { const onClosePasswordModal = () => { setPasswordModalVisible(false) + setSubmitting(false) } const updateTitle = useCallback((title: string, id: string) => { diff --git a/client/components/new-post/password/index.tsx b/client/components/new-post/password/index.tsx index 2305e945..fca2bba8 100644 --- a/client/components/new-post/password/index.tsx +++ b/client/components/new-post/password/index.tsx @@ -2,12 +2,13 @@ import { Input, Modal, Note, Spacer } from "@geist-ui/core" import { useState } from "react" type Props = { + warning?: boolean isOpen: boolean onClose: () => void onSubmit: (password: string) => void } -const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify }: Props) => { +const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify, warning }: Props) => { const [password, setPassword] = useState<string>() const [confirmPassword, setConfirmPassword] = useState<string>() const [error, setError] = useState<string>() @@ -30,7 +31,7 @@ const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify }: Props {<Modal visible={isOpen} > <Modal.Title>Enter a password</Modal.Title> <Modal.Content> - {!error && <Note type="warning" label='Warning'> + {!error && warning && <Note type="warning" label='Warning'> This doesn't protect your post from the server administrator. </Note>} {error && <Note type="error" label='Error'> diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index c19faeb4..32884247 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -3,16 +3,17 @@ import { useEffect, useState } from "react"; const useSignedIn = () => { const [signedIn, setSignedIn] = useState(typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); + const token = Cookies.get("drift-token") useEffect(() => { - if (Cookies.get("drift-token")) { + if (token) { setSignedIn(true); } else { setSignedIn(false); } - }, []); + }, [token]); - return signedIn; + return { signedIn, token }; } export default useSignedIn; diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts index c10b748a..a6cbd13c 100644 --- a/client/lib/types.d.ts +++ b/client/lib/types.d.ts @@ -10,3 +10,19 @@ export type Document = { content: string id: string } + +type File = { + id: string + title: string + content: string +} + +type Files = File[] + +export type Post = { + id: string + title: string + description: string + visibility: PostVisibility + files: Files +} diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 809a8834..a5aeebab 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -14,7 +14,6 @@ type AppProps<P = any> = { pageProps: P; } & Omit<NextAppProps<P>, "pageProps">; -export type DriftProps = ThemeProps function MyApp({ Component, pageProps }: AppProps<ThemeProps>) { const [themeType, setThemeType] = useSharedState<string>('theme', Cookies.get('drift-theme') || 'light') diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index 20c91bc8..b181b342 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -6,24 +6,10 @@ import VisibilityBadge from "@components/visibility-badge"; import PageSeo from "components/page-seo"; import styles from './styles.module.css'; import type { GetStaticPaths, GetStaticProps } from "next"; -import { PostVisibility, ThemeProps } from "@lib/types"; - -type File = { - id: string - title: string - content: string -} - -type Files = File[] +import { Post, ThemeProps } from "@lib/types"; export type PostProps = ThemeProps & { - post: { - id: string - title: string - description: string - visibility: PostVisibility - files: Files - } + post: Post } const Post = ({ post, theme, changeTheme }: PostProps) => { diff --git a/client/pages/post/protected/[id].tsx b/client/pages/post/protected/[id].tsx new file mode 100644 index 00000000..72c49790 --- /dev/null +++ b/client/pages/post/protected/[id].tsx @@ -0,0 +1,134 @@ +import { Button, Page, Text, useToasts } from "@geist-ui/core"; + +import Document from '@components/document' +import Header from "@components/header"; +import VisibilityBadge from "@components/visibility-badge"; +import PageSeo from "components/page-seo"; +import styles from '../styles.module.css'; +import { Post, ThemeProps } from "@lib/types"; +import PasswordModal from "@components/new-post/password"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/router"; +import Cookies from "js-cookie"; + +const Post = ({ theme, changeTheme }: ThemeProps) => { + const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true); + const [post, setPost] = useState<Post>() + const router = useRouter() + const { setToast } = useToasts() + const download = async () => { + if (!post) return; + const clientZip = require("client-zip") + + const blob = await clientZip.downloadZip(post.files.map((file: any) => { + return { + name: file.title, + input: file.content, + lastModified: new Date(file.updatedAt) + } + })).blob() + const link = document.createElement("a") + link.href = URL.createObjectURL(blob) + link.download = `${post.title}.zip` + link.click() + link.remove() + } + + useEffect(() => { + if (router.isReady) { + const fetchPostWithAuth = async () => { + const resp = await fetch(`/server-api/posts/${router.query.id}`, { + headers: { + Authorization: `Bearer ${Cookies.get('drift-token')}` + } + }) + if (!resp.ok) return + const post = await resp.json() + + if (!post) return + setPost(post) + } + fetchPostWithAuth() + } + }, [router.isReady, router.query.id]) + + const onSubmit = async (password: string) => { + const res = await fetch(`/server-api/posts/${router.query.id}?password=${password}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + } + }) + + if (!res.ok) { + setToast({ + type: "error", + text: "Wrong password" + }) + return + } + + const data = await res.json() + if (data) { + if (data.error) { + setToast({ + text: data.error, + type: "error" + }) + } else { + setPost(data) + setIsPasswordModalOpen(false) + } + } + } + + const onClose = () => { + setIsPasswordModalOpen(false); + } + + if (!router.isReady) { + return <></> + } + + if (!post) { + return <Page><PasswordModal warning={false} onClose={onClose} onSubmit={onSubmit} isOpen={isPasswordModalOpen} /></Page> + } + + return ( + <Page width={"100%"}> + <PageSeo + title={`${post.title} - Drift`} + description={post.description} + isPrivate={true} + /> + <Page.Header> + <Header theme={theme} changeTheme={changeTheme} /> + </Page.Header> + <Page.Content width={"var(--main-content-width)"} margin="auto"> + {/* {!isLoading && <PostFileExplorer files={post.files} />} */} + <div className={styles.header}> + <div className={styles.titleAndBadge}> + <Text h2>{post.title}</Text> + <span><VisibilityBadge visibility={post.visibility} /></span> + </div> + <Button auto onClick={download}> + Download as ZIP archive + </Button> + </div> + {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( + <Document + key={id} + id={id} + content={content} + title={title} + editable={false} + initialTab={'preview'} + /> + ))} + </Page.Content> + </Page > + ) +} + +export default Post + diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index fde52b6b..23089597 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -27,9 +27,19 @@ posts.post('/create', jwt, async (req, res, next) => { throw new Error("Please provide a visibility.") } + if (req.body.visibility === 'protected' && !req.body.password) { + throw new Error("Please provide a password.") + } + + let hashedPassword: string = '' + if (req.body.visibility === 'protected') { + hashedPassword = crypto.createHash('sha256').update(req.body.password).digest('hex'); + } + const newPost = new Post({ title: req.body.title, visibility: req.body.visibility, + password: hashedPassword, }) await newPost.save() @@ -98,7 +108,7 @@ posts.get("/mine", jwt, secretKey, async (req: UserJwtRequest, res, next) => { } }) -posts.get("/:id", secretKey, async (req, res, next) => { +posts.get("/:id", async (req, res, next) => { try { const post = await Post.findOne({ where: { @@ -117,18 +127,33 @@ posts.get("/:id", secretKey, async (req, res, next) => { }, ] }) + if (!post) { throw new Error("Post not found.") } + if (post.visibility === 'public' || post?.visibility === 'unlisted') { - res.json(post); + secretKey(req, res, () => { + res.json(post); + }) } else if (post.visibility === 'private') { jwt(req as UserJwtRequest, res, () => { res.json(post); }) } else if (post.visibility === 'protected') { + const { password } = req.query + if (!password || typeof password !== 'string') { + return jwt(req as UserJwtRequest, res, () => { + res.json(post); + }) + } + const hash = crypto.createHash('sha256').update(password).digest('hex').toString() + if (hash !== post.password) { + return res.status(400).json({ error: "Incorrect password." }) + } + res.json(post); } } catch (e) { From c1dcfb6a58826cbbd0c31a243d3249b29af3cebf Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 18:59:50 -0700 Subject: [PATCH 29/63] client: don't require confirming password accessing protected page --- client/components/new-post/password/index.tsx | 12 ++++++------ client/pages/post/protected/[id].tsx | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/components/new-post/password/index.tsx b/client/components/new-post/password/index.tsx index fca2bba8..267a1220 100644 --- a/client/components/new-post/password/index.tsx +++ b/client/components/new-post/password/index.tsx @@ -2,24 +2,24 @@ import { Input, Modal, Note, Spacer } from "@geist-ui/core" import { useState } from "react" type Props = { - warning?: boolean + creating?: boolean isOpen: boolean onClose: () => void onSubmit: (password: string) => void } -const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify, warning }: Props) => { +const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify, creating }: Props) => { const [password, setPassword] = useState<string>() const [confirmPassword, setConfirmPassword] = useState<string>() const [error, setError] = useState<string>() const onSubmit = () => { - if (!password || !confirmPassword) { + if (!password || (creating && !confirmPassword)) { setError('Please enter a password') return } - if (password !== confirmPassword) { + if (password !== confirmPassword && creating) { setError("Passwords do not match") return } @@ -31,7 +31,7 @@ const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify, warning {<Modal visible={isOpen} > <Modal.Title>Enter a password</Modal.Title> <Modal.Content> - {!error && warning && <Note type="warning" label='Warning'> + {!error && creating && <Note type="warning" label='Warning'> This doesn't protect your post from the server administrator. </Note>} {error && <Note type="error" label='Error'> @@ -39,7 +39,7 @@ const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify, warning </Note>} <Spacer /> <Input width={"100%"} label="Password" marginBottom={1} htmlType="password" placeholder="Password" onChange={(e) => setPassword(e.target.value)} /> - <Input width={"100%"} label="Confirm" htmlType="password" placeholder="Confirm Password" onChange={(e) => setConfirmPassword(e.target.value)} /> + {creating && <Input width={"100%"} label="Confirm" htmlType="password" placeholder="Confirm Password" onChange={(e) => setConfirmPassword(e.target.value)} />} </Modal.Content> <Modal.Action passive onClick={onClose}>Cancel</Modal.Action> <Modal.Action onClick={onSubmit}>Submit</Modal.Action> diff --git a/client/pages/post/protected/[id].tsx b/client/pages/post/protected/[id].tsx index 72c49790..05ccf4b2 100644 --- a/client/pages/post/protected/[id].tsx +++ b/client/pages/post/protected/[id].tsx @@ -91,7 +91,7 @@ const Post = ({ theme, changeTheme }: ThemeProps) => { } if (!post) { - return <Page><PasswordModal warning={false} onClose={onClose} onSubmit={onSubmit} isOpen={isPasswordModalOpen} /></Page> + return <Page><PasswordModal creating={false} onClose={onClose} onSubmit={onSubmit} isOpen={isPasswordModalOpen} /></Page> } return ( From 266848e6b2d0dcf47de685c00473e7eb20c9e17c Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 19:14:40 -0700 Subject: [PATCH 30/63] client: fix uploading files when no files are present --- client/components/new-post/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 4c68c504..d042976b 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -88,7 +88,7 @@ const Post = () => { const uploadDocs = useCallback((files: DocumentType[]) => { // if no title is set and the only document is empty, - const isFirstDocEmpty = docs.length <= 1 && docs[0].title === '' && docs[0].content === '' + const isFirstDocEmpty = docs.length <= 1 && (docs.length ? docs[0].title === '' : true) const shouldSetTitle = !title && isFirstDocEmpty if (shouldSetTitle) { if (files.length === 1) { From 12cc8bccaa2b1545277e2a2eb71983454ea68759 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 20:30:45 -0700 Subject: [PATCH 31/63] client: refactor view page components and optimize geist-ui imports --- client/components/Link.tsx | 3 +- .../document/formatting-icons/index.tsx | 3 +- client/components/document/index.tsx | 10 +- client/components/header/controls.tsx | 2 +- client/components/header/index.tsx | 9 +- client/components/new-post/index.tsx | 5 +- client/components/new-post/password/index.tsx | 5 +- client/components/new-post/title/index.tsx | 4 +- client/components/post-list/index.tsx | 2 +- .../post-list/list-item-skeleton.tsx | 6 +- client/components/post-list/list-item.tsx | 10 +- client/components/post-page/index.tsx | 71 ++++++++ .../post-page/post-page.module.css} | 0 client/components/visibility-badge/index.tsx | 4 +- client/{next.config.js => next.config.mjs} | 13 +- client/package.json | 2 +- client/pages/_app.tsx | 7 +- client/pages/_document.tsx | 2 +- client/pages/index.tsx | 5 +- client/pages/mine.tsx | 6 +- client/pages/new.tsx | 9 +- client/pages/post/[id].tsx | 68 +------ client/pages/post/private/[id].tsx | 61 +------ client/pages/post/protected/[id].tsx | 63 +------ client/pages/signin.tsx | 4 +- client/pages/signup.tsx | 4 +- client/yarn.lock | 170 ++++++++++-------- 27 files changed, 255 insertions(+), 293 deletions(-) create mode 100644 client/components/post-page/index.tsx rename client/{pages/post/styles.module.css => components/post-page/post-page.module.css} (100%) rename client/{next.config.js => next.config.mjs} (67%) diff --git a/client/components/Link.tsx b/client/components/Link.tsx index 97a285ec..9f2e376d 100644 --- a/client/components/Link.tsx +++ b/client/components/Link.tsx @@ -1,4 +1,5 @@ -import { Link as GeistLink, LinkProps } from "@geist-ui/core" +import type { LinkProps } from "@geist-ui/core" +import GeistLink from "@geist-ui/core/dist/link" import { useRouter } from "next/router"; const Link = (props: LinkProps) => { diff --git a/client/components/document/formatting-icons/index.tsx b/client/components/document/formatting-icons/index.tsx index 288e7daa..279162df 100644 --- a/client/components/document/formatting-icons/index.tsx +++ b/client/components/document/formatting-icons/index.tsx @@ -1,4 +1,5 @@ -import { ButtonGroup, Button } from "@geist-ui/core" +import ButtonGroup from "@geist-ui/core/dist/button-group" +import Button from "@geist-ui/core/dist/button" import Bold from '@geist-ui/icons/bold' import Italic from '@geist-ui/icons/italic' import Link from '@geist-ui/icons/link' diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index 55f31066..52b31799 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -1,4 +1,12 @@ -import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } from "@geist-ui/core" +import Button from "@geist-ui/core/dist/button" +import Card from "@geist-ui/core/dist/card" +import ButtonGroup from "@geist-ui/core/dist/button-group" +import Input from "@geist-ui/core/dist/input" +import Spacer from "@geist-ui/core/dist/spacer" +import Tabs from "@geist-ui/core/dist/tabs" +import Textarea from "@geist-ui/core/dist/textarea" +import Tooltip from "@geist-ui/core/dist/tooltip" + import { ChangeEvent, memo, useCallback, useMemo, useRef, useState } from "react" import styles from './document.module.css' import MarkdownPreview from '../preview' diff --git a/client/components/header/controls.tsx b/client/components/header/controls.tsx index 333453a7..6d4ee88c 100644 --- a/client/components/header/controls.tsx +++ b/client/components/header/controls.tsx @@ -1,7 +1,7 @@ import React from 'react' import MoonIcon from '@geist-ui/icons/moon' import SunIcon from '@geist-ui/icons/sun' -import { Select } from '@geist-ui/core' +import Select from '@geist-ui/core/dist/select' // import { useAllThemes, useTheme } from '@geist-ui/core' import styles from './header.module.css' import { ThemeProps } from '@lib/types' diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx index cf1f37af..7dec0873 100644 --- a/client/components/header/index.tsx +++ b/client/components/header/index.tsx @@ -1,4 +1,11 @@ -import { Page, ButtonGroup, Button, useBodyScroll, useMediaQuery, Tabs, Spacer } from "@geist-ui/core"; +import Page from "@geist-ui/core/dist/page"; +import ButtonGroup from "@geist-ui/core/dist/button-group"; +import Button from "@geist-ui/core/dist/button"; +import useBodyScroll from "@geist-ui/core/dist/use-body-scroll"; +import useMediaQuery from "@geist-ui/core/dist/use-media-query"; +import Tabs from "@geist-ui/core/dist/tabs"; +import Spacer from "@geist-ui/core/dist/spacer"; + import { useEffect, useState } from "react"; import styles from './header.module.css'; import { useRouter } from "next/router"; diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index d042976b..5a98cb89 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -1,4 +1,7 @@ -import { Button, ButtonDropdown, useToasts } from '@geist-ui/core' +import Button from '@geist-ui/core/dist/button' +import useToasts from '@geist-ui/core/dist/use-toasts' +import ButtonDropdown from '@geist-ui/core/dist/button-dropdown' + import { useRouter } from 'next/router'; import { useCallback, useState } from 'react' import generateUUID from '@lib/generate-uuid'; diff --git a/client/components/new-post/password/index.tsx b/client/components/new-post/password/index.tsx index 267a1220..8615a30e 100644 --- a/client/components/new-post/password/index.tsx +++ b/client/components/new-post/password/index.tsx @@ -1,4 +1,7 @@ -import { Input, Modal, Note, Spacer } from "@geist-ui/core" +import Input from "@geist-ui/core/dist/input" +import Modal from "@geist-ui/core/dist/modal" +import Note from "@geist-ui/core/dist/note" +import Spacer from "@geist-ui/core/dist/spacer" import { useState } from "react" type Props = { diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index 3865c858..eb02b87d 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,5 +1,7 @@ import { memo } from 'react' -import { Text, Input } from '@geist-ui/core' +import Text from '@geist-ui/core/dist/text' +import Input from '@geist-ui/core/dist/input' + import ShiftBy from '@components/shift-by' import styles from '../post.module.css' diff --git a/client/components/post-list/index.tsx b/client/components/post-list/index.tsx index 915d7720..39af6d3c 100644 --- a/client/components/post-list/index.tsx +++ b/client/components/post-list/index.tsx @@ -1,4 +1,4 @@ -import { Text } from "@geist-ui/core" +import Text from "@geist-ui/core/dist/text" import NextLink from "next/link" import Link from '../Link' diff --git a/client/components/post-list/list-item-skeleton.tsx b/client/components/post-list/list-item-skeleton.tsx index 5d834c4a..150c4ee7 100644 --- a/client/components/post-list/list-item-skeleton.tsx +++ b/client/components/post-list/list-item-skeleton.tsx @@ -1,4 +1,8 @@ -import { Card, Spacer, Grid, Divider } from "@geist-ui/core"; +import Card from "@geist-ui/core/dist/card"; +import Spacer from "@geist-ui/core/dist/spacer"; +import Grid from "@geist-ui/core/dist/grid"; +import Divider from "@geist-ui/core/dist/divider"; + import Skeleton from "react-loading-skeleton"; const ListItemSkeleton = () => (<Card> diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index c034117f..36b2616f 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -1,4 +1,12 @@ -import { Card, Spacer, Grid, Divider, Link, Text, Input, Tooltip } from "@geist-ui/core" +import Card from "@geist-ui/core/dist/card" +import Spacer from "@geist-ui/core/dist/spacer" +import Grid from "@geist-ui/core/dist/grid" +import Divider from "@geist-ui/core/dist/divider" +import Link from "@geist-ui/core/dist/link" +import Text from "@geist-ui/core/dist/text" +import Input from "@geist-ui/core/dist/input" +import Tooltip from "@geist-ui/core/dist/tooltip" + import NextLink from "next/link" import { useEffect, useMemo, useState } from "react" import timeAgo from "@lib/time-ago" diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx new file mode 100644 index 00000000..9ecf720a --- /dev/null +++ b/client/components/post-page/index.tsx @@ -0,0 +1,71 @@ +import Header from "@components/header" +import PageSeo from "@components/page-seo" +import VisibilityBadge from "@components/visibility-badge" +import Page from "@geist-ui/core/dist/page" +import Button from "@geist-ui/core/dist/button" +import Text from "@geist-ui/core/dist/text" +import DocumentComponent from '@components/document' +import clientZip from 'client-zip' +import styles from './post-page.module.css' + +import type { Post, ThemeProps } from "@lib/types" + +type Props = ThemeProps & { + post: Post +} + +const PostPage = ({ post, changeTheme, theme }: Props) => { + const download = async () => { + + const blob = await clientZip.downloadZip(post.files.map((file: any) => { + return { + name: file.title, + input: file.content, + lastModified: new Date(file.updatedAt) + } + })).blob() + const link = document.createElement("a") + link.href = URL.createObjectURL(blob) + link.download = `${post.title}.zip` + link.click() + link.remove() + } + + return ( + <Page width={"100%"}> + <PageSeo + title={`${post.title} - Drift`} + description={post.description} + isPrivate={false} + /> + + <Page.Header> + <Header theme={theme} changeTheme={changeTheme} /> + </Page.Header> + <Page.Content width={"var(--main-content-width)"} margin="auto"> + {/* {!isLoading && <PostFileExplorer files={post.files} />} */} + <div className={styles.header}> + <div className={styles.titleAndBadge}> + <Text h2>{post.title}</Text> + <span><VisibilityBadge visibility={post.visibility} /></span> + </div> + <Button auto onClick={download}> + Download as ZIP archive + </Button> + </div> + {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( + <DocumentComponent + key={id} + id={id} + content={content} + title={title} + editable={false} + initialTab={'preview'} + /> + ))} + </Page.Content> + </Page > + ) +} + +export default PostPage \ No newline at end of file diff --git a/client/pages/post/styles.module.css b/client/components/post-page/post-page.module.css similarity index 100% rename from client/pages/post/styles.module.css rename to client/components/post-page/post-page.module.css diff --git a/client/components/visibility-badge/index.tsx b/client/components/visibility-badge/index.tsx index aac799d4..e5344502 100644 --- a/client/components/visibility-badge/index.tsx +++ b/client/components/visibility-badge/index.tsx @@ -1,5 +1,5 @@ -import { Badge } from "@geist-ui/core" -import { PostVisibility } from "@lib/types" +import Badge from "@geist-ui/core/dist/badge"; +import type { PostVisibility } from "@lib/types" type Props = { visibility: PostVisibility diff --git a/client/next.config.js b/client/next.config.mjs similarity index 67% rename from client/next.config.js rename to client/next.config.mjs index d802e769..a34e3e0d 100644 --- a/client/next.config.js +++ b/client/next.config.mjs @@ -1,15 +1,14 @@ -const dotenv = require("dotenv"); -dotenv.config(); +import dotenv from "dotenv"; +import bundleAnalyzer from "@next/bundle-analyzer"; -const withBundleAnalyzer = require("@next/bundle-analyzer")({ - enabled: process.env.ANALYZE === "true", -}); +dotenv.config(); /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, experimental: { outputStandalone: true, + esmExternals: true, }, async rewrites() { return [ @@ -25,4 +24,6 @@ const nextConfig = { }, }; -module.exports = withBundleAnalyzer(nextConfig); +export default bundleAnalyzer({ enabled: process.env.ANALYZE === "true" })( + nextConfig +); diff --git a/client/package.json b/client/package.json index 3e73c530..42019600 100644 --- a/client/package.json +++ b/client/package.json @@ -45,7 +45,7 @@ "@types/react-dom": "^17.0.14", "@types/react-syntax-highlighter": "^13.5.2", "eslint": "8.10.0", - "eslint-config-next": "12.1.0", + "eslint-config-next": "^12.1.1-canary.16", "typescript": "4.6.2", "typescript-plugin-css-modules": "^3.4.0" } diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index a5aeebab..f29abeba 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -1,5 +1,8 @@ import '@styles/globals.css' -import { GeistProvider, CssBaseline, useTheme } from '@geist-ui/core' +import GeistProvider from '@geist-ui/core/dist/geist-provider' +import CssBaseline from '@geist-ui/core/dist/css-baseline' +import useTheme from '@geist-ui/core/dist/use-theme' + import { useEffect, useMemo, useState } from 'react' import type { AppProps as NextAppProps } from "next/app"; import useSharedState from '@lib/hooks/use-shared-state'; @@ -7,7 +10,7 @@ import useSharedState from '@lib/hooks/use-shared-state'; import 'react-loading-skeleton/dist/skeleton.css' import { SkeletonTheme } from 'react-loading-skeleton'; import Head from 'next/head'; -import { ThemeProps } from '@lib/types'; +import type { ThemeProps } from '@lib/types'; import Cookies from 'js-cookie'; type AppProps<P = any> = { diff --git a/client/pages/_document.tsx b/client/pages/_document.tsx index 7fc67ac7..c732c7a7 100644 --- a/client/pages/_document.tsx +++ b/client/pages/_document.tsx @@ -1,5 +1,5 @@ import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document' -import { CssBaseline } from '@geist-ui/core' +import CssBaseline from '@geist-ui/core/dist/css-baseline' class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { diff --git a/client/pages/index.tsx b/client/pages/index.tsx index c8303d83..0c24fe59 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -1,6 +1,7 @@ import styles from '@styles/Home.module.css' -import { Page, Spacer, Text } from '@geist-ui/core' - +import Page from '@geist-ui/core/dist/page' +import Spacer from '@geist-ui/core/dist/spacer' +import Text from '@geist-ui/core/dist/text' import Header from '@components/header' import Document from '@components/document' import Image from 'next/image' diff --git a/client/pages/mine.tsx b/client/pages/mine.tsx index 429c9dc6..6cb50e80 100644 --- a/client/pages/mine.tsx +++ b/client/pages/mine.tsx @@ -1,11 +1,11 @@ import styles from '@styles/Home.module.css' -import { Page } from '@geist-ui/core' +import Page from '@geist-ui/core/dist/page' import Header from '@components/header' import MyPosts from '@components/my-posts' import cookie from "cookie"; -import { GetServerSideProps } from 'next'; -import { ThemeProps } from '@lib/types'; +import type { GetServerSideProps } from 'next'; +import type { ThemeProps } from '@lib/types'; const Home = ({ posts, error, theme, changeTheme }: ThemeProps & { posts: any; error: any; }) => { return ( diff --git a/client/pages/new.tsx b/client/pages/new.tsx index 7a6f5948..3eb521e2 100644 --- a/client/pages/new.tsx +++ b/client/pages/new.tsx @@ -1,14 +1,11 @@ import styles from '@styles/Home.module.css' import NewPost from '@components/new-post' -import { Page } from '@geist-ui/core' -import useSignedIn from '@lib/hooks/use-signed-in' +import Page from '@geist-ui/core/dist/page' import Header from '@components/header' import PageSeo from '@components/page-seo' -import { ThemeProps } from '@lib/types' +import type { ThemeProps } from '@lib/types' const New = ({ theme, changeTheme }: ThemeProps) => { - const isSignedIn = useSignedIn() - return ( <Page className={styles.container} width="100%"> <PageSeo title="Drift - New" /> @@ -18,7 +15,7 @@ const New = ({ theme, changeTheme }: ThemeProps) => { </Page.Header> <Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}> - {isSignedIn && <NewPost />} + <NewPost /> </Page.Content> </Page > ) diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index b181b342..d2eb31cf 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -1,70 +1,14 @@ -import { Button, Page, Text } from "@geist-ui/core"; - -import Document from '@components/document' -import Header from "@components/header"; -import VisibilityBadge from "@components/visibility-badge"; -import PageSeo from "components/page-seo"; -import styles from './styles.module.css'; import type { GetStaticPaths, GetStaticProps } from "next"; -import { Post, ThemeProps } from "@lib/types"; + +import type { Post, ThemeProps } from "@lib/types"; +import PostPage from "@components/post-page"; export type PostProps = ThemeProps & { post: Post } -const Post = ({ post, theme, changeTheme }: PostProps) => { - const download = async () => { - const clientZip = require("client-zip") - - const blob = await clientZip.downloadZip(post.files.map((file: any) => { - return { - name: file.title, - input: file.content, - lastModified: new Date(file.updatedAt) - } - })).blob() - const link = document.createElement("a") - link.href = URL.createObjectURL(blob) - link.download = `${post.title}.zip` - link.click() - link.remove() - } - - return ( - <Page width={"100%"}> - <PageSeo - title={`${post.title} - Drift`} - description={post.description} - isPrivate={false} - /> - - <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> - </Page.Header> - <Page.Content width={"var(--main-content-width)"} margin="auto"> - {/* {!isLoading && <PostFileExplorer files={post.files} />} */} - <div className={styles.header}> - <div className={styles.titleAndBadge}> - <Text h2>{post.title}</Text> - <span><VisibilityBadge visibility={post.visibility} /></span> - </div> - <Button auto onClick={download}> - Download as ZIP archive - </Button> - </div> - {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( - <Document - key={id} - id={id} - content={content} - title={title} - editable={false} - initialTab={'preview'} - /> - ))} - </Page.Content> - </Page > - ) +const PostView = ({ post, theme, changeTheme }: PostProps) => { + return <PostPage post={post} theme={theme} changeTheme={changeTheme} /> } export const getStaticPaths: GetStaticPaths = async () => { @@ -101,5 +45,5 @@ export const getStaticProps: GetStaticProps = async ({ params }) => { } } -export default Post +export default PostView diff --git a/client/pages/post/private/[id].tsx b/client/pages/post/private/[id].tsx index c9b43583..c93f7172 100644 --- a/client/pages/post/private/[id].tsx +++ b/client/pages/post/private/[id].tsx @@ -1,13 +1,7 @@ -import { Button, Page, Text } from "@geist-ui/core"; - -import Document from '@components/document' -import Header from "@components/header"; -import VisibilityBadge from "@components/visibility-badge"; -import PageSeo from "components/page-seo"; -import styles from '../styles.module.css'; import cookie from "cookie"; import type { GetServerSideProps } from "next"; import { PostVisibility, ThemeProps } from "@lib/types"; +import PostPage from "@components/post-page"; type File = { id: string @@ -28,58 +22,7 @@ export type PostProps = ThemeProps & { } const Post = ({ post, theme, changeTheme }: PostProps) => { - const download = async () => { - const clientZip = require("client-zip") - - const blob = await clientZip.downloadZip(post.files.map((file: any) => { - return { - name: file.title, - input: file.content, - lastModified: new Date(file.updatedAt) - } - })).blob() - const link = document.createElement("a") - link.href = URL.createObjectURL(blob) - link.download = `${post.title}.zip` - link.click() - link.remove() - } - - return ( - <Page width={"100%"}> - <PageSeo - title={`${post.title} - Drift`} - description={post.description} - isPrivate={true} - /> - - <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> - </Page.Header> - <Page.Content width={"var(--main-content-width)"} margin="auto"> - {/* {!isLoading && <PostFileExplorer files={post.files} />} */} - <div className={styles.header}> - <div className={styles.titleAndBadge}> - <Text h2>{post.title}</Text> - <span><VisibilityBadge visibility={post.visibility} /></span> - </div> - <Button auto onClick={download}> - Download as ZIP archive - </Button> - </div> - {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( - <Document - key={id} - id={id} - content={content} - title={title} - editable={false} - initialTab={'preview'} - /> - ))} - </Page.Content> - </Page > - ) + return (<PostPage post={post} changeTheme={changeTheme} theme={theme} />) } export const getServerSideProps: GetServerSideProps = async (context) => { diff --git a/client/pages/post/protected/[id].tsx b/client/pages/post/protected/[id].tsx index 05ccf4b2..787042bd 100644 --- a/client/pages/post/protected/[id].tsx +++ b/client/pages/post/protected/[id].tsx @@ -1,38 +1,18 @@ -import { Button, Page, Text, useToasts } from "@geist-ui/core"; +import useToasts from "@geist-ui/core/dist/use-toasts"; +import Page from "@geist-ui/core/dist/page"; -import Document from '@components/document' -import Header from "@components/header"; -import VisibilityBadge from "@components/visibility-badge"; -import PageSeo from "components/page-seo"; -import styles from '../styles.module.css'; -import { Post, ThemeProps } from "@lib/types"; +import type { Post, ThemeProps } from "@lib/types"; import PasswordModal from "@components/new-post/password"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Cookies from "js-cookie"; +import PostPage from "@components/post-page"; const Post = ({ theme, changeTheme }: ThemeProps) => { const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true); const [post, setPost] = useState<Post>() const router = useRouter() const { setToast } = useToasts() - const download = async () => { - if (!post) return; - const clientZip = require("client-zip") - - const blob = await clientZip.downloadZip(post.files.map((file: any) => { - return { - name: file.title, - input: file.content, - lastModified: new Date(file.updatedAt) - } - })).blob() - const link = document.createElement("a") - link.href = URL.createObjectURL(blob) - link.download = `${post.title}.zip` - link.click() - link.remove() - } useEffect(() => { if (router.isReady) { @@ -94,40 +74,7 @@ const Post = ({ theme, changeTheme }: ThemeProps) => { return <Page><PasswordModal creating={false} onClose={onClose} onSubmit={onSubmit} isOpen={isPasswordModalOpen} /></Page> } - return ( - <Page width={"100%"}> - <PageSeo - title={`${post.title} - Drift`} - description={post.description} - isPrivate={true} - /> - <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> - </Page.Header> - <Page.Content width={"var(--main-content-width)"} margin="auto"> - {/* {!isLoading && <PostFileExplorer files={post.files} />} */} - <div className={styles.header}> - <div className={styles.titleAndBadge}> - <Text h2>{post.title}</Text> - <span><VisibilityBadge visibility={post.visibility} /></span> - </div> - <Button auto onClick={download}> - Download as ZIP archive - </Button> - </div> - {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( - <Document - key={id} - id={id} - content={content} - title={title} - editable={false} - initialTab={'preview'} - /> - ))} - </Page.Content> - </Page > - ) + return (<PostPage post={post} changeTheme={changeTheme} theme={theme} />) } export default Post diff --git a/client/pages/signin.tsx b/client/pages/signin.tsx index 0b98aabc..e36776d3 100644 --- a/client/pages/signin.tsx +++ b/client/pages/signin.tsx @@ -1,8 +1,8 @@ -import { Page } from "@geist-ui/core"; +import Page from "@geist-ui/core/dist/page"; import PageSeo from "@components/page-seo"; import Auth from "@components/auth"; import Header from "@components/header"; -import { ThemeProps } from "@lib/types"; +import type { ThemeProps } from "@lib/types"; const SignIn = ({ theme, changeTheme }: ThemeProps) => ( <Page width={"100%"}> diff --git a/client/pages/signup.tsx b/client/pages/signup.tsx index 9aa7b734..d2ac5100 100644 --- a/client/pages/signup.tsx +++ b/client/pages/signup.tsx @@ -1,8 +1,8 @@ -import { Page } from "@geist-ui/core"; +import Page from "@geist-ui/core/dist/page"; import Auth from "@components/auth"; import Header from "@components/header"; import PageSeo from '@components/page-seo'; -import { ThemeProps } from "@lib/types"; +import type { ThemeProps } from "@lib/types"; const SignUp = ({ theme, changeTheme }: ThemeProps) => ( <Page width="100%"> diff --git a/client/yarn.lock b/client/yarn.lock index 7dbfe7ad..599edcbc 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -70,10 +70,10 @@ resolved "https://registry.yarnpkg.com/@next/env/-/env-12.1.1-canary.15.tgz#d1c210df31c8865042f2b81ffb37660b9cc70045" integrity sha512-2r7r5r/+hSgCTeGTMErGwlxiawNX3RGCKrgWGyyIYKGcJ2xunxN3ScDKIAjGa77eOWGm4JccB4T6xXwmPUut5g== -"@next/eslint-plugin-next@12.1.0": - version "12.1.0" - resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.1.0.tgz#32586a11378b3ffa5a93ac40a3c44ad99d70e95a" - integrity sha512-WFiyvSM2G5cQmh32t/SiQuJ+I2O+FHVlK/RFw5b1565O2kEM/36EXncjt88Pa+X5oSc+1SS+tWxowWJd1lqI+g== +"@next/eslint-plugin-next@12.1.1-canary.16": + version "12.1.1-canary.16" + resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-12.1.1-canary.16.tgz#3fa21ef32c88e7b73b479ef559843c936dbddc95" + integrity sha512-u0uYF7hmexjMoqwai9NaVFbgTB4cdWdwSq697FK/JUqgazoT/fecvpOYOLZ9v8oTZvKpSUpANwp8WhAytAysuw== dependencies: glob "7.1.7" @@ -163,10 +163,10 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== -"@rushstack/eslint-patch@^1.0.8": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.0.tgz#7f698254aadf921e48dda8c0a6b304026b8a9323" - integrity sha512-JLo+Y592QzIE+q7Dl2pMUtt4q8SKYI5jDrZxrozEQxnGVOyYE+GWK9eLkwTaeN9DDctlaRAQ3TBmzZ1qdLE30A== +"@rushstack/eslint-patch@1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.0.8.tgz#be3e914e84eacf16dbebd311c0d0b44aa1174c64" + integrity sha512-ZK5v4bJwgXldAUA8r3q9YKfCwOqoHTK/ZqRjSeRXQrBXWouoPnS4MQtgC4AXGiiBuUu5wxrRgTlv0ktmM4P1Aw== "@types/cookie@^0.4.1": version "0.4.1" @@ -272,48 +272,48 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== -"@typescript-eslint/parser@^5.0.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.13.0.tgz#0394ed8f2f849273c0bf4b811994d177112ced5c" - integrity sha512-GdrU4GvBE29tm2RqWOM0P5QfCtgCyN4hXICj/X9ibKED16136l9ZpoJvCL5pSKtmJzA+NRDzQ312wWMejCVVfg== +"@typescript-eslint/parser@5.10.1": + version "5.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.10.1.tgz#4ce9633cc33fc70bc13786cb793c1a76fe5ad6bd" + integrity sha512-GReo3tjNBwR5RnRO0K2wDIDN31cM3MmDtgyQ85oAxAmC5K3j/g85IjP+cDfcqDsDDBf1HNKQAD0WqOYL8jXqUA== dependencies: - "@typescript-eslint/scope-manager" "5.13.0" - "@typescript-eslint/types" "5.13.0" - "@typescript-eslint/typescript-estree" "5.13.0" + "@typescript-eslint/scope-manager" "5.10.1" + "@typescript-eslint/types" "5.10.1" + "@typescript-eslint/typescript-estree" "5.10.1" debug "^4.3.2" -"@typescript-eslint/scope-manager@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.13.0.tgz#cf6aff61ca497cb19f0397eea8444a58f46156b6" - integrity sha512-T4N8UvKYDSfVYdmJq7g2IPJYCRzwtp74KyDZytkR4OL3NRupvswvmJQJ4CX5tDSurW2cvCc1Ia1qM7d0jpa7IA== +"@typescript-eslint/scope-manager@5.10.1": + version "5.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.10.1.tgz#f0539c73804d2423506db2475352a4dec36cd809" + integrity sha512-Lyvi559Gvpn94k7+ElXNMEnXu/iundV5uFmCUNnftbFrUbAJ1WBoaGgkbOBm07jVZa682oaBU37ao/NGGX4ZDg== dependencies: - "@typescript-eslint/types" "5.13.0" - "@typescript-eslint/visitor-keys" "5.13.0" + "@typescript-eslint/types" "5.10.1" + "@typescript-eslint/visitor-keys" "5.10.1" -"@typescript-eslint/types@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.13.0.tgz#da1de4ae905b1b9ff682cab0bed6b2e3be9c04e5" - integrity sha512-LmE/KO6DUy0nFY/OoQU0XelnmDt+V8lPQhh8MOVa7Y5k2gGRd6U9Kp3wAjhB4OHg57tUO0nOnwYQhRRyEAyOyg== +"@typescript-eslint/types@5.10.1": + version "5.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.1.tgz#dca9bd4cb8c067fc85304a31f38ec4766ba2d1ea" + integrity sha512-ZvxQ2QMy49bIIBpTqFiOenucqUyjTQ0WNLhBM6X1fh1NNlYAC6Kxsx8bRTY3jdYsYg44a0Z/uEgQkohbR0H87Q== -"@typescript-eslint/typescript-estree@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.13.0.tgz#b37c07b748ff030a3e93d87c842714e020b78141" - integrity sha512-Q9cQow0DeLjnp5DuEDjLZ6JIkwGx3oYZe+BfcNuw/POhtpcxMTy18Icl6BJqTSd+3ftsrfuVb7mNHRZf7xiaNA== +"@typescript-eslint/typescript-estree@5.10.1": + version "5.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.10.1.tgz#b268e67be0553f8790ba3fe87113282977adda15" + integrity sha512-PwIGnH7jIueXv4opcwEbVGDATjGPO1dx9RkUl5LlHDSe+FXxPwFL5W/qYd5/NHr7f6lo/vvTrAzd0KlQtRusJQ== dependencies: - "@typescript-eslint/types" "5.13.0" - "@typescript-eslint/visitor-keys" "5.13.0" + "@typescript-eslint/types" "5.10.1" + "@typescript-eslint/visitor-keys" "5.10.1" debug "^4.3.2" globby "^11.0.4" is-glob "^4.0.3" semver "^7.3.5" tsutils "^3.21.0" -"@typescript-eslint/visitor-keys@5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.13.0.tgz#f45ff55bcce16403b221ac9240fbeeae4764f0fd" - integrity sha512-HLKEAS/qA1V7d9EzcpLFykTePmOQqOFim8oCvhY3pZgQ8Hi38hYpHd9e5GN6nQBFQNecNhws5wkS9Y5XIO0s/g== +"@typescript-eslint/visitor-keys@5.10.1": + version "5.10.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.1.tgz#29102de692f59d7d34ecc457ed59ab5fc558010b" + integrity sha512-NjQ0Xinhy9IL979tpoTRuLKxMc0zJC7QVSdeerXs2/QvOy2yRkzX5dRb10X5woNUdJgU8G3nYRDlI33sq1K4YQ== dependencies: - "@typescript-eslint/types" "5.13.0" + "@typescript-eslint/types" "5.10.1" eslint-visitor-keys "^3.0.0" acorn-jsx@^5.3.1: @@ -707,7 +707,7 @@ debug@^3.2.6, debug@^3.2.7: dependencies: ms "^2.1.1" -debug@^4.0.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: +debug@^4.0.0, debug@^4.1.1, debug@^4.3.2: version "4.3.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664" integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q== @@ -866,22 +866,30 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== -eslint-config-next@12.1.0: - version "12.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.0.tgz#8ace680dc5207e6ab6c915f3989adec122f582e7" - integrity sha512-tBhuUgoDITcdcM7xFvensi9I5WTI4dnvH4ETGRg1U8ZKpXrZsWQFdOKIDzR3RLP5HR3xXrLviaMM4c3zVoE/pA== +eslint-config-next@^12.1.1-canary.16: + version "12.1.1-canary.16" + resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.1-canary.16.tgz#737f3c060c48d05da68b1c0b0eceb7ac99addd97" + integrity sha512-CyA4cd0gqlNLDgMgbDEIHt9gtXADQhbIP/5UVB4XIVmAP1z1cya5PxW203up+I7mzUNDtqwNMzt/JWCLCREBpg== dependencies: - "@next/eslint-plugin-next" "12.1.0" - "@rushstack/eslint-patch" "^1.0.8" - "@typescript-eslint/parser" "^5.0.0" - eslint-import-resolver-node "^0.3.4" - eslint-import-resolver-typescript "^2.4.0" - eslint-plugin-import "^2.25.2" - eslint-plugin-jsx-a11y "^6.5.1" - eslint-plugin-react "^7.27.0" - eslint-plugin-react-hooks "^4.3.0" + "@next/eslint-plugin-next" "12.1.1-canary.16" + "@rushstack/eslint-patch" "1.0.8" + "@typescript-eslint/parser" "5.10.1" + eslint-import-resolver-node "0.3.4" + eslint-import-resolver-typescript "2.4.0" + eslint-plugin-import "2.25.2" + eslint-plugin-jsx-a11y "6.5.1" + eslint-plugin-react "7.29.1" + eslint-plugin-react-hooks "4.3.0" -eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: +eslint-import-resolver-node@0.3.4: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + +eslint-import-resolver-node@^0.3.6: version "0.3.6" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.6.tgz#4048b958395da89668252001dbd9eca6b83bacbd" integrity sha512-0En0w03NRVMn9Uiyn8YRPDKvWjxCWkslUEhGNTdGx15RvPJYQ+lbOlqrlNI2vEAs4pDYK4f/HN2TbDmk5TP0iw== @@ -889,18 +897,18 @@ eslint-import-resolver-node@^0.3.4, eslint-import-resolver-node@^0.3.6: debug "^3.2.7" resolve "^1.20.0" -eslint-import-resolver-typescript@^2.4.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.5.0.tgz#07661966b272d14ba97f597b51e1a588f9722f0a" - integrity sha512-qZ6e5CFr+I7K4VVhQu3M/9xGv9/YmwsEXrsm3nimw8vWaVHRDrQRp26BgCypTxBp3vUp4o5aVEJRiy0F2DFddQ== +eslint-import-resolver-typescript@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.4.0.tgz#ec1e7063ebe807f0362a7320543aaed6fe1100e1" + integrity sha512-useJKURidCcldRLCNKWemr1fFQL1SzB3G4a0li6lFGvlc5xGe1hY343bvG07cbpCzPuM/lK19FIJB3XGFSkplA== dependencies: - debug "^4.3.1" - glob "^7.1.7" + debug "^4.1.1" + glob "^7.1.6" is-glob "^4.0.1" - resolve "^1.20.0" + resolve "^1.17.0" tsconfig-paths "^3.9.0" -eslint-module-utils@^2.7.2: +eslint-module-utils@^2.7.0: version "2.7.3" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.7.3.tgz#ad7e3a10552fdd0642e1e55292781bd6e34876ee" integrity sha512-088JEC7O3lDZM9xGe0RerkOMd0EjFl+Yvd1jPWIkMT5u3H9+HC34mWWPnqPrN13gieT9pBOO+Qt07Nb/6TresQ== @@ -908,26 +916,26 @@ eslint-module-utils@^2.7.2: debug "^3.2.7" find-up "^2.1.0" -eslint-plugin-import@^2.25.2: - version "2.25.4" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.4.tgz#322f3f916a4e9e991ac7af32032c25ce313209f1" - integrity sha512-/KJBASVFxpu0xg1kIBn9AUa8hQVnszpwgE7Ld0lKAlx7Ie87yzEzCgSkekt+le/YVhiaosO4Y14GDAOc41nfxA== +eslint-plugin-import@2.25.2: + version "2.25.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.25.2.tgz#b3b9160efddb702fc1636659e71ba1d10adbe9e9" + integrity sha512-qCwQr9TYfoBHOFcVGKY9C9unq05uOxxdklmBXLVvcwo68y5Hta6/GzCZEMx2zQiu0woKNEER0LE7ZgaOfBU14g== dependencies: array-includes "^3.1.4" array.prototype.flat "^1.2.5" debug "^2.6.9" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.6" - eslint-module-utils "^2.7.2" + eslint-module-utils "^2.7.0" has "^1.0.3" - is-core-module "^2.8.0" + is-core-module "^2.7.0" is-glob "^4.0.3" minimatch "^3.0.4" object.values "^1.1.5" resolve "^1.20.0" - tsconfig-paths "^3.12.0" + tsconfig-paths "^3.11.0" -eslint-plugin-jsx-a11y@^6.5.1: +eslint-plugin-jsx-a11y@6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.5.1.tgz#cdbf2df901040ca140b6ec14715c988889c2a6d8" integrity sha512-sVCFKX9fllURnXT2JwLN5Qgo24Ug5NF6dxhkmxsMEUZhXRcGg+X3e1JbJ84YePQKBl5E0ZjAH5Q4rkdcGY99+g== @@ -945,15 +953,15 @@ eslint-plugin-jsx-a11y@^6.5.1: language-tags "^1.0.5" minimatch "^3.0.4" -eslint-plugin-react-hooks@^4.3.0: +eslint-plugin-react-hooks@4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz#318dbf312e06fab1c835a4abef00121751ac1172" integrity sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA== -eslint-plugin-react@^7.27.0: - version "7.29.3" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.3.tgz#f4eab757f2756d25d6d4c2a58a9e20b004791f05" - integrity sha512-MzW6TuCnDOcta67CkpDyRfRsEVx9FNMDV8wZsDqe1luHPdGTrQIUaUXD27Ja3gHsdOIs/cXzNchWGlqm+qRVRg== +eslint-plugin-react@7.29.1: + version "7.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.29.1.tgz#6c40bc83142bb63d132a1b3565e2ea655411f800" + integrity sha512-WtzRpHMhsOX05ZrkyaaqmLl2uXGqmYooCfBxftJKlkYdsltiufGgfU7uuoHwR2lBam2Kh/EIVID4aU9e3kbCMA== dependencies: array-includes "^3.1.4" array.prototype.flatmap "^1.2.5" @@ -1240,7 +1248,7 @@ glob@7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.3, glob@^7.1.6, glob@^7.1.7: +glob@^7.1.3, glob@^7.1.6: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -1574,7 +1582,7 @@ is-callable@^1.1.4, is-callable@^1.2.4: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== -is-core-module@^2.2.0, is-core-module@^2.8.0, is-core-module@^2.8.1: +is-core-module@^2.2.0, is-core-module@^2.7.0, is-core-module@^2.8.1: version "2.8.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211" integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA== @@ -2980,7 +2988,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.20.0: +resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -3267,7 +3275,17 @@ trough@^2.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== -tsconfig-paths@^3.12.0, tsconfig-paths@^3.9.0: +tsconfig-paths@^3.11.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz#4fcc48f9ccea8826c41b9ca093479de7f5018976" + integrity sha512-cg/1jAZoL57R39+wiw4u/SCC6Ic9Q5NqjBOb+9xISedOYurfog9ZNmKJSxAnb2m/5Bq4lE9lhUcau33Ml8DM0g== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" + +tsconfig-paths@^3.9.0: version "3.13.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.13.0.tgz#f3e9b8f6876698581d94470c03c95b3a48c0e3d7" integrity sha512-nWuffZppoaYK0vQ1SQmkSsQzJoHA4s6uzdb2waRpD806x9yfq153AdVsWz4je2qZcW+pENrMQXbGQ3sMCkXuhw== From 97f354a2713f92b25831c93a7c1cbe09cb0bf475 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 20:34:05 -0700 Subject: [PATCH 32/63] client: fix downloading zip files --- client/components/post-page/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx index 9ecf720a..8d846101 100644 --- a/client/components/post-page/index.tsx +++ b/client/components/post-page/index.tsx @@ -5,7 +5,7 @@ import Page from "@geist-ui/core/dist/page" import Button from "@geist-ui/core/dist/button" import Text from "@geist-ui/core/dist/text" import DocumentComponent from '@components/document' -import clientZip from 'client-zip' +import { downloadZip } from 'client-zip' import styles from './post-page.module.css' import type { Post, ThemeProps } from "@lib/types" @@ -17,7 +17,7 @@ type Props = ThemeProps & { const PostPage = ({ post, changeTheme, theme }: Props) => { const download = async () => { - const blob = await clientZip.downloadZip(post.files.map((file: any) => { + const blob = await downloadZip(post.files.map((file: any) => { return { name: file.title, input: file.content, From d83cdf3eeb014ddf2105a2e98eb77e862c6f98d6 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 20:43:50 -0700 Subject: [PATCH 33/63] client: use next/dynamic for react markdown rendering --- client/components/document/index.tsx | 6 +++++- client/components/new-post/drag-and-drop/index.tsx | 2 ++ client/components/post-page/index.tsx | 3 +-- client/components/preview/react-markdown-preview.tsx | 4 +++- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index 52b31799..f77b0b8f 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -9,12 +9,16 @@ import Tooltip from "@geist-ui/core/dist/tooltip" import { ChangeEvent, memo, useCallback, useMemo, useRef, useState } from "react" import styles from './document.module.css' -import MarkdownPreview from '../preview' import Trash from '@geist-ui/icons/trash' import Download from '@geist-ui/icons/download' import ExternalLink from '@geist-ui/icons/externalLink' import FormattingIcons from "./formatting-icons" import Skeleton from "react-loading-skeleton" + +import dynamic from "next/dynamic"; + +const MarkdownPreview = dynamic(() => import("../preview")) + // import Link from "next/link" type Props = { editable?: boolean diff --git a/client/components/new-post/drag-and-drop/index.tsx b/client/components/new-post/drag-and-drop/index.tsx index 25de2bd7..611e8dc6 100644 --- a/client/components/new-post/drag-and-drop/index.tsx +++ b/client/components/new-post/drag-and-drop/index.tsx @@ -91,6 +91,8 @@ const allowedFileExtensions = [ 'sql', 'xml', 'webmanifest', + 'vue', + 'vuex', ] function FileDropzone({ setDocs }: { setDocs: ((docs: Document[]) => void) }) { diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx index 8d846101..9e66ce08 100644 --- a/client/components/post-page/index.tsx +++ b/client/components/post-page/index.tsx @@ -5,7 +5,6 @@ import Page from "@geist-ui/core/dist/page" import Button from "@geist-ui/core/dist/button" import Text from "@geist-ui/core/dist/text" import DocumentComponent from '@components/document' -import { downloadZip } from 'client-zip' import styles from './post-page.module.css' import type { Post, ThemeProps } from "@lib/types" @@ -16,7 +15,7 @@ type Props = ThemeProps & { const PostPage = ({ post, changeTheme, theme }: Props) => { const download = async () => { - + const downloadZip = (await import("client-zip")).downloadZip const blob = await downloadZip(post.files.map((file: any) => { return { name: file.title, diff --git a/client/components/preview/react-markdown-preview.tsx b/client/components/preview/react-markdown-preview.tsx index 5507aae2..319b4bb0 100644 --- a/client/components/preview/react-markdown-preview.tsx +++ b/client/components/preview/react-markdown-preview.tsx @@ -1,4 +1,3 @@ -import ReactMarkdown from "react-markdown" import remarkGfm from "remark-gfm" import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/prism-async-light'; import rehypeSlug from 'rehype-slug' @@ -11,6 +10,8 @@ import styles from './preview.module.css' import dark from 'react-syntax-highlighter/dist/cjs/styles/prism/vsc-dark-plus' import light from 'react-syntax-highlighter/dist/cjs/styles/prism/vs' import useSharedState from "@lib/hooks/use-shared-state"; +import ReactMarkdown from "react-markdown"; + type Props = { content: string | undefined @@ -19,6 +20,7 @@ type Props = { const ReactMarkdownPreview = ({ content, height }: Props) => { const [themeType] = useSharedState<string>('theme') + return (<div style={{ height }}> <ReactMarkdown className={styles.markdownPreview} remarkPlugins={[remarkGfm]} From da46422764366a14ccd2d458d3572d2d7b02a612 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Mon, 21 Mar 2022 22:50:25 -0700 Subject: [PATCH 34/63] client: stop some unncessary re-renders on new page --- client/components/document-list/index.tsx | 34 ++ client/components/document/index.tsx | 27 +- .../drag-and-drop/drag-and-drop.module.css | 4 - client/components/new-post/index.tsx | 51 ++- client/components/new-post/title/index.tsx | 9 +- .../preview/react-markdown-preview.tsx | 4 +- client/package.json | 9 +- client/styles/globals.css | 4 + client/yarn.lock | 324 +++--------------- 9 files changed, 133 insertions(+), 333 deletions(-) create mode 100644 client/components/document-list/index.tsx diff --git a/client/components/document-list/index.tsx b/client/components/document-list/index.tsx new file mode 100644 index 00000000..7ad88007 --- /dev/null +++ b/client/components/document-list/index.tsx @@ -0,0 +1,34 @@ +import type { Document } from "@lib/types" +import DocumentComponent from "@components/document" +import { ChangeEvent, memo, useCallback } from "react" + +const DocumentList = ({ docs, removeDoc, updateDocContent, updateDocTitle }: { + docs: Document[], + updateDocTitle: (i: number) => (title: string) => void + updateDocContent: (i: number) => (content: string) => void + removeDoc: (i: number) => () => void +}) => { + const handleOnChange = useCallback((i) => (e: ChangeEvent<HTMLTextAreaElement>) => { + updateDocContent(i)(e.target.value) + }, [updateDocContent]) + + return (<>{ + docs.map(({ content, id, title }, i) => { + return ( + <DocumentComponent + key={id} + remove={removeDoc(i)} + editable={true} + setContent={updateDocContent(i)} + setTitle={updateDocTitle(i)} + handleOnContentChange={handleOnChange(i)} + content={content} + title={title} + /> + ) + }) + } + </>) +} + +export default memo(DocumentList) diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index f77b0b8f..b34f1b19 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -27,6 +27,7 @@ type Props = { content?: string setTitle?: (title: string) => void setContent?: (content: string) => void + handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void initialTab?: "edit" | "preview" skeleton?: boolean id?: string @@ -60,7 +61,7 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => { } -const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit', skeleton, id }: Props) => { +const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit', skeleton, id, handleOnContentChange }: Props) => { const codeEditorRef = useRef<HTMLTextAreaElement>(null) const [tab, setTab] = useState(initialTab) // const height = editable ? "500px" : '100%' @@ -73,14 +74,16 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init setTab(newTab as 'edit' | 'preview') } - const getType = useMemo(() => { + const getType = useCallback(() => { if (!title) return const pathParts = title.split(".") const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" return language }, [title]) - const removeFile = (remove?: () => void) => { + const onTitleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null, [setTitle]) + + const removeFile = useCallback(() => (remove?: () => void) => { if (remove) { if (content && content.trim().length > 0) { const confirmed = window.confirm("Are you sure you want to remove this file?") @@ -91,13 +94,13 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init remove() } } - } + }, [content]) - const rawLink = useMemo(() => { + const rawLink = () => { if (id) { return `/file/raw/${id}` } - }, [id]) + } if (skeleton) { return <> @@ -114,6 +117,8 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </Card> </> } + + return ( <> <Spacer height={1} /> @@ -122,7 +127,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init <Input placeholder="MyFile.md" value={title} - onChange={(event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null} + onChange={onTitleChange} marginTop="var(--gap-double)" size={1.2} font={1.2} @@ -131,11 +136,11 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init width={"100%"} id={title} /> - {remove && editable && <Button type="abort" ghost icon={<Trash />} auto height={'36px'} width={'36px'} onClick={() => removeFile(remove)} />} + {remove && editable && <Button type="abort" ghost icon={<Trash />} auto height={'36px'} width={'36px'} onClick={removeFile} />} </div> <div className={styles.descriptionContainer}> {tab === 'edit' && editable && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />} - {rawLink && <DownloadButton rawLink={rawLink} />} + {rawLink && <DownloadButton rawLink={rawLink()} />} <Tabs onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}> <Tabs.Item label={editable ? "Edit" : "Raw"} value="edit"> {/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */} @@ -144,7 +149,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init ref={codeEditorRef} placeholder="Type some contents..." value={content} - onChange={(event) => setContent ? setContent(event.target.value) : null} + onChange={handleOnContentChange} width="100%" disabled={!editable} // TODO: Textarea should grow to fill parent if height == 100% @@ -155,7 +160,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </div> </Tabs.Item> <Tabs.Item label="Preview" value="preview"> - <MarkdownPreview height={height} content={content} type={getType} /> + <MarkdownPreview height={height} content={content} type={getType()} /> </Tabs.Item> </Tabs> diff --git a/client/components/new-post/drag-and-drop/drag-and-drop.module.css b/client/components/new-post/drag-and-drop/drag-and-drop.module.css index bacc5ff7..7f5cbfa1 100644 --- a/client/components/new-post/drag-and-drop/drag-and-drop.module.css +++ b/client/components/new-post/drag-and-drop/drag-and-drop.module.css @@ -35,10 +35,6 @@ padding: 20px; } -.error > li:before { - content: ""; -} - .error ul { margin: 0; padding-left: var(--gap-double); diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 5a98cb89..95bda113 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -13,6 +13,8 @@ import Cookies from 'js-cookie' import type { PostVisibility, Document as DocumentType } from '@lib/types'; import PasswordModal from './password'; import getPostPath from '@lib/get-post-path'; +import DocumentList from '@components/document-list'; +import { ChangeEvent } from 'react'; const Post = () => { const { setToast } = useToasts() @@ -23,6 +25,7 @@ const Post = () => { content: '', id: generateUUID() }]) + const [passwordModalVisible, setPasswordModalVisible] = useState(false) const sendRequest = useCallback(async (url: string, data: { visibility?: PostVisibility, title?: string, files?: DocumentType[], password?: string, userId: string }) => { const res = await fetch(url, { @@ -55,12 +58,7 @@ const Post = () => { const [isSubmitting, setSubmitting] = useState(false) - const remove = (id: string) => { - setDocs(docs.filter((doc) => doc.id !== id)) - } - const onSubmit = async (visibility: PostVisibility, password?: string) => { - console.log(visibility, password, passwordModalVisible) if (visibility === 'protected' && !password) { setPasswordModalVisible(true) return @@ -81,13 +79,23 @@ const Post = () => { setSubmitting(false) } - const updateTitle = useCallback((title: string, id: string) => { - setDocs(docs.map((doc) => doc.id === id ? { ...doc, title } : doc)) - }, [docs]) + const onChangeTitle = useCallback((e: ChangeEvent<HTMLInputElement>) => { + setTitle(e.target.value) + }, [setTitle]) + + + const updateDocTitle = useCallback((i: number) => (title: string) => { + setDocs((docs) => docs.map((doc, index) => i === index ? { ...doc, title } : doc)) + }, [setDocs]) + + const updateDocContent = useCallback((i: number) => (content: string) => { + setDocs((docs) => docs.map((doc, index) => i === index ? { ...doc, content } : doc)) + }, [setDocs]) + + const removeDoc = useCallback((i: number) => () => { + setDocs((docs) => docs.filter((_, index) => i !== index)) + }, [setDocs]) - const updateContent = useCallback((content: string, id: string) => { - setDocs(docs.map((doc) => doc.id === id ? { ...doc, content } : doc)) - }, [docs]) const uploadDocs = useCallback((files: DocumentType[]) => { // if no title is set and the only document is empty, @@ -102,29 +110,14 @@ const Post = () => { } if (isFirstDocEmpty) setDocs(files) - else setDocs([...docs, ...files]) + else setDocs((docs) => [...docs, ...files]) }, [docs, title]) return ( <div> - <Title title={title} setTitle={setTitle} /> + <Title title={title} onChange={onChangeTitle} /> <FileDropzone setDocs={uploadDocs} /> - { - docs.map(({ content, id, title }) => { - return ( - <DocumentComponent - remove={() => remove(id)} - key={id} - editable={true} - setContent={(content) => updateContent(content, id)} - setTitle={(title) => updateTitle(title, id)} - content={content} - title={title} - /> - ) - }) - } - + <DocumentList docs={docs} updateDocTitle={updateDocTitle} updateDocContent={updateDocContent} removeDoc={removeDoc} /> <div className={styles.buttons}> <Button className={styles.button} diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index eb02b87d..89d79689 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,4 +1,4 @@ -import { memo } from 'react' +import { ChangeEvent, memo, useCallback } from 'react' import Text from '@geist-ui/core/dist/text' import Input from '@geist-ui/core/dist/input' @@ -16,19 +16,18 @@ const titlePlaceholders = [ ] type props = { - setTitle: (title: string) => void + onChange: (e: ChangeEvent<HTMLInputElement>) => void title?: string } -const Title = ({ setTitle, title }: props) => { - +const Title = ({ onChange, title }: props) => { return (<div className={styles.title}> <Text h1 width={"150px"} className={styles.drift}>Drift</Text> <ShiftBy y={-3}> <Input placeholder={titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]} value={title || ""} - onChange={(event) => setTitle(event.target.value)} + onChange={onChange} height={"55px"} font={1.5} label="Post title" diff --git a/client/components/preview/react-markdown-preview.tsx b/client/components/preview/react-markdown-preview.tsx index 319b4bb0..cb2cde40 100644 --- a/client/components/preview/react-markdown-preview.tsx +++ b/client/components/preview/react-markdown-preview.tsx @@ -2,6 +2,7 @@ import remarkGfm from "remark-gfm" import SyntaxHighlighter from 'react-syntax-highlighter/dist/cjs/prism-async-light'; import rehypeSlug from 'rehype-slug' import rehypeAutolinkHeadings from 'rehype-autolink-headings' +import rehypeRaw from 'rehype-raw' // @ts-ignore because of no types in remark-a11y-emoji // import a11yEmoji from '@fec/remark-a11y-emoji'; @@ -12,7 +13,6 @@ import light from 'react-syntax-highlighter/dist/cjs/styles/prism/vs' import useSharedState from "@lib/hooks/use-shared-state"; import ReactMarkdown from "react-markdown"; - type Props = { content: string | undefined height: number | string @@ -24,7 +24,7 @@ const ReactMarkdownPreview = ({ content, height }: Props) => { return (<div style={{ height }}> <ReactMarkdown className={styles.markdownPreview} remarkPlugins={[remarkGfm]} - rehypePlugins={[rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }]]} + rehypePlugins={[rehypeSlug, [rehypeAutolinkHeadings, { behavior: 'wrap' }], rehypeRaw]} components={{ code({ node, inline, className, children, ...props }) { const match = /language-(\w+)/.exec(className || '') diff --git a/client/package.json b/client/package.json index 42019600..b17c82ca 100644 --- a/client/package.json +++ b/client/package.json @@ -15,27 +15,20 @@ "@types/cookie": "^0.4.1", "@types/js-cookie": "^3.0.1", "client-zip": "^2.0.0", - "comlink": "^4.3.1", "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", "next": "^12.1.1-canary.15", - "prism-react-renderer": "^1.3.1", - "prismjs": "^1.27.0", "react": "17.0.2", - "react-debounce-render": "^8.0.2", "react-dom": "17.0.2", "react-dropzone": "^12.0.4", "react-loading-skeleton": "^3.0.3", "react-markdown": "^8.0.0", "react-syntax-highlighter": "^15.4.5", - "react-syntax-highlighter-virtualized-renderer": "^1.1.0", "rehype-autolink-headings": "^6.1.1", - "rehype-katex": "^6.0.2", - "rehype-remark": "^9.1.2", + "rehype-raw": "^6.1.1", "rehype-slug": "^5.0.1", "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1", "swr": "^1.2.2" }, "devDependencies": { diff --git a/client/styles/globals.css b/client/styles/globals.css index ffeab85f..4bcf1f4a 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -30,3 +30,7 @@ a { * { box-sizing: border-box; } + +li:before { + content: "" !important; +} diff --git a/client/yarn.lock b/client/yarn.lock index 599edcbc..112e3aaa 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -10,7 +10,7 @@ core-js-pure "^3.20.2" regenerator-runtime "^0.13.4" -"@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.3.1", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.10.2", "@babel/runtime@^7.16.3", "@babel/runtime@^7.16.7", "@babel/runtime@^7.3.1": version "7.17.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941" integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw== @@ -180,11 +180,6 @@ dependencies: "@types/ms" "*" -"@types/extend@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/extend/-/extend-3.0.1.tgz#923dc2d707d944382433e01d6cc0c69030ab2c75" - integrity sha512-R1g/VyKFFI2HLC1QGAeTtCBWCo6n75l41OnsVYNbmKG+kempOESaodf6BeJyUM3Q0rKa/NQcTHbB2+66lNnxLw== - "@types/hast@^2.0.0": version "2.3.4" resolved "https://registry.yarnpkg.com/@types/hast/-/hast-2.3.4.tgz#8aa5ef92c117d20d974a82bdfb6a648b08c0bafc" @@ -202,11 +197,6 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= -"@types/katex@^0.11.0": - version "0.11.1" - resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5" - integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg== - "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" @@ -440,14 +430,6 @@ axobject-query@^2.2.0: resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== -babel-runtime@^6.18.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" - integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= - dependencies: - core-js "^2.4.0" - regenerator-runtime "^0.11.0" - bail@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" @@ -563,11 +545,6 @@ client-zip@^2.0.0: resolved "https://registry.yarnpkg.com/client-zip/-/client-zip-2.0.0.tgz#c93676c92ddb40c858da83517c27297a53874f8d" integrity sha512-JFd4zdhxk5F01NmNnBq3+iMgJkkh0ku9NsI1wZlUjZ52inPJX92vR5TlSkjxRhmHJBPI7YqanD71wDEiKhdWtw== -clsx@^1.0.4: - version "1.1.1" - resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188" - integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA== - color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -592,11 +569,6 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -comlink@^4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/comlink/-/comlink-4.3.1.tgz#0c6b9d69bcd293715c907c33fe8fc45aecad13c5" - integrity sha512-+YbhUdNrpBZggBAHWcgQMLPLH1KDF3wJpeqrCKieWQ8RL7atmgsgTQko1XEBK6PsecfopWNntopJ+ByYG1lRaA== - comma-separated-tokens@^1.0.0: version "1.0.8" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" @@ -612,11 +584,6 @@ commander@^6.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== -commander@^8.0.0: - version "8.3.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" - integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -639,11 +606,6 @@ core-js-pure@^3.20.2: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== -core-js@^2.4.0: - version "2.6.12" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec" - integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== - cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -776,14 +738,6 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" -dom-helpers@^5.1.3: - version "5.2.1" - resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" - integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== - dependencies: - "@babel/runtime" "^7.8.7" - csstype "^3.0.2" - dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" @@ -1120,7 +1074,7 @@ fastq@^1.6.0: dependencies: reusify "^1.0.4" -fault@^1.0.0, fault@^1.0.2: +fault@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13" integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA== @@ -1325,12 +1279,18 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hast-util-embedded@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/hast-util-embedded/-/hast-util-embedded-2.0.0.tgz#877f4261044854743fc2621f728930ca61c8376f" - integrity sha512-vEr54rDu2CheBM4nLkWbW8Rycf8HhkA/KsrDnlyKnvBTyhyO+vAG6twHnfUbiRGo56YeUBNCI4HFfHg3Wu+tig== +hast-to-hyperscript@^10.0.0: + version "10.0.1" + resolved "https://registry.yarnpkg.com/hast-to-hyperscript/-/hast-to-hyperscript-10.0.1.tgz#3decd7cb4654bca8883f6fcbd4fb3695628c4296" + integrity sha512-dhIVGoKCQVewFi+vz3Vt567E4ejMppS1haBRL6TEmeLeJVB1i/FJIIg/e6s1Bwn0g5qtYojHEKvyGA+OZuyifw== dependencies: - hast-util-is-element "^2.0.0" + "@types/unist" "^2.0.0" + comma-separated-tokens "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^0.3.0" + unist-util-is "^5.0.0" + web-namespaces "^2.0.0" hast-util-from-parse5@^7.0.0: version "7.1.0" @@ -1378,25 +1338,34 @@ hast-util-parse-selector@^3.0.0: dependencies: "@types/hast" "^2.0.0" -hast-util-to-mdast@^8.3.0: - version "8.3.1" - resolved "https://registry.yarnpkg.com/hast-util-to-mdast/-/hast-util-to-mdast-8.3.1.tgz#b2e3b666968d3ab96a0e6ec991cca0ca5c79e3b2" - integrity sha512-nxLcom1oW5y/1CyaV24K16LOfYbAIS74BDbCPW6WduzAYTAVDp3g/DM1CY6Ngo+Dx5itLzvmCm7SnUHBZd3NVQ== +hast-util-raw@^7.2.0: + version "7.2.1" + resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-7.2.1.tgz#6e964cee098dbdd93d1b77cf180b5827d48048ab" + integrity sha512-wgtppqXVdXzkDXDFclLLdAyVUJSKMYYi6LWIAbA8oFqEdwksYIcPGM3RkKV1Dfn5GElvxhaOCs0jmCOMayxd3A== dependencies: - "@types/extend" "^3.0.0" "@types/hast" "^2.0.0" - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - extend "^3.0.0" - hast-util-has-property "^2.0.0" - hast-util-is-element "^2.0.0" - hast-util-to-text "^3.0.0" - mdast-util-phrasing "^3.0.0" - mdast-util-to-string "^3.0.0" - rehype-minify-whitespace "^5.0.0" - trim-trailing-lines "^2.0.0" - unist-util-is "^5.0.0" + "@types/parse5" "^6.0.0" + hast-util-from-parse5 "^7.0.0" + hast-util-to-parse5 "^7.0.0" + html-void-elements "^2.0.0" + parse5 "^6.0.0" + unist-util-position "^4.0.0" unist-util-visit "^4.0.0" + vfile "^5.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-parse5@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-7.0.0.tgz#a39808e69005d10afeed1866029a1fb137df3f7c" + integrity sha512-YHiS6aTaZ3N0Q3nxaY/Tj98D6kM8QX5Q8xqgg8G45zR7PvWnPGPP0vcKCgb/moIydEJ/QWczVrX0JODCVeoV7A== + dependencies: + "@types/hast" "^2.0.0" + "@types/parse5" "^6.0.0" + hast-to-hyperscript "^10.0.0" + property-information "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" hast-util-to-string@^2.0.0: version "2.0.0" @@ -1405,15 +1374,6 @@ hast-util-to-string@^2.0.0: dependencies: "@types/hast" "^2.0.0" -hast-util-to-text@^3.0.0, hast-util-to-text@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-3.1.1.tgz#b7699a75f7a61af6e0befb67660cd78460d96dc6" - integrity sha512-7S3mOBxACy8syL45hCn3J7rHqYaXkxRfsX6LXEU5Shz4nt4GxdjtMUtG+T6G/ZLUHd7kslFAf14kAN71bz30xA== - dependencies: - "@types/hast" "^2.0.0" - hast-util-is-element "^2.0.0" - unist-util-find-after "^4.0.0" - hast-util-whitespace@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz#4fc1086467cc1ef5ba20673cb6b03cec3a970f1c" @@ -1446,17 +1406,10 @@ highlight.js@^10.4.1, highlight.js@~10.7.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== -highlight.js@~9.12.0: - version "9.12.0" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.12.0.tgz#e6d9dbe57cbefe60751f02af336195870c90c01e" - integrity sha1-5tnb5Xy+/mB1HwKvM2GVhwyQwB4= - -hoist-non-react-statics@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" - integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== - dependencies: - react-is "^16.7.0" +html-void-elements@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-2.0.1.tgz#29459b8b05c200b6c5ee98743c41b979d577549f" + integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== iconv-lite@^0.4.4: version "0.4.24" @@ -1731,20 +1684,6 @@ json5@^1.0.1: array-includes "^3.1.3" object.assign "^4.1.2" -katex@^0.13.0: - version "0.13.24" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.13.24.tgz#fe55455eb455698cb24b911a353d16a3c855d905" - integrity sha512-jZxYuKCma3VS5UuxOx/rFV1QyGSl3Uy/i0kTJF3HgQ5xMinCQVF8Zd4bMY/9aI9b9A2pjIBOsjSSm68ykTAr8w== - dependencies: - commander "^8.0.0" - -katex@^0.15.0: - version "0.15.2" - resolved "https://registry.yarnpkg.com/katex/-/katex-0.15.2.tgz#c05ece41ab497597b17abca2cecde3e4c0127f9d" - integrity sha512-FfZ/f6f8bQdLmJ3McXDNTkKenQkoXkItpW0I9bsG2wgb+8JAY5bwpXFtI8ZVrg5hc1wo1X/UIhdkVMpok46tEQ== - dependencies: - commander "^8.0.0" - kleur@^4.0.3: version "4.1.4" resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.4.tgz#8c202987d7e577766d039a8cd461934c01cda04d" @@ -1815,11 +1754,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -1850,14 +1784,6 @@ lowlight@^1.17.0: fault "^1.0.0" highlight.js "~10.7.0" -lowlight@~1.9.1: - version "1.9.2" - resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.9.2.tgz#0b9127e3cec2c3021b7795dd81005c709a42fdd1" - integrity sha512-Ek18ElVCf/wF/jEm1b92gTnigh94CtBNWiZ2ad+vTgW7cTmQxUY3I98BjHK68gZAJEWmybGBZgx9qv3QxLQB/Q== - dependencies: - fault "^1.0.2" - highlight.js "~9.12.0" - lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1968,23 +1894,6 @@ mdast-util-gfm@^2.0.0: mdast-util-gfm-table "^1.0.0" mdast-util-gfm-task-list-item "^1.0.0" -mdast-util-math@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/mdast-util-math/-/mdast-util-math-2.0.1.tgz#141b8e7e43731d2a7423c5eb8c0335c05d257ad2" - integrity sha512-ZZtjyRwobsiVg4bY0Q5CzAZztpbjRIA7ZlMMb0PNkwTXOnJTUoHvzBhVG95LIuek5Mlj1l2P+jBvWviqW7G+0A== - dependencies: - "@types/mdast" "^3.0.0" - longest-streak "^3.0.0" - mdast-util-to-markdown "^1.3.0" - -mdast-util-phrasing@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-3.0.0.tgz#c44fcf6be61a3cb8da54ab2df22320e61d4537ce" - integrity sha512-S+QYsDRLkGi8U7o5JF1agKa/sdP+CNGXXLqC17pdTVL8FHHgQEiwFGa9yE5aYtUxNiFGYoaDy9V1kC85Sz86Gg== - dependencies: - "@types/mdast" "^3.0.0" - unist-util-is "^5.0.0" - mdast-util-to-hast@^12.1.0: version "12.1.1" resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-12.1.1.tgz#89a2bb405eaf3b05eb8bf45157678f35eef5dbca" @@ -2130,19 +2039,6 @@ micromark-extension-gfm@^2.0.0: micromark-util-combine-extensions "^1.0.0" micromark-util-types "^1.0.0" -micromark-extension-math@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/micromark-extension-math/-/micromark-extension-math-2.0.2.tgz#bb7d28b907b17f1813dd3d0df2a6df6bb1a4d0e1" - integrity sha512-cFv2B/E4pFPBBFuGgLHkkNiFAIQv08iDgPH2HCuR2z3AUgMLecES5Cq7AVtwOtZeRrbA80QgMUk8VVW0Z+D2FA== - dependencies: - "@types/katex" "^0.11.0" - katex "^0.13.0" - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" - micromark-factory-destination@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz#fef1cb59ad4997c496f887b6977aa3034a5a277e" @@ -2663,17 +2559,12 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prism-react-renderer@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" - integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== - -prismjs@^1.25.0, prismjs@^1.27.0, prismjs@~1.27.0: +prismjs@^1.25.0, prismjs@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== -prop-types@^15.0.0, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.0.0, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -2709,14 +2600,6 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-debounce-render@^8.0.2: - version "8.0.2" - resolved "https://registry.yarnpkg.com/react-debounce-render/-/react-debounce-render-8.0.2.tgz#ce37864e9bd8d086dbd3e3e784d3270bac2cf2d3" - integrity sha512-tVdMjXJl38LK2JSr5KiCHdnHi8LlQA4jFXiMQfw0ZWDuMmAtDP63lK49RKeYnCXM9r0HKgKIFmNPZxQpeGjQmg== - dependencies: - hoist-non-react-statics "^3.3.2" - lodash.debounce "^4.0.8" - react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -2735,7 +2618,7 @@ react-dropzone@^12.0.4: file-selector "^0.4.0" prop-types "^15.8.1" -react-is@^16.13.1, react-is@^16.7.0: +react-is@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -2745,11 +2628,6 @@ react-is@^17.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== -react-lifecycles-compat@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" - integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== - react-loading-skeleton@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/react-loading-skeleton/-/react-loading-skeleton-3.0.3.tgz#4f45467cf3193fb25456bc02aeb81922b82b8f1f" @@ -2775,14 +2653,6 @@ react-markdown@^8.0.0: unist-util-visit "^4.0.0" vfile "^5.0.0" -react-syntax-highlighter-virtualized-renderer@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/react-syntax-highlighter-virtualized-renderer/-/react-syntax-highlighter-virtualized-renderer-1.1.0.tgz#7536d9f18f9cce736fac15031a891b8cbaabe90b" - integrity sha1-dTbZ8Y+cznNvrBUDGokbjLqr6Qs= - dependencies: - react-syntax-highlighter "^5.1.2" - react-virtualized "^9.3.0" - react-syntax-highlighter@^15.4.5: version "15.4.5" resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-15.4.5.tgz#db900d411d32a65c8e90c39cd64555bf463e712e" @@ -2794,27 +2664,6 @@ react-syntax-highlighter@^15.4.5: prismjs "^1.25.0" refractor "^3.2.0" -react-syntax-highlighter@^5.1.2: - version "5.8.0" - resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-5.8.0.tgz#a220c010fd0641751d93532509ba7159cc3a4383" - integrity sha512-+FolT9NhFBqE4SsZDelSzsYJJS/JCnQqo4+GxLrFPoML548uvr8f4Eh5nnd5o6ERKFW7ryiygOX9SPnxdnlpkg== - dependencies: - babel-runtime "^6.18.0" - highlight.js "~9.12.0" - lowlight "~1.9.1" - -react-virtualized@^9.3.0: - version "9.22.3" - resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421" - integrity sha512-MKovKMxWTcwPSxE1kK1HcheQTWfuCxAuBoSTf2gwyMM21NdX/PXUhnoP8Uc5dRKd+nKm8v41R36OellhdCpkrw== - dependencies: - "@babel/runtime" "^7.7.2" - clsx "^1.0.4" - dom-helpers "^5.1.3" - loose-envify "^1.4.0" - prop-types "^15.7.2" - react-lifecycles-compat "^3.0.4" - react@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -2839,11 +2688,6 @@ refractor@^3.2.0: parse-entities "^2.0.0" prismjs "~1.27.0" -regenerator-runtime@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" - integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== - regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" @@ -2875,50 +2719,13 @@ rehype-autolink-headings@^6.1.1: unified "^10.0.0" unist-util-visit "^4.0.0" -rehype-katex@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/rehype-katex/-/rehype-katex-6.0.2.tgz#20197bbc10bdf79f6b999bffa6689d7f17226c35" - integrity sha512-C4gDAlS1+l0hJqctyiU64f9CvT00S03qV1T6HiMzbSuLBgWUtcqydWHY9OpKrm0SpkK16FNd62CDKyWLwV2ppg== +rehype-raw@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-6.1.1.tgz#81bbef3793bd7abacc6bf8335879d1b6c868c9d4" + integrity sha512-d6AKtisSRtDRX4aSPsJGTfnzrX2ZkHQLE5kiUuGOeEoLpbEulFF4hj0mLPbsa+7vmguDKOVVEQdHKDSwoaIDsQ== dependencies: "@types/hast" "^2.0.0" - "@types/katex" "^0.11.0" - hast-util-to-text "^3.1.0" - katex "^0.15.0" - rehype-parse "^8.0.0" - unified "^10.0.0" - unist-util-remove-position "^4.0.0" - unist-util-visit "^4.0.0" - -rehype-minify-whitespace@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/rehype-minify-whitespace/-/rehype-minify-whitespace-5.0.0.tgz#f3eec0ac94e78adbb868133e64864343ba9fff1f" - integrity sha512-cUkQldYx8jpWM6FZmCkOF/+mev09+5NAtBzUFZgnfXsIoglxo3h6t3S3JbNouiaqIDE6vbpI+GQpOg0xD3Z7kg== - dependencies: - "@types/hast" "^2.0.0" - hast-util-embedded "^2.0.0" - hast-util-is-element "^2.0.0" - hast-util-whitespace "^2.0.0" - unified "^10.0.0" - unist-util-is "^5.0.0" - -rehype-parse@^8.0.0: - version "8.0.4" - resolved "https://registry.yarnpkg.com/rehype-parse/-/rehype-parse-8.0.4.tgz#3d17c9ff16ddfef6bbcc8e6a25a99467b482d688" - integrity sha512-MJJKONunHjoTh4kc3dsM1v3C9kGrrxvA3U8PxZlP2SjH8RNUSrb+lF7Y0KVaUDnGH2QZ5vAn7ulkiajM9ifuqg== - dependencies: - "@types/hast" "^2.0.0" - hast-util-from-parse5 "^7.0.0" - parse5 "^6.0.0" - unified "^10.0.0" - -rehype-remark@^9.1.2: - version "9.1.2" - resolved "https://registry.yarnpkg.com/rehype-remark/-/rehype-remark-9.1.2.tgz#b4ed84d7e692426c3269e72ec477906cec659c05" - integrity sha512-c0fG3/CrJ95zAQ07xqHSkdpZybwdsY7X5dNWvgL2XqLKZuqmG3+vk6kP/4miCnp+R+x/0uKKRSpfXb9aGR8Z5w== - dependencies: - "@types/hast" "^2.0.0" - "@types/mdast" "^3.0.0" - hast-util-to-mdast "^8.3.0" + hast-util-raw "^7.2.0" unified "^10.0.0" rehype-slug@^5.0.1: @@ -2944,16 +2751,6 @@ remark-gfm@^3.0.1: micromark-extension-gfm "^2.0.0" unified "^10.0.0" -remark-math@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/remark-math/-/remark-math-5.1.1.tgz#459e798d978d4ca032e745af0bac81ddcdf94964" - integrity sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw== - dependencies: - "@types/mdast" "^3.0.0" - mdast-util-math "^2.0.0" - micromark-extension-math "^2.0.0" - unified "^10.0.0" - remark-parse@^10.0.0: version "10.0.1" resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-10.0.1.tgz#6f60ae53edbf0cf38ea223fe643db64d112e0775" @@ -3265,11 +3062,6 @@ totalist@^1.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== -trim-trailing-lines@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-2.0.0.tgz#1fdfbc20db6651bed117bc5c736f6cf052bcf421" - integrity sha512-lyJ/STqmYO8k9aWZbodPTrVkRh/50yFwNZ/HxKp/30eM9zf898MgQCG/NxfMnx7fZSKbaEcuZ1V+QgWrXFH6rg== - trough@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" @@ -3378,14 +3170,6 @@ unist-builder@^3.0.0: dependencies: "@types/unist" "^2.0.0" -unist-util-find-after@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-4.0.0.tgz#1101cebf5fed88ae3c6f3fa676e86fd5772a4f32" - integrity sha512-gfpsxKQde7atVF30n5Gff2fQhAc4/HTOV4CvkXpTg9wRfQhZWdXitpyXHWB6YcYgnsxLx+4gGHeVjCTAAp9sjw== - dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^5.0.0" - unist-util-generated@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-2.0.0.tgz#86fafb77eb6ce9bfa6b663c3f5ad4f8e56a60113" @@ -3401,14 +3185,6 @@ unist-util-position@^4.0.0: resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-4.0.1.tgz#f8484b2da19a897a0180556d160c28633070dbb9" integrity sha512-mgy/zI9fQ2HlbOtTdr2w9lhVaiFUHWQnZrFF2EUoVOqtAUdzqMtNiD99qA5a1IcjWVR8O6aVYE9u7Z2z1v0SQA== -unist-util-remove-position@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-4.0.1.tgz#d5b46a7304ac114c8d91990ece085ca7c2c135c8" - integrity sha512-0yDkppiIhDlPrfHELgB+NLQD5mfjup3a8UYclHruTJWmY74je8g+CIFr79x5f6AkmzSwlvKLbs63hC0meOMowQ== - dependencies: - "@types/unist" "^2.0.0" - unist-util-visit "^4.0.0" - unist-util-stringify-position@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz#5c6aa07c90b1deffd9153be170dce628a869a447" From d1ee9d857fa0bf2c5e1e39c3bcafa12f4337f113 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 17:37:21 -0700 Subject: [PATCH 35/63] client: beging markdown rendering on server --- client/lib/hooks/use-trace-route.ts | 19 ++ client/lib/render-markdown.tsx | 140 +++++++++++++++ client/pages/api/markdown/[id].tsx | 44 +++++ client/styles/Home.module.css | 31 +--- client/styles/globals.css | 265 +++++++++++++++++++++++++--- client/styles/inter.css | 100 +++++++++++ client/styles/markdown.css | 140 +++++++++++++++ client/styles/nprogress.css | 23 +++ client/styles/syntax.css | 24 +++ 9 files changed, 737 insertions(+), 49 deletions(-) create mode 100644 client/lib/hooks/use-trace-route.ts create mode 100644 client/lib/render-markdown.tsx create mode 100644 client/pages/api/markdown/[id].tsx create mode 100644 client/styles/inter.css create mode 100644 client/styles/markdown.css create mode 100644 client/styles/nprogress.css create mode 100644 client/styles/syntax.css diff --git a/client/lib/hooks/use-trace-route.ts b/client/lib/hooks/use-trace-route.ts new file mode 100644 index 00000000..b0268e32 --- /dev/null +++ b/client/lib/hooks/use-trace-route.ts @@ -0,0 +1,19 @@ +import { useRef, useEffect } from "react"; + +function useTraceUpdate(props: { [key: string]: any }) { + const prev = useRef(props) + useEffect(() => { + const changedProps = Object.entries(props).reduce((ps, [k, v]) => { + if (prev.current[k] !== v) { + ps[k] = [prev.current[k], v] + } + return ps + }, {} as { [key: string]: any }) + if (Object.keys(changedProps).length > 0) { + console.log('Changed props:', changedProps) + } + prev.current = props + }); +} + +export default useTraceUpdate \ No newline at end of file diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx new file mode 100644 index 00000000..5f6f467d --- /dev/null +++ b/client/lib/render-markdown.tsx @@ -0,0 +1,140 @@ +import Link from '@components/Link' +import { marked } from 'marked' +import Highlight, { defaultProps, Language } from 'prism-react-renderer' +import { renderToStaticMarkup } from 'react-dom/server' +//@ts-ignore +delete defaultProps.theme +// import linkStyles from '../components/link/link.module.css' + +const renderer = new marked.Renderer() + +renderer.heading = (text, level, _, slugger) => { + const id = slugger.slug(text) + const Component = `h${level}` + + return renderToStaticMarkup( + //@ts-ignore + <Component> + <a href={`#${id}`} id={id} style={{ color: "inherit" }} > + {text} + </a> + </Component> + ) +} + +renderer.link = (href, _, text) => { + const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') + if (isHrefLocal) { + return renderToStaticMarkup( + <a href={href || ''}> + {text} + </a> + ) + } + return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` +} + +renderer.image = function (href, _, text) { + return `<Image loading="lazy" src="${href}" alt="${text}" layout="fill" />` +} + +renderer.checkbox = () => '' +renderer.listitem = (text, task, checked) => { + if (task) { + return `<li class="reset"><span class="check">​<input type="checkbox" disabled ${checked ? 'checked' : '' + } /></span><span>${text}</span></li>` + } + + return `<li>${text}</li>` +} + +renderer.code = (code: string, language: string) => { + return renderToStaticMarkup( + <pre> + {/* {title && <code>{title} </code>} */} + {/* {language && title && <code style={{}}> {language} </code>} */} + <Code + language={language} + // title={title} + code={code} + // highlight={highlight} + /> + </pre> + ) +} + +marked.setOptions({ + gfm: true, + breaks: true, + headerIds: true, + renderer, +}) + +const markdown = (markdown: string) => marked(markdown) + +export default markdown + +const Code = ({ code, language, highlight, title, ...props }: { + code: string, + language: string, + highlight?: string, + title?: string, +}) => { + if (!language) + return ( + <> + <code {...props} dangerouslySetInnerHTML={{ __html: code } + } /> + </> + ) + + const highlightedLines = highlight + //@ts-ignore + ? highlight.split(',').reduce((lines, h) => { + if (h.includes('-')) { + // Expand ranges like 3-5 into [3,4,5] + const [start, end] = h.split('-').map(Number) + const x = Array(end - start + 1) + .fill(undefined) + .map((_, i) => i + start) + return [...lines, ...x] + } + + return [...lines, Number(h)] + }, []) + : '' + + // https://mdxjs.com/guides/syntax-harkedighlighting#all-together + return ( + <> + <Highlight {...defaultProps} code={code.trim()} language={language as Language} > + {({ className, style, tokens, getLineProps, getTokenProps }) => ( + <code className={className} style={{ ...style }}> + { + tokens.map((line, i) => ( + <div + key={i} + {...getLineProps({ line, key: i })} + style={ + highlightedLines.includes((i + 1).toString()) + ? { + background: 'var(--highlight)', + margin: '0 -1rem', + padding: '0 1rem', + } + : undefined + } + > + { + line.map((token, key) => ( + <span key={key} {...getTokenProps({ token, key })} /> + )) + } + </div> + ))} + </code> + )} + </Highlight> + </> + ) +} diff --git a/client/pages/api/markdown/[id].tsx b/client/pages/api/markdown/[id].tsx new file mode 100644 index 00000000..359c33ec --- /dev/null +++ b/client/pages/api/markdown/[id].tsx @@ -0,0 +1,44 @@ +import type { NextApiHandler } from "next"; + +import markdown from "@lib/render-markdown"; + +const renderMarkdown: NextApiHandler = async (req, res) => { + const { id } = req.query + const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { + headers: { + 'Accept': 'text/plain', + 'x-secret-key': process.env.SECRET_KEY || '', + 'Authorization': `Bearer ${req.cookies['drift-token']}`, + } + }) + + + const json = await file.json() + const { content, title } = json + const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = content; + + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} +${content} +~~~` + } + + if (typeof contentToRender !== 'string') { + res.status(400).send('content must be a string') + return + } + + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Cache-Control', 'public, max-age=4800') + res.status(200).write(markdown(contentToRender)) + res.end() +} + +export default renderMarkdown diff --git a/client/styles/Home.module.css b/client/styles/Home.module.css index f770e0b2..3c3959ed 100644 --- a/client/styles/Home.module.css +++ b/client/styles/Home.module.css @@ -1,28 +1,11 @@ -.main { - min-height: 100vh; - flex: 1; - display: flex; - flex-direction: column; - margin: 0 auto; - width: var(--main-content-width); -} - -.container { +.wrapper { + height: 100% !important; + padding-bottom: var(--small-gap) !important; width: 100% !important; } -@media screen and (max-width: 768px) { - .container { - width: 100%; - margin: 0 auto !important; - padding: 0; - } - - .container h1 { - font-size: 2rem; - } - - .main { - width: 100%; - } +.main { + max-width: var(--main-content) !important; + margin: 0 auto !important; + padding: 0 var(--gap) !important; } diff --git a/client/styles/globals.css b/client/styles/globals.css index 4bcf1f4a..2e93b838 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -1,36 +1,251 @@ +@import "./syntax.css"; +@import "./markdown.css"; +@import "./nprogress.css"; +@import "./inter.css"; + :root { - --main-content-width: 800px; - --page-nav-height: 60px; - --gap: 8px; - --gap-half: calc(var(--gap) / 2); - --gap-double: calc(var(--gap) * 2); - --border-radius: 4px; - --font-size: 16px; + /* Spacing */ + --gap-quarter: 0.25rem; + --gap-half: 0.5rem; + --gap: 1rem; + --gap-double: 2rem; + --small-gap: 4rem; + --big-gap: 4rem; + --main-content: 55rem; + --radius: 8px; + --inline-radius: 5px; + + /* Typography */ + --font-sans: "Inter", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, + Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + --font-mono: ui-monospace, "SFMono-Regular", "Consolas", "Liberation Mono", + "Menlo", monospace; + + /* Transitions */ + --transition: 0.1s ease-in-out; + --transition-slow: 0.3s ease-in-out; + + /* Dark Mode Colors */ + --bg: #131415; + --fg: #fafbfc; + --gray: #666; + --light-gray: #444; + --lighter-gray: #222; + --lightest-gray: #1a1a1a; + --article-color: #eaeaea; + --header-bg: rgba(19, 20, 21, 0.45); + --gray-alpha: rgba(255, 255, 255, 0.5); + --selection: rgba(255, 255, 255, 0.99); + + /* Forms */ + --input-height: 2.5rem; + --input-border: 1px solid var(--light-gray); + --input-border-focus: 1px solid var(--gray); + --input-border-error: 1px solid var(--red); + --input-bg: var(--bg); + --input-fg: var(--fg); + --input-placeholder-fg: var(--light-gray); + --input-bg-hover: var(--lightest-gray); + + /* Syntax Highlighting */ + --token: #999; + --comment: #999; + --keyword: #fff; + --name: #fff; + --highlight: #2e2e2e; } -@media screen and (max-width: 768px) { - :root { - --main-content-width: 100%; - } -} +[data-theme="light"] { + --bg: #fff; + --fg: #000; + --gray: #888; + --light-gray: #dedede; + --lighter-gray: #f5f5f5; + --lightest-gray: #fafafa; + --article-color: #212121; + --header-bg: rgba(255, 255, 255, 0.8); + --gray-alpha: rgba(19, 20, 21, 0.5); + --selection: rgba(0, 0, 0, 0.99); -html, -body { - padding: 0; - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; -} - -a { - color: inherit; - text-decoration: none; + --token: #666; + --comment: #999; + --keyword: #000; + --name: #333; + --highlight: #eaeaea; } * { box-sizing: border-box; } -li:before { - content: "" !important; +::selection { + text-shadow: none; + background: var(--selection); + color: var(--bg); +} + +html { + line-height: 1.5; +} + +html, +body { + padding: 0; + margin: 0; + font-size: 15px; + background: var(--bg); + color: var(--fg); + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + min-height: 100vh; + font-family: var(--font-sans); + display: flex; + flex-direction: column; +} + +p, +li { + letter-spacing: -0.33px; + font-size: 1.125rem; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-family: var(--font-sans); + font-weight: 600; + line-height: 1.75; +} + +h1 { + font-size: 2.5rem; + font-weight: 600; + line-height: 1.25; + letter-spacing: -0.89px; +} + +h2 { + font-size: 2rem; + letter-spacing: -0.69px; +} + +h3 { + font-size: 1.5rem; + letter-spacing: -0.47px; +} + +h4 { + font-size: 1.25rem; + letter-spacing: -0.33px; +} + +hr { + border: none; + border-bottom: 1px solid var(--light-gray); +} + +blockquote { + font-style: italic; + margin: 0; + padding-left: 1rem; + border-left: 3px solid var(--light-gray); +} + +button { + border: none; + padding: 0; + margin: 0; + line-height: inherit; + font-size: inherit; +} + +p a, +a.reset { + outline: none; + color: var(--fg); + text-decoration: none; +} + +p a:hover, +p a:focus, +p a:active, +a.reset:hover, +a.reset:focus { + color: var(--gray); +} + +pre, +code { + font-family: var(--font-mono); +} + +.clamp { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 1; + overflow: hidden; +} + +.clamp-2 { + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; + overflow: hidden; +} + +.flex { + display: flex; +} + +kbd { + font-family: var(--font-sans); + font-size: 1rem; + padding: 2px 7px; + font-weight: 600; + background: var(--lighter-gray); + border-radius: 5px; +} + +summary { + cursor: pointer; + outline: none; +} + +details { + border-radius: var(--radius); + background: var(--lightest-gray); + padding: 1rem; + border-radius: var(--radius); +} + +@media print { + :root { + --bg: #fff; + --fg: #000; + --gray: #888; + --light-gray: #dedede; + --lighter-gray: #f5f5f5; + --lightest-gray: #fafafa; + --article-color: #212121; + --header-bg: rgba(255, 255, 255, 0.8); + --gray-alpha: rgba(19, 20, 21, 0.5); + --selection: rgba(0, 0, 0, 0.99); + + --token: #666; + --comment: #999; + --keyword: #000; + --name: #333; + --highlight: #eaeaea; + } + + * { + text-shadow: none !important; + } } diff --git a/client/styles/inter.css b/client/styles/inter.css new file mode 100644 index 00000000..61f0df75 --- /dev/null +++ b/client/styles/inter.css @@ -0,0 +1,100 @@ +/* latin */ +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 100; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 200; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 300; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 400; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 500; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 600; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 700; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 800; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} +@font-face { + font-family: "Inter"; + font-style: normal; + font-weight: 900; + font-display: block; + src: url(https://assets.vercel.com/raw/upload/v1587415301/fonts/2/inter-var-latin.woff2) + format("woff2"); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, + U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, + U+FEFF, U+FFFD; +} diff --git a/client/styles/markdown.css b/client/styles/markdown.css new file mode 100644 index 00000000..fb3fc107 --- /dev/null +++ b/client/styles/markdown.css @@ -0,0 +1,140 @@ +article { + max-width: var(--main-content); + margin: 0 auto; + line-height: 1.9; +} + +article > * + * { + margin-top: 2em; +} + +article p { + color: var(--article-color); +} + +article img { + max-width: 100%; + /* width: var(--main-content); */ + width: auto; + margin: auto; + display: block; + border-radius: var(--radius); +} + +article [id]::before { + content: ""; + display: block; + height: 70px; + margin-top: -70px; + visibility: hidden; +} + +/* Lists */ + +article ul { + padding: 0; + list-style-position: inside; + list-style-type: circle; +} + +article ol { + padding: 0; + list-style-position: inside; +} + +article ul li.reset { + display: flex; + align-items: flex-start; + + list-style-type: none; + margin-left: -0.5rem; +} + +article ul li.reset .check { + display: flex; + align-items: center; + margin-right: 0.51rem; +} + +/* Checkbox */ + +input[type="checkbox"] { + vertical-align: middle; + appearance: none; + display: inline-block; + background-origin: border-box; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + background-color: var(--bg); + color: var(--fg); + border: 1px solid var(--fg); + border-radius: 3px; +} + +input[type="checkbox"]:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +html[data-theme="light"] input[type="checkbox"]:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); +} + +input[type="checkbox"]:focus { + outline: none; + box-shadow: 0 0 0 2px var(--gray); + border-color: var(--fg); +} + +/* Code Snippets */ + +.token-line:not(:last-child) { + min-height: 1.4rem; +} + +article *:not(pre) > code { + font-weight: 600; + font-family: var(--font-sans); + font-size: 1rem; + padding: 0 3px; +} + +article *:not(pre) > code::before, +article *:not(pre) > code::after { + content: "\`"; + color: var(--gray); + user-select: none; +} + +article pre { + overflow-x: auto; + background: var(--lightest-gray); + border-radius: var(--inline-radius); + line-height: 1.8; + padding: 1rem; + font-size: 0.875rem; +} + +/* Linkable Headers */ + +.header-link { + color: inherit; + text-decoration: none; +} + +.header-link::after { + opacity: 0; + content: "#"; + margin-left: var(--gap-half); + color: var(--gray); +} + +.header-link:hover::after { + opacity: 1; +} diff --git a/client/styles/nprogress.css b/client/styles/nprogress.css new file mode 100644 index 00000000..0db6e676 --- /dev/null +++ b/client/styles/nprogress.css @@ -0,0 +1,23 @@ +#nprogress { + pointer-events: none; +} + +#nprogress .bar { + position: fixed; + z-index: 2000; + top: 0; + left: 0; + width: 100%; + height: 5px; + background: var(--fg); +} + +#nprogress::after { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 5px; + background: transparent; +} diff --git a/client/styles/syntax.css b/client/styles/syntax.css new file mode 100644 index 00000000..42a27295 --- /dev/null +++ b/client/styles/syntax.css @@ -0,0 +1,24 @@ +.keyword { + font-weight: bold; + color: var(--keyword); +} + +.token.operator, +.token.punctuation, +.token.string, +.token.number, +.token.builtin, +.token.variable { + color: var(--token); +} + +.token.comment { + color: var(--comment); +} + +.token.class-name, +.token.function, +.token.tag, +.token.attr-name { + color: var(--name); +} From 34b1ab979f9dcef6a529623c4de4e7d8dc3583f8 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 20:06:15 -0700 Subject: [PATCH 36/63] client: overhaul markdown rendering (now server-side), refactor theming --- client/components/Link.tsx | 2 +- client/components/auth/index.tsx | 6 +- .../button-dropdown/dropdown.module.css | 26 +++ client/components/button-dropdown/index.tsx | 116 ++++++++++ client/components/button/button.module.css | 62 +++++ client/components/button/index.tsx | 28 +++ .../document/formatting-icons/index.tsx | 3 +- client/components/document/index.tsx | 29 +-- client/components/head/index.tsx | 27 +++ client/components/header/controls.tsx | 7 +- client/components/header/header.tsx | 179 +++++++++++++++ client/components/header/index.tsx | 212 +----------------- client/components/input/index.tsx | 24 ++ client/components/input/input.module.css | 57 +++++ client/components/new-post/index.tsx | 5 +- client/components/new-post/password/index.tsx | 6 +- client/components/new-post/title/index.tsx | 4 +- client/components/post-list/index.tsx | 2 +- .../post-list/list-item-skeleton.tsx | 6 +- client/components/post-list/list-item.tsx | 9 +- client/components/post-page/index.tsx | 17 +- client/components/preview/index.tsx | 59 +++-- client/components/visibility-badge/index.tsx | 2 +- client/lib/hooks/use-signed-in.ts | 11 +- client/lib/hooks/use-theme.ts | 27 +++ client/lib/render-markdown.tsx | 1 - client/package.json | 3 + client/pages/_app.tsx | 41 +--- client/pages/_document.tsx | 2 +- .../pages/api/markdown/{[id].tsx => [id].ts} | 5 +- client/pages/api/render-markdown.ts | 30 +++ client/pages/index.tsx | 15 +- client/pages/mine.tsx | 12 +- client/pages/new.tsx | 9 +- client/pages/post/[id].tsx | 8 +- client/pages/post/private/[id].tsx | 24 +- client/pages/post/protected/[id].tsx | 9 +- client/pages/signin.tsx | 13 +- client/pages/signup.tsx | 12 +- client/styles/globals.css | 128 +---------- client/yarn.lock | 15 ++ 41 files changed, 735 insertions(+), 518 deletions(-) create mode 100644 client/components/button-dropdown/dropdown.module.css create mode 100644 client/components/button-dropdown/index.tsx create mode 100644 client/components/button/button.module.css create mode 100644 client/components/button/index.tsx create mode 100644 client/components/head/index.tsx create mode 100644 client/components/header/header.tsx create mode 100644 client/components/input/index.tsx create mode 100644 client/components/input/input.module.css create mode 100644 client/lib/hooks/use-theme.ts rename client/pages/api/markdown/{[id].tsx => [id].ts} (87%) create mode 100644 client/pages/api/render-markdown.ts diff --git a/client/components/Link.tsx b/client/components/Link.tsx index 9f2e376d..e96f89bb 100644 --- a/client/components/Link.tsx +++ b/client/components/Link.tsx @@ -1,5 +1,5 @@ import type { LinkProps } from "@geist-ui/core" -import GeistLink from "@geist-ui/core/dist/link" +import { Link as GeistLink } from "@geist-ui/core" import { useRouter } from "next/router"; const Link = (props: LinkProps) => { diff --git a/client/components/auth/index.tsx b/client/components/auth/index.tsx index 43b1d28f..5f48f3cf 100644 --- a/client/components/auth/index.tsx +++ b/client/components/auth/index.tsx @@ -4,6 +4,7 @@ import styles from './auth.module.css' import { useRouter } from 'next/router' import Link from '../Link' import Cookies from "js-cookie"; +import useSignedIn from '@lib/hooks/use-signed-in' const NO_EMPTY_SPACE_REGEX = /^\S*$/; const ERROR_MESSAGE = "Provide a non empty username and a password with at least 6 characters"; @@ -17,7 +18,7 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { const [errorMsg, setErrorMsg] = useState(''); const [requiresServerPassword, setRequiresServerPassword] = useState(false); const signingIn = page === 'signin' - + const { signin } = useSignedIn(); useEffect(() => { async function fetchRequiresPass() { if (!signingIn) { @@ -37,7 +38,7 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { const handleJson = (json: any) => { - Cookies.set('drift-token', json.token); + signin(json.token) Cookies.set('drift-userid', json.userId); router.push('/') @@ -65,7 +66,6 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => { handleJson(json) } catch (err: any) { - console.log(err) setErrorMsg(err.message ?? "Something went wrong") } } diff --git a/client/components/button-dropdown/dropdown.module.css b/client/components/button-dropdown/dropdown.module.css new file mode 100644 index 00000000..dd03da08 --- /dev/null +++ b/client/components/button-dropdown/dropdown.module.css @@ -0,0 +1,26 @@ +.main { + margin-bottom: 2rem; +} + +.dropdown { + position: relative; + display: inline-block; + vertical-align: middle; + cursor: pointer; + padding: 0; + border: 0; + background: transparent; +} + +.dropdownContent { + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; + box-shadow: 0 3px 12px rgba(0, 0, 0, 0.15); +} + +.icon { + display: flex; + align-items: center; + justify-content: center; +} diff --git a/client/components/button-dropdown/index.tsx b/client/components/button-dropdown/index.tsx new file mode 100644 index 00000000..7000059b --- /dev/null +++ b/client/components/button-dropdown/index.tsx @@ -0,0 +1,116 @@ +import Button from "@components/button" +import React, { useCallback, useEffect } from "react" +import { useState } from "react" +import styles from './dropdown.module.css' +import DownIcon from '@geist-ui/icons/arrowDown' +type Props = { + type?: "primary" | "secondary" + loading?: boolean + disabled?: boolean + className?: string + iconHeight?: number +} + +type Attrs = Omit<React.HTMLAttributes<any>, keyof Props> +type ButtonDropdownProps = Props & Attrs + +const ButtonDropdown: React.FC<React.PropsWithChildren<ButtonDropdownProps>> = ({ + type, + className, + disabled, + loading, + iconHeight = 24, + ...props +}) => { + const [visible, setVisible] = useState(false) + const [dropdown, setDropdown] = useState<HTMLDivElement | null>(null) + + const onClick = (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + setVisible(!visible) + } + + const onBlur = () => { + setVisible(false) + } + + const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + } + + const onMouseUp = (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + } + + const onMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + setVisible(false) + } + + const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { + if (e.key === "Escape") { + setVisible(false) + } + } + + const onClickOutside = useCallback(() => (e: React.MouseEvent<HTMLDivElement>) => { + if (dropdown && !dropdown.contains(e.target as Node)) { + setVisible(false) + } + }, [dropdown]) + + useEffect(() => { + if (visible) { + document.addEventListener("mousedown", onClickOutside) + } else { + document.removeEventListener("mousedown", onClickOutside) + } + + return () => { + document.removeEventListener("mousedown", onClickOutside) + } + }, [visible, onClickOutside]) + + if (!Array.isArray(props.children)) { + return null + } + + return ( + <div + className={`${styles.main} ${className}`} + onMouseDown={onMouseDown} + onMouseUp={onMouseUp} + onMouseLeave={onMouseLeave} + onKeyDown={onKeyDown} + onBlur={onBlur} + > + <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}> + {props.children[0]} + <Button style={{ height: iconHeight, width: iconHeight }} className={styles.icon} onClick={() => setVisible(!visible)}><DownIcon /></Button> + </div> + { + visible && ( + <div + className={`${styles.dropdown}`} + > + <div + className={`${styles.dropdownContent}`} + > + {props.children.slice(1)} + + </div> + </div> + ) + } + </div > + ) + + + +} + +export default ButtonDropdown \ No newline at end of file diff --git a/client/components/button/button.module.css b/client/components/button/button.module.css new file mode 100644 index 00000000..381f3962 --- /dev/null +++ b/client/components/button/button.module.css @@ -0,0 +1,62 @@ +.button { + user-select: none; + cursor: pointer; + border-radius: var(--radius); + color: var(--input-fg); + font-weight: 400; + font-size: 1.1rem; + background: var(--input-bg); + border: var(--input-border); + height: 2rem; + display: flex; + align-items: center; + padding: var(--gap-quarter) var(--gap-half); + transition: background-color var(--transition), color var(--transition); + width: 100%; + height: var(--input-height); +} + +/* +--input-height: 2.5rem; +--input-border: 1px solid var(--light-gray); +--input-border-focus: 1px solid var(--gray); +--input-border-error: 1px solid var(--red); +--input-bg: var(--bg); +--input-fg: var(--fg); +--input-placeholder-fg: var(--light-gray); */ + +.button:hover, +.button:focus { + outline: none; + background: var(--input-bg-hover); + border: var(--input-border-focus); +} + +.button[disabled] { + cursor: not-allowed; + background: var(--lighter-gray); + color: var(--gray); +} + +.secondary { + background: var(--bg); + color: var(--fg); +} + +/* +--bg: #131415; + --fg: #fafbfc; + --gray: #666; + --light-gray: #444; + --lighter-gray: #222; + --lightest-gray: #1a1a1a; + --article-color: #eaeaea; + --header-bg: rgba(19, 20, 21, 0.45); + --gray-alpha: rgba(255, 255, 255, 0.5); + --selection: rgba(255, 255, 255, 0.99); + */ + +.primary { + background: var(--fg); + color: var(--bg); +} diff --git a/client/components/button/index.tsx b/client/components/button/index.tsx new file mode 100644 index 00000000..0e85a79a --- /dev/null +++ b/client/components/button/index.tsx @@ -0,0 +1,28 @@ +import styles from './button.module.css' +import { forwardRef, Ref } from 'react' + +type Props = React.HTMLProps<HTMLButtonElement> & { + children: React.ReactNode + buttonType?: 'primary' | 'secondary' + className?: string + onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void +} + +// eslint-disable-next-line react/display-name +const Button = forwardRef<HTMLButtonElement, Props>( + ({ children, onClick, className, buttonType = 'primary', type = 'button', disabled = false, ...props }, ref) => { + return ( + <button + ref={ref} + className={`${styles.button} ${styles[type]} ${className}`} + disabled={disabled} + onClick={onClick} + {...props} + > + {children} + </button> + ) + } +) + +export default Button diff --git a/client/components/document/formatting-icons/index.tsx b/client/components/document/formatting-icons/index.tsx index 279162df..1ef79c8e 100644 --- a/client/components/document/formatting-icons/index.tsx +++ b/client/components/document/formatting-icons/index.tsx @@ -1,11 +1,10 @@ -import ButtonGroup from "@geist-ui/core/dist/button-group" -import Button from "@geist-ui/core/dist/button" import Bold from '@geist-ui/icons/bold' import Italic from '@geist-ui/icons/italic' import Link from '@geist-ui/icons/link' import ImageIcon from '@geist-ui/icons/image' import { RefObject, useCallback, useMemo } from "react" import styles from '../document.module.css' +import { Button, ButtonGroup } from "@geist-ui/core" // TODO: clean up diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index b34f1b19..d8200c86 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -1,11 +1,4 @@ -import Button from "@geist-ui/core/dist/button" -import Card from "@geist-ui/core/dist/card" -import ButtonGroup from "@geist-ui/core/dist/button-group" -import Input from "@geist-ui/core/dist/input" -import Spacer from "@geist-ui/core/dist/spacer" -import Tabs from "@geist-ui/core/dist/tabs" -import Textarea from "@geist-ui/core/dist/textarea" -import Tooltip from "@geist-ui/core/dist/tooltip" + import { ChangeEvent, memo, useCallback, useMemo, useRef, useState } from "react" import styles from './document.module.css' @@ -15,9 +8,8 @@ import ExternalLink from '@geist-ui/icons/externalLink' import FormattingIcons from "./formatting-icons" import Skeleton from "react-loading-skeleton" -import dynamic from "next/dynamic"; - -const MarkdownPreview = dynamic(() => import("../preview")) +import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } from "@geist-ui/core" +import Preview from "@components/preview" // import Link from "next/link" type Props = { @@ -74,13 +66,6 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init setTab(newTab as 'edit' | 'preview') } - const getType = useCallback(() => { - if (!title) return - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - }, [title]) - const onTitleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null, [setTitle]) const removeFile = useCallback(() => (remove?: () => void) => { @@ -140,14 +125,14 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </div> <div className={styles.descriptionContainer}> {tab === 'edit' && editable && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />} - {rawLink && <DownloadButton rawLink={rawLink()} />} + {rawLink && id && <DownloadButton rawLink={rawLink()} />} <Tabs onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}> <Tabs.Item label={editable ? "Edit" : "Raw"} value="edit"> {/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */} - <div style={{ display: 'flex', flexDirection: 'column' }}> + <div style={{ marginTop: 'var(--gap)', display: 'flex', flexDirection: 'column' }}> <Textarea ref={codeEditorRef} - placeholder="Type some contents..." + placeholder="" value={content} onChange={handleOnContentChange} width="100%" @@ -160,7 +145,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </div> </Tabs.Item> <Tabs.Item label="Preview" value="preview"> - <MarkdownPreview height={height} content={content} type={getType()} /> + <Preview height={height} fileId={id} title={title} content={content} /> </Tabs.Item> </Tabs> diff --git a/client/components/head/index.tsx b/client/components/head/index.tsx new file mode 100644 index 00000000..1bc53d0c --- /dev/null +++ b/client/components/head/index.tsx @@ -0,0 +1,27 @@ +import Head from "next/head"; +import React from "react"; + +type PageSeoProps = { + title?: string; + description?: string; + isLoading?: boolean; + isPrivate?: boolean +}; + +const PageSeo = ({ + title = 'Drift', + description = "A self-hostable clone of GitHub Gist", + isPrivate = false +}: PageSeoProps) => { + + return ( + <> + <Head> + <title>{title} + {!isPrivate && } + + + ); +}; + +export default PageSeo; diff --git a/client/components/header/controls.tsx b/client/components/header/controls.tsx index 6d4ee88c..09b4afac 100644 --- a/client/components/header/controls.tsx +++ b/client/components/header/controls.tsx @@ -1,19 +1,16 @@ import React from 'react' import MoonIcon from '@geist-ui/icons/moon' import SunIcon from '@geist-ui/icons/sun' -import Select from '@geist-ui/core/dist/select' // import { useAllThemes, useTheme } from '@geist-ui/core' import styles from './header.module.css' import { ThemeProps } from '@lib/types' -import Cookies from 'js-cookie' +import { Select } from '@geist-ui/core' const Controls = ({ changeTheme, theme }: ThemeProps) => { - const switchThemes = (type: string | string[]) => { + const switchThemes = () => { changeTheme() - Cookies.set('drift-theme', Array.isArray(type) ? type[0] : type) } - return (
+
+ + ) +}) + +export default Input diff --git a/client/components/input/input.module.css b/client/components/input/input.module.css new file mode 100644 index 00000000..2a5cf7f7 --- /dev/null +++ b/client/components/input/input.module.css @@ -0,0 +1,57 @@ +.wrapper { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + height: 100%; + font-size: 1rem; +} + +.input { + height: 2.5rem; + border-radius: var(--inline-radius); + background: var(--bg); + color: var(--fg); + border: 1px solid var(--light-gray); + padding: 0 var(--gap-half); + outline: none; + transition: border-color var(--transition); + display: flex; + justify-content: center; + margin: 0; + width: 100%; +} + +.input::placeholder { + font-size: 1.5rem; +} + +.input:focus { + border-color: var(--input-border-focus); +} + +.label { + display: inline-flex; + width: initial; + height: 100%; + align-items: center; + pointer-events: none; + margin: 0; + padding: 0 var(--gap-half); + color: var(--fg); + background-color: var(--light-gray); + border-top-left-radius: var(--radius); + border-bottom-left-radius: var(--radius); + border-top: 1px solid var(--input-border); + border-left: 1px solid var(--input-border); + border-bottom: 1px solid var(--input-border); + font-size: inherit; + line-height: 1; + white-space: nowrap; +} + +@media screen and (max-width: 768px) { + .wrapper { + margin-bottom: var(--gap); + } +} diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 95bda113..c28ce166 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -1,7 +1,4 @@ -import Button from '@geist-ui/core/dist/button' -import useToasts from '@geist-ui/core/dist/use-toasts' -import ButtonDropdown from '@geist-ui/core/dist/button-dropdown' - +import { Button, useToasts, ButtonDropdown } from '@geist-ui/core' import { useRouter } from 'next/router'; import { useCallback, useState } from 'react' import generateUUID from '@lib/generate-uuid'; diff --git a/client/components/new-post/password/index.tsx b/client/components/new-post/password/index.tsx index 8615a30e..9ab7a8fc 100644 --- a/client/components/new-post/password/index.tsx +++ b/client/components/new-post/password/index.tsx @@ -1,7 +1,5 @@ -import Input from "@geist-ui/core/dist/input" -import Modal from "@geist-ui/core/dist/modal" -import Note from "@geist-ui/core/dist/note" -import Spacer from "@geist-ui/core/dist/spacer" + +import { Modal, Note, Spacer, Input } from "@geist-ui/core" import { useState } from "react" type Props = { diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index 89d79689..e5b5f82f 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,9 +1,9 @@ import { ChangeEvent, memo, useCallback } from 'react' -import Text from '@geist-ui/core/dist/text' -import Input from '@geist-ui/core/dist/input' +import { Text } from '@geist-ui/core' import ShiftBy from '@components/shift-by' import styles from '../post.module.css' +import { Input } from '@geist-ui/core' const titlePlaceholders = [ "How to...", diff --git a/client/components/post-list/index.tsx b/client/components/post-list/index.tsx index 39af6d3c..915d7720 100644 --- a/client/components/post-list/index.tsx +++ b/client/components/post-list/index.tsx @@ -1,4 +1,4 @@ -import Text from "@geist-ui/core/dist/text" +import { Text } from "@geist-ui/core" import NextLink from "next/link" import Link from '../Link' diff --git a/client/components/post-list/list-item-skeleton.tsx b/client/components/post-list/list-item-skeleton.tsx index 150c4ee7..60610e3d 100644 --- a/client/components/post-list/list-item-skeleton.tsx +++ b/client/components/post-list/list-item-skeleton.tsx @@ -1,9 +1,7 @@ -import Card from "@geist-ui/core/dist/card"; -import Spacer from "@geist-ui/core/dist/spacer"; -import Grid from "@geist-ui/core/dist/grid"; -import Divider from "@geist-ui/core/dist/divider"; + import Skeleton from "react-loading-skeleton"; +import { Card, Divider, Grid, Spacer } from "@geist-ui/core"; const ListItemSkeleton = () => ( diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index 36b2616f..72082b7a 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -1,11 +1,3 @@ -import Card from "@geist-ui/core/dist/card" -import Spacer from "@geist-ui/core/dist/spacer" -import Grid from "@geist-ui/core/dist/grid" -import Divider from "@geist-ui/core/dist/divider" -import Link from "@geist-ui/core/dist/link" -import Text from "@geist-ui/core/dist/text" -import Input from "@geist-ui/core/dist/input" -import Tooltip from "@geist-ui/core/dist/tooltip" import NextLink from "next/link" import { useEffect, useMemo, useState } from "react" @@ -13,6 +5,7 @@ import timeAgo from "@lib/time-ago" import ShiftBy from "../shift-by" import VisibilityBadge from "../visibility-badge" import getPostPath from "@lib/get-post-path" +import { Input, Link, Text, Card, Spacer, Grid, Tooltip, Divider } from "@geist-ui/core" const FilenameInput = ({ title }: { title: string }) => { +const PostPage = ({ post }: Props) => { const download = async () => { const downloadZip = (await import("client-zip")).downloadZip const blob = await downloadZip(post.files.map((file: any) => { @@ -39,9 +38,9 @@ const PostPage = ({ post, changeTheme, theme }: Props) => { /> -
+
- + {/* {!isLoading && } */}
diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index 02170b66..3ad4be6d 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -1,28 +1,55 @@ +import useTheme from "@lib/hooks/use-theme" import { memo, useEffect, useState } from "react" -import ReactMarkdownPreview from "./react-markdown-preview" type Props = { - content?: string height?: number | string + fileId?: string + content?: string + title?: string // file extensions we can highlight - type?: string } -const MarkdownPreview = ({ content = '', height = 500, type = 'markdown' }: Props) => { - const [contentToRender, setContent] = useState(content) +const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { + const [preview, setPreview] = useState(content || "") + const [isLoading, setIsLoading] = useState(true) + const { theme } = useTheme() useEffect(() => { - // 'm' so it doesn't flash code when you change the type to md - const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] - if (!renderAsMarkdown.includes(type)) { - setContent(`~~~${type} -${content} -~~~ -`) - } else { - setContent(content) + async function fetchPost() { + if (fileId) { + const resp = await fetch(`/api/markdown/${fileId}`, { + method: "GET", + }) + if (resp.ok) { + const res = await resp.text() + setPreview(res) + setIsLoading(false) + } + } else { + const resp = await fetch(`/api/render-markdown`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title, + content, + }), + }) + if (resp.ok) { + const res = await resp.text() + setPreview(res) + setIsLoading(false) + } + } } - }, [type, content]) - return () + fetchPost() + }, [content, fileId, title]) + return (<> + {isLoading ?
Loading...
:
} + ) + } export default memo(MarkdownPreview) diff --git a/client/components/visibility-badge/index.tsx b/client/components/visibility-badge/index.tsx index e5344502..d907fda3 100644 --- a/client/components/visibility-badge/index.tsx +++ b/client/components/visibility-badge/index.tsx @@ -1,4 +1,4 @@ -import Badge from "@geist-ui/core/dist/badge"; +import { Badge } from "@geist-ui/core" import type { PostVisibility } from "@lib/types" type Props = { diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index 32884247..e6da5bd3 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -1,9 +1,14 @@ import Cookies from "js-cookie"; import { useEffect, useState } from "react"; +import useSharedState from "./use-shared-state"; const useSignedIn = () => { - const [signedIn, setSignedIn] = useState(typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); + const [signedIn, setSignedIn] = useSharedState('signedIn', typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); const token = Cookies.get("drift-token") + const signin = (token: string) => { + setSignedIn(true); + Cookies.set("drift-token", token); + } useEffect(() => { if (token) { @@ -11,9 +16,9 @@ const useSignedIn = () => { } else { setSignedIn(false); } - }, [token]); + }, [setSignedIn, token]); - return { signedIn, token }; + return { signedIn, signin, token }; } export default useSignedIn; diff --git a/client/lib/hooks/use-theme.ts b/client/lib/hooks/use-theme.ts new file mode 100644 index 00000000..0a333320 --- /dev/null +++ b/client/lib/hooks/use-theme.ts @@ -0,0 +1,27 @@ +import { useCallback, useEffect } from "react" +import useSharedState from "./use-shared-state" + +const useTheme = () => { + const isClient = typeof window === "object" + const [themeType, setThemeType] = useSharedState('theme', 'light') + + useEffect(() => { + if (!isClient) return + const storedTheme = localStorage.getItem('drift-theme') + if (storedTheme) { + setThemeType(storedTheme) + } + }, [isClient, setThemeType]) + + const changeTheme = useCallback(() => { + setThemeType(last => { + const newTheme = last === 'dark' ? 'light' : 'dark' + localStorage.setItem('drift-theme', newTheme) + return newTheme + }) + }, [setThemeType]) + + return { theme: themeType, changeTheme } +} + +export default useTheme \ No newline at end of file diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 5f6f467d..b92569e4 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -1,4 +1,3 @@ -import Link from '@components/Link' import { marked } from 'marked' import Highlight, { defaultProps, Language } from 'prism-react-renderer' import { renderToStaticMarkup } from 'react-dom/server' diff --git a/client/package.json b/client/package.json index b17c82ca..332b25a7 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,9 @@ "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", + "marked": "^4.0.12", "next": "^12.1.1-canary.15", + "prism-react-renderer": "^1.3.1", "react": "17.0.2", "react-dom": "17.0.2", "react-dropzone": "^12.0.4", @@ -33,6 +35,7 @@ }, "devDependencies": { "@next/bundle-analyzer": "^12.1.0", + "@types/marked": "^4.0.3", "@types/node": "17.0.21", "@types/react": "17.0.39", "@types/react-dom": "^17.0.14", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index f29abeba..2a11ca68 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -1,46 +1,21 @@ import '@styles/globals.css' -import GeistProvider from '@geist-ui/core/dist/geist-provider' -import CssBaseline from '@geist-ui/core/dist/css-baseline' -import useTheme from '@geist-ui/core/dist/use-theme' - -import { useEffect, useMemo, useState } from 'react' import type { AppProps as NextAppProps } from "next/app"; -import useSharedState from '@lib/hooks/use-shared-state'; import 'react-loading-skeleton/dist/skeleton.css' import { SkeletonTheme } from 'react-loading-skeleton'; import Head from 'next/head'; -import type { ThemeProps } from '@lib/types'; -import Cookies from 'js-cookie'; +import useTheme from '@lib/hooks/use-theme'; +import { CssBaseline, GeistProvider } from '@geist-ui/core'; type AppProps

= { pageProps: P; } & Omit, "pageProps">; -function MyApp({ Component, pageProps }: AppProps) { - const [themeType, setThemeType] = useSharedState('theme', Cookies.get('drift-theme') || 'light') - - useEffect(() => { - const storedTheme = Cookies.get('drift-theme') - if (storedTheme) setThemeType(storedTheme) - // TODO: useReducer? - }, [setThemeType, themeType]) - - const changeTheme = () => { - const newTheme = themeType === 'dark' ? 'light' : 'dark' - localStorage.setItem('drift-theme', newTheme) - setThemeType(last => (last === 'dark' ? 'light' : 'dark')) - } - - const skeletonBaseColor = useMemo(() => { - if (themeType === 'dark') return '#333' - return '#eee' - }, [themeType]) - const skeletonHighlightColor = useMemo(() => { - if (themeType === 'dark') return '#555' - return '#ddd' - }, [themeType]) +function MyApp({ Component, pageProps }: AppProps) { + const { theme } = useTheme() + const skeletonBaseColor = 'var(--light-gray)' + const skeletonHighlightColor = 'var(--lighter-gray)' return ( <> @@ -58,10 +33,10 @@ function MyApp({ Component, pageProps }: AppProps) { Drift - + - + diff --git a/client/pages/_document.tsx b/client/pages/_document.tsx index c732c7a7..4f43e764 100644 --- a/client/pages/_document.tsx +++ b/client/pages/_document.tsx @@ -1,5 +1,5 @@ +import { CssBaseline } from '@geist-ui/core' import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document' -import CssBaseline from '@geist-ui/core/dist/css-baseline' class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { diff --git a/client/pages/api/markdown/[id].tsx b/client/pages/api/markdown/[id].ts similarity index 87% rename from client/pages/api/markdown/[id].tsx rename to client/pages/api/markdown/[id].ts index 359c33ec..14d34d98 100644 --- a/client/pages/api/markdown/[id].tsx +++ b/client/pages/api/markdown/[id].ts @@ -12,17 +12,16 @@ const renderMarkdown: NextApiHandler = async (req, res) => { } }) - const json = await file.json() const { content, title } = json - const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] + const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] const fileType = () => { const pathParts = title.split(".") const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" return language } const type = fileType() - let contentToRender: string = content; + let contentToRender: string = '\n' + content; if (!renderAsMarkdown.includes(type)) { contentToRender = `~~~${type} diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts new file mode 100644 index 00000000..a9e4162e --- /dev/null +++ b/client/pages/api/render-markdown.ts @@ -0,0 +1,30 @@ +import type { NextApiHandler } from "next"; + +import markdown from "@lib/render-markdown"; + +const renderMarkdown: NextApiHandler = async (req, res) => { + const { content, title } = req.body + const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = '\n' + (content || ''); + + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} +${content} +~~~` + } + + if (typeof contentToRender !== 'string') { + res.status(400).send('content must be a string') + return + } + res.status(200).write(markdown(contentToRender)) + res.end() +} + +export default renderMarkdown diff --git a/client/pages/index.tsx b/client/pages/index.tsx index 0c24fe59..5d965335 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -1,13 +1,10 @@ import styles from '@styles/Home.module.css' -import Page from '@geist-ui/core/dist/page' -import Spacer from '@geist-ui/core/dist/spacer' -import Text from '@geist-ui/core/dist/text' import Header from '@components/header' import Document from '@components/document' import Image from 'next/image' import ShiftBy from '@components/shift-by' import PageSeo from '@components/page-seo' -import { ThemeProps } from '@lib/types' +import { Page, Text, Spacer } from '@geist-ui/core' export function getStaticProps() { const introDoc = process.env.WELCOME_CONTENT @@ -19,19 +16,19 @@ export function getStaticProps() { } } -type Props = ThemeProps & { +type Props = { introContent: string } -const Home = ({ theme, changeTheme, introContent }: Props) => { +const Home = ({ introContent }: Props) => { return ( - + -

+
- +
diff --git a/client/pages/mine.tsx b/client/pages/mine.tsx index 6cb50e80..6e5eeedb 100644 --- a/client/pages/mine.tsx +++ b/client/pages/mine.tsx @@ -1,19 +1,19 @@ import styles from '@styles/Home.module.css' -import Page from '@geist-ui/core/dist/page' import Header from '@components/header' import MyPosts from '@components/my-posts' import cookie from "cookie"; import type { GetServerSideProps } from 'next'; -import type { ThemeProps } from '@lib/types'; +import { Post } from '@lib/types'; +import { Page } from '@geist-ui/core'; -const Home = ({ posts, error, theme, changeTheme }: ThemeProps & { posts: any; error: any; }) => { +const Home = ({ posts, error }: { posts: Post[]; error: any; }) => { return ( - + -
+
- + diff --git a/client/pages/new.tsx b/client/pages/new.tsx index 3eb521e2..14e4bce5 100644 --- a/client/pages/new.tsx +++ b/client/pages/new.tsx @@ -1,20 +1,19 @@ import styles from '@styles/Home.module.css' import NewPost from '@components/new-post' -import Page from '@geist-ui/core/dist/page' import Header from '@components/header' import PageSeo from '@components/page-seo' -import type { ThemeProps } from '@lib/types' +import { Page } from '@geist-ui/core' -const New = ({ theme, changeTheme }: ThemeProps) => { +const New = () => { return ( -
+
- + diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index d2eb31cf..3327ce24 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -1,14 +1,14 @@ import type { GetStaticPaths, GetStaticProps } from "next"; -import type { Post, ThemeProps } from "@lib/types"; +import type { Post } from "@lib/types"; import PostPage from "@components/post-page"; -export type PostProps = ThemeProps & { +export type PostProps = { post: Post } -const PostView = ({ post, theme, changeTheme }: PostProps) => { - return +const PostView = ({ post }: PostProps) => { + return } export const getStaticPaths: GetStaticPaths = async () => { diff --git a/client/pages/post/private/[id].tsx b/client/pages/post/private/[id].tsx index c93f7172..f3c7d1e1 100644 --- a/client/pages/post/private/[id].tsx +++ b/client/pages/post/private/[id].tsx @@ -1,28 +1,14 @@ import cookie from "cookie"; import type { GetServerSideProps } from "next"; -import { PostVisibility, ThemeProps } from "@lib/types"; +import { Post } from "@lib/types"; import PostPage from "@components/post-page"; -type File = { - id: string - title: string - content: string +export type PostProps = { + post: Post } -type Files = File[] - -export type PostProps = ThemeProps & { - post: { - id: string - title: string - description: string - visibility: PostVisibility - files: Files - } -} - -const Post = ({ post, theme, changeTheme }: PostProps) => { - return () +const Post = ({ post, }: PostProps) => { + return () } export const getServerSideProps: GetServerSideProps = async (context) => { diff --git a/client/pages/post/protected/[id].tsx b/client/pages/post/protected/[id].tsx index 787042bd..d34e9704 100644 --- a/client/pages/post/protected/[id].tsx +++ b/client/pages/post/protected/[id].tsx @@ -1,14 +1,13 @@ -import useToasts from "@geist-ui/core/dist/use-toasts"; -import Page from "@geist-ui/core/dist/page"; +import { Page, useToasts } from '@geist-ui/core'; -import type { Post, ThemeProps } from "@lib/types"; +import type { Post } from "@lib/types"; import PasswordModal from "@components/new-post/password"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Cookies from "js-cookie"; import PostPage from "@components/post-page"; -const Post = ({ theme, changeTheme }: ThemeProps) => { +const Post = () => { const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true); const [post, setPost] = useState() const router = useRouter() @@ -74,7 +73,7 @@ const Post = ({ theme, changeTheme }: ThemeProps) => { return } - return () + return () } export default Post diff --git a/client/pages/signin.tsx b/client/pages/signin.tsx index e36776d3..4e1ed7cb 100644 --- a/client/pages/signin.tsx +++ b/client/pages/signin.tsx @@ -1,17 +1,16 @@ -import Page from "@geist-ui/core/dist/page"; +import { Page } from '@geist-ui/core'; import PageSeo from "@components/page-seo"; import Auth from "@components/auth"; -import Header from "@components/header"; -import type { ThemeProps } from "@lib/types"; - -const SignIn = ({ theme, changeTheme }: ThemeProps) => ( +import Header from "@components/header/header"; +import styles from '@styles/Home.module.css' +const SignIn = () => ( -
+
- + diff --git a/client/pages/signup.tsx b/client/pages/signup.tsx index d2ac5100..383f4619 100644 --- a/client/pages/signup.tsx +++ b/client/pages/signup.tsx @@ -1,17 +1,17 @@ -import Page from "@geist-ui/core/dist/page"; +import { Page } from '@geist-ui/core'; import Auth from "@components/auth"; -import Header from "@components/header"; +import Header from "@components/header/header"; import PageSeo from '@components/page-seo'; -import type { ThemeProps } from "@lib/types"; +import styles from '@styles/Home.module.css' -const SignUp = ({ theme, changeTheme }: ThemeProps) => ( +const SignUp = () => ( -
+
- + diff --git a/client/styles/globals.css b/client/styles/globals.css index 2e93b838..63af768a 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -25,29 +25,7 @@ --transition: 0.1s ease-in-out; --transition-slow: 0.3s ease-in-out; - /* Dark Mode Colors */ - --bg: #131415; - --fg: #fafbfc; - --gray: #666; - --light-gray: #444; - --lighter-gray: #222; - --lightest-gray: #1a1a1a; - --article-color: #eaeaea; - --header-bg: rgba(19, 20, 21, 0.45); - --gray-alpha: rgba(255, 255, 255, 0.5); - --selection: rgba(255, 255, 255, 0.99); - - /* Forms */ - --input-height: 2.5rem; - --input-border: 1px solid var(--light-gray); - --input-border-focus: 1px solid var(--gray); - --input-border-error: 1px solid var(--red); - --input-bg: var(--bg); - --input-fg: var(--fg); - --input-placeholder-fg: var(--light-gray); - --input-bg-hover: var(--lightest-gray); - - /* Syntax Highlighting */ + --page-nav-height: 64px; --token: #999; --comment: #999; --keyword: #fff; @@ -56,17 +34,6 @@ } [data-theme="light"] { - --bg: #fff; - --fg: #000; - --gray: #888; - --light-gray: #dedede; - --lighter-gray: #f5f5f5; - --lightest-gray: #fafafa; - --article-color: #212121; - --header-bg: rgba(255, 255, 255, 0.8); - --gray-alpha: rgba(19, 20, 21, 0.5); - --selection: rgba(0, 0, 0, 0.99); - --token: #666; --comment: #999; --keyword: #000; @@ -81,11 +48,6 @@ ::selection { text-shadow: none; background: var(--selection); - color: var(--bg); -} - -html { - line-height: 1.5; } html, @@ -93,8 +55,6 @@ body { padding: 0; margin: 0; font-size: 15px; - background: var(--bg); - color: var(--fg); text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -113,44 +73,6 @@ li { font-size: 1.125rem; } -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: var(--font-sans); - font-weight: 600; - line-height: 1.75; -} - -h1 { - font-size: 2.5rem; - font-weight: 600; - line-height: 1.25; - letter-spacing: -0.89px; -} - -h2 { - font-size: 2rem; - letter-spacing: -0.69px; -} - -h3 { - font-size: 1.5rem; - letter-spacing: -0.47px; -} - -h4 { - font-size: 1.25rem; - letter-spacing: -0.33px; -} - -hr { - border: none; - border-bottom: 1px solid var(--light-gray); -} - blockquote { font-style: italic; margin: 0; @@ -158,52 +80,16 @@ blockquote { border-left: 3px solid var(--light-gray); } -button { - border: none; - padding: 0; - margin: 0; - line-height: inherit; - font-size: inherit; -} - -p a, a.reset { outline: none; - color: var(--fg); text-decoration: none; } -p a:hover, -p a:focus, -p a:active, -a.reset:hover, -a.reset:focus { - color: var(--gray); -} - pre, code { font-family: var(--font-mono); } -.clamp { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - overflow: hidden; -} - -.clamp-2 { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; -} - -.flex { - display: flex; -} - kbd { font-family: var(--font-sans); font-size: 1rem; @@ -213,18 +99,6 @@ kbd { border-radius: 5px; } -summary { - cursor: pointer; - outline: none; -} - -details { - border-radius: var(--radius); - background: var(--lightest-gray); - padding: 1rem; - border-radius: var(--radius); -} - @media print { :root { --bg: #fff; diff --git a/client/yarn.lock b/client/yarn.lock index 112e3aaa..69809e3a 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -197,6 +197,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/marked@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.3.tgz#2098f4a77adaba9ce881c9e0b6baf29116e5acc4" + integrity sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg== + "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" @@ -1804,6 +1809,11 @@ markdown-table@^3.0.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== +marked@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d" + integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== + mdast-util-definitions@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817" @@ -2559,6 +2569,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== + prismjs@^1.25.0, prismjs@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" From eaffebb53c364eb9467df71560e204489a6637a5 Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Tue, 22 Mar 2022 20:16:24 -0700 Subject: [PATCH 37/63] client: optimize fetching rendered markdown --- client/components/document/index.tsx | 5 +++-- client/components/new-post/index.tsx | 2 +- client/components/preview/index.tsx | 6 ++++-- client/pages/api/render-markdown.ts | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/client/components/document/index.tsx b/client/components/document/index.tsx index d8200c86..415e31fa 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -68,7 +68,8 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init const onTitleChange = useCallback((event: ChangeEvent) => setTitle ? setTitle(event.target.value) : null, [setTitle]) - const removeFile = useCallback(() => (remove?: () => void) => { + const removeFile = useCallback((remove?: () => void) => { + console.log(remove) if (remove) { if (content && content.trim().length > 0) { const confirmed = window.confirm("Are you sure you want to remove this file?") @@ -121,7 +122,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init width={"100%"} id={title} /> - {remove && editable &&
{tab === 'edit' && editable && } diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index c28ce166..9eb79c88 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -111,7 +111,7 @@ const Post = () => { }, [docs, title]) return ( -
+
<FileDropzone setDocs={uploadDocs} /> <DocumentList docs={docs} updateDocTitle={updateDocTitle} updateDocContent={updateDocContent} removeDoc={removeDoc} /> diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index 3ad4be6d..1dc24a2e 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -1,5 +1,6 @@ import useTheme from "@lib/hooks/use-theme" import { memo, useEffect, useState } from "react" +import styles from './preview.module.css' type Props = { height?: number | string @@ -24,7 +25,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { setPreview(res) setIsLoading(false) } - } else { + } else if (content) { const resp = await fetch(`/api/render-markdown`, { method: "POST", headers: { @@ -41,11 +42,12 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { setIsLoading(false) } } + setIsLoading(false) } fetchPost() }, [content, fileId, title]) return (<> - {isLoading ? <div>Loading...</div> : <div data-theme={theme} dangerouslySetInnerHTML={{ __html: preview }} style={{ + {isLoading ? <div>Loading...</div> : <div data-theme={theme} className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{ height }} />} </>) diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts index a9e4162e..34df3f23 100644 --- a/client/pages/api/render-markdown.ts +++ b/client/pages/api/render-markdown.ts @@ -11,12 +11,14 @@ const renderMarkdown: NextApiHandler = async (req, res) => { return language } const type = fileType() - let contentToRender: string = '\n' + (content || ''); + let contentToRender: string = (content || ''); if (!renderAsMarkdown.includes(type)) { contentToRender = `~~~${type} ${content} ~~~` + } else { + contentToRender = '\n' + content; } if (typeof contentToRender !== 'string') { From 30e32e33cfb87437e3a6d87f2a4a7a44b8fb8114 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 20:27:39 -0700 Subject: [PATCH 38/63] client: improve markdown styles --- client/components/preview/index.tsx | 2 +- client/components/preview/preview.module.css | 9 --------- client/lib/render-markdown.tsx | 3 ++- client/styles/markdown.css | 4 ---- 4 files changed, 3 insertions(+), 15 deletions(-) diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index 1dc24a2e..ac72f4fa 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -47,7 +47,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { fetchPost() }, [content, fileId, title]) return (<> - {isLoading ? <div>Loading...</div> : <div data-theme={theme} className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{ + {isLoading ? <div>Loading...</div> : <article data-theme={theme} className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{ height }} />} </>) diff --git a/client/components/preview/preview.module.css b/client/components/preview/preview.module.css index bfd181da..396ef98c 100644 --- a/client/components/preview/preview.module.css +++ b/client/components/preview/preview.module.css @@ -75,19 +75,10 @@ content: ""; } -.markdownPreview ul ul { - list-style: circle; -} - -.markdownPreview ul ul li { - margin-left: var(--gap); -} - .markdownPreview code { border-radius: 3px; white-space: pre-wrap; word-wrap: break-word; - padding: 2px 4px; } .markdownPreview code::before, diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index b92569e4..0147b03b 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -1,6 +1,7 @@ import { marked } from 'marked' -import Highlight, { defaultProps, Language } from 'prism-react-renderer' +import Highlight, { defaultProps, Language, } from 'prism-react-renderer' import { renderToStaticMarkup } from 'react-dom/server' + //@ts-ignore delete defaultProps.theme // import linkStyles from '../components/link/link.module.css' diff --git a/client/styles/markdown.css b/client/styles/markdown.css index fb3fc107..ede485e2 100644 --- a/client/styles/markdown.css +++ b/client/styles/markdown.css @@ -8,10 +8,6 @@ article > * + * { margin-top: 2em; } -article p { - color: var(--article-color); -} - article img { max-width: 100%; /* width: var(--main-content); */ From 19988e49ed4f2ea2fa588762de86fa3fc6bff04f Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 21:18:26 -0700 Subject: [PATCH 39/63] server: store and render markdown on server --- .../index.tsx | 3 +- .../document.module.css | 0 .../formatting-icons/index.tsx | 0 .../{document => edit-document}/index.tsx | 28 +--- client/components/new-post/index.tsx | 3 +- client/components/post-list/list-item.tsx | 2 +- client/components/post-page/index.tsx | 12 +- client/components/preview/html.tsx | 20 +++ .../components/view-document-list/index.tsx | 22 +++ .../view-document/document.module.css | 41 +++++ client/components/view-document/index.tsx | 133 +++++++++++++++++ client/lib/render-markdown.tsx | 1 + client/lib/types.d.ts | 3 +- client/pages/index.tsx | 2 +- server/config/config.json | 18 +++ .../20220323033259-postAddHtmlColumn.js | 27 ++++ server/package.json | 9 +- server/src/lib/models/File.ts | 3 + server/src/lib/render-markdown.tsx | 141 ++++++++++++++++++ server/src/routes/posts.ts | 22 ++- server/src/server.ts | 2 +- server/tsconfig.json | 1 + server/yarn.lock | 80 +++++++++- 23 files changed, 536 insertions(+), 37 deletions(-) rename client/components/{document-list => edit-document-list}/index.tsx (92%) rename client/components/{document => edit-document}/document.module.css (100%) rename client/components/{document => edit-document}/formatting-icons/index.tsx (100%) rename client/components/{document => edit-document}/index.tsx (83%) create mode 100644 client/components/preview/html.tsx create mode 100644 client/components/view-document-list/index.tsx create mode 100644 client/components/view-document/document.module.css create mode 100644 client/components/view-document/index.tsx create mode 100644 server/config/config.json create mode 100644 server/migrations/20220323033259-postAddHtmlColumn.js create mode 100644 server/src/lib/render-markdown.tsx diff --git a/client/components/document-list/index.tsx b/client/components/edit-document-list/index.tsx similarity index 92% rename from client/components/document-list/index.tsx rename to client/components/edit-document-list/index.tsx index 7ad88007..49144473 100644 --- a/client/components/document-list/index.tsx +++ b/client/components/edit-document-list/index.tsx @@ -1,5 +1,5 @@ import type { Document } from "@lib/types" -import DocumentComponent from "@components/document" +import DocumentComponent from "@components/edit-document" import { ChangeEvent, memo, useCallback } from "react" const DocumentList = ({ docs, removeDoc, updateDocContent, updateDocTitle }: { @@ -18,7 +18,6 @@ const DocumentList = ({ docs, removeDoc, updateDocContent, updateDocTitle }: { <DocumentComponent key={id} remove={removeDoc(i)} - editable={true} setContent={updateDocContent(i)} setTitle={updateDocTitle(i)} handleOnContentChange={handleOnChange(i)} diff --git a/client/components/document/document.module.css b/client/components/edit-document/document.module.css similarity index 100% rename from client/components/document/document.module.css rename to client/components/edit-document/document.module.css diff --git a/client/components/document/formatting-icons/index.tsx b/client/components/edit-document/formatting-icons/index.tsx similarity index 100% rename from client/components/document/formatting-icons/index.tsx rename to client/components/edit-document/formatting-icons/index.tsx diff --git a/client/components/document/index.tsx b/client/components/edit-document/index.tsx similarity index 83% rename from client/components/document/index.tsx rename to client/components/edit-document/index.tsx index 415e31fa..222a8730 100644 --- a/client/components/document/index.tsx +++ b/client/components/edit-document/index.tsx @@ -13,8 +13,6 @@ import Preview from "@components/preview" // import Link from "next/link" type Props = { - editable?: boolean - remove?: () => void title?: string content?: string setTitle?: (title: string) => void @@ -22,7 +20,7 @@ type Props = { handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void initialTab?: "edit" | "preview" skeleton?: boolean - id?: string + remove?: () => void } const DownloadButton = ({ rawLink }: { rawLink?: string }) => { @@ -53,7 +51,7 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => { } -const Document = ({ remove, editable, title, content, setTitle, setContent, initialTab = 'edit', skeleton, id, handleOnContentChange }: Props) => { +const Document = ({ remove, title, content, setTitle, setContent, initialTab = 'edit', skeleton, handleOnContentChange }: Props) => { const codeEditorRef = useRef<HTMLTextAreaElement>(null) const [tab, setTab] = useState(initialTab) // const height = editable ? "500px" : '100%' @@ -68,8 +66,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init const onTitleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null, [setTitle]) - const removeFile = useCallback((remove?: () => void) => { - console.log(remove) + const removeFile = useCallback((remove?: () => void) => () => { if (remove) { if (content && content.trim().length > 0) { const confirmed = window.confirm("Are you sure you want to remove this file?") @@ -82,19 +79,13 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init } }, [content]) - const rawLink = () => { - if (id) { - return `/file/raw/${id}` - } - } - if (skeleton) { return <> <Spacer height={1} /> <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}> <div className={styles.fileNameContainer}> <Skeleton width={275} height={36} /> - {editable && <Skeleton width={36} height={36} />} + {remove && <Skeleton width={36} height={36} />} </div> <div className={styles.descriptionContainer}> <div style={{ flexDirection: 'row', display: 'flex' }}><Skeleton width={125} height={36} /></div> @@ -118,17 +109,15 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init size={1.2} font={1.2} label="Filename" - disabled={!editable} width={"100%"} id={title} /> - {remove && editable && <Button type="abort" ghost icon={<Trash />} auto height={'36px'} width={'36px'} onClick={() => removeFile(remove)} />} + {remove && <Button type="abort" ghost icon={<Trash />} auto height={'36px'} width={'36px'} onClick={() => removeFile(remove)} />} </div> <div className={styles.descriptionContainer}> - {tab === 'edit' && editable && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />} - {rawLink && id && <DownloadButton rawLink={rawLink()} />} + {tab === 'edit' && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />} <Tabs onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}> - <Tabs.Item label={editable ? "Edit" : "Raw"} value="edit"> + <Tabs.Item label={"Edit"} value="edit"> {/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */} <div style={{ marginTop: 'var(--gap)', display: 'flex', flexDirection: 'column' }}> <Textarea @@ -137,7 +126,6 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init value={content} onChange={handleOnContentChange} width="100%" - disabled={!editable} // TODO: Textarea should grow to fill parent if height == 100% style={{ flex: 1, minHeight: 350 }} resize="vertical" @@ -146,7 +134,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </div> </Tabs.Item> <Tabs.Item label="Preview" value="preview"> - <Preview height={height} fileId={id} title={title} content={content} /> + <Preview height={height} title={title} content={content} /> </Tabs.Item> </Tabs> diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 9eb79c88..7b2adbe6 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -2,7 +2,6 @@ import { Button, useToasts, ButtonDropdown } from '@geist-ui/core' import { useRouter } from 'next/router'; import { useCallback, useState } from 'react' import generateUUID from '@lib/generate-uuid'; -import DocumentComponent from '../document'; import FileDropzone from './drag-and-drop'; import styles from './post.module.css' import Title from './title'; @@ -10,7 +9,7 @@ import Cookies from 'js-cookie' import type { PostVisibility, Document as DocumentType } from '@lib/types'; import PasswordModal from './password'; import getPostPath from '@lib/get-post-path'; -import DocumentList from '@components/document-list'; +import DocumentList from '@components/edit-document-list'; import { ChangeEvent } from 'react'; const Post = () => { diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index 72082b7a..d35bae10 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -9,7 +9,7 @@ import { Input, Link, Text, Card, Spacer, Grid, Tooltip, Divider } from "@geist- const FilenameInput = ({ title }: { title: string }) => <Input value={title} - marginTop="var(--gap-double)" + marginTop="var(--gap)" size={1.2} font={1.2} label="Filename" diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx index 4182277a..3fdbc6e4 100644 --- a/client/components/post-page/index.tsx +++ b/client/components/post-page/index.tsx @@ -1,11 +1,11 @@ import Header from "@components/header/header" import PageSeo from "@components/page-seo" import VisibilityBadge from "@components/visibility-badge" -import DocumentComponent from '@components/document' +import DocumentComponent from '@components/view-document' import styles from './post-page.module.css' import homeStyles from '@styles/Home.module.css' -import type { Post } from "@lib/types" +import type { File, Post } from "@lib/types" import { Page, Button, Text } from "@geist-ui/core" type Props = { @@ -51,14 +51,14 @@ const PostPage = ({ post }: Props) => { Download as ZIP archive </Button> </div> - {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => ( + {post.files.map(({ id, content, html, title }: File) => ( <DocumentComponent key={id} - id={id} - content={content} title={title} - editable={false} initialTab={'preview'} + id={id} + html={html} + content={content} /> ))} </Page.Content> diff --git a/client/components/preview/html.tsx b/client/components/preview/html.tsx new file mode 100644 index 00000000..f681eecb --- /dev/null +++ b/client/components/preview/html.tsx @@ -0,0 +1,20 @@ +import useTheme from "@lib/hooks/use-theme" +import { memo, useEffect, useState } from "react" +import styles from './preview.module.css' + +type Props = { + height?: number | string + html: string + // file extensions we can highlight +} + +const HtmlPreview = ({ height = 500, html }: Props) => { + const { theme } = useTheme() + return (<article + data-theme={theme} + className={styles.markdownPreview} + dangerouslySetInnerHTML={{ __html: html }} + style={{ height }} />) +} + +export default HtmlPreview diff --git a/client/components/view-document-list/index.tsx b/client/components/view-document-list/index.tsx new file mode 100644 index 00000000..c369a893 --- /dev/null +++ b/client/components/view-document-list/index.tsx @@ -0,0 +1,22 @@ +import type { Document } from "@lib/types" +import DocumentComponent from "@components/edit-document" +import { ChangeEvent, memo, useCallback } from "react" + +const DocumentList = ({ docs }: { + docs: Document[], +}) => { + return (<>{ + docs.map(({ content, id, title }) => { + return ( + <DocumentComponent + key={id} + content={content} + title={title} + /> + ) + }) + } + </>) +} + +export default memo(DocumentList) diff --git a/client/components/view-document/document.module.css b/client/components/view-document/document.module.css new file mode 100644 index 00000000..135f89c8 --- /dev/null +++ b/client/components/view-document/document.module.css @@ -0,0 +1,41 @@ +.input { + background: #efefef; +} + +.descriptionContainer { + display: flex; + flex-direction: column; + min-height: 400px; + overflow: auto; +} + +.fileNameContainer { + display: flex; + justify-content: space-between; + align-items: center; + height: 36px; +} + +.fileNameContainer { + display: flex; + align-content: center; +} + +.fileNameContainer > div { + /* Override geist-ui styling */ + margin: 0 !important; +} + +.textarea { + height: 100%; +} + +.actionWrapper { + position: relative; + z-index: 1; +} + +.actionWrapper .actions { + position: absolute; + right: 0; +} diff --git a/client/components/view-document/index.tsx b/client/components/view-document/index.tsx new file mode 100644 index 00000000..8b2503ce --- /dev/null +++ b/client/components/view-document/index.tsx @@ -0,0 +1,133 @@ + + +import { memo, useRef, useState } from "react" +import styles from './document.module.css' +import Download from '@geist-ui/icons/download' +import ExternalLink from '@geist-ui/icons/externalLink' +import Skeleton from "react-loading-skeleton" + +import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } from "@geist-ui/core" +import Preview from "@components/preview" +import HtmlPreview from "@components/preview/html" + +// import Link from "next/link" +type Props = { + title: string + html: string + initialTab?: "edit" | "preview" + skeleton?: boolean + id: string + content: string +} + +const DownloadButton = ({ rawLink }: { rawLink?: string }) => { + return (<div className={styles.actionWrapper}> + <ButtonGroup className={styles.actions}> + <Tooltip text="Download"> + <a href={`${rawLink}?download=true`} target="_blank" rel="noopener noreferrer"> + <Button + scale={2 / 3} px={0.6} + icon={<Download />} + auto + aria-label="Download" + /> + </a> + </Tooltip> + <Tooltip text="Open raw in new tab"> + <a href={rawLink} target="_blank" rel="noopener noreferrer"> + <Button + scale={2 / 3} px={0.6} + icon={<ExternalLink />} + auto + aria-label="Open raw file in new tab" + /> + </a> + </Tooltip> + </ButtonGroup> + </div>) +} + + +const Document = ({ content, title, html, initialTab = 'edit', skeleton, id }: Props) => { + const codeEditorRef = useRef<HTMLTextAreaElement>(null) + const [tab, setTab] = useState(initialTab) + // const height = editable ? "500px" : '100%' + const height = "100%"; + + const handleTabChange = (newTab: string) => { + if (newTab === 'edit') { + codeEditorRef.current?.focus() + } + setTab(newTab as 'edit' | 'preview') + } + + const rawLink = () => { + if (id) { + return `/file/raw/${id}` + } + } + + if (skeleton) { + return <> + <Spacer height={1} /> + <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}> + <div className={styles.fileNameContainer}> + <Skeleton width={275} height={36} /> + </div> + <div className={styles.descriptionContainer}> + <div style={{ flexDirection: 'row', display: 'flex' }}><Skeleton width={125} height={36} /></div> + <Skeleton width={'100%'} height={350} /> + </div > + </Card> + </> + } + + + return ( + <> + <Spacer height={1} /> + <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}> + <div className={styles.fileNameContainer}> + <Input + value={title} + readOnly + marginTop="var(--gap-double)" + size={1.2} + font={1.2} + label="Filename" + width={"100%"} + id={title} + /> + </div> + <div className={styles.descriptionContainer}> + <DownloadButton rawLink={rawLink()} /> + <Tabs onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}> + <Tabs.Item label={"Raw"} value="edit"> + {/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */} + <div style={{ marginTop: 'var(--gap)', display: 'flex', flexDirection: 'column' }}> + <Textarea + readOnly + ref={codeEditorRef} + value={content} + width="100%" + // TODO: Textarea should grow to fill parent if height == 100% + style={{ flex: 1, minHeight: 350 }} + resize="vertical" + className={styles.textarea} + /> + </div> + </Tabs.Item> + <Tabs.Item label="Preview" value="preview"> + <HtmlPreview height={height} html={html} /> + </Tabs.Item> + </Tabs> + + </div > + </Card > + <Spacer height={1} /> + </> + ) +} + + +export default memo(Document) \ No newline at end of file diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 0147b03b..820df8d5 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -116,6 +116,7 @@ const Code = ({ code, language, highlight, title, ...props }: { key={i} {...getLineProps({ line, key: i })} style={ + //@ts-ignore highlightedLines.includes((i + 1).toString()) ? { background: 'var(--highlight)', diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts index a6cbd13c..2a359f8c 100644 --- a/client/lib/types.d.ts +++ b/client/lib/types.d.ts @@ -11,10 +11,11 @@ export type Document = { id: string } -type File = { +export type File = { id: string title: string content: string + html: string } type Files = File[] diff --git a/client/pages/index.tsx b/client/pages/index.tsx index 5d965335..db93943b 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -1,6 +1,6 @@ import styles from '@styles/Home.module.css' import Header from '@components/header' -import Document from '@components/document' +import Document from '@components/edit-document' import Image from 'next/image' import ShiftBy from '@components/shift-by' import PageSeo from '@components/page-seo' diff --git a/server/config/config.json b/server/config/config.json new file mode 100644 index 00000000..07f91292 --- /dev/null +++ b/server/config/config.json @@ -0,0 +1,18 @@ +{ + "production": { + "database": "../drift.sqlite", + "host": "127.0.0.1", + "port": "3306", + "user": "root", + "password": "root", + "dialect": "sqlite" + }, + "development": { + "database": "../drift.sqlite", + "host": "127.0.0.1", + "port": "3306", + "user": "root", + "password": "root", + "dialect": "sqlite" + } +} diff --git a/server/migrations/20220323033259-postAddHtmlColumn.js b/server/migrations/20220323033259-postAddHtmlColumn.js new file mode 100644 index 00000000..3b0dce54 --- /dev/null +++ b/server/migrations/20220323033259-postAddHtmlColumn.js @@ -0,0 +1,27 @@ +const { DataTypes } = require("sequelize"); + +async function up(qi) { + try { + await qi.addColumn("Posts", "html", { + allowNull: true, + type: DataTypes.STRING, + }); + } catch (e) { + console.error(e); + throw e; + } +} + +async function down(qi) { + try { + await qi.removeColumn("Posts", "html"); + } catch (e) { + console.error(e); + throw e; + } +} + +module.exports = { + up, + down, +}; diff --git a/server/package.json b/server/package.json index 1a78e7cd..2af531dd 100644 --- a/server/package.json +++ b/server/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "ts-node index.ts", "dev": "nodemon index.ts", - "build": "tsc -p ." + "build": "tsc -p .", + "migrate": "sequelize db:migrate" }, "author": "", "license": "ISC", @@ -18,7 +19,11 @@ "express": "^4.16.2", "express-jwt": "^6.1.1", "jsonwebtoken": "^8.5.1", + "marked": "^4.0.12", "nodemon": "^2.0.15", + "prism-react-renderer": "^1.3.1", + "react": "^17.0.2", + "react-dom": "^17.0.2", "reflect-metadata": "^0.1.10", "sequelize": "^6.17.0", "sequelize-typescript": "^2.1.3", @@ -31,7 +36,9 @@ "@types/express": "^4.0.39", "@types/express-jwt": "^6.0.4", "@types/jsonwebtoken": "^8.5.8", + "@types/marked": "^4.0.3", "@types/node": "^17.0.21", + "@types/react-dom": "^17.0.14", "ts-node": "^10.6.0", "tslint": "^6.1.3", "typescript": "^4.6.2" diff --git a/server/src/lib/models/File.ts b/server/src/lib/models/File.ts index 35ec6a70..f52b4ad3 100644 --- a/server/src/lib/models/File.ts +++ b/server/src/lib/models/File.ts @@ -35,6 +35,9 @@ export class File extends Model { @Column sha!: string; + @Column + html!: string; + @ForeignKey(() => User) @BelongsTo(() => User, 'userId') user!: User; diff --git a/server/src/lib/render-markdown.tsx b/server/src/lib/render-markdown.tsx new file mode 100644 index 00000000..820df8d5 --- /dev/null +++ b/server/src/lib/render-markdown.tsx @@ -0,0 +1,141 @@ +import { marked } from 'marked' +import Highlight, { defaultProps, Language, } from 'prism-react-renderer' +import { renderToStaticMarkup } from 'react-dom/server' + +//@ts-ignore +delete defaultProps.theme +// import linkStyles from '../components/link/link.module.css' + +const renderer = new marked.Renderer() + +renderer.heading = (text, level, _, slugger) => { + const id = slugger.slug(text) + const Component = `h${level}` + + return renderToStaticMarkup( + //@ts-ignore + <Component> + <a href={`#${id}`} id={id} style={{ color: "inherit" }} > + {text} + </a> + </Component> + ) +} + +renderer.link = (href, _, text) => { + const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') + if (isHrefLocal) { + return renderToStaticMarkup( + <a href={href || ''}> + {text} + </a> + ) + } + return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` +} + +renderer.image = function (href, _, text) { + return `<Image loading="lazy" src="${href}" alt="${text}" layout="fill" />` +} + +renderer.checkbox = () => '' +renderer.listitem = (text, task, checked) => { + if (task) { + return `<li class="reset"><span class="check">​<input type="checkbox" disabled ${checked ? 'checked' : '' + } /></span><span>${text}</span></li>` + } + + return `<li>${text}</li>` +} + +renderer.code = (code: string, language: string) => { + return renderToStaticMarkup( + <pre> + {/* {title && <code>{title} </code>} */} + {/* {language && title && <code style={{}}> {language} </code>} */} + <Code + language={language} + // title={title} + code={code} + // highlight={highlight} + /> + </pre> + ) +} + +marked.setOptions({ + gfm: true, + breaks: true, + headerIds: true, + renderer, +}) + +const markdown = (markdown: string) => marked(markdown) + +export default markdown + +const Code = ({ code, language, highlight, title, ...props }: { + code: string, + language: string, + highlight?: string, + title?: string, +}) => { + if (!language) + return ( + <> + <code {...props} dangerouslySetInnerHTML={{ __html: code } + } /> + </> + ) + + const highlightedLines = highlight + //@ts-ignore + ? highlight.split(',').reduce((lines, h) => { + if (h.includes('-')) { + // Expand ranges like 3-5 into [3,4,5] + const [start, end] = h.split('-').map(Number) + const x = Array(end - start + 1) + .fill(undefined) + .map((_, i) => i + start) + return [...lines, ...x] + } + + return [...lines, Number(h)] + }, []) + : '' + + // https://mdxjs.com/guides/syntax-harkedighlighting#all-together + return ( + <> + <Highlight {...defaultProps} code={code.trim()} language={language as Language} > + {({ className, style, tokens, getLineProps, getTokenProps }) => ( + <code className={className} style={{ ...style }}> + { + tokens.map((line, i) => ( + <div + key={i} + {...getLineProps({ line, key: i })} + style={ + //@ts-ignore + highlightedLines.includes((i + 1).toString()) + ? { + background: 'var(--highlight)', + margin: '0 -1rem', + padding: '0 1rem', + } + : undefined + } + > + { + line.map((token, key) => ( + <span key={key} {...getTokenProps({ token, key })} /> + )) + } + </div> + ))} + </code> + )} + </Highlight> + </> + ) +} diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index 23089597..66c39ce4 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -6,6 +6,7 @@ import jwt, { UserJwtRequest } from '../lib/middleware/jwt'; import * as crypto from "crypto"; import { User } from '../lib/models/User'; import secretKey from '../lib/middleware/secret-key'; +import markdown from '../lib/render-markdown'; export const posts = Router() @@ -45,10 +46,29 @@ posts.post('/create', jwt, async (req, res, next) => { await newPost.save() await newPost.$add('users', req.body.userId); const newFiles = await Promise.all(req.body.files.map(async (file) => { + const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] + const fileType = () => { + const pathParts = file.title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = (file.content || ''); + + if (!renderAsMarkdown.includes(type)) { + contentToRender = + `~~~${type} +${file.content} +~~~` + } else { + contentToRender = '\n' + file.content; + } + const html = markdown(contentToRender) const newFile = new File({ title: file.title, content: file.content, sha: crypto.createHash('sha256').update(file.content).digest('hex').toString(), + html }) await newFile.$set("user", req.body.userId); @@ -118,7 +138,7 @@ posts.get("/:id", async (req, res, next) => { { model: File, as: "files", - attributes: ["id", "title", "content", "sha", "createdAt", "updatedAt"], + attributes: ["id", "title", "content", "sha", "createdAt", "updatedAt", "html"], }, { model: User, diff --git a/server/src/server.ts b/server/src/server.ts index b3670024..5cf5bf97 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,7 +4,7 @@ import config from './lib/config'; import { sequelize } from './lib/sequelize'; (async () => { - await sequelize.sync(); + await sequelize.sync({ alter: true }); createServer(app) .listen( config.port, diff --git a/server/tsconfig.json b/server/tsconfig.json index 765d8a8c..eb71dbf0 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -4,6 +4,7 @@ "target": "es6", "module": "commonjs", "moduleResolution": "node", + "jsx": "react-jsxdev", "experimentalDecorators": true, "emitDecoratorMetadata": true, "noUnusedLocals": true, diff --git a/server/yarn.lock b/server/yarn.lock index 8adac559..300c8a41 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -157,6 +157,11 @@ dependencies: "@types/node" "*" +"@types/marked@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.3.tgz#2098f4a77adaba9ce881c9e0b6baf29116e5acc4" + integrity sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg== + "@types/mime@^1": version "1.3.2" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" @@ -172,6 +177,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== +"@types/prop-types@*": + version "15.7.4" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" + integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ== + "@types/qs@*": version "6.9.7" resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" @@ -182,6 +192,27 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== +"@types/react-dom@^17.0.14": + version "17.0.14" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.14.tgz#c8f917156b652ddf807711f5becbd2ab018dea9f" + integrity sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ== + dependencies: + "@types/react" "*" + +"@types/react@*": + version "17.0.41" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.41.tgz#6e179590d276394de1e357b3f89d05d7d3da8b85" + integrity sha512-chYZ9ogWUodyC7VUTRBfblysKLjnohhFY9bGLwvnUFFy48+vB9DikmB3lW0qTFmBcKSzmdglcvkHK71IioOlDA== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + +"@types/scheduler@*": + version "0.16.2" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" + integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + "@types/serve-static@*": version "1.13.10" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" @@ -671,6 +702,11 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +csstype@^3.0.2: + version "3.0.11" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" + integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1377,7 +1413,7 @@ jake@^10.6.1: filelist "^1.0.1" minimatch "^3.0.4" -js-tokens@^4.0.0: +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== @@ -1526,6 +1562,13 @@ lodash@^4.17.20, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +loose-envify@^1.1.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" @@ -1562,6 +1605,11 @@ map-age-cleaner@^0.1.3: dependencies: p-defer "^1.0.0" +marked@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d" + integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== + md5@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" @@ -1913,6 +1961,11 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" @@ -1991,6 +2044,23 @@ rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-dom@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" + integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + scheduler "^0.20.2" + +react@^17.0.2: + version "17.0.2" + resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" + integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + readable-stream@^2.0.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" @@ -2108,6 +2178,14 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +scheduler@^0.20.2: + version "0.20.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" + integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== + dependencies: + loose-envify "^1.1.0" + object-assign "^4.1.1" + semver-diff@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/semver-diff/-/semver-diff-3.1.1.tgz#05f77ce59f325e00e2706afd67bb506ddb1ca32b" From 118c06f2720c3f75496cce69a8d4341ec30bfe1a Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 21:19:55 -0700 Subject: [PATCH 40/63] client: fix useless prop --- client/pages/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/client/pages/index.tsx b/client/pages/index.tsx index db93943b..e885c61d 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -35,7 +35,6 @@ const Home = ({ introContent }: Props) => { <Text style={{ display: 'inline' }} h1> Welcome to Drift</Text> </div> <Document - editable={false} content={introContent} title={`Welcome to Drift.md`} initialTab={`preview`} From 0a724f6f97ae00a5195373f3f789184fce94375d Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 21:24:11 -0700 Subject: [PATCH 41/63] client: improved code styles in markdown --- client/styles/globals.css | 2 +- client/styles/markdown.css | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/client/styles/globals.css b/client/styles/globals.css index 63af768a..496cb987 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -87,7 +87,7 @@ a.reset { pre, code { - font-family: var(--font-mono); + font-family: var(--font-mono) !important; } kbd { diff --git a/client/styles/markdown.css b/client/styles/markdown.css index ede485e2..dc4fdabc 100644 --- a/client/styles/markdown.css +++ b/client/styles/markdown.css @@ -95,17 +95,9 @@ input[type="checkbox"]:focus { } article *:not(pre) > code { - font-weight: 600; + font-weight: 500; font-family: var(--font-sans); font-size: 1rem; - padding: 0 3px; -} - -article *:not(pre) > code::before, -article *:not(pre) > code::after { - content: "\`"; - color: var(--gray); - user-select: none; } article pre { From 26a9639589536a4b82efdbe0ffce11bad6836b3a Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 21:37:27 -0700 Subject: [PATCH 42/63] client: nprogress --- client/components/edit-document/index.tsx | 3 +-- client/package.json | 4 ++++ client/pages/_app.tsx | 17 ++++++++++++++ client/styles/markdown.css | 6 ++++- client/yarn.lock | 27 +++++++++++++++++++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/client/components/edit-document/index.tsx b/client/components/edit-document/index.tsx index 222a8730..4d6ab562 100644 --- a/client/components/edit-document/index.tsx +++ b/client/components/edit-document/index.tsx @@ -66,7 +66,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' const onTitleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null, [setTitle]) - const removeFile = useCallback((remove?: () => void) => () => { + const removeFile = useCallback((remove?: () => void) => { if (remove) { if (content && content.trim().length > 0) { const confirmed = window.confirm("Are you sure you want to remove this file?") @@ -95,7 +95,6 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' </> } - return ( <> <Spacer height={1} /> diff --git a/client/package.json b/client/package.json index 332b25a7..2c39cfe9 100644 --- a/client/package.json +++ b/client/package.json @@ -18,8 +18,10 @@ "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", + "lodash.debounce": "^4.0.8", "marked": "^4.0.12", "next": "^12.1.1-canary.15", + "nprogress": "^0.2.0", "prism-react-renderer": "^1.3.1", "react": "17.0.2", "react-dom": "17.0.2", @@ -35,8 +37,10 @@ }, "devDependencies": { "@next/bundle-analyzer": "^12.1.0", + "@types/lodash.debounce": "^4.0.6", "@types/marked": "^4.0.3", "@types/node": "17.0.21", + "@types/nprogress": "^0.2.0", "@types/react": "17.0.39", "@types/react-dom": "^17.0.14", "@types/react-syntax-highlighter": "^13.5.2", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 2a11ca68..ebbb9535 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -7,6 +7,23 @@ import Head from 'next/head'; import useTheme from '@lib/hooks/use-theme'; import { CssBaseline, GeistProvider } from '@geist-ui/core'; +import nprogress from 'nprogress' +import debounce from 'lodash.debounce' +import Router from 'next/router'; + +// Only show nprogress after 500ms (slow loading) +const start = debounce(nprogress.start, 500) +Router.events.on('routeChangeStart', start) +Router.events.on('routeChangeComplete', () => { + start.cancel() + nprogress.done() + window.scrollTo(0, 0) +}) +Router.events.on('routeChangeError', () => { + start.cancel() + nprogress.done() +}) + type AppProps<P = any> = { pageProps: P; } & Omit<NextAppProps<P>, "pageProps">; diff --git a/client/styles/markdown.css b/client/styles/markdown.css index dc4fdabc..ec6c58a6 100644 --- a/client/styles/markdown.css +++ b/client/styles/markdown.css @@ -100,6 +100,11 @@ article *:not(pre) > code { font-size: 1rem; } +article li > p { + font-family: var(--font-mono); + display: inline-block; +} + article pre { overflow-x: auto; background: var(--lightest-gray); @@ -120,7 +125,6 @@ article pre { opacity: 0; content: "#"; margin-left: var(--gap-half); - color: var(--gray); } .header-link:hover::after { diff --git a/client/yarn.lock b/client/yarn.lock index 69809e3a..b69056c8 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -197,6 +197,18 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/lodash.debounce@^4.0.6": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60" + integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.180" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.180.tgz#4ab7c9ddfc92ec4a887886483bc14c79fb380670" + integrity sha512-XOKXa1KIxtNXgASAnwj7cnttJxS4fksBRywK/9LzRV5YxrF80BXZIGeQSuoESQ/VkUj30Ae0+YcuHc15wJCB2g== + "@types/marked@^4.0.3": version "4.0.3" resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.3.tgz#2098f4a77adaba9ce881c9e0b6baf29116e5acc4" @@ -224,6 +236,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.21.tgz#864b987c0c68d07b4345845c3e63b75edd143644" integrity sha512-DBZCJbhII3r90XbQxI8Y9IjjiiOGlZ0Hr32omXIZvwwZ7p4DMMXGrKXVyPfuoBOri9XNtL0UK69jYIBIsRX3QQ== +"@types/nprogress@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@types/nprogress/-/nprogress-0.2.0.tgz#86c593682d4199212a0509cc3c4d562bbbd6e45f" + integrity sha512-1cYJrqq9GezNFPsWTZpFut/d4CjpZqA0vhqDUPFWYKF1oIyBz5qnoYMzR+0C/T96t3ebLAC1SSnwrVOm5/j74A== + "@types/parse5@^6.0.0": version "6.0.3" resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-6.0.3.tgz#705bb349e789efa06f43f128cef51240753424cb" @@ -1759,6 +1776,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -2325,6 +2347,11 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== +nprogress@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" + integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= + object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" From ef16bfc5653dea36cdd7c0c82e36172ce255985d Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 21:57:11 -0700 Subject: [PATCH 43/63] client: better link hover symbol --- client/components/preview/preview.module.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/components/preview/preview.module.css b/client/components/preview/preview.module.css index 396ef98c..c503dd25 100644 --- a/client/components/preview/preview.module.css +++ b/client/components/preview/preview.module.css @@ -36,11 +36,11 @@ .markdownPreview h4 a:hover::after, .markdownPreview h5 a:hover::after, .markdownPreview h6 a:hover::after { - content: "🔗"; - filter: grayscale(100%); + content: "#"; font-size: 0.7em; margin-left: 0.25em; font-weight: normal; + filter: opacity(0.5); } .markdownPreview h1 { From c0c18e5b61d897422ec6c4fea1362b249892746a Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 22:05:26 -0700 Subject: [PATCH 44/63] client: improve makrdown handling of nested elements --- client/lib/render-markdown.tsx | 47 +++++++++++++++--------------- client/styles/markdown.css | 1 - server/src/lib/render-markdown.tsx | 47 +++++++++++++++--------------- 3 files changed, 48 insertions(+), 47 deletions(-) diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 820df8d5..e3c30843 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -8,31 +8,32 @@ delete defaultProps.theme const renderer = new marked.Renderer() -renderer.heading = (text, level, _, slugger) => { - const id = slugger.slug(text) - const Component = `h${level}` +// renderer.heading = (text, level, _, slugger) => { +// const id = slugger.slug(text) +// const Component = `h${level}` - return renderToStaticMarkup( - //@ts-ignore - <Component> - <a href={`#${id}`} id={id} style={{ color: "inherit" }} > - {text} - </a> - </Component> - ) -} +// return renderToStaticMarkup( +// //@ts-ignore +// <Component> +// <a href={`#${id}`} id={id} style={{ color: "inherit" }} > +// {text} +// </a> +// </Component> +// ) +// } -renderer.link = (href, _, text) => { - const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') - if (isHrefLocal) { - return renderToStaticMarkup( - <a href={href || ''}> - {text} - </a> - ) - } - return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` -} +// TODO: support elements inside link +// renderer.link = (href, _, text) => { +// const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') +// if (isHrefLocal) { +// return renderToStaticMarkup( +// <a href={href || ''}> +// {text} +// </a> +// ) +// } +// return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` +// } renderer.image = function (href, _, text) { return `<Image loading="lazy" src="${href}" alt="${text}" layout="fill" />` diff --git a/client/styles/markdown.css b/client/styles/markdown.css index ec6c58a6..0060c342 100644 --- a/client/styles/markdown.css +++ b/client/styles/markdown.css @@ -97,7 +97,6 @@ input[type="checkbox"]:focus { article *:not(pre) > code { font-weight: 500; font-family: var(--font-sans); - font-size: 1rem; } article li > p { diff --git a/server/src/lib/render-markdown.tsx b/server/src/lib/render-markdown.tsx index 820df8d5..e3c30843 100644 --- a/server/src/lib/render-markdown.tsx +++ b/server/src/lib/render-markdown.tsx @@ -8,31 +8,32 @@ delete defaultProps.theme const renderer = new marked.Renderer() -renderer.heading = (text, level, _, slugger) => { - const id = slugger.slug(text) - const Component = `h${level}` +// renderer.heading = (text, level, _, slugger) => { +// const id = slugger.slug(text) +// const Component = `h${level}` - return renderToStaticMarkup( - //@ts-ignore - <Component> - <a href={`#${id}`} id={id} style={{ color: "inherit" }} > - {text} - </a> - </Component> - ) -} +// return renderToStaticMarkup( +// //@ts-ignore +// <Component> +// <a href={`#${id}`} id={id} style={{ color: "inherit" }} > +// {text} +// </a> +// </Component> +// ) +// } -renderer.link = (href, _, text) => { - const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') - if (isHrefLocal) { - return renderToStaticMarkup( - <a href={href || ''}> - {text} - </a> - ) - } - return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` -} +// TODO: support elements inside link +// renderer.link = (href, _, text) => { +// const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') +// if (isHrefLocal) { +// return renderToStaticMarkup( +// <a href={href || ''}> +// {text} +// </a> +// ) +// } +// return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` +// } renderer.image = function (href, _, text) { return `<Image loading="lazy" src="${href}" alt="${text}" layout="fill" />` From f92d854336e63514f9a5bd798bf5fcfcb6ad7835 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 22:18:07 -0700 Subject: [PATCH 45/63] client/server: linkify headers and transform html entities in markdown --- client/lib/render-markdown.tsx | 67 +++++++++++++++++++++++------- server/src/lib/render-markdown.tsx | 67 +++++++++++++++++++++++------- 2 files changed, 106 insertions(+), 28 deletions(-) diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index e3c30843..7919c5c2 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -8,21 +8,56 @@ delete defaultProps.theme const renderer = new marked.Renderer() -// renderer.heading = (text, level, _, slugger) => { -// const id = slugger.slug(text) -// const Component = `h${level}` +const convertHtmlEntities = (str: string) => { + const quot = '"' + const apos = ''' + const amp = '&' + const nbsp = ' ' + const lt = '<' + const gt = '>' + const code = '<code>' + const endCode = '</code>' + const combinedRegex = new RegExp(`${code}|${endCode}|${quot}|${apos}|${amp}|${nbsp}|${lt}|${gt}`, 'g') -// return renderToStaticMarkup( -// //@ts-ignore -// <Component> -// <a href={`#${id}`} id={id} style={{ color: "inherit" }} > -// {text} -// </a> -// </Component> -// ) -// } + return str.replace(combinedRegex, (match) => { + switch (match) { + case quot: + return '"' + case apos: + return "'" + case amp: + return '&' + case nbsp: + return ' ' + case lt: + return '<' + case gt: + return '>' + case code: + case endCode: + return '`' + default: + return match + } + }) +} + +renderer.heading = (text, level, _, slugger) => { + const id = slugger.slug(text) + const Component = `h${level}` + + + + return renderToStaticMarkup( + //@ts-ignore + <Component> + <a href={`#${id}`} id={id} style={{ color: "inherit" }} > + {convertHtmlEntities(text)} + </a> + </Component> + ) +} -// TODO: support elements inside link // renderer.link = (href, _, text) => { // const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') // if (isHrefLocal) { @@ -32,9 +67,13 @@ const renderer = new marked.Renderer() // </a> // ) // } -// return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` + +// // dirty hack +// // if text contains elements, render as html +// return `<a href='${href}' target="_blank" rel="noopener noreferrer">${convertHtmlEntities(text)}</a>` // } + renderer.image = function (href, _, text) { return `<Image loading="lazy" src="${href}" alt="${text}" layout="fill" />` } diff --git a/server/src/lib/render-markdown.tsx b/server/src/lib/render-markdown.tsx index e3c30843..7919c5c2 100644 --- a/server/src/lib/render-markdown.tsx +++ b/server/src/lib/render-markdown.tsx @@ -8,21 +8,56 @@ delete defaultProps.theme const renderer = new marked.Renderer() -// renderer.heading = (text, level, _, slugger) => { -// const id = slugger.slug(text) -// const Component = `h${level}` +const convertHtmlEntities = (str: string) => { + const quot = '"' + const apos = ''' + const amp = '&' + const nbsp = ' ' + const lt = '<' + const gt = '>' + const code = '<code>' + const endCode = '</code>' + const combinedRegex = new RegExp(`${code}|${endCode}|${quot}|${apos}|${amp}|${nbsp}|${lt}|${gt}`, 'g') -// return renderToStaticMarkup( -// //@ts-ignore -// <Component> -// <a href={`#${id}`} id={id} style={{ color: "inherit" }} > -// {text} -// </a> -// </Component> -// ) -// } + return str.replace(combinedRegex, (match) => { + switch (match) { + case quot: + return '"' + case apos: + return "'" + case amp: + return '&' + case nbsp: + return ' ' + case lt: + return '<' + case gt: + return '>' + case code: + case endCode: + return '`' + default: + return match + } + }) +} + +renderer.heading = (text, level, _, slugger) => { + const id = slugger.slug(text) + const Component = `h${level}` + + + + return renderToStaticMarkup( + //@ts-ignore + <Component> + <a href={`#${id}`} id={id} style={{ color: "inherit" }} > + {convertHtmlEntities(text)} + </a> + </Component> + ) +} -// TODO: support elements inside link // renderer.link = (href, _, text) => { // const isHrefLocal = href?.startsWith('/') || href?.startsWith('#') // if (isHrefLocal) { @@ -32,9 +67,13 @@ const renderer = new marked.Renderer() // </a> // ) // } -// return `<a href="${href}" target="_blank" rel="noopener noreferrer">${text}</a>` + +// // dirty hack +// // if text contains elements, render as html +// return `<a href='${href}' target="_blank" rel="noopener noreferrer">${convertHtmlEntities(text)}</a>` // } + renderer.image = function (href, _, text) { return `<Image loading="lazy" src="${href}" alt="${text}" layout="fill" />` } From a139acc747039018533a88ed47338874c25a9498 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 23:16:34 -0700 Subject: [PATCH 46/63] client/server: render nested elements in headings --- client/lib/render-markdown.tsx | 39 ++---------------------------- server/src/lib/render-markdown.tsx | 39 ++---------------------------- 2 files changed, 4 insertions(+), 74 deletions(-) diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 7919c5c2..d57b5862 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -8,40 +8,6 @@ delete defaultProps.theme const renderer = new marked.Renderer() -const convertHtmlEntities = (str: string) => { - const quot = '"' - const apos = ''' - const amp = '&' - const nbsp = ' ' - const lt = '<' - const gt = '>' - const code = '<code>' - const endCode = '</code>' - const combinedRegex = new RegExp(`${code}|${endCode}|${quot}|${apos}|${amp}|${nbsp}|${lt}|${gt}`, 'g') - - return str.replace(combinedRegex, (match) => { - switch (match) { - case quot: - return '"' - case apos: - return "'" - case amp: - return '&' - case nbsp: - return ' ' - case lt: - return '<' - case gt: - return '>' - case code: - case endCode: - return '`' - default: - return match - } - }) -} - renderer.heading = (text, level, _, slugger) => { const id = slugger.slug(text) const Component = `h${level}` @@ -51,8 +17,7 @@ renderer.heading = (text, level, _, slugger) => { return renderToStaticMarkup( //@ts-ignore <Component> - <a href={`#${id}`} id={id} style={{ color: "inherit" }} > - {convertHtmlEntities(text)} + <a href={`#${id}`} id={id} style={{ color: "inherit" }} dangerouslySetInnerHTML={{ __html: (text) }} > </a> </Component> ) @@ -70,7 +35,7 @@ renderer.heading = (text, level, _, slugger) => { // // dirty hack // // if text contains elements, render as html -// return `<a href='${href}' target="_blank" rel="noopener noreferrer">${convertHtmlEntities(text)}</a>` +// return <a href={href || ""} target="_blank" rel="noopener noreferrer" dangerouslySetInnerHTML={{ __html: convertHtmlEntities(text) }} ></a> // } diff --git a/server/src/lib/render-markdown.tsx b/server/src/lib/render-markdown.tsx index 7919c5c2..d57b5862 100644 --- a/server/src/lib/render-markdown.tsx +++ b/server/src/lib/render-markdown.tsx @@ -8,40 +8,6 @@ delete defaultProps.theme const renderer = new marked.Renderer() -const convertHtmlEntities = (str: string) => { - const quot = '"' - const apos = ''' - const amp = '&' - const nbsp = ' ' - const lt = '<' - const gt = '>' - const code = '<code>' - const endCode = '</code>' - const combinedRegex = new RegExp(`${code}|${endCode}|${quot}|${apos}|${amp}|${nbsp}|${lt}|${gt}`, 'g') - - return str.replace(combinedRegex, (match) => { - switch (match) { - case quot: - return '"' - case apos: - return "'" - case amp: - return '&' - case nbsp: - return ' ' - case lt: - return '<' - case gt: - return '>' - case code: - case endCode: - return '`' - default: - return match - } - }) -} - renderer.heading = (text, level, _, slugger) => { const id = slugger.slug(text) const Component = `h${level}` @@ -51,8 +17,7 @@ renderer.heading = (text, level, _, slugger) => { return renderToStaticMarkup( //@ts-ignore <Component> - <a href={`#${id}`} id={id} style={{ color: "inherit" }} > - {convertHtmlEntities(text)} + <a href={`#${id}`} id={id} style={{ color: "inherit" }} dangerouslySetInnerHTML={{ __html: (text) }} > </a> </Component> ) @@ -70,7 +35,7 @@ renderer.heading = (text, level, _, slugger) => { // // dirty hack // // if text contains elements, render as html -// return `<a href='${href}' target="_blank" rel="noopener noreferrer">${convertHtmlEntities(text)}</a>` +// return <a href={href || ""} target="_blank" rel="noopener noreferrer" dangerouslySetInnerHTML={{ __html: convertHtmlEntities(text) }} ></a> // } From 534cd87dc924b3f0eaa4e3240938bfef1c1d7735 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 23:34:33 -0700 Subject: [PATCH 47/63] client: improve link accessibility in markdown --- client/components/preview/preview.module.css | 1 + client/lib/render-markdown.tsx | 2 -- client/styles/markdown.css | 2 -- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/client/components/preview/preview.module.css b/client/components/preview/preview.module.css index c503dd25..7287dd4f 100644 --- a/client/components/preview/preview.module.css +++ b/client/components/preview/preview.module.css @@ -79,6 +79,7 @@ border-radius: 3px; white-space: pre-wrap; word-wrap: break-word; + color: inherit !important; } .markdownPreview code::before, diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index d57b5862..4ddaebf4 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -12,8 +12,6 @@ renderer.heading = (text, level, _, slugger) => { const id = slugger.slug(text) const Component = `h${level}` - - return renderToStaticMarkup( //@ts-ignore <Component> diff --git a/client/styles/markdown.css b/client/styles/markdown.css index 0060c342..670d20d5 100644 --- a/client/styles/markdown.css +++ b/client/styles/markdown.css @@ -84,7 +84,6 @@ html[data-theme="light"] input[type="checkbox"]:checked { input[type="checkbox"]:focus { outline: none; - box-shadow: 0 0 0 2px var(--gray); border-color: var(--fg); } @@ -106,7 +105,6 @@ article li > p { article pre { overflow-x: auto; - background: var(--lightest-gray); border-radius: var(--inline-radius); line-height: 1.8; padding: 1rem; From 9bdff8f28f712061c3955e394c73fe10493e241f Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Wed, 23 Mar 2022 12:36:29 -0700 Subject: [PATCH 48/63] client/server: load rendered html after page load, fix raw exporting --- client/components/header/header.tsx | 144 +++++++++++++++------- client/components/post-page/index.tsx | 3 +- client/components/preview/html.tsx | 20 --- client/components/preview/index.tsx | 4 +- client/components/view-document/index.tsx | 8 +- client/lib/hooks/use-signed-in.ts | 10 +- client/pages/api/raw/[id].ts | 10 +- client/styles/Home.module.css | 2 +- server/src/lib/models/File.ts | 3 +- server/src/lib/models/Post.ts | 3 +- server/src/lib/models/PostAuthor.ts | 3 +- server/src/lib/models/User.ts | 3 +- server/src/routes/files.ts | 24 ++++ server/src/routes/posts.ts | 2 +- server/src/server.ts | 2 +- 15 files changed, 154 insertions(+), 87 deletions(-) delete mode 100644 client/components/preview/html.tsx diff --git a/client/components/header/header.tsx b/client/components/header/header.tsx index 4f07b548..0e89b226 100644 --- a/client/components/header/header.tsx +++ b/client/components/header/header.tsx @@ -16,7 +16,6 @@ import NewIcon from '@geist-ui/icons/plusCircle'; import YourIcon from '@geist-ui/icons/list' import MoonIcon from '@geist-ui/icons/moon'; import SunIcon from '@geist-ui/icons/sun'; -import type { ThemeProps } from "@lib/types"; import useTheme from "@lib/hooks/use-theme"; import { Button } from "@geist-ui/core"; @@ -36,7 +35,7 @@ const Header = () => { const [expanded, setExpanded] = useState<boolean>(false) const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) const isMobile = useMediaQuery('xs', { match: 'down' }) - const { signedIn: isSignedIn } = useSignedIn() + const { signedIn: isSignedIn, signout } = useSignedIn() const [pages, setPages] = useState<Tab[]>([]) const { changeTheme, theme } = useTheme() useEffect(() => { @@ -50,49 +49,7 @@ const Header = () => { }, [isMobile]) useEffect(() => { - const pageList: Tab[] = [ - { - name: "Home", - href: "/", - icon: <HomeIcon />, - condition: !isSignedIn, - value: "home" - }, - { - name: "New", - href: "/new", - icon: <NewIcon />, - condition: isSignedIn, - value: "new" - }, - { - name: "Yours", - href: "/mine", - icon: <YourIcon />, - condition: isSignedIn, - value: "mine" - }, - { - name: "Sign out", - href: "/signout", - icon: <SignOutIcon />, - condition: isSignedIn, - value: "signout" - }, - { - name: "Sign in", - href: "/signin", - icon: <SignInIcon />, - condition: !isSignedIn, - value: "signin" - }, - { - name: "Sign up", - href: "/signup", - icon: <SignUpIcon />, - condition: !isSignedIn, - value: "signup" - }, + const defaultPages: Tab[] = [ { name: isMobile ? "GitHub" : "", href: "https://github.com/maxleiter/drift", @@ -113,9 +70,104 @@ const Header = () => { } ] - setPages(pageList.filter(page => page.condition)) + if (isSignedIn) + setPages([ + { + name: 'home', + icon: <HomeIcon />, + value: 'home', + href: '/' + }, + { + name: 'yours', + icon: <YourIcon />, + value: 'yours', + href: '/mine' + }, + { + name: 'sign out', + icon: <SignOutIcon />, + value: 'signout', + onClick: signout + }, + ...defaultPages + ]) + else + setPages([ + { + name: 'home', + icon: <HomeIcon />, + value: 'home', + href: '/' + }, + { + name: 'Sign in', + icon: <SignInIcon />, + value: 'signin', + href: '/signin' + }, + { + name: 'Sign up', + icon: <SignUpIcon />, + value: 'signup', + href: '/signup' + }, + ...defaultPages + ]) + // TODO: investigate deps causing infinite loop + // eslint-disable-next-line react-hooks/exhaustive-deps }, [changeTheme, isMobile, isSignedIn, theme]) + // useEffect(() => { + // const pageList: Tab[] = [ + // { + // name: "Home", + // href: "/", + // icon: <HomeIcon />, + // condition: !isSignedIn, + // value: "home" + // }, + // { + // name: "New", + // href: "/new", + // icon: <NewIcon />, + // condition: isSignedIn, + // value: "new" + // }, + // { + // name: "Yours", + // href: "/mine", + // icon: <YourIcon />, + // condition: isSignedIn, + // value: "mine" + // }, + // { + // name: "Sign out", + // href: "/signout", + // icon: <SignOutIcon />, + // condition: isSignedIn, + // value: "signout" + // }, + // { + // name: "Sign in", + // href: "/signin", + // icon: <SignInIcon />, + // condition: !isSignedIn, + // value: "signin" + // }, + // { + // name: "Sign up", + // href: "/signup", + // icon: <SignUpIcon />, + // condition: !isSignedIn, + // value: "signup" + // }, + + // ] + + // setPages(pageList.filter(page => page.condition)) + // }, [changeTheme, isMobile, isSignedIn, theme]) + const onTabChange = useCallback((tab: string) => { if (typeof window === 'undefined') return diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx index 3fdbc6e4..8484beb3 100644 --- a/client/components/post-page/index.tsx +++ b/client/components/post-page/index.tsx @@ -51,13 +51,12 @@ const PostPage = ({ post }: Props) => { Download as ZIP archive </Button> </div> - {post.files.map(({ id, content, html, title }: File) => ( + {post.files.map(({ id, content, title }: File) => ( <DocumentComponent key={id} title={title} initialTab={'preview'} id={id} - html={html} content={content} /> ))} diff --git a/client/components/preview/html.tsx b/client/components/preview/html.tsx deleted file mode 100644 index f681eecb..00000000 --- a/client/components/preview/html.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import useTheme from "@lib/hooks/use-theme" -import { memo, useEffect, useState } from "react" -import styles from './preview.module.css' - -type Props = { - height?: number | string - html: string - // file extensions we can highlight -} - -const HtmlPreview = ({ height = 500, html }: Props) => { - const { theme } = useTheme() - return (<article - data-theme={theme} - className={styles.markdownPreview} - dangerouslySetInnerHTML={{ __html: html }} - style={{ height }} />) -} - -export default HtmlPreview diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index ac72f4fa..c179ad08 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -17,11 +17,13 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { useEffect(() => { async function fetchPost() { if (fileId) { - const resp = await fetch(`/api/markdown/${fileId}`, { + const resp = await fetch(`/server-api/files/html/${fileId}`, { method: "GET", }) + console.log(resp) if (resp.ok) { const res = await resp.text() + console.log(res) setPreview(res) setIsLoading(false) } diff --git a/client/components/view-document/index.tsx b/client/components/view-document/index.tsx index 8b2503ce..213f51ea 100644 --- a/client/components/view-document/index.tsx +++ b/client/components/view-document/index.tsx @@ -7,13 +7,11 @@ import ExternalLink from '@geist-ui/icons/externalLink' import Skeleton from "react-loading-skeleton" import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } from "@geist-ui/core" -import Preview from "@components/preview" -import HtmlPreview from "@components/preview/html" +import HtmlPreview from "@components/preview" // import Link from "next/link" type Props = { title: string - html: string initialTab?: "edit" | "preview" skeleton?: boolean id: string @@ -48,7 +46,7 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => { } -const Document = ({ content, title, html, initialTab = 'edit', skeleton, id }: Props) => { +const Document = ({ content, title, initialTab = 'edit', skeleton, id }: Props) => { const codeEditorRef = useRef<HTMLTextAreaElement>(null) const [tab, setTab] = useState(initialTab) // const height = editable ? "500px" : '100%' @@ -118,7 +116,7 @@ const Document = ({ content, title, html, initialTab = 'edit', skeleton, id }: P </div> </Tabs.Item> <Tabs.Item label="Preview" value="preview"> - <HtmlPreview height={height} html={html} /> + <HtmlPreview height={height} fileId={id} /> </Tabs.Item> </Tabs> diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index e6da5bd3..6da97c50 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -1,15 +1,23 @@ import Cookies from "js-cookie"; +import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import useSharedState from "./use-shared-state"; const useSignedIn = () => { const [signedIn, setSignedIn] = useSharedState('signedIn', typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); const token = Cookies.get("drift-token") + const router = useRouter(); const signin = (token: string) => { setSignedIn(true); Cookies.set("drift-token", token); } + const signout = () => { + setSignedIn(false); + Cookies.remove("drift-token"); + router.push("/"); + } + useEffect(() => { if (token) { setSignedIn(true); @@ -18,7 +26,7 @@ const useSignedIn = () => { } }, [setSignedIn, token]); - return { signedIn, signin, token }; + return { signedIn, signin, token, signout }; } export default useSignedIn; diff --git a/client/pages/api/raw/[id].ts b/client/pages/api/raw/[id].ts index 4cfe4e52..c671c584 100644 --- a/client/pages/api/raw/[id].ts +++ b/client/pages/api/raw/[id].ts @@ -9,12 +9,11 @@ const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { 'Authorization': `Bearer ${req.cookies['drift-token']}`, } }) - - res.setHeader("Content-Type", "text/plain") + const json = await file.json() + res.setHeader("Content-Type", "text/plain; charset=utf-8") res.setHeader('Cache-Control', 's-maxage=86400'); - if (file.ok) { - const data = await file.json() + const data = json const { title, content } = data // serve the file raw as plain text @@ -24,7 +23,8 @@ const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { res.setHeader("Content-Disposition", `inline; filename="${title}"`) } - res.status(200).send(content) + res.status(200).write(content, 'utf-8') + res.end() } else { res.status(404).send("File not found") } diff --git a/client/styles/Home.module.css b/client/styles/Home.module.css index 3c3959ed..1baae48f 100644 --- a/client/styles/Home.module.css +++ b/client/styles/Home.module.css @@ -7,5 +7,5 @@ .main { max-width: var(--main-content) !important; margin: 0 auto !important; - padding: 0 var(--gap) !important; + padding: 0 0 !important; } diff --git a/server/src/lib/models/File.ts b/server/src/lib/models/File.ts index f52b4ad3..85f19e18 100644 --- a/server/src/lib/models/File.ts +++ b/server/src/lib/models/File.ts @@ -1,4 +1,4 @@ -import { BelongsTo, Column, CreatedAt, DataType, ForeignKey, IsUUID, Model, PrimaryKey, Scopes, Table } from 'sequelize-typescript'; +import { BelongsTo, Column, CreatedAt, DataType, ForeignKey, IsUUID, Model, PrimaryKey, Scopes, Table, Unique } from 'sequelize-typescript'; import { Post } from './Post'; import { User } from './User'; @@ -20,6 +20,7 @@ import { User } from './User'; export class File extends Model { @IsUUID(4) @PrimaryKey + @Unique @Column({ type: DataType.UUID, defaultValue: DataType.UUIDV4, diff --git a/server/src/lib/models/Post.ts b/server/src/lib/models/Post.ts index 2be1b024..0992d078 100644 --- a/server/src/lib/models/Post.ts +++ b/server/src/lib/models/Post.ts @@ -1,4 +1,4 @@ -import { BelongsToMany, Column, CreatedAt, DataType, HasMany, IsUUID, Model, PrimaryKey, Scopes, Table, UpdatedAt } from 'sequelize-typescript'; +import { BelongsToMany, Column, CreatedAt, DataType, HasMany, IsUUID, Model, PrimaryKey, Scopes, Table, Unique, UpdatedAt } from 'sequelize-typescript'; import { PostAuthor } from './PostAuthor'; import { User } from './User'; import { File } from './File'; @@ -26,6 +26,7 @@ import { File } from './File'; export class Post extends Model { @IsUUID(4) @PrimaryKey + @Unique @Column({ type: DataType.UUID, defaultValue: DataType.UUIDV4, diff --git a/server/src/lib/models/PostAuthor.ts b/server/src/lib/models/PostAuthor.ts index 1c718cce..76af896f 100644 --- a/server/src/lib/models/PostAuthor.ts +++ b/server/src/lib/models/PostAuthor.ts @@ -1,4 +1,4 @@ -import { Model, Column, Table, ForeignKey, IsUUID, PrimaryKey, DataType } from "sequelize-typescript"; +import { Model, Column, Table, ForeignKey, IsUUID, PrimaryKey, DataType, Unique } from "sequelize-typescript"; import { Post } from "./Post"; import { User } from "./User"; @@ -6,6 +6,7 @@ import { User } from "./User"; export class PostAuthor extends Model { @IsUUID(4) @PrimaryKey + @Unique @Column({ type: DataType.UUID, defaultValue: DataType.UUIDV4, diff --git a/server/src/lib/models/User.ts b/server/src/lib/models/User.ts index daa7de70..d050e956 100644 --- a/server/src/lib/models/User.ts +++ b/server/src/lib/models/User.ts @@ -1,4 +1,4 @@ -import { Model, Column, Table, BelongsToMany, Scopes, CreatedAt, UpdatedAt, IsUUID, PrimaryKey, DataType } from "sequelize-typescript"; +import { Model, Column, Table, BelongsToMany, Scopes, CreatedAt, UpdatedAt, IsUUID, PrimaryKey, DataType, Unique } from "sequelize-typescript"; import { Post } from "./Post"; import { PostAuthor } from "./PostAuthor"; @@ -22,6 +22,7 @@ import { PostAuthor } from "./PostAuthor"; export class User extends Model { @IsUUID(4) @PrimaryKey + @Unique @Column({ type: DataType.UUID, defaultValue: DataType.UUIDV4, diff --git a/server/src/routes/files.ts b/server/src/routes/files.ts index 70a43cde..acaaf9f2 100644 --- a/server/src/routes/files.ts +++ b/server/src/routes/files.ts @@ -27,3 +27,27 @@ files.get("/raw/:id", secretKey, async (req, res, next) => { next(e); } }); + + +files.get("/html/:id", async (req, res, next) => { + try { + const file = await File.findOne({ + where: { + id: req.params.id + }, + attributes: ["html"], + }) + + if (!file) { + return res.status(404).json({ error: "File not found" }) + } + + console.log(file.html) + res.setHeader('Content-Type', 'text/plain') + res.setHeader('Cache-Control', 'public, max-age=4800') + res.status(200).write(file.html) + res.end() + } catch (error) { + next(error) + } +}) \ No newline at end of file diff --git a/server/src/routes/posts.ts b/server/src/routes/posts.ts index 66c39ce4..343ed435 100644 --- a/server/src/routes/posts.ts +++ b/server/src/routes/posts.ts @@ -138,7 +138,7 @@ posts.get("/:id", async (req, res, next) => { { model: File, as: "files", - attributes: ["id", "title", "content", "sha", "createdAt", "updatedAt", "html"], + attributes: ["id", "title", "content", "sha", "createdAt", "updatedAt"], }, { model: User, diff --git a/server/src/server.ts b/server/src/server.ts index 5cf5bf97..dd741cd0 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -4,7 +4,7 @@ import config from './lib/config'; import { sequelize } from './lib/sequelize'; (async () => { - await sequelize.sync({ alter: true }); + await sequelize.sync({}); createServer(app) .listen( config.port, From d4120e6f415864d8ca88be9e021ac47bee1a9e7d Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Wed, 23 Mar 2022 15:34:23 -0700 Subject: [PATCH 49/63] client: add prettier, switch to preact --- client/.prettierrc | 6 + client/lib/generate-uuid.ts | 65 +- client/lib/get-post-path.ts | 18 +- client/lib/hooks/use-shared-state.ts | 8 +- client/lib/hooks/use-signed-in.ts | 55 +- client/lib/hooks/use-theme.ts | 36 +- client/lib/hooks/use-trace-route.ts | 30 +- client/lib/time-ago.ts | 52 +- client/lib/types.d.ts | 28 +- client/next.config.mjs | 32 +- client/package.json | 25 +- client/pages/_app.tsx | 17 - client/pages/api/markdown/[id].ts | 73 +- client/pages/api/raw/[id].ts | 52 +- client/pages/api/render-markdown.ts | 55 +- client/postcss.config.json | 18 + client/styles/globals.css | 1 - client/styles/nprogress.css | 23 - client/yarn.lock | 1187 +++++++++++++++++++++++++- 19 files changed, 1482 insertions(+), 299 deletions(-) create mode 100644 client/.prettierrc create mode 100644 client/postcss.config.json delete mode 100644 client/styles/nprogress.css diff --git a/client/.prettierrc b/client/.prettierrc new file mode 100644 index 00000000..c424cfdc --- /dev/null +++ b/client/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": false, + "trailingComma": "none", + "singleQuote": false, + "printWidth": 80 +} diff --git a/client/lib/generate-uuid.ts b/client/lib/generate-uuid.ts index a3d19f2a..b207aa0e 100644 --- a/client/lib/generate-uuid.ts +++ b/client/lib/generate-uuid.ts @@ -1,30 +1,39 @@ export default function generateUUID() { - if (typeof crypto === 'object') { - if (typeof crypto.randomUUID === 'function') { - // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID - return crypto.randomUUID(); - } - if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') { - // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid - const callback = (c: string) => { - const num = Number(c); - return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16); - }; - return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback); - } + if (typeof crypto === "object") { + if (typeof crypto.randomUUID === "function") { + // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID + return crypto.randomUUID() } - let timestamp = new Date().getTime(); - let perforNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0; - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { - let random = Math.random() * 16; - if (timestamp > 0) { - random = (timestamp + random) % 16 | 0; - timestamp = Math.floor(timestamp / 16); - } else { - random = (perforNow + random) % 16 | 0; - perforNow = Math.floor(perforNow / 16); - } - return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16); - }); -}; - + if ( + typeof crypto.getRandomValues === "function" && + typeof Uint8Array === "function" + ) { + // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid + const callback = (c: string) => { + const num = Number(c) + return ( + num ^ + (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4))) + ).toString(16) + } + return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback) + } + } + let timestamp = new Date().getTime() + let perforNow = + (typeof performance !== "undefined" && + performance.now && + performance.now() * 1000) || + 0 + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + let random = Math.random() * 16 + if (timestamp > 0) { + random = (timestamp + random) % 16 | 0 + timestamp = Math.floor(timestamp / 16) + } else { + random = (perforNow + random) % 16 | 0 + perforNow = Math.floor(perforNow / 16) + } + return (c === "x" ? random : (random & 0x3) | 0x8).toString(16) + }) +} diff --git a/client/lib/get-post-path.ts b/client/lib/get-post-path.ts index 47745319..77e85b2d 100644 --- a/client/lib/get-post-path.ts +++ b/client/lib/get-post-path.ts @@ -1,13 +1,13 @@ import type { PostVisibility } from "./types" export default function getPostPath(visibility: PostVisibility, id: string) { - switch (visibility) { - case "private": - return `/post/private/${id}` - case "protected": - return `/post/protected/${id}` - case "unlisted": - case "public": - return `/post/${id}` - } + switch (visibility) { + case "private": + return `/post/private/${id}` + case "protected": + return `/post/protected/${id}` + case "unlisted": + case "public": + return `/post/${id}` + } } diff --git a/client/lib/hooks/use-shared-state.ts b/client/lib/hooks/use-shared-state.ts index 8c7e351a..cc111400 100644 --- a/client/lib/hooks/use-shared-state.ts +++ b/client/lib/hooks/use-shared-state.ts @@ -2,10 +2,10 @@ import useSWR from "swr" // https://2020.paco.me/blog/shared-hook-state-with-swr const useSharedState = <T>(key: string, initial?: T) => { - const { data: state, mutate: setState } = useSWR(key, { - fallbackData: initial - }) - return [state, setState] as const + const { data: state, mutate: setState } = useSWR(key, { + fallbackData: initial + }) + return [state, setState] as const } export default useSharedState diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index 6da97c50..3f97123e 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -1,32 +1,35 @@ -import Cookies from "js-cookie"; -import { useRouter } from "next/router"; -import { useEffect, useState } from "react"; -import useSharedState from "./use-shared-state"; +import Cookies from "js-cookie" +import { useRouter } from "next/router" +import { useEffect, useState } from "react" +import useSharedState from "./use-shared-state" const useSignedIn = () => { - const [signedIn, setSignedIn] = useSharedState('signedIn', typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); - const token = Cookies.get("drift-token") - const router = useRouter(); - const signin = (token: string) => { - setSignedIn(true); - Cookies.set("drift-token", token); + const [signedIn, setSignedIn] = useSharedState( + "signedIn", + typeof window === "undefined" ? false : !!Cookies.get("drift-token") + ) + const token = Cookies.get("drift-token") + const router = useRouter() + const signin = (token: string) => { + setSignedIn(true) + Cookies.set("drift-token", token) + } + + const signout = () => { + setSignedIn(false) + Cookies.remove("drift-token") + router.push("/") + } + + useEffect(() => { + if (token) { + setSignedIn(true) + } else { + setSignedIn(false) } + }, [setSignedIn, token]) - const signout = () => { - setSignedIn(false); - Cookies.remove("drift-token"); - router.push("/"); - } - - useEffect(() => { - if (token) { - setSignedIn(true); - } else { - setSignedIn(false); - } - }, [setSignedIn, token]); - - return { signedIn, signin, token, signout }; + return { signedIn, signin, token, signout } } -export default useSignedIn; +export default useSignedIn diff --git a/client/lib/hooks/use-theme.ts b/client/lib/hooks/use-theme.ts index 0a333320..be71719d 100644 --- a/client/lib/hooks/use-theme.ts +++ b/client/lib/hooks/use-theme.ts @@ -2,26 +2,26 @@ import { useCallback, useEffect } from "react" import useSharedState from "./use-shared-state" const useTheme = () => { - const isClient = typeof window === "object" - const [themeType, setThemeType] = useSharedState<string>('theme', 'light') + const isClient = typeof window === "object" + const [themeType, setThemeType] = useSharedState<string>("theme", "light") - useEffect(() => { - if (!isClient) return - const storedTheme = localStorage.getItem('drift-theme') - if (storedTheme) { - setThemeType(storedTheme) - } - }, [isClient, setThemeType]) + useEffect(() => { + if (!isClient) return + const storedTheme = localStorage.getItem("drift-theme") + if (storedTheme) { + setThemeType(storedTheme) + } + }, [isClient, setThemeType]) - const changeTheme = useCallback(() => { - setThemeType(last => { - const newTheme = last === 'dark' ? 'light' : 'dark' - localStorage.setItem('drift-theme', newTheme) - return newTheme - }) - }, [setThemeType]) + const changeTheme = useCallback(() => { + setThemeType((last) => { + const newTheme = last === "dark" ? "light" : "dark" + localStorage.setItem("drift-theme", newTheme) + return newTheme + }) + }, [setThemeType]) - return { theme: themeType, changeTheme } + return { theme: themeType, changeTheme } } -export default useTheme \ No newline at end of file +export default useTheme diff --git a/client/lib/hooks/use-trace-route.ts b/client/lib/hooks/use-trace-route.ts index b0268e32..863ae808 100644 --- a/client/lib/hooks/use-trace-route.ts +++ b/client/lib/hooks/use-trace-route.ts @@ -1,19 +1,19 @@ -import { useRef, useEffect } from "react"; +import { useRef, useEffect } from "react" function useTraceUpdate(props: { [key: string]: any }) { - const prev = useRef(props) - useEffect(() => { - const changedProps = Object.entries(props).reduce((ps, [k, v]) => { - if (prev.current[k] !== v) { - ps[k] = [prev.current[k], v] - } - return ps - }, {} as { [key: string]: any }) - if (Object.keys(changedProps).length > 0) { - console.log('Changed props:', changedProps) - } - prev.current = props - }); + const prev = useRef(props) + useEffect(() => { + const changedProps = Object.entries(props).reduce((ps, [k, v]) => { + if (prev.current[k] !== v) { + ps[k] = [prev.current[k], v] + } + return ps + }, {} as { [key: string]: any }) + if (Object.keys(changedProps).length > 0) { + console.log("Changed props:", changedProps) + } + prev.current = props + }) } -export default useTraceUpdate \ No newline at end of file +export default useTraceUpdate diff --git a/client/lib/time-ago.ts b/client/lib/time-ago.ts index 75e66245..9d723282 100644 --- a/client/lib/time-ago.ts +++ b/client/lib/time-ago.ts @@ -2,40 +2,42 @@ // which is based on https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site const epochs = [ - ['year', 31536000], - ['month', 2592000], - ['day', 86400], - ['hour', 3600], - ['minute', 60], - ['second', 1] -] as const; + ["year", 31536000], + ["month", 2592000], + ["day", 86400], + ["hour", 3600], + ["minute", 60], + ["second", 1] +] as const // Get duration const getDuration = (timeAgoInSeconds: number) => { - for (let [name, seconds] of epochs) { - const interval = Math.floor(timeAgoInSeconds / seconds); + for (let [name, seconds] of epochs) { + const interval = Math.floor(timeAgoInSeconds / seconds) - if (interval >= 1) { - return { - interval: interval, - epoch: name - }; - } + if (interval >= 1) { + return { + interval: interval, + epoch: name + } } + } - return { - interval: 0, - epoch: 'second' - } -}; + return { + interval: 0, + epoch: "second" + } +} // Calculate const timeAgo = (date: Date) => { - const timeAgoInSeconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000); - const { interval, epoch } = getDuration(timeAgoInSeconds); - const suffix = interval === 1 ? '' : 's'; + const timeAgoInSeconds = Math.floor( + (new Date().getTime() - new Date(date).getTime()) / 1000 + ) + const { interval, epoch } = getDuration(timeAgoInSeconds) + const suffix = interval === 1 ? "" : "s" - return `${interval} ${epoch}${suffix} ago`; -}; + return `${interval} ${epoch}${suffix} ago` +} export default timeAgo diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts index 2a359f8c..fec78e12 100644 --- a/client/lib/types.d.ts +++ b/client/lib/types.d.ts @@ -1,29 +1,29 @@ export type PostVisibility = "unlisted" | "private" | "public" | "protected" export type ThemeProps = { - theme: "light" | "dark" | string, - changeTheme: () => void + theme: "light" | "dark" | string + changeTheme: () => void } export type Document = { - title: string - content: string - id: string + title: string + content: string + id: string } export type File = { - id: string - title: string - content: string - html: string + id: string + title: string + content: string + html: string } type Files = File[] export type Post = { - id: string - title: string - description: string - visibility: PostVisibility - files: Files + id: string + title: string + description: string + visibility: PostVisibility + files: Files } diff --git a/client/next.config.mjs b/client/next.config.mjs index a34e3e0d..06cf7ba9 100644 --- a/client/next.config.mjs +++ b/client/next.config.mjs @@ -1,29 +1,39 @@ -import dotenv from "dotenv"; -import bundleAnalyzer from "@next/bundle-analyzer"; +import dotenv from "dotenv" +import bundleAnalyzer from "@next/bundle-analyzer" -dotenv.config(); +dotenv.config() /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, experimental: { outputStandalone: true, - esmExternals: true, + esmExternals: true + }, + webpack: (config, { dev, isServer }) => { + if (!dev && !isServer) { + Object.assign(config.resolve.alias, { + react: "preact/compat", + "react-dom/test-utils": "preact/test-utils", + "react-dom": "preact/compat" + }) + } + return config }, async rewrites() { return [ { source: "/server-api/:path*", - destination: `${process.env.API_URL}/:path*`, + destination: `${process.env.API_URL}/:path*` }, { source: "/file/raw/:id", - destination: `/api/raw/:id`, - }, - ]; - }, -}; + destination: `/api/raw/:id` + } + ] + } +} export default bundleAnalyzer({ enabled: process.env.ANALYZE === "true" })( nextConfig -); +) diff --git a/client/package.json b/client/package.json index 2c39cfe9..68b3ff40 100644 --- a/client/package.json +++ b/client/package.json @@ -6,8 +6,9 @@ "dev": "next dev --port 3001", "build": "next build", "start": "next start", - "lint": "next lint", - "analyze": "ANALYZE=true next build" + "lint": "next lint && prettier --config .prettierrc '{components,lib,pages}/**/*.ts' --write", + "analyze": "ANALYZE=true next build", + "find:unused": "next-unused" }, "dependencies": { "@geist-ui/core": "^2.3.5", @@ -18,10 +19,13 @@ "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", - "lodash.debounce": "^4.0.8", "marked": "^4.0.12", "next": "^12.1.1-canary.15", - "nprogress": "^0.2.0", + "postcss": "^8.4.12", + "postcss-flexbugs-fixes": "^5.0.2", + "postcss-hover-media-feature": "^1.0.2", + "postcss-preset-env": "^7.4.3", + "preact": "^10.6.6", "prism-react-renderer": "^1.3.1", "react": "17.0.2", "react-dom": "17.0.2", @@ -46,7 +50,20 @@ "@types/react-syntax-highlighter": "^13.5.2", "eslint": "8.10.0", "eslint-config-next": "^12.1.1-canary.16", + "next-unused": "^0.0.6", + "prettier": "^2.6.0", "typescript": "4.6.2", "typescript-plugin-css-modules": "^3.4.0" + }, + "next-unused": { + "alias": { + "@components": "components/", + "@lib": "lib/", + "@styles": "styles/" + }, + "include": [ + "components", + "lib" + ] } } diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index ebbb9535..2a11ca68 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -7,23 +7,6 @@ import Head from 'next/head'; import useTheme from '@lib/hooks/use-theme'; import { CssBaseline, GeistProvider } from '@geist-ui/core'; -import nprogress from 'nprogress' -import debounce from 'lodash.debounce' -import Router from 'next/router'; - -// Only show nprogress after 500ms (slow loading) -const start = debounce(nprogress.start, 500) -Router.events.on('routeChangeStart', start) -Router.events.on('routeChangeComplete', () => { - start.cancel() - nprogress.done() - window.scrollTo(0, 0) -}) -Router.events.on('routeChangeError', () => { - start.cancel() - nprogress.done() -}) - type AppProps<P = any> = { pageProps: P; } & Omit<NextAppProps<P>, "pageProps">; diff --git a/client/pages/api/markdown/[id].ts b/client/pages/api/markdown/[id].ts index 14d34d98..8ac01ff3 100644 --- a/client/pages/api/markdown/[id].ts +++ b/client/pages/api/markdown/[id].ts @@ -1,43 +1,54 @@ -import type { NextApiHandler } from "next"; +import type { NextApiHandler } from "next" -import markdown from "@lib/render-markdown"; +import markdown from "@lib/render-markdown" const renderMarkdown: NextApiHandler = async (req, res) => { - const { id } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { - headers: { - 'Accept': 'text/plain', - 'x-secret-key': process.env.SECRET_KEY || '', - 'Authorization': `Bearer ${req.cookies['drift-token']}`, - } - }) - - const json = await file.json() - const { content, title } = json - const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] - const fileType = () => { - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language + const { id } = req.query + const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { + headers: { + Accept: "text/plain", + "x-secret-key": process.env.SECRET_KEY || "", + Authorization: `Bearer ${req.cookies["drift-token"]}` } - const type = fileType() - let contentToRender: string = '\n' + content; + }) - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} + const json = await file.json() + const { content, title } = json + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = "\n" + content + + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} ${content} ~~~` - } + } - if (typeof contentToRender !== 'string') { - res.status(400).send('content must be a string') - return - } + if (typeof contentToRender !== "string") { + res.status(400).send("content must be a string") + return + } - res.setHeader('Content-Type', 'text/plain') - res.setHeader('Cache-Control', 'public, max-age=4800') - res.status(200).write(markdown(contentToRender)) - res.end() + res.setHeader("Content-Type", "text/plain") + res.setHeader("Cache-Control", "public, max-age=4800") + res.status(200).write(markdown(contentToRender)) + res.end() } export default renderMarkdown diff --git a/client/pages/api/raw/[id].ts b/client/pages/api/raw/[id].ts index c671c584..cd665253 100644 --- a/client/pages/api/raw/[id].ts +++ b/client/pages/api/raw/[id].ts @@ -1,33 +1,33 @@ import { NextApiRequest, NextApiResponse } from "next" const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { - const { id, download } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { - headers: { - 'Accept': 'text/plain', - 'x-secret-key': process.env.SECRET_KEY || '', - 'Authorization': `Bearer ${req.cookies['drift-token']}`, - } - }) - const json = await file.json() - res.setHeader("Content-Type", "text/plain; charset=utf-8") - res.setHeader('Cache-Control', 's-maxage=86400'); - if (file.ok) { - const data = json - const { title, content } = data - // serve the file raw as plain text - - if (download) { - res.setHeader("Content-Disposition", `attachment; filename="${title}"`) - } else { - res.setHeader("Content-Disposition", `inline; filename="${title}"`) - } - - res.status(200).write(content, 'utf-8') - res.end() - } else { - res.status(404).send("File not found") + const { id, download } = req.query + const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { + headers: { + Accept: "text/plain", + "x-secret-key": process.env.SECRET_KEY || "", + Authorization: `Bearer ${req.cookies["drift-token"]}` } + }) + const json = await file.json() + res.setHeader("Content-Type", "text/plain; charset=utf-8") + res.setHeader("Cache-Control", "s-maxage=86400") + if (file.ok) { + const data = json + const { title, content } = data + // serve the file raw as plain text + + if (download) { + res.setHeader("Content-Disposition", `attachment; filename="${title}"`) + } else { + res.setHeader("Content-Disposition", `inline; filename="${title}"`) + } + + res.status(200).write(content, "utf-8") + res.end() + } else { + res.status(404).send("File not found") + } } export default getRawFile diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts index 34df3f23..5e5e76f0 100644 --- a/client/pages/api/render-markdown.ts +++ b/client/pages/api/render-markdown.ts @@ -1,32 +1,43 @@ -import type { NextApiHandler } from "next"; +import type { NextApiHandler } from "next" -import markdown from "@lib/render-markdown"; +import markdown from "@lib/render-markdown" const renderMarkdown: NextApiHandler = async (req, res) => { - const { content, title } = req.body - const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] - const fileType = () => { - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = (content || ''); + const { content, title } = req.body + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = content || "" - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} ${content} ~~~` - } else { - contentToRender = '\n' + content; - } + } else { + contentToRender = "\n" + content + } - if (typeof contentToRender !== 'string') { - res.status(400).send('content must be a string') - return - } - res.status(200).write(markdown(contentToRender)) - res.end() + if (typeof contentToRender !== "string") { + res.status(400).send("content must be a string") + return + } + res.status(200).write(markdown(contentToRender)) + res.end() } export default renderMarkdown diff --git a/client/postcss.config.json b/client/postcss.config.json new file mode 100644 index 00000000..b223f5a4 --- /dev/null +++ b/client/postcss.config.json @@ -0,0 +1,18 @@ +{ + "plugins": [ + "postcss-flexbugs-fixes", + "postcss-hover-media-feature", + [ + "postcss-preset-env", + { + "autoprefixer": { + "flexbox": "no-2009" + }, + "stage": 3, + "features": { + "custom-properties": false + } + } + ] + ] +} diff --git a/client/styles/globals.css b/client/styles/globals.css index 496cb987..3a5cf488 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -1,6 +1,5 @@ @import "./syntax.css"; @import "./markdown.css"; -@import "./nprogress.css"; @import "./inter.css"; :root { diff --git a/client/styles/nprogress.css b/client/styles/nprogress.css deleted file mode 100644 index 0db6e676..00000000 --- a/client/styles/nprogress.css +++ /dev/null @@ -1,23 +0,0 @@ -#nprogress { - pointer-events: none; -} - -#nprogress .bar { - position: fixed; - z-index: 2000; - top: 0; - left: 0; - width: 100%; - height: 5px; - background: var(--fg); -} - -#nprogress::after { - content: ""; - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 5px; - background: transparent; -} diff --git a/client/yarn.lock b/client/yarn.lock index b69056c8..80eced45 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@babel/parser@^7.0.0": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.17.8.tgz#2817fb9d885dd8132ea0f8eb615a6388cca1c240" + integrity sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ== + "@babel/runtime-corejs3@^7.10.2": version "7.17.2" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.17.2.tgz#fdca2cd05fba63388babe85d349b6801b008fd13" @@ -17,6 +22,65 @@ dependencies: regenerator-runtime "^0.13.4" +"@csstools/postcss-color-function@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@csstools/postcss-color-function/-/postcss-color-function-1.0.3.tgz#251c961a852c99e9aabdbbdbefd50e9a96e8a9ff" + integrity sha512-J26I69pT2B3MYiLY/uzCGKVJyMYVg9TCpXkWsRlt+Yfq+nELUEm72QXIMYXs4xA9cJA4Oqs2EylrfokKl3mJEQ== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-font-format-keywords@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-1.0.0.tgz#7e7df948a83a0dfb7eb150a96e2390ac642356a1" + integrity sha512-oO0cZt8do8FdVBX8INftvIA4lUrKUSCcWUf9IwH9IPWOgKT22oAZFXeHLoDK7nhB2SmkNycp5brxfNMRLIhd6Q== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-hwb-function@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-hwb-function/-/postcss-hwb-function-1.0.0.tgz#d6785c1c5ba8152d1d392c66f3a6a446c6034f6d" + integrity sha512-VSTd7hGjmde4rTj1rR30sokY3ONJph1reCBTUXqeW1fKwETPy1x4t/XIeaaqbMbC5Xg4SM/lyXZ2S8NELT2TaA== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-ic-unit@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-ic-unit/-/postcss-ic-unit-1.0.0.tgz#f484db59fc94f35a21b6d680d23b0ec69b286b7f" + integrity sha512-i4yps1mBp2ijrx7E96RXrQXQQHm6F4ym1TOD0D69/sjDjZvQ22tqiEvaNw7pFZTUO5b9vWRHzbHzP9+UKuw+bA== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-is-pseudo-class@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-2.0.1.tgz#472fff2cf434bdf832f7145b2a5491587e790c9e" + integrity sha512-Og5RrTzwFhrKoA79c3MLkfrIBYmwuf/X83s+JQtz/Dkk/MpsaKtqHV1OOzYkogQ+tj3oYp5Mq39XotBXNqVc3Q== + dependencies: + postcss-selector-parser "^6.0.9" + +"@csstools/postcss-normalize-display-values@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-1.0.0.tgz#ce698f688c28517447aedf15a9037987e3d2dc97" + integrity sha512-bX+nx5V8XTJEmGtpWTO6kywdS725t71YSLlxWt78XoHUbELWgoCXeOFymRJmL3SU1TLlKSIi7v52EWqe60vJTQ== + dependencies: + postcss-value-parser "^4.2.0" + +"@csstools/postcss-oklab-function@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@csstools/postcss-oklab-function/-/postcss-oklab-function-1.0.2.tgz#87cd646e9450347a5721e405b4f7cc35157b7866" + integrity sha512-QwhWesEkMlp4narAwUi6pgc6kcooh8cC7zfxa9LSQNYXqzcdNUtNBzbGc5nuyAVreb7uf5Ox4qH1vYT3GA1wOg== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +"@csstools/postcss-progressive-custom-properties@^1.1.0", "@csstools/postcss-progressive-custom-properties@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-1.3.0.tgz#542292558384361776b45c85226b9a3a34f276fa" + integrity sha512-ASA9W1aIy5ygskZYuWams4BzafD12ULvSypmaLJT2jvQ8G0M3I8PRQhC0h7mG0Z3LI05+agZjqSR9+K9yaQQjA== + dependencies: + postcss-value-parser "^4.2.0" + "@eslint/eslintrc@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.0.tgz#7ce1547a5c46dfe56e1e45c3c9ed18038c721c6a" @@ -302,6 +366,11 @@ "@typescript-eslint/types" "5.10.1" "@typescript-eslint/visitor-keys" "5.10.1" +"@typescript-eslint/types@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.33.0.tgz#a1e59036a3b53ae8430ceebf2a919dc7f9af6d72" + integrity sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ== + "@typescript-eslint/types@5.10.1": version "5.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.10.1.tgz#dca9bd4cb8c067fc85304a31f38ec4766ba2d1ea" @@ -320,6 +389,27 @@ semver "^7.3.5" tsutils "^3.21.0" +"@typescript-eslint/typescript-estree@^4.33.0", "@typescript-eslint/typescript-estree@^4.8.2": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz#0dfb51c2908f68c5c08d82aefeaf166a17c24609" + integrity sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA== + dependencies: + "@typescript-eslint/types" "4.33.0" + "@typescript-eslint/visitor-keys" "4.33.0" + debug "^4.3.1" + globby "^11.0.3" + is-glob "^4.0.1" + semver "^7.3.5" + tsutils "^3.21.0" + +"@typescript-eslint/visitor-keys@4.33.0": + version "4.33.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz#2a22f77a41604289b7a186586e9ec48ca92ef1dd" + integrity sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg== + dependencies: + "@typescript-eslint/types" "4.33.0" + eslint-visitor-keys "^2.0.0" + "@typescript-eslint/visitor-keys@5.10.1": version "5.10.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.10.1.tgz#29102de692f59d7d34ecc457ed59ab5fc558010b" @@ -380,6 +470,11 @@ anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +app-module-path@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/app-module-path/-/app-module-path-2.2.0.tgz#641aa55dfb7d6a6f0a8141c4b9c0aa50b6c24dd5" + integrity sha1-ZBqlXft9am8KgUHEucCqULbCTdU= + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -427,6 +522,16 @@ array.prototype.flatmap@^1.2.5: define-properties "^1.1.3" es-abstract "^1.19.0" +ast-module-types@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-2.7.1.tgz#3f7989ef8dfa1fdb82dfe0ab02bdfc7c77a57dd3" + integrity sha512-Rnnx/4Dus6fn7fTqdeLEAn5vUll5w7/vts0RN608yFa6si/rDOUonlIIiwugHBFWjylHjxm9owoSZn71KwG4gw== + +ast-module-types@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ast-module-types/-/ast-module-types-3.0.0.tgz#9a6d8a80f438b6b8fe4995699d700297f398bf81" + integrity sha512-CMxMCOCS+4D+DkOQfuZf+vLrSEmY/7xtORwdxs4wtcC1wVgvk2MqFFTwQCFhvWsI4KPU9lcWXPI8DgRiz+xetQ== + ast-types-flow@^0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ast-types-flow/-/ast-types-flow-0.0.7.tgz#f70b735c6bca1a5c9c22d982c3e39e7feba3bdad" @@ -442,6 +547,18 @@ attr-accept@^2.2.2: resolved "https://registry.yarnpkg.com/attr-accept/-/attr-accept-2.2.2.tgz#646613809660110749e92f2c10833b70968d929b" integrity sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg== +autoprefixer@^10.4.4: + version "10.4.4" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.4.tgz#3e85a245b32da876a893d3ac2ea19f01e7ea5a1e" + integrity sha512-Tm8JxsB286VweiZ5F0anmbyGiNI3v3wGv3mz9W+cxEDYB/6jbnj6GM9H9mK3wIL8ftgl+C07Lcwb8PG5PCCPzA== + dependencies: + browserslist "^4.20.2" + caniuse-lite "^1.0.30001317" + fraction.js "^4.2.0" + normalize-range "^0.1.2" + picocolors "^1.0.0" + postcss-value-parser "^4.2.0" + axe-core@^4.3.5: version "4.4.1" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.4.1.tgz#7dbdc25989298f9ad006645cd396782443757413" @@ -462,16 +579,35 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + big.js@^3.1.3: version "3.2.0" resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q== +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bl@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" + integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== + dependencies: + buffer "^5.5.0" + inherits "^2.0.4" + readable-stream "^3.4.0" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -487,6 +623,25 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +browserslist@^4.20.2: + version "4.20.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.2.tgz#567b41508757ecd904dab4d1c646c612cd3d4f88" + integrity sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA== + dependencies: + caniuse-lite "^1.0.30001317" + electron-to-chromium "^1.4.84" + escalade "^3.1.1" + node-releases "^2.0.2" + picocolors "^1.0.0" + +buffer@^5.5.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -505,12 +660,17 @@ caniuse-lite@^1.0.30001283: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001313.tgz#a380b079db91621e1b7120895874e2fd62ed2e2f" integrity sha512-rI1UN0koZUiKINjysQDuRi2VeSCce3bYJNmDcj3PIKREiAmjakugBul1QSkg/fPrlULYl6oWfGg3PbgOSY9X4Q== +caniuse-lite@^1.0.30001317: + version "1.0.30001319" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001319.tgz#eb4da4eb3ecdd409f7ba1907820061d56096e88f" + integrity sha512-xjlIAFHucBRSMUo1kb5D4LYgcN1M45qdKP++lhqowDpwJwGkpIRTt5qQqnhxjj1vHcI7nrJxWhCC1ATrCEBTcw== + ccount@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== -chalk@^2.4.1: +chalk@^2.3.0, chalk@^2.4.1: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -562,11 +722,28 @@ character-reference-invalid@^1.0.0: optionalDependencies: fsevents "~2.3.2" +cli-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" + integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== + dependencies: + restore-cursor "^3.1.0" + +cli-spinners@^2.5.0: + version "2.6.1" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" + integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== + client-zip@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/client-zip/-/client-zip-2.0.0.tgz#c93676c92ddb40c858da83517c27297a53874f8d" integrity sha512-JFd4zdhxk5F01NmNnBq3+iMgJkkh0ku9NsI1wZlUjZ52inPJX92vR5TlSkjxRhmHJBPI7YqanD71wDEiKhdWtw== +clone@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" + integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -601,11 +778,21 @@ comma-separated-tokens@^2.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.2.tgz#d4c25abb679b7751c880be623c1179780fe1dd98" integrity sha512-G5yTt3KQN4Yn7Yk4ed73hlZ1evrFKXeUW3086p3PRFNp7m2vIjI6Pg+Kgb+oyzhd9F2qdcoj67+y3SdxL5XWsg== -commander@^6.2.0: +commander@^2.16.0, commander@^2.20.3, commander@^2.8.1: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^6.2.0, commander@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -628,6 +815,11 @@ core-js-pure@^3.20.2: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.21.1.tgz#8c4d1e78839f5f46208de7230cebfb72bc3bdb51" integrity sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -637,6 +829,20 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +css-blank-pseudo@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" + integrity sha512-VS90XWtsHGqoM0t4KpH053c4ehxZ2E6HtGI7x68YFV0pTo/QmkV/YFA+NnlvK8guxZVNWGQhVNJGC39Q8XF4OQ== + dependencies: + postcss-selector-parser "^6.0.9" + +css-has-pseudo@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/css-has-pseudo/-/css-has-pseudo-3.0.4.tgz#57f6be91ca242d5c9020ee3e51bbb5b89fc7af73" + integrity sha512-Vse0xpR1K9MNlp2j5w1pgWIJtm1a8qS0JwS9goFYcImjlHEmywP9VUF05aGBXzGpDJF86QXk4L0ypBmwPhGArw== + dependencies: + postcss-selector-parser "^6.0.9" + css-parse@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/css-parse/-/css-parse-2.0.0.tgz#a468ee667c16d81ccf05c58c38d2a97c780dbfd4" @@ -644,6 +850,11 @@ css-parse@~2.0.0: dependencies: css "^2.0.0" +css-prefers-color-scheme@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz#ca8a22e5992c10a5b9d315155e7caee625903349" + integrity sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA== + css-selector-tokenizer@^0.7.0: version "0.7.3" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.3.tgz#735f26186e67c749aaf275783405cf0661fae8f1" @@ -662,6 +873,11 @@ css@^2.0.0: source-map-resolve "^0.5.2" urix "^0.1.0" +cssdb@^6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/cssdb/-/cssdb-6.5.0.tgz#61264b71f29c834f09b59cb3e5b43c8226590122" + integrity sha512-Rh7AAopF2ckPXe/VBcoUS9JrCZNSyc60+KpgE6X25vpVxA32TmiqvExjkfhwP4wGSb6Xe8Z/JIyGqwgx/zZYFA== + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -698,6 +914,13 @@ debug@^4.0.0, debug@^4.1.1, debug@^4.3.2: dependencies: ms "2.1.2" +debug@^4.0.1, debug@^4.1.0, debug@^4.3.1, debug@^4.3.3: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -717,11 +940,23 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-is@^0.1.3: +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +defaults@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" + integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= + dependencies: + clone "^1.0.2" + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -729,11 +964,107 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" +dependency-tree@^8.0.0: + version "8.1.2" + resolved "https://registry.yarnpkg.com/dependency-tree/-/dependency-tree-8.1.2.tgz#c9e652984f53bd0239bc8a3e50cbd52f05b2e770" + integrity sha512-c4CL1IKxkKng0oT5xrg4uNiiMVFqTGOXqHSFx7XEFdgSsp6nw3AGGruICppzJUrfad/r7GLqt26rmWU4h4j39A== + dependencies: + commander "^2.20.3" + debug "^4.3.1" + filing-cabinet "^3.0.1" + precinct "^8.0.0" + typescript "^3.9.7" + dequal@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.2.tgz#85ca22025e3a87e65ef75a7a437b35284a7e319d" integrity sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug== +detective-amd@^3.0.1, detective-amd@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/detective-amd/-/detective-amd-3.1.2.tgz#bf55eb5291c218b76d6224a3d07932ef13a9a357" + integrity sha512-jffU26dyqJ37JHR/o44La6CxtrDf3Rt9tvd2IbImJYxWKTMdBjctp37qoZ6ZcY80RHg+kzWz4bXn39e4P7cctQ== + dependencies: + ast-module-types "^3.0.0" + escodegen "^2.0.0" + get-amd-module-type "^3.0.0" + node-source-walk "^4.2.0" + +detective-cjs@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/detective-cjs/-/detective-cjs-3.1.3.tgz#50e107d67b37f459b0ec02966ceb7e20a73f268b" + integrity sha512-ljs7P0Yj9MK64B7G0eNl0ThWSYjhAaSYy+fQcpzaKalYl/UoQBOzOeLCSFEY1qEBhziZ3w7l46KG/nH+s+L7BQ== + dependencies: + ast-module-types "^3.0.0" + node-source-walk "^4.0.0" + +detective-es6@^2.1.0, detective-es6@^2.2.0, detective-es6@^2.2.1: + version "2.2.2" + resolved "https://registry.yarnpkg.com/detective-es6/-/detective-es6-2.2.2.tgz#ee5f880981d9fecae9a694007029a2f6f26d8d28" + integrity sha512-eZUKCUsbHm8xoeoCM0z6JFwvDfJ5Ww5HANo+jPR7AzkFpW9Mun3t/TqIF2jjeWa2TFbAiGaWESykf2OQp3oeMw== + dependencies: + node-source-walk "^4.0.0" + +detective-less@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/detective-less/-/detective-less-1.0.2.tgz#a68af9ca5f69d74b7d0aa190218b211d83b4f7e3" + integrity sha512-Rps1xDkEEBSq3kLdsdnHZL1x2S4NGDcbrjmd4q+PykK5aJwDdP5MBgrJw1Xo+kyUHuv3JEzPqxr+Dj9ryeDRTA== + dependencies: + debug "^4.0.0" + gonzales-pe "^4.2.3" + node-source-walk "^4.0.0" + +detective-postcss@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detective-postcss/-/detective-postcss-4.0.0.tgz#24e69b465e5fefe7a6afd05f7e894e34595dbf51" + integrity sha512-Fwc/g9VcrowODIAeKRWZfVA/EufxYL7XfuqJQFroBKGikKX83d2G7NFw6kDlSYGG3LNQIyVa+eWv1mqre+v4+A== + dependencies: + debug "^4.1.1" + is-url "^1.2.4" + postcss "^8.1.7" + postcss-values-parser "^2.0.1" + +detective-sass@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/detective-sass/-/detective-sass-3.0.2.tgz#e0f35aac79a4d2f6409c284d95b8f7ecd5973afd" + integrity sha512-DNVYbaSlmti/eztFGSfBw4nZvwsTaVXEQ4NsT/uFckxhJrNRFUh24d76KzoCC3aarvpZP9m8sC2L1XbLej4F7g== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^4.0.0" + +detective-scss@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/detective-scss/-/detective-scss-2.0.2.tgz#7d2a642616d44bf677963484fa8754d9558b8235" + integrity sha512-hDWnWh/l0tht/7JQltumpVea/inmkBaanJUcXRB9kEEXVwVUMuZd6z7eusQ6GcBFrfifu3pX/XPyD7StjbAiBg== + dependencies: + gonzales-pe "^4.3.0" + node-source-walk "^4.0.0" + +detective-stylus@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detective-stylus/-/detective-stylus-1.0.3.tgz#20a702936c9fd7d4203fd7a903314b5dd43ac713" + integrity sha512-4/bfIU5kqjwugymoxLXXLltzQNeQfxGoLm2eIaqtnkWxqbhap9puDVpJPVDx96hnptdERzS5Cy6p9N8/08A69Q== + +detective-typescript@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-6.0.0.tgz#394062118d7c7da53425647ca41e0081169aa2b3" + integrity sha512-vTidcSDK3QostdbrH2Rwf9FhvrgJ4oIaVw5jbolgruTejexk6nNa9DShGpuS8CFVDb1IP86jct5BaZt1wSxpkA== + dependencies: + "@typescript-eslint/typescript-estree" "^4.8.2" + ast-module-types "^2.7.1" + node-source-walk "^4.2.0" + typescript "^3.9.7" + +detective-typescript@^7.0.0: + version "7.0.2" + resolved "https://registry.yarnpkg.com/detective-typescript/-/detective-typescript-7.0.2.tgz#c6e00b4c28764741ef719662250e6b014a5f3c8e" + integrity sha512-unqovnhxzvkCz3m1/W4QW4qGsvXCU06aU2BAm8tkza+xLnp9SOFnob2QsTxUv5PdnQKfDvWcv9YeOeFckWejwA== + dependencies: + "@typescript-eslint/typescript-estree" "^4.33.0" + ast-module-types "^2.7.1" + node-source-walk "^4.2.0" + typescript "^3.9.10" + diff@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" @@ -775,6 +1106,11 @@ duplexer@^0.1.2: resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== +electron-to-chromium@^1.4.84: + version "1.4.91" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.91.tgz#842bbc97fd639abe7e46e7da530e3af5f6ca2831" + integrity sha512-Z7Jkc4+ouEg8F6RrrgLOs0kkJjI0cnyFQmnGVpln8pPifuKBNbUr37GMgJsCTSwy6Z9TK7oTwW33Oe+3aERYew== + emoji-regex@^9.2.2: version "9.2.2" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" @@ -785,7 +1121,29 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= -errno@^0.1.1: +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +enhanced-resolve@^4.0.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz#2f3cfd84dbe3b487f18f2db2ef1e064a571ca5ec" + integrity sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg== + dependencies: + graceful-fs "^4.1.2" + memory-fs "^0.5.0" + tapable "^1.0.0" + +enhanced-resolve@^5.8.3: + version "5.9.2" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.9.2.tgz#0224dcd6a43389ebfb2d55efee517e5466772dd9" + integrity sha512-GIm3fQfwLJ8YZx2smuHpBKkXC1yOk+OBEmKckVyL0i/ea8mqDEykK3ld5dgH1QYPNyT/lIllxV2LULnxCHaHkA== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +errno@^0.1.1, errno@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.8.tgz#8bb3e9c7d463be4976ff888f76b4809ebc2e811f" integrity sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A== @@ -827,6 +1185,11 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" @@ -842,6 +1205,18 @@ escape-string-regexp@^5.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== +escodegen@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" + integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== + dependencies: + esprima "^4.0.1" + estraverse "^5.2.0" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.6.1" + eslint-config-next@^12.1.1-canary.16: version "12.1.1-canary.16" resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-12.1.1-canary.16.tgz#737f3c060c48d05da68b1c0b0eceb7ac99addd97" @@ -1029,6 +1404,11 @@ espree@^9.3.1: acorn-jsx "^5.3.1" eslint-visitor-keys "^3.3.0" +esprima@^4.0.0, esprima@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== + esquery@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" @@ -1079,7 +1459,7 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== -fast-levenshtein@^2.0.6: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -1117,6 +1497,24 @@ file-selector@^0.4.0: dependencies: tslib "^2.0.3" +filing-cabinet@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/filing-cabinet/-/filing-cabinet-3.1.0.tgz#3f2a347f0392faad772744de099e25b6dd6f86fd" + integrity sha512-ZFutWTo14Z1xmog76UoQzDKEza1fSpqc+HvUN6K6GILrfhIn6NbR8fHQktltygF+wbt7PZ/EvfLK6yJnebd40A== + dependencies: + app-module-path "^2.2.0" + commander "^2.20.3" + debug "^4.3.3" + enhanced-resolve "^5.8.3" + is-relative-path "^1.0.2" + module-definition "^3.3.1" + module-lookup-amd "^7.0.1" + resolve "^1.21.0" + resolve-dependency-path "^2.0.0" + sass-lookup "^3.0.0" + stylus-lookup "^3.0.1" + typescript "^3.9.7" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1144,11 +1542,21 @@ flatted@^3.1.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.5.tgz#76c8584f4fc843db64702a6bd04ab7a8bd666da3" integrity sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg== +flatten@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" + integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== + format@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b" integrity sha1-1hcBB+nv3E7TDJ3DkBbflCtctYs= +fraction.js@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" + integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1176,6 +1584,14 @@ generic-names@^1.0.2: dependencies: loader-utils "^0.2.16" +get-amd-module-type@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.2.tgz#46550cee2b8e1fa4c3f2c8a5753c36990aa49ab0" + integrity sha512-PcuKwB8ouJnKuAPn6Hk3UtdfKoUV3zXRqVEvj8XGIXqjWfgd1j7QGdXy5Z9OdQfzVt1Sk29HVe/P+X74ccOuqw== + dependencies: + ast-module-types "^3.0.0" + node-source-walk "^4.2.2" + get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -1185,6 +1601,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-own-enumerable-property-symbols@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" + integrity sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -1243,7 +1664,7 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globby@^11.0.4: +globby@^11.0.3, globby@^11.0.4: version "11.1.0" resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -1255,11 +1676,25 @@ globby@^11.0.4: merge2 "^1.4.1" slash "^3.0.0" -graceful-fs@^4.1.2: +gonzales-pe@^4.2.3, gonzales-pe@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" + integrity sha512-otgSPpUmdWJ43VXyiNgEYE4luzHCL2pz4wQ0OnDluC6Eg4Ko3Vexy/SrSynglw/eR+OhkzmqFCZa/OFa/RgAOQ== + dependencies: + minimist "^1.2.5" + +graceful-fs@^4.1.2, graceful-fs@^4.2.4: version "4.2.9" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graphviz@0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/graphviz/-/graphviz-0.0.9.tgz#0bbf1df588c6a92259282da35323622528c4bbc4" + integrity sha512-SmoY2pOtcikmMCqCSy2NO1YsRfu9OO0wpTlOYW++giGjfX1a6gax/m1Fo8IdUd0/3H15cTOfR1SMKwohj4LKsg== + dependencies: + temp "~0.4.0" + gzip-size@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" @@ -1452,6 +1887,11 @@ icss-utils@^5.1.0: resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" integrity sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA== +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -1485,6 +1925,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= +indexes-of@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" + integrity sha1-8w9xbI4r00bHtn0985FVZqfAVgc= + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -1493,11 +1938,16 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.3: +inherits@2, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ini@~1.3.0: + version "1.3.8" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" + integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== + inline-style-parser@0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1" @@ -1593,6 +2043,11 @@ is-hexadecimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz#cc35c97588da4bd49a8eedd6bc4082d44dcb23a7" integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw== +is-interactive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" + integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== + is-negative-zero@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150" @@ -1610,6 +2065,11 @@ is-number@^7.0.0: resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== +is-obj@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f" + integrity sha1-PkcprB9f3gJc19g6iW2rn09n2w8= + is-plain-obj@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.0.0.tgz#06c0999fd7574edf5a906ba5644ad0feb3a84d22" @@ -1623,6 +2083,16 @@ is-regex@^1.1.4: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" + integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk= + +is-relative-path@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-relative-path/-/is-relative-path-1.0.2.tgz#091b46a0d67c1ed0fe85f1f8cfdde006bb251d46" + integrity sha1-CRtGoNZ8HtD+hfH4z93gBrslHUY= + is-shared-array-buffer@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" @@ -1642,6 +2112,16 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +is-url@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/is-url/-/is-url-1.2.4.tgz#04a4df46d28c4cff3d73d01ff06abeb318a1aa52" + integrity sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww== + is-weakref@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2" @@ -1654,6 +2134,11 @@ is-what@^3.14.1: resolved "https://registry.yarnpkg.com/is-what/-/is-what-3.14.1.tgz#e1222f46ddda85dead0fd1c9df131760e77755c1" integrity sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA== +isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1748,6 +2233,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + lilconfig@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" @@ -1763,6 +2256,15 @@ loader-utils@^0.2.16: json5 "^0.5.0" object-assign "^4.0.1" +loader-utils@^1.0.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" + integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^1.0.1" + locate-path@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" @@ -1776,11 +2278,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" - integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= - lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -1791,6 +2288,14 @@ lodash@^4.17.20, lodash@^4.17.4: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== +log-symbols@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + longest-streak@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.0.1.tgz#c97315b7afa0e7d9525db9a5a2953651432bdc5d" @@ -1818,6 +2323,34 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +madge@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/madge/-/madge-4.0.2.tgz#56a3aff8021a5844f8713e0789f6ee94095f2f41" + integrity sha512-l5bnA2dvyk0azLKDbOTCI+wDZ6nB007PhvPdmiYlPmqwVi49JPbhQrH/t4u8E6Akp3gwji1GZuA+v/F5q6yoWQ== + dependencies: + chalk "^4.1.0" + commander "^6.2.1" + commondir "^1.0.1" + debug "^4.0.1" + dependency-tree "^8.0.0" + detective-amd "^3.0.1" + detective-cjs "^3.1.1" + detective-es6 "^2.1.0" + detective-less "^1.0.2" + detective-postcss "^4.0.0" + detective-sass "^3.0.1" + detective-scss "^2.0.1" + detective-stylus "^1.0.0" + detective-typescript "^7.0.0" + graphviz "0.0.9" + ora "^5.1.0" + pluralize "^8.0.0" + precinct "^7.0.0" + pretty-ms "^7.0.0" + rc "^1.2.7" + typescript "^3.9.5" + walkdir "^0.4.1" + make-dir@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5" @@ -1965,6 +2498,14 @@ mdurl@^1.0.0: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +memory-fs@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.5.0.tgz#324c01288b88652966d161db77838720845a8e3c" + integrity sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA== + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -2244,7 +2785,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" -micromatch@^4.0.4: +micromatch@^4.0.0, micromatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== @@ -2257,6 +2798,11 @@ mime@^1.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== +mimic-fn@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" + integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== + minimatch@^3.0.4, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2269,11 +2815,35 @@ minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +minimist@^1.2.5: + version "1.2.6" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" + integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== + mkdirp@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +module-definition@^3.3.1: + version "3.4.0" + resolved "https://registry.yarnpkg.com/module-definition/-/module-definition-3.4.0.tgz#953a3861f65df5e43e80487df98bb35b70614c2b" + integrity sha512-XxJ88R1v458pifaSkPNLUTdSPNVGMP2SXVncVmApGO+gAfrLANiYe6JofymCzVceGOMwQE2xogxBSc8uB7XegA== + dependencies: + ast-module-types "^3.0.0" + node-source-walk "^4.0.0" + +module-lookup-amd@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/module-lookup-amd/-/module-lookup-amd-7.0.1.tgz#d67c1a93f2ff8e38b8774b99a638e9a4395774b2" + integrity sha512-w9mCNlj0S8qviuHzpakaLVc+/7q50jl9a/kmJ/n8bmXQZgDPkQHnPBb8MUOYh3WpAYkXuNc2c+khsozhIp/amQ== + dependencies: + commander "^2.8.1" + debug "^4.1.0" + glob "^7.1.6" + requirejs "^2.3.5" + requirejs-config-file "^4.0.0" + mri@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" @@ -2318,6 +2888,15 @@ needle@^2.5.2: iconv-lite "^0.4.4" sax "^1.2.4" +next-unused@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/next-unused/-/next-unused-0.0.6.tgz#dbefa300bf5586e33d5bfde909130fb19ab04a64" + integrity sha512-dHFNNBanFq4wvYrULtsjfWyZ6BzOnr5VYI9EYMGAZYF2vkAhFpj2JOuT5Wu2o3LbFSG92PmAZnSUF/LstF82pA== + dependencies: + madge "^4.0.1" + ts-loader "^7.0.0" + typescript "^4.2.3" + next@^12.1.1-canary.15: version "12.1.1-canary.15" resolved "https://registry.yarnpkg.com/next/-/next-12.1.1-canary.15.tgz#a31c99a512b29d98aa0f6d837931d5c7f2aa2c35" @@ -2342,15 +2921,27 @@ next@^12.1.1-canary.15: "@next/swc-win32-ia32-msvc" "12.1.1-canary.15" "@next/swc-win32-x64-msvc" "12.1.1-canary.15" +node-releases@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.2.tgz#7139fe71e2f4f11b47d4d2986aaf8c48699e0c01" + integrity sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg== + +node-source-walk@^4.0.0, node-source-walk@^4.2.0, node-source-walk@^4.2.2: + version "4.3.0" + resolved "https://registry.yarnpkg.com/node-source-walk/-/node-source-walk-4.3.0.tgz#8336b56cfed23ac5180fe98f1e3bb6b11fd5317c" + integrity sha512-8Q1hXew6ETzqKRAs3jjLioSxNfT1cx74ooiF8RlAONwVMcfq+UdzLC2eB5qcPldUxaE5w3ytLkrmV1TGddhZTA== + dependencies: + "@babel/parser" "^7.0.0" + normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== -nprogress@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" - integrity sha1-y480xTIT2JVyP8urkH6UIq28r7E= +normalize-range@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" + integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI= object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" @@ -2419,11 +3010,30 @@ once@^1.3.0: dependencies: wrappy "1" +onetime@^5.1.0: + version "5.1.2" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" + integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== + dependencies: + mimic-fn "^2.1.0" + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== +optionator@^0.8.1: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + optionator@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" @@ -2436,6 +3046,21 @@ optionator@^0.9.1: type-check "^0.4.0" word-wrap "^1.2.3" +ora@^5.1.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" + integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== + dependencies: + bl "^4.1.0" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-spinners "^2.5.0" + is-interactive "^1.0.0" + is-unicode-supported "^0.1.0" + log-symbols "^4.1.0" + strip-ansi "^6.0.0" + wcwidth "^1.0.1" + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -2474,6 +3099,11 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" +parse-ms@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/parse-ms/-/parse-ms-2.1.0.tgz#348565a753d4391fa524029956b172cb7753097d" + integrity sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA== + parse-node-version@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" @@ -2524,6 +3154,87 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + +postcss-attribute-case-insensitive@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-5.0.0.tgz#39cbf6babf3ded1e4abf37d09d6eda21c644105c" + integrity sha512-b4g9eagFGq9T5SWX4+USfVyjIb3liPnjhHHRMP7FMB2kFVpYyfEscV0wP3eaXhKlcHKUut8lt5BGoeylWA/dBQ== + dependencies: + postcss-selector-parser "^6.0.2" + +postcss-clamp@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/postcss-clamp/-/postcss-clamp-4.1.0.tgz#7263e95abadd8c2ba1bd911b0b5a5c9c93e02363" + integrity sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-functional-notation@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/postcss-color-functional-notation/-/postcss-color-functional-notation-4.2.2.tgz#f59ccaeb4ee78f1b32987d43df146109cc743073" + integrity sha512-DXVtwUhIk4f49KK5EGuEdgx4Gnyj6+t2jBSEmxvpIK9QI40tWrpS2Pua8Q7iIZWBrki2QOaeUdEaLPPa91K0RQ== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-hex-alpha@^8.0.3: + version "8.0.3" + resolved "https://registry.yarnpkg.com/postcss-color-hex-alpha/-/postcss-color-hex-alpha-8.0.3.tgz#61a0fd151d28b128aa6a8a21a2dad24eebb34d52" + integrity sha512-fESawWJCrBV035DcbKRPAVmy21LpoyiXdPTuHUfWJ14ZRjY7Y7PA6P4g8z6LQGYhU1WAxkTxjIjurXzoe68Glw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-color-rebeccapurple@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-7.0.2.tgz#5d397039424a58a9ca628762eb0b88a61a66e079" + integrity sha512-SFc3MaocHaQ6k3oZaFwH8io6MdypkUtEy/eXzXEB1vEQlO3S3oDc/FSZA8AsS04Z25RirQhlDlHLh3dn7XewWw== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-custom-media@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz#1be6aff8be7dc9bf1fe014bde3b71b92bb4552f1" + integrity sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g== + +postcss-custom-properties@^12.1.5: + version "12.1.5" + resolved "https://registry.yarnpkg.com/postcss-custom-properties/-/postcss-custom-properties-12.1.5.tgz#e669cfff89b0ea6fc85c45864a32b450cb6b196f" + integrity sha512-FHbbB/hRo/7cxLGkc2NS7cDRIDN1oFqQnUKBiyh4b/gwk8DD8udvmRDpUhEK836kB8ggUCieHVOvZDnF9XhI3g== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-custom-selectors@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/postcss-custom-selectors/-/postcss-custom-selectors-6.0.0.tgz#022839e41fbf71c47ae6e316cb0e6213012df5ef" + integrity sha512-/1iyBhz/W8jUepjGyu7V1OPcGbc636snN1yXEQCinb6Bwt7KxsiU7/bLQlp8GwAXzCh7cobBU5odNn/2zQWR8Q== + dependencies: + postcss-selector-parser "^6.0.4" + +postcss-dir-pseudo-class@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-6.0.4.tgz#9afe49ea631f0cb36fa0076e7c2feb4e7e3f049c" + integrity sha512-I8epwGy5ftdzNWEYok9VjW9whC4xnelAtbajGv4adql4FIF09rnrxnA9Y8xSHN47y7gqFIv10C5+ImsLeJpKBw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-double-position-gradients@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/postcss-double-position-gradients/-/postcss-double-position-gradients-3.1.1.tgz#a12cfdb7d11fa1a99ccecc747f0c19718fb37152" + integrity sha512-jM+CGkTs4FcG53sMPjrrGE0rIvLDdCrqMzgDC5fLI7JHDO7o6QG8C5TQBtExb13hdBdoH9C2QVbG4jo2y9lErQ== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + +postcss-env-function@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/postcss-env-function/-/postcss-env-function-4.0.6.tgz#7b2d24c812f540ed6eda4c81f6090416722a8e7a" + integrity sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA== + dependencies: + postcss-value-parser "^4.2.0" + postcss-filter-plugins@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/postcss-filter-plugins/-/postcss-filter-plugins-3.0.1.tgz#9d226e946d56542ab7c26123053459a331df545d" @@ -2531,6 +3242,42 @@ postcss-filter-plugins@^3.0.1: dependencies: postcss "^6.0.14" +postcss-flexbugs-fixes@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-5.0.2.tgz#2028e145313074fc9abe276cb7ca14e5401eb49d" + integrity sha512-18f9voByak7bTktR2QgDveglpn9DTbBWPUzSOe9g0N4WR/2eSt6Vrcbf0hmspvMI6YWGywz6B9f7jzpFNJJgnQ== + +postcss-focus-visible@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-visible/-/postcss-focus-visible-6.0.4.tgz#50c9ea9afa0ee657fb75635fabad25e18d76bf9e" + integrity sha512-QcKuUU/dgNsstIK6HELFRT5Y3lbrMLEOwG+A4s5cA+fx3A3y/JTq3X9LaOj3OC3ALH0XqyrgQIgey/MIZ8Wczw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-focus-within@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-focus-within/-/postcss-focus-within-5.0.4.tgz#5b1d2ec603195f3344b716c0b75f61e44e8d2e20" + integrity sha512-vvjDN++C0mu8jz4af5d52CB184ogg/sSxAFS+oUJQq2SuCe7T5U2iIsVJtsCp2d6R4j0jr5+q3rPkBVZkXD9fQ== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-font-variant@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz#efd59b4b7ea8bb06127f2d031bfbb7f24d32fa66" + integrity sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA== + +postcss-gap-properties@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz#6401bb2f67d9cf255d677042928a70a915e6ba60" + integrity sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ== + +postcss-hover-media-feature@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/postcss-hover-media-feature/-/postcss-hover-media-feature-1.0.2.tgz#7c2046ddee1521148660195864b014d6c1d13b3c" + integrity sha512-o5xDUqCQ4xtilWOcvo5LKYxFVSLWcBN0IlTqa0IJwAwHTd4pxKmX4c0fGIpgLQCcBB/+aFizt2NVWNGJWG4Izg== + dependencies: + postcss-selector-parser "^6.0.4" + postcss-icss-keyframes@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/postcss-icss-keyframes/-/postcss-icss-keyframes-0.2.1.tgz#80c4455e0112b0f2f9c3c05ac7515062bb9ff295" @@ -2551,6 +3298,26 @@ postcss-icss-selectors@^2.0.3: lodash "^4.17.4" postcss "^6.0.2" +postcss-image-set-function@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/postcss-image-set-function/-/postcss-image-set-function-4.0.6.tgz#bcff2794efae778c09441498f40e0c77374870a9" + integrity sha512-KfdC6vg53GC+vPd2+HYzsZ6obmPqOk6HY09kttU19+Gj1nC3S3XBVEXDHxkhxTohgZqzbUb94bKXvKDnYWBm/A== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-initial@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/postcss-initial/-/postcss-initial-4.0.1.tgz#529f735f72c5724a0fb30527df6fb7ac54d7de42" + integrity sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ== + +postcss-lab-function@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/postcss-lab-function/-/postcss-lab-function-4.1.2.tgz#b75afe43ba9c1f16bfe9bb12c8109cabd55b5fc2" + integrity sha512-isudf5ldhg4fk16M8viAwAbg6Gv14lVO35N3Z/49NhbwPQ2xbiEoHgrRgpgQojosF4vF7jY653ktB6dDrUOR8Q== + dependencies: + "@csstools/postcss-progressive-custom-properties" "^1.1.0" + postcss-value-parser "^4.2.0" + postcss-load-config@^3.0.1: version "3.1.3" resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.3.tgz#21935b2c43b9a86e6581a576ca7ee1bde2bd1d23" @@ -2559,11 +3326,140 @@ postcss-load-config@^3.0.1: lilconfig "^2.0.4" yaml "^1.10.2" +postcss-logical@^5.0.4: + version "5.0.4" + resolved "https://registry.yarnpkg.com/postcss-logical/-/postcss-logical-5.0.4.tgz#ec75b1ee54421acc04d5921576b7d8db6b0e6f73" + integrity sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g== + +postcss-media-minmax@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz#7140bddec173e2d6d657edbd8554a55794e2a5b5" + integrity sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ== + +postcss-nesting@^10.1.3: + version "10.1.3" + resolved "https://registry.yarnpkg.com/postcss-nesting/-/postcss-nesting-10.1.3.tgz#f0b1cd7ae675c697ab6a5a5ca1feea4784a2ef77" + integrity sha512-wUC+/YCik4wH3StsbC5fBG1s2Z3ZV74vjGqBFYtmYKlVxoio5TYGM06AiaKkQPPlkXWn72HKfS7Cw5PYxnoXSw== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-opacity-percentage@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/postcss-opacity-percentage/-/postcss-opacity-percentage-1.1.2.tgz#bd698bb3670a0a27f6d657cc16744b3ebf3b1145" + integrity sha512-lyUfF7miG+yewZ8EAk9XUBIlrHyUE6fijnesuz+Mj5zrIHIEw6KcIZSOk/elVMqzLvREmXB83Zi/5QpNRYd47w== + +postcss-overflow-shorthand@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz#ebcfc0483a15bbf1b27fdd9b3c10125372f4cbc2" + integrity sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg== + +postcss-page-break@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/postcss-page-break/-/postcss-page-break-3.0.4.tgz#7fbf741c233621622b68d435babfb70dd8c1ee5f" + integrity sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ== + +postcss-place@^7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/postcss-place/-/postcss-place-7.0.4.tgz#eb026650b7f769ae57ca4f938c1addd6be2f62c9" + integrity sha512-MrgKeiiu5OC/TETQO45kV3npRjOFxEHthsqGtkh3I1rPbZSbXGD/lZVi9j13cYh+NA8PIAPyk6sGjT9QbRyvSg== + dependencies: + postcss-value-parser "^4.2.0" + +postcss-preset-env@^7.4.3: + version "7.4.3" + resolved "https://registry.yarnpkg.com/postcss-preset-env/-/postcss-preset-env-7.4.3.tgz#fb1c8b4cb405da042da0ddb8c5eda7842c08a449" + integrity sha512-dlPA65g9KuGv7YsmGyCKtFkZKCPLkoVMUE3omOl6yM+qrynVHxFvf0tMuippIrXB/sB/MyhL1FgTIbrO+qMERg== + dependencies: + "@csstools/postcss-color-function" "^1.0.3" + "@csstools/postcss-font-format-keywords" "^1.0.0" + "@csstools/postcss-hwb-function" "^1.0.0" + "@csstools/postcss-ic-unit" "^1.0.0" + "@csstools/postcss-is-pseudo-class" "^2.0.1" + "@csstools/postcss-normalize-display-values" "^1.0.0" + "@csstools/postcss-oklab-function" "^1.0.2" + "@csstools/postcss-progressive-custom-properties" "^1.3.0" + autoprefixer "^10.4.4" + browserslist "^4.20.2" + css-blank-pseudo "^3.0.3" + css-has-pseudo "^3.0.4" + css-prefers-color-scheme "^6.0.3" + cssdb "^6.5.0" + postcss-attribute-case-insensitive "^5.0.0" + postcss-clamp "^4.1.0" + postcss-color-functional-notation "^4.2.2" + postcss-color-hex-alpha "^8.0.3" + postcss-color-rebeccapurple "^7.0.2" + postcss-custom-media "^8.0.0" + postcss-custom-properties "^12.1.5" + postcss-custom-selectors "^6.0.0" + postcss-dir-pseudo-class "^6.0.4" + postcss-double-position-gradients "^3.1.1" + postcss-env-function "^4.0.6" + postcss-focus-visible "^6.0.4" + postcss-focus-within "^5.0.4" + postcss-font-variant "^5.0.0" + postcss-gap-properties "^3.0.3" + postcss-image-set-function "^4.0.6" + postcss-initial "^4.0.1" + postcss-lab-function "^4.1.2" + postcss-logical "^5.0.4" + postcss-media-minmax "^5.0.0" + postcss-nesting "^10.1.3" + postcss-opacity-percentage "^1.1.2" + postcss-overflow-shorthand "^3.0.3" + postcss-page-break "^3.0.4" + postcss-place "^7.0.4" + postcss-pseudo-class-any-link "^7.1.1" + postcss-replace-overflow-wrap "^4.0.0" + postcss-selector-not "^5.0.0" + postcss-value-parser "^4.2.0" + +postcss-pseudo-class-any-link@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-7.1.1.tgz#534eb1dadd9945eb07830dbcc06fb4d5d865b8e0" + integrity sha512-JRoLFvPEX/1YTPxRxp1JO4WxBVXJYrSY7NHeak5LImwJ+VobFMwYDQHvfTXEpcn+7fYIeGkC29zYFhFWIZD8fg== + dependencies: + postcss-selector-parser "^6.0.9" + +postcss-replace-overflow-wrap@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz#d2df6bed10b477bf9c52fab28c568b4b29ca4319" + integrity sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw== + +postcss-selector-not@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/postcss-selector-not/-/postcss-selector-not-5.0.0.tgz#ac5fc506f7565dd872f82f5314c0f81a05630dc7" + integrity sha512-/2K3A4TCP9orP4TNS7u3tGdRFVKqz/E6pX3aGnriPG0jU78of8wsUcqE4QAhWEU0d+WnMSF93Ah3F//vUtK+iQ== + dependencies: + balanced-match "^1.0.0" + +postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4, postcss-selector-parser@^6.0.9: + version "6.0.9" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.9.tgz#ee71c3b9ff63d9cd130838876c13a2ec1a992b2f" + integrity sha512-UO3SgnZOVTwu4kyLR22UQ1xZh086RyNZppb7lLAKBFK8a32ttG5i87Y/P3+2bRSjZNyJ1B7hfFNo273tKe9YxQ== + dependencies: + cssesc "^3.0.0" + util-deprecate "^1.0.2" + postcss-value-parser@^3.3.0: version "3.3.1" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281" integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ== +postcss-value-parser@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" + integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== + +postcss-values-parser@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/postcss-values-parser/-/postcss-values-parser-2.0.1.tgz#da8b472d901da1e205b47bdc98637b9e9e550e5f" + integrity sha512-2tLuBsA6P4rYTNKCXYG/71C7j1pU6pK503suYOmn4xYrQIzW+opD+7FAFNuGSdZC/3Qfy334QbeMu7MEb8gOxg== + dependencies: + flatten "^1.0.2" + indexes-of "^1.0.1" + uniq "^1.0.1" + postcss@8.4.5: version "8.4.5" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95" @@ -2582,7 +3478,7 @@ postcss@^6.0.14, postcss@^6.0.2: source-map "^0.6.1" supports-color "^5.4.0" -postcss@^8.3.0: +postcss@^8.1.7, postcss@^8.3.0, postcss@^8.4.12: version "8.4.12" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.12.tgz#1e7de78733b28970fa4743f7da6f3763648b1905" integrity sha512-lg6eITwYe9v6Hr5CncVbK70SoioNQIq81nsaG86ev5hAidQvmOeETBqs7jm43K2F5/Ley3ytDtriImV6TpNiSg== @@ -2591,11 +3487,71 @@ postcss@^8.3.0: picocolors "^1.0.0" source-map-js "^1.0.2" +preact@^10.6.6: + version "10.6.6" + resolved "https://registry.yarnpkg.com/preact/-/preact-10.6.6.tgz#f1899bc8dab7c0788b858481532cb3b5d764a520" + integrity sha512-dgxpTFV2vs4vizwKohYKkk7g7rmp1wOOcfd4Tz3IB3Wi+ivZzsn/SpeKJhRENSE+n8sUfsAl4S3HiCVT923ABw== + +precinct@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/precinct/-/precinct-7.1.0.tgz#a0311e0b59029647eaf57c2d30b8efa9c85d129a" + integrity sha512-I1RkW5PX51/q6Xl39//D7x9NgaKNGHpR5DCNaoxP/b2+KbzzXDNhauJUMV17KSYkJA41CSpwYUPRtRoNxbshWA== + dependencies: + commander "^2.20.3" + debug "^4.3.1" + detective-amd "^3.0.1" + detective-cjs "^3.1.1" + detective-es6 "^2.2.0" + detective-less "^1.0.2" + detective-postcss "^4.0.0" + detective-sass "^3.0.1" + detective-scss "^2.0.1" + detective-stylus "^1.0.0" + detective-typescript "^6.0.0" + module-definition "^3.3.1" + node-source-walk "^4.2.0" + +precinct@^8.0.0: + version "8.3.1" + resolved "https://registry.yarnpkg.com/precinct/-/precinct-8.3.1.tgz#94b99b623df144eed1ce40e0801c86078466f0dc" + integrity sha512-pVppfMWLp2wF68rwHqBIpPBYY8Kd12lDhk8LVQzOwqllifVR15qNFyod43YLyFpurKRZQKnE7E4pofAagDOm2Q== + dependencies: + commander "^2.20.3" + debug "^4.3.3" + detective-amd "^3.1.0" + detective-cjs "^3.1.1" + detective-es6 "^2.2.1" + detective-less "^1.0.2" + detective-postcss "^4.0.0" + detective-sass "^3.0.1" + detective-scss "^2.0.1" + detective-stylus "^1.0.0" + detective-typescript "^7.0.0" + module-definition "^3.3.1" + node-source-walk "^4.2.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= + +prettier@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.0.tgz#12f8f504c4d8ddb76475f441337542fa799207d4" + integrity sha512-m2FgJibYrBGGgQXNzfd0PuDGShJgRavjUoRCw1mZERIWVSXF0iLzLm+aOqTAbLnC3n6JzUhAA8uZnFVghHJ86A== + +pretty-ms@^7.0.0: + version "7.0.1" + resolved "https://registry.yarnpkg.com/pretty-ms/-/pretty-ms-7.0.1.tgz#7d903eaab281f7d8e03c66f867e239dc32fb73e8" + integrity sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q== + dependencies: + parse-ms "^2.1.0" + prism-react-renderer@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" @@ -2606,6 +3562,11 @@ prismjs@^1.25.0, prismjs@~1.27.0: resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== +process-nextick-args@~2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" + integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + prop-types@^15.0.0, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" @@ -2642,6 +3603,16 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + react-dom@17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -2714,6 +3685,28 @@ react@17.0.2: loose-envify "^1.1.0" object-assign "^4.1.1" +readable-stream@^2.0.1: + version "2.3.7" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" + integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readable-stream@^3.4.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -2812,11 +3805,29 @@ remark-rehype@^10.0.0: mdast-util-to-hast "^12.1.0" unified "^10.0.0" +requirejs-config-file@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/requirejs-config-file/-/requirejs-config-file-4.0.0.tgz#4244da5dd1f59874038cc1091d078d620abb6ebc" + integrity sha512-jnIre8cbWOyvr8a5F2KuqBnY+SDA4NXr/hzEZJG79Mxm2WiFQz2dzhC8ibtPJS7zkmBEl1mxSwp5HhC1W4qpxw== + dependencies: + esprima "^4.0.0" + stringify-object "^3.2.1" + +requirejs@^2.3.5: + version "2.3.6" + resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.6.tgz#e5093d9601c2829251258c0b9445d4d19fa9e7c9" + integrity sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg== + reserved-words@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1" integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE= +resolve-dependency-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/resolve-dependency-path/-/resolve-dependency-path-2.0.0.tgz#11700e340717b865d216c66cabeb4a2a3c696736" + integrity sha512-DIgu+0Dv+6v2XwRaNWnumKu7GPufBBOr5I1gRPJHkvghrfCGOooJODFvgFimX/KRxk9j0whD2MnKHzM1jYvk9w== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -2827,7 +3838,7 @@ resolve-url@^0.2.1: resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= -resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0: +resolve@^1.13.1, resolve@^1.17.0, resolve@^1.20.0, resolve@^1.21.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw== @@ -2844,6 +3855,14 @@ resolve@^2.0.0-next.3: is-core-module "^2.2.0" path-parse "^1.0.6" +restore-cursor@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" + integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== + dependencies: + onetime "^5.1.0" + signal-exit "^3.0.2" + reusify@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" @@ -2870,11 +3889,28 @@ sade@^1.7.3: dependencies: mri "^1.1.0" +safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sass-lookup@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/sass-lookup/-/sass-lookup-3.0.0.tgz#3b395fa40569738ce857bc258e04df2617c48cac" + integrity sha512-TTsus8CfFRn1N44bvdEai1no6PqdmDiQUiqW5DlpmtT+tYnIt1tXtDIph5KA1efC+LmioJXSnCtUVpcK9gaKIg== + dependencies: + commander "^2.16.0" + sass@^1.32.13: version "1.49.9" resolved "https://registry.yarnpkg.com/sass/-/sass-1.49.9.tgz#b15a189ecb0ca9e24634bae5d1ebc191809712f9" @@ -2902,7 +3938,7 @@ semver@^5.6.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@^6.3.0: +semver@^6.0.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== @@ -2935,6 +3971,11 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +signal-exit@^3.0.2: + version "3.0.7" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" + integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== + sirv@^1.0.7: version "1.0.19" resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.19.tgz#1d73979b38c7fe91fcba49c85280daa9c2363b49" @@ -2970,7 +4011,7 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.6.1, source-map@~0.6.0: +source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -3020,7 +4061,30 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -strip-ansi@^6.0.1: +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +stringify-object@^3.2.1: + version "3.3.0" + resolved "https://registry.yarnpkg.com/stringify-object/-/stringify-object-3.3.0.tgz#703065aefca19300d3ce88af4f5b3956d7556629" + integrity sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw== + dependencies: + get-own-enumerable-property-symbols "^3.0.0" + is-obj "^1.0.1" + is-regexp "^1.0.0" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -3037,6 +4101,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + style-to-object@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46" @@ -3049,6 +4118,14 @@ styled-jsx@5.0.1: resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.1.tgz#78fecbbad2bf95ce6cd981a08918ce4696f5fc80" integrity sha512-+PIZ/6Uk40mphiQJJI1202b+/dYeTVd9ZnMPR80pgiWbjIwvN2zIp4r9et0BgqBuShh48I0gttPlAXA7WVvBxw== +stylus-lookup@^3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/stylus-lookup/-/stylus-lookup-3.0.2.tgz#c9eca3ff799691020f30b382260a67355fefdddd" + integrity sha512-oEQGHSjg/AMaWlKe7gqsnYzan8DLcGIHe0dUaFkucZZ14z4zjENRlQMCHT4FNsiWnJf17YN9OvrCfCoi7VvOyg== + dependencies: + commander "^2.8.1" + debug "^4.1.0" + stylus@^0.54.8: version "0.54.8" resolved "https://registry.yarnpkg.com/stylus/-/stylus-0.54.8.tgz#3da3e65966bc567a7b044bfe0eece653e099d147" @@ -3087,6 +4164,21 @@ swr@^1.2.2: resolved "https://registry.yarnpkg.com/swr/-/swr-1.2.2.tgz#6cae09928d30593a7980d80f85823e57468fac5d" integrity sha512-ky0BskS/V47GpW8d6RU7CPsr6J8cr7mQD6+do5eky3bM0IyJaoi3vO8UhvrzJaObuTlGhPl2szodeB2dUd76Xw== +tapable@^1.0.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" + integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== + +tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +temp@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/temp/-/temp-0.4.0.tgz#671ad63d57be0fe9d7294664b3fc400636678a60" + integrity sha1-ZxrWPVe+D+nXKUZks/xABjZnimA= + text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -3109,6 +4201,17 @@ trough@^2.0.0: resolved "https://registry.yarnpkg.com/trough/-/trough-2.1.0.tgz#0f7b511a4fde65a46f18477ab38849b22c554876" integrity sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g== +ts-loader@^7.0.0: + version "7.0.5" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-7.0.5.tgz#789338fb01cb5dc0a33c54e50558b34a73c9c4c5" + integrity sha512-zXypEIT6k3oTc+OZNx/cqElrsbBtYqDknf48OZos0NQ3RTt045fBIU8RRSu+suObBzYB355aIPGOe/3kj9h7Ig== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + tsconfig-paths@^3.11.0: version "3.14.0" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.0.tgz#4fcc48f9ccea8826c41b9ca093479de7f5018976" @@ -3153,6 +4256,13 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= + dependencies: + prelude-ls "~1.1.2" + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -3177,11 +4287,16 @@ typescript-plugin-css-modules@^3.4.0: stylus "^0.54.8" tsconfig-paths "^3.9.0" -typescript@4.6.2: +typescript@4.6.2, typescript@^4.2.3: version "4.6.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +typescript@^3.9.10, typescript@^3.9.5, typescript@^3.9.7: + version "3.9.10" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.10.tgz#70f3910ac7a51ed6bef79da7800690b19bf778b8" + integrity sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -3205,6 +4320,11 @@ unified@^10.0.0: trough "^2.0.0" vfile "^5.0.0" +uniq@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" + integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8= + unist-builder@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-3.0.0.tgz#728baca4767c0e784e1e64bb44b5a5a753021a04" @@ -3287,6 +4407,11 @@ use-subscription@1.5.1: dependencies: object-assign "^4.1.1" +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + uvu@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae" @@ -3328,6 +4453,18 @@ vfile@^5.0.0: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" +walkdir@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/walkdir/-/walkdir-0.4.1.tgz#dc119f83f4421df52e3061e514228a2db20afa39" + integrity sha512-3eBwRyEln6E1MSzcxcVpQIhRG8Q1jLvEqRmCZqS3dsfXEDR/AhOF4d+jHg1qvDCpYaVRZjENPQyrVxAkQqxPgQ== + +wcwidth@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" + integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= + dependencies: + defaults "^1.0.3" + web-namespaces@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-2.0.1.tgz#1010ff7c650eccb2592cebeeaf9a1b253fd40692" @@ -3366,7 +4503,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -word-wrap@^1.2.3: +word-wrap@^1.2.3, word-wrap@~1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== From 48a8e9f6a97f584b76f5ef426751932c22bcb7d7 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Wed, 23 Mar 2022 15:40:47 -0700 Subject: [PATCH 50/63] client: fix hydration error with placeholder for post title --- client/components/edit-document/index.tsx | 4 ++-- client/components/new-post/title/index.tsx | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/client/components/edit-document/index.tsx b/client/components/edit-document/index.tsx index 4d6ab562..0bd2ff19 100644 --- a/client/components/edit-document/index.tsx +++ b/client/components/edit-document/index.tsx @@ -82,7 +82,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' if (skeleton) { return <> <Spacer height={1} /> - <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}> + <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 'var(--main-content)', margin: "0 auto" }}> <div className={styles.fileNameContainer}> <Skeleton width={275} height={36} /> {remove && <Skeleton width={36} height={36} />} @@ -98,7 +98,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' return ( <> <Spacer height={1} /> - <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}> + <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 'var(--main-content)', margin: "0 auto" }}> <div className={styles.fileNameContainer}> <Input placeholder="MyFile.md" diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index e5b5f82f..9d4c453c 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, memo, useCallback } from 'react' +import { ChangeEvent, memo, useCallback, useEffect, useState } from 'react' import { Text } from '@geist-ui/core' import ShiftBy from '@components/shift-by' @@ -21,11 +21,16 @@ type props = { } const Title = ({ onChange, title }: props) => { + const [placeholder, setPlaceholder] = useState(titlePlaceholders[0]) + useEffect(() => { + // set random placeholder on load + setPlaceholder(titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]) + }, []) return (<div className={styles.title}> <Text h1 width={"150px"} className={styles.drift}>Drift</Text> <ShiftBy y={-3}> <Input - placeholder={titlePlaceholders[Math.floor(Math.random() * titlePlaceholders.length)]} + placeholder={placeholder} value={title || ""} onChange={onChange} height={"55px"} From 60f2ab99b3884045f3c2e8a94cebc4d39e011b95 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Wed, 23 Mar 2022 15:42:22 -0700 Subject: [PATCH 51/63] client: lint with useTabs --- client/.prettierrc | 3 +- client/components/new-post/title/index.tsx | 2 +- client/lib/generate-uuid.ts | 74 ++++++++++---------- client/lib/get-post-path.ts | 18 ++--- client/lib/hooks/use-shared-state.ts | 8 +-- client/lib/hooks/use-signed-in.ts | 46 ++++++------- client/lib/hooks/use-theme.ts | 34 ++++----- client/lib/hooks/use-trace-route.ts | 26 +++---- client/lib/time-ago.ts | 50 +++++++------- client/lib/types.d.ts | 28 ++++---- client/pages/api/markdown/[id].ts | 80 +++++++++++----------- client/pages/api/raw/[id].ts | 50 +++++++------- client/pages/api/render-markdown.ts | 62 ++++++++--------- 13 files changed, 241 insertions(+), 240 deletions(-) diff --git a/client/.prettierrc b/client/.prettierrc index c424cfdc..cda1eceb 100644 --- a/client/.prettierrc +++ b/client/.prettierrc @@ -2,5 +2,6 @@ "semi": false, "trailingComma": "none", "singleQuote": false, - "printWidth": 80 + "printWidth": 80, + "useTabs": true } diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index 9d4c453c..001752fa 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,4 +1,4 @@ -import { ChangeEvent, memo, useCallback, useEffect, useState } from 'react' +import { ChangeEvent, memo, useEffect, useState } from 'react' import { Text } from '@geist-ui/core' import ShiftBy from '@components/shift-by' diff --git a/client/lib/generate-uuid.ts b/client/lib/generate-uuid.ts index b207aa0e..9083f37f 100644 --- a/client/lib/generate-uuid.ts +++ b/client/lib/generate-uuid.ts @@ -1,39 +1,39 @@ export default function generateUUID() { - if (typeof crypto === "object") { - if (typeof crypto.randomUUID === "function") { - // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID - return crypto.randomUUID() - } - if ( - typeof crypto.getRandomValues === "function" && - typeof Uint8Array === "function" - ) { - // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid - const callback = (c: string) => { - const num = Number(c) - return ( - num ^ - (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4))) - ).toString(16) - } - return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback) - } - } - let timestamp = new Date().getTime() - let perforNow = - (typeof performance !== "undefined" && - performance.now && - performance.now() * 1000) || - 0 - return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { - let random = Math.random() * 16 - if (timestamp > 0) { - random = (timestamp + random) % 16 | 0 - timestamp = Math.floor(timestamp / 16) - } else { - random = (perforNow + random) % 16 | 0 - perforNow = Math.floor(perforNow / 16) - } - return (c === "x" ? random : (random & 0x3) | 0x8).toString(16) - }) + if (typeof crypto === "object") { + if (typeof crypto.randomUUID === "function") { + // https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID + return crypto.randomUUID() + } + if ( + typeof crypto.getRandomValues === "function" && + typeof Uint8Array === "function" + ) { + // https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid + const callback = (c: string) => { + const num = Number(c) + return ( + num ^ + (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4))) + ).toString(16) + } + return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, callback) + } + } + let timestamp = new Date().getTime() + let perforNow = + (typeof performance !== "undefined" && + performance.now && + performance.now() * 1000) || + 0 + return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { + let random = Math.random() * 16 + if (timestamp > 0) { + random = (timestamp + random) % 16 | 0 + timestamp = Math.floor(timestamp / 16) + } else { + random = (perforNow + random) % 16 | 0 + perforNow = Math.floor(perforNow / 16) + } + return (c === "x" ? random : (random & 0x3) | 0x8).toString(16) + }) } diff --git a/client/lib/get-post-path.ts b/client/lib/get-post-path.ts index 77e85b2d..5235cdfa 100644 --- a/client/lib/get-post-path.ts +++ b/client/lib/get-post-path.ts @@ -1,13 +1,13 @@ import type { PostVisibility } from "./types" export default function getPostPath(visibility: PostVisibility, id: string) { - switch (visibility) { - case "private": - return `/post/private/${id}` - case "protected": - return `/post/protected/${id}` - case "unlisted": - case "public": - return `/post/${id}` - } + switch (visibility) { + case "private": + return `/post/private/${id}` + case "protected": + return `/post/protected/${id}` + case "unlisted": + case "public": + return `/post/${id}` + } } diff --git a/client/lib/hooks/use-shared-state.ts b/client/lib/hooks/use-shared-state.ts index cc111400..c21b4915 100644 --- a/client/lib/hooks/use-shared-state.ts +++ b/client/lib/hooks/use-shared-state.ts @@ -2,10 +2,10 @@ import useSWR from "swr" // https://2020.paco.me/blog/shared-hook-state-with-swr const useSharedState = <T>(key: string, initial?: T) => { - const { data: state, mutate: setState } = useSWR(key, { - fallbackData: initial - }) - return [state, setState] as const + const { data: state, mutate: setState } = useSWR(key, { + fallbackData: initial + }) + return [state, setState] as const } export default useSharedState diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index 3f97123e..343609ba 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -4,32 +4,32 @@ import { useEffect, useState } from "react" import useSharedState from "./use-shared-state" const useSignedIn = () => { - const [signedIn, setSignedIn] = useSharedState( - "signedIn", - typeof window === "undefined" ? false : !!Cookies.get("drift-token") - ) - const token = Cookies.get("drift-token") - const router = useRouter() - const signin = (token: string) => { - setSignedIn(true) - Cookies.set("drift-token", token) - } + const [signedIn, setSignedIn] = useSharedState( + "signedIn", + typeof window === "undefined" ? false : !!Cookies.get("drift-token") + ) + const token = Cookies.get("drift-token") + const router = useRouter() + const signin = (token: string) => { + setSignedIn(true) + Cookies.set("drift-token", token) + } - const signout = () => { - setSignedIn(false) - Cookies.remove("drift-token") - router.push("/") - } + const signout = () => { + setSignedIn(false) + Cookies.remove("drift-token") + router.push("/") + } - useEffect(() => { - if (token) { - setSignedIn(true) - } else { - setSignedIn(false) - } - }, [setSignedIn, token]) + useEffect(() => { + if (token) { + setSignedIn(true) + } else { + setSignedIn(false) + } + }, [setSignedIn, token]) - return { signedIn, signin, token, signout } + return { signedIn, signin, token, signout } } export default useSignedIn diff --git a/client/lib/hooks/use-theme.ts b/client/lib/hooks/use-theme.ts index be71719d..9f08a145 100644 --- a/client/lib/hooks/use-theme.ts +++ b/client/lib/hooks/use-theme.ts @@ -2,26 +2,26 @@ import { useCallback, useEffect } from "react" import useSharedState from "./use-shared-state" const useTheme = () => { - const isClient = typeof window === "object" - const [themeType, setThemeType] = useSharedState<string>("theme", "light") + const isClient = typeof window === "object" + const [themeType, setThemeType] = useSharedState<string>("theme", "light") - useEffect(() => { - if (!isClient) return - const storedTheme = localStorage.getItem("drift-theme") - if (storedTheme) { - setThemeType(storedTheme) - } - }, [isClient, setThemeType]) + useEffect(() => { + if (!isClient) return + const storedTheme = localStorage.getItem("drift-theme") + if (storedTheme) { + setThemeType(storedTheme) + } + }, [isClient, setThemeType]) - const changeTheme = useCallback(() => { - setThemeType((last) => { - const newTheme = last === "dark" ? "light" : "dark" - localStorage.setItem("drift-theme", newTheme) - return newTheme - }) - }, [setThemeType]) + const changeTheme = useCallback(() => { + setThemeType((last) => { + const newTheme = last === "dark" ? "light" : "dark" + localStorage.setItem("drift-theme", newTheme) + return newTheme + }) + }, [setThemeType]) - return { theme: themeType, changeTheme } + return { theme: themeType, changeTheme } } export default useTheme diff --git a/client/lib/hooks/use-trace-route.ts b/client/lib/hooks/use-trace-route.ts index 863ae808..ff74bfc6 100644 --- a/client/lib/hooks/use-trace-route.ts +++ b/client/lib/hooks/use-trace-route.ts @@ -1,19 +1,19 @@ import { useRef, useEffect } from "react" function useTraceUpdate(props: { [key: string]: any }) { - const prev = useRef(props) - useEffect(() => { - const changedProps = Object.entries(props).reduce((ps, [k, v]) => { - if (prev.current[k] !== v) { - ps[k] = [prev.current[k], v] - } - return ps - }, {} as { [key: string]: any }) - if (Object.keys(changedProps).length > 0) { - console.log("Changed props:", changedProps) - } - prev.current = props - }) + const prev = useRef(props) + useEffect(() => { + const changedProps = Object.entries(props).reduce((ps, [k, v]) => { + if (prev.current[k] !== v) { + ps[k] = [prev.current[k], v] + } + return ps + }, {} as { [key: string]: any }) + if (Object.keys(changedProps).length > 0) { + console.log("Changed props:", changedProps) + } + prev.current = props + }) } export default useTraceUpdate diff --git a/client/lib/time-ago.ts b/client/lib/time-ago.ts index 9d723282..46a14e84 100644 --- a/client/lib/time-ago.ts +++ b/client/lib/time-ago.ts @@ -2,42 +2,42 @@ // which is based on https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site const epochs = [ - ["year", 31536000], - ["month", 2592000], - ["day", 86400], - ["hour", 3600], - ["minute", 60], - ["second", 1] + ["year", 31536000], + ["month", 2592000], + ["day", 86400], + ["hour", 3600], + ["minute", 60], + ["second", 1] ] as const // Get duration const getDuration = (timeAgoInSeconds: number) => { - for (let [name, seconds] of epochs) { - const interval = Math.floor(timeAgoInSeconds / seconds) + for (let [name, seconds] of epochs) { + const interval = Math.floor(timeAgoInSeconds / seconds) - if (interval >= 1) { - return { - interval: interval, - epoch: name - } - } - } + if (interval >= 1) { + return { + interval: interval, + epoch: name + } + } + } - return { - interval: 0, - epoch: "second" - } + return { + interval: 0, + epoch: "second" + } } // Calculate const timeAgo = (date: Date) => { - const timeAgoInSeconds = Math.floor( - (new Date().getTime() - new Date(date).getTime()) / 1000 - ) - const { interval, epoch } = getDuration(timeAgoInSeconds) - const suffix = interval === 1 ? "" : "s" + const timeAgoInSeconds = Math.floor( + (new Date().getTime() - new Date(date).getTime()) / 1000 + ) + const { interval, epoch } = getDuration(timeAgoInSeconds) + const suffix = interval === 1 ? "" : "s" - return `${interval} ${epoch}${suffix} ago` + return `${interval} ${epoch}${suffix} ago` } export default timeAgo diff --git a/client/lib/types.d.ts b/client/lib/types.d.ts index fec78e12..462c8803 100644 --- a/client/lib/types.d.ts +++ b/client/lib/types.d.ts @@ -1,29 +1,29 @@ export type PostVisibility = "unlisted" | "private" | "public" | "protected" export type ThemeProps = { - theme: "light" | "dark" | string - changeTheme: () => void + theme: "light" | "dark" | string + changeTheme: () => void } export type Document = { - title: string - content: string - id: string + title: string + content: string + id: string } export type File = { - id: string - title: string - content: string - html: string + id: string + title: string + content: string + html: string } type Files = File[] export type Post = { - id: string - title: string - description: string - visibility: PostVisibility - files: Files + id: string + title: string + description: string + visibility: PostVisibility + files: Files } diff --git a/client/pages/api/markdown/[id].ts b/client/pages/api/markdown/[id].ts index 8ac01ff3..cfb734f0 100644 --- a/client/pages/api/markdown/[id].ts +++ b/client/pages/api/markdown/[id].ts @@ -3,52 +3,52 @@ import type { NextApiHandler } from "next" import markdown from "@lib/render-markdown" const renderMarkdown: NextApiHandler = async (req, res) => { - const { id } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { - headers: { - Accept: "text/plain", - "x-secret-key": process.env.SECRET_KEY || "", - Authorization: `Bearer ${req.cookies["drift-token"]}` - } - }) + const { id } = req.query + const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { + headers: { + Accept: "text/plain", + "x-secret-key": process.env.SECRET_KEY || "", + Authorization: `Bearer ${req.cookies["drift-token"]}` + } + }) - const json = await file.json() - const { content, title } = json - const renderAsMarkdown = [ - "markdown", - "md", - "mdown", - "mkdn", - "mkd", - "mdwn", - "mdtxt", - "mdtext", - "text", - "" - ] - const fileType = () => { - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = "\n" + content + const json = await file.json() + const { content, title } = json + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = "\n" + content - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} ${content} ~~~` - } + } - if (typeof contentToRender !== "string") { - res.status(400).send("content must be a string") - return - } + if (typeof contentToRender !== "string") { + res.status(400).send("content must be a string") + return + } - res.setHeader("Content-Type", "text/plain") - res.setHeader("Cache-Control", "public, max-age=4800") - res.status(200).write(markdown(contentToRender)) - res.end() + res.setHeader("Content-Type", "text/plain") + res.setHeader("Cache-Control", "public, max-age=4800") + res.status(200).write(markdown(contentToRender)) + res.end() } export default renderMarkdown diff --git a/client/pages/api/raw/[id].ts b/client/pages/api/raw/[id].ts index cd665253..d07eb525 100644 --- a/client/pages/api/raw/[id].ts +++ b/client/pages/api/raw/[id].ts @@ -1,33 +1,33 @@ import { NextApiRequest, NextApiResponse } from "next" const getRawFile = async (req: NextApiRequest, res: NextApiResponse) => { - const { id, download } = req.query - const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { - headers: { - Accept: "text/plain", - "x-secret-key": process.env.SECRET_KEY || "", - Authorization: `Bearer ${req.cookies["drift-token"]}` - } - }) - const json = await file.json() - res.setHeader("Content-Type", "text/plain; charset=utf-8") - res.setHeader("Cache-Control", "s-maxage=86400") - if (file.ok) { - const data = json - const { title, content } = data - // serve the file raw as plain text + const { id, download } = req.query + const file = await fetch(`${process.env.API_URL}/files/raw/${id}`, { + headers: { + Accept: "text/plain", + "x-secret-key": process.env.SECRET_KEY || "", + Authorization: `Bearer ${req.cookies["drift-token"]}` + } + }) + const json = await file.json() + res.setHeader("Content-Type", "text/plain; charset=utf-8") + res.setHeader("Cache-Control", "s-maxage=86400") + if (file.ok) { + const data = json + const { title, content } = data + // serve the file raw as plain text - if (download) { - res.setHeader("Content-Disposition", `attachment; filename="${title}"`) - } else { - res.setHeader("Content-Disposition", `inline; filename="${title}"`) - } + if (download) { + res.setHeader("Content-Disposition", `attachment; filename="${title}"`) + } else { + res.setHeader("Content-Disposition", `inline; filename="${title}"`) + } - res.status(200).write(content, "utf-8") - res.end() - } else { - res.status(404).send("File not found") - } + res.status(200).write(content, "utf-8") + res.end() + } else { + res.status(404).send("File not found") + } } export default getRawFile diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts index 5e5e76f0..9b02b60b 100644 --- a/client/pages/api/render-markdown.ts +++ b/client/pages/api/render-markdown.ts @@ -3,41 +3,41 @@ import type { NextApiHandler } from "next" import markdown from "@lib/render-markdown" const renderMarkdown: NextApiHandler = async (req, res) => { - const { content, title } = req.body - const renderAsMarkdown = [ - "markdown", - "md", - "mdown", - "mkdn", - "mkd", - "mdwn", - "mdtxt", - "mdtext", - "text", - "" - ] - const fileType = () => { - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - } - const type = fileType() - let contentToRender: string = content || "" + const { content, title } = req.body + const renderAsMarkdown = [ + "markdown", + "md", + "mdown", + "mkdn", + "mkd", + "mdwn", + "mdtxt", + "mdtext", + "text", + "" + ] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = content || "" - if (!renderAsMarkdown.includes(type)) { - contentToRender = `~~~${type} + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} ${content} ~~~` - } else { - contentToRender = "\n" + content - } + } else { + contentToRender = "\n" + content + } - if (typeof contentToRender !== "string") { - res.status(400).send("content must be a string") - return - } - res.status(200).write(markdown(contentToRender)) - res.end() + if (typeof contentToRender !== "string") { + res.status(400).send("content must be a string") + return + } + res.status(200).write(markdown(contentToRender)) + res.end() } export default renderMarkdown From c55ca681b446584693f69e0e3c84f7db3a270350 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Wed, 23 Mar 2022 16:28:39 -0700 Subject: [PATCH 52/63] client: improve markdown rendering --- .../edit-document/document.module.css | 43 +++-- .../edit-document/formatting-icons/index.tsx | 9 - client/components/edit-document/index.tsx | 33 +--- client/components/post-list/list-item.tsx | 2 +- client/components/preview/index.tsx | 2 +- client/components/preview/preview.module.css | 63 ++++--- .../view-document/document.module.css | 43 +++-- client/components/view-document/index.tsx | 5 +- client/lib/hooks/use-debounce.ts | 18 ++ client/lib/render-markdown.tsx | 13 +- client/package.json | 1 + client/pages/_app.tsx | 6 +- client/styles/globals.css | 177 ++++++++++-------- client/styles/markdown.css | 124 ++++++------ client/yarn.lock | 5 + 15 files changed, 285 insertions(+), 259 deletions(-) create mode 100644 client/lib/hooks/use-debounce.ts diff --git a/client/components/edit-document/document.module.css b/client/components/edit-document/document.module.css index 135f89c8..98b9e9b1 100644 --- a/client/components/edit-document/document.module.css +++ b/client/components/edit-document/document.module.css @@ -1,41 +1,48 @@ +.card { + max-width: var(--main-content); + margin: var(--gap) auto; + padding: 2; + border: 1px solid var(--light-gray); +} + .input { - background: #efefef; + background: #efefef; } .descriptionContainer { - display: flex; - flex-direction: column; - min-height: 400px; - overflow: auto; + display: flex; + flex-direction: column; + min-height: 400px; + overflow: auto; } .fileNameContainer { - display: flex; - justify-content: space-between; - align-items: center; - height: 36px; + display: flex; + justify-content: space-between; + align-items: center; + height: 36px; } .fileNameContainer { - display: flex; - align-content: center; + display: flex; + align-content: center; } .fileNameContainer > div { - /* Override geist-ui styling */ - margin: 0 !important; + /* Override geist-ui styling */ + margin: 0 !important; } .textarea { - height: 100%; + height: 100%; } .actionWrapper { - position: relative; - z-index: 1; + position: relative; + z-index: 1; } .actionWrapper .actions { - position: absolute; - right: 0; + position: absolute; + right: 0; } diff --git a/client/components/edit-document/formatting-icons/index.tsx b/client/components/edit-document/formatting-icons/index.tsx index 1ef79c8e..5224466b 100644 --- a/client/components/edit-document/formatting-icons/index.tsx +++ b/client/components/edit-document/formatting-icons/index.tsx @@ -34,9 +34,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM const newText = `${before}**${selectedText}**${after}` setText(newText) - - // TODO; fails because settext async - textareaRef.current.setSelectionRange(before.length + 2, before.length + 2 + selectedText.length) } }, [setText, textareaRef]) @@ -50,8 +47,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM const selectedText = text.substring(selectionStart, selectionEnd) const newText = `${before}*${selectedText}*${after}` setText(newText) - textareaRef.current.focus() - textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length) } }, [setText, textareaRef]) @@ -71,8 +66,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM } const newText = `${before}${formattedText}${after}` setText(newText) - textareaRef.current.focus() - textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length) } }, [setText, textareaRef]) @@ -92,8 +85,6 @@ const FormattingIcons = ({ textareaRef, setText }: { textareaRef?: RefObject<HTM } const newText = `${before}${formattedText}${after}` setText(newText) - textareaRef.current.focus() - textareaRef.current.setSelectionRange(before.length + 1, before.length + 1 + selectedText.length) } }, [setText, textareaRef]) diff --git a/client/components/edit-document/index.tsx b/client/components/edit-document/index.tsx index 0bd2ff19..59990946 100644 --- a/client/components/edit-document/index.tsx +++ b/client/components/edit-document/index.tsx @@ -23,34 +23,6 @@ type Props = { remove?: () => void } -const DownloadButton = ({ rawLink }: { rawLink?: string }) => { - return (<div className={styles.actionWrapper}> - <ButtonGroup className={styles.actions}> - <Tooltip text="Download"> - <a href={`${rawLink}?download=true`} target="_blank" rel="noopener noreferrer"> - <Button - scale={2 / 3} px={0.6} - icon={<Download />} - auto - aria-label="Download" - /> - </a> - </Tooltip> - <Tooltip text="Open raw in new tab"> - <a href={rawLink} target="_blank" rel="noopener noreferrer"> - <Button - scale={2 / 3} px={0.6} - icon={<ExternalLink />} - auto - aria-label="Open raw file in new tab" - /> - </a> - </Tooltip> - </ButtonGroup> - </div>) -} - - const Document = ({ remove, title, content, setTitle, setContent, initialTab = 'edit', skeleton, handleOnContentChange }: Props) => { const codeEditorRef = useRef<HTMLTextAreaElement>(null) const [tab, setTab] = useState(initialTab) @@ -98,7 +70,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' return ( <> <Spacer height={1} /> - <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 'var(--main-content)', margin: "0 auto" }}> + <div className={styles.card}> <div className={styles.fileNameContainer}> <Input placeholder="MyFile.md" @@ -138,8 +110,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' </Tabs> </div > - </Card > - <Spacer height={1} /> + </div > </> ) } diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index d35bae10..2eef160b 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -47,7 +47,7 @@ const ListItem = ({ post }: { post: any }) => { <Divider h="1px" my={0} /> - <Card.Content > + <Card.Content> {post.files.map((file: any) => { return <FilenameInput key={file.id} title={file.title} /> })} diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index c179ad08..f301264d 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -49,7 +49,7 @@ const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { fetchPost() }, [content, fileId, title]) return (<> - {isLoading ? <div>Loading...</div> : <article data-theme={theme} className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{ + {isLoading ? <div>Loading...</div> : <article className={styles.markdownPreview} dangerouslySetInnerHTML={{ __html: preview }} style={{ height }} />} </>) diff --git a/client/components/preview/preview.module.css b/client/components/preview/preview.module.css index 7287dd4f..fe50d74e 100644 --- a/client/components/preview/preview.module.css +++ b/client/components/preview/preview.module.css @@ -1,12 +1,12 @@ .markdownPreview pre { - border-radius: 3px; - font-family: "Courier New", Courier, monospace; - font-size: 14px; - line-height: 1.42857143; - margin: 0; - padding: 10px; - white-space: pre-wrap; - word-wrap: break-word; + border-radius: 3px; + font-family: "Courier New", Courier, monospace; + font-size: 14px; + line-height: 1.42857143; + margin: 0; + padding: 10px; + white-space: pre-wrap; + word-wrap: break-word; } .markdownPreview h1, @@ -15,8 +15,8 @@ .markdownPreview h4, .markdownPreview h5, .markdownPreview h6 { - margin-top: 0; - margin-bottom: 0.5rem; + margin-top: 0; + margin-bottom: 0.5rem; } /* Auto-linked headers */ @@ -26,7 +26,7 @@ .markdownPreview h4 a, .markdownPreview h5 a, .markdownPreview h6 a { - color: inherit; + color: inherit; } /* Auto-linked headers */ @@ -36,53 +36,58 @@ .markdownPreview h4 a:hover::after, .markdownPreview h5 a:hover::after, .markdownPreview h6 a:hover::after { - content: "#"; - font-size: 0.7em; - margin-left: 0.25em; - font-weight: normal; - filter: opacity(0.5); + content: "#"; + font-size: 0.7em; + margin-left: 0.25em; + font-weight: normal; + filter: opacity(0.5); } .markdownPreview h1 { - font-size: 2rem; + font-size: 2rem; } .markdownPreview h2 { - font-size: 1.5rem; + font-size: 1.5rem; } .markdownPreview h3 { - font-size: 1.25rem; + font-size: 1.25rem; } .markdownPreview h4 { - font-size: 1rem; + font-size: 1rem; } .markdownPreview h5 { - font-size: 0.875rem; + font-size: 0.875rem; } .markdownPreview h6 { - font-size: 0.75rem; + font-size: 0.75rem; } .markdownPreview ul { - list-style: inside; + list-style: inside; } .markdownPreview ul li::before { - content: ""; + content: ""; } .markdownPreview code { - border-radius: 3px; - white-space: pre-wrap; - word-wrap: break-word; - color: inherit !important; + border-radius: 3px; + white-space: pre-wrap; + word-wrap: break-word; + color: inherit !important; } .markdownPreview code::before, .markdownPreview code::after { - content: ""; + content: ""; +} + +.markdownPreview img { + max-width: 100%; + max-height: 350px; } diff --git a/client/components/view-document/document.module.css b/client/components/view-document/document.module.css index 135f89c8..f62781cd 100644 --- a/client/components/view-document/document.module.css +++ b/client/components/view-document/document.module.css @@ -1,41 +1,40 @@ -.input { - background: #efefef; +.card { + margin: var(--gap) auto; + padding: var(--gap); + border: 1px solid var(--light-gray); + border-radius: var(--radius); } .descriptionContainer { - display: flex; - flex-direction: column; - min-height: 400px; - overflow: auto; + display: flex; + flex-direction: column; + min-height: 400px; + overflow: auto; } .fileNameContainer { - display: flex; - justify-content: space-between; - align-items: center; - height: 36px; + display: flex; + justify-content: space-between; + align-items: center; + height: 36px; } .fileNameContainer { - display: flex; - align-content: center; + display: flex; + align-content: center; } .fileNameContainer > div { - /* Override geist-ui styling */ - margin: 0 !important; -} - -.textarea { - height: 100%; + /* Override geist-ui styling */ + margin: 0 !important; } .actionWrapper { - position: relative; - z-index: 1; + position: relative; + z-index: 1; } .actionWrapper .actions { - position: absolute; - right: 0; + position: absolute; + right: 0; } diff --git a/client/components/view-document/index.tsx b/client/components/view-document/index.tsx index 213f51ea..9c21957a 100644 --- a/client/components/view-document/index.tsx +++ b/client/components/view-document/index.tsx @@ -84,7 +84,7 @@ const Document = ({ content, title, initialTab = 'edit', skeleton, id }: Props) return ( <> <Spacer height={1} /> - <Card marginBottom={'var(--gap)'} marginTop={'var(--gap)'} style={{ maxWidth: 980, margin: "0 auto" }}> + <div className={styles.card}> <div className={styles.fileNameContainer}> <Input value={title} @@ -121,8 +121,7 @@ const Document = ({ content, title, initialTab = 'edit', skeleton, id }: Props) </Tabs> </div > - </Card > - <Spacer height={1} /> + </div > </> ) } diff --git a/client/lib/hooks/use-debounce.ts b/client/lib/hooks/use-debounce.ts new file mode 100644 index 00000000..ed3e5225 --- /dev/null +++ b/client/lib/hooks/use-debounce.ts @@ -0,0 +1,18 @@ +// useDebounce.js +import { useState, useEffect } from 'react'; + +export default function useDebounce(value: any, delay: number) { + const [debouncedValue, setDebouncedValue] = useState(value); + + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(value); + }, delay); + + return () => { + clearTimeout(handler); + }; + }, [value, delay]); + + return debouncedValue; +} diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 4ddaebf4..0e1d79b6 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -1,7 +1,18 @@ -import { marked } from 'marked' +import { marked, Lexer } from 'marked' import Highlight, { defaultProps, Language, } from 'prism-react-renderer' import { renderToStaticMarkup } from 'react-dom/server' + + +// image sizes. DDoS Safe? +const imageSizeLink = /^!?\[((?:\[[^\[\]]*\]|\\[\[\]]?|`[^`]*`|[^\[\]\\])*?)\]\(\s*(<(?:\\[<>]?|[^\s<>\\])*>|(?:\\[()]?|\([^\s\x00-\x1f()\\]*\)|[^\s\x00-\x1f()\\])*?(?:\s+=(?:[\w%]+)?x(?:[\w%]+)?)?)(?:\s+("(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)))?\s*\)/; +//@ts-ignore +Lexer.rules.inline.normal.link = imageSizeLink; +//@ts-ignore +Lexer.rules.inline.gfm.link = imageSizeLink; +//@ts-ignore +Lexer.rules.inline.breaks.link = imageSizeLink; + //@ts-ignore delete defaultProps.theme // import linkStyles from '../components/link/link.module.css' diff --git a/client/package.json b/client/package.json index 68b3ff40..42b93032 100644 --- a/client/package.json +++ b/client/package.json @@ -19,6 +19,7 @@ "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", + "lodash.debounce": "^4.0.8", "marked": "^4.0.12", "next": "^12.1.1-canary.15", "postcss": "^8.4.12", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index 2a11ca68..2013345e 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -18,7 +18,7 @@ function MyApp({ Component, pageProps }: AppProps) { const skeletonHighlightColor = 'var(--lighter-gray)' return ( - <> + <div data-theme={theme}> <Head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> @@ -33,13 +33,13 @@ function MyApp({ Component, pageProps }: AppProps) { <meta name="theme-color" content="#ffffff" /> <title>Drift - + - +
) } diff --git a/client/styles/globals.css b/client/styles/globals.css index 3a5cf488..bc983b31 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -3,122 +3,145 @@ @import "./inter.css"; :root { - /* Spacing */ - --gap-quarter: 0.25rem; - --gap-half: 0.5rem; - --gap: 1rem; - --gap-double: 2rem; - --small-gap: 4rem; - --big-gap: 4rem; - --main-content: 55rem; - --radius: 8px; - --inline-radius: 5px; + /* Spacing */ + --gap-quarter: 0.25rem; + --gap-half: 0.5rem; + --gap: 1rem; + --gap-double: 2rem; + --small-gap: 4rem; + --big-gap: 4rem; + --main-content: 55rem; + --radius: 8px; + --inline-radius: 5px; - /* Typography */ - --font-sans: "Inter", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, - Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; - --font-mono: ui-monospace, "SFMono-Regular", "Consolas", "Liberation Mono", - "Menlo", monospace; + /* Typography */ + --font-sans: "Inter", -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, + Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + --font-mono: ui-monospace, "SFMono-Regular", "Consolas", "Liberation Mono", + "Menlo", monospace; - /* Transitions */ - --transition: 0.1s ease-in-out; - --transition-slow: 0.3s ease-in-out; + /* Transitions */ + --transition: 0.1s ease-in-out; + --transition-slow: 0.3s ease-in-out; - --page-nav-height: 64px; - --token: #999; - --comment: #999; - --keyword: #fff; - --name: #fff; - --highlight: #2e2e2e; + --page-nav-height: 64px; + --token: #999; + --comment: #999; + --keyword: #fff; + --name: #fff; + --highlight: #2e2e2e; + + /* Dark Mode Colors */ + --bg: #131415; + --fg: #fafbfc; + --gray: #666; + --light-gray: #444; + --lighter-gray: #222; + --lightest-gray: #1a1a1a; + --article-color: #eaeaea; + --header-bg: rgba(19, 20, 21, 0.45); + --gray-alpha: rgba(255, 255, 255, 0.5); + --selection: rgba(255, 255, 255, 0.99); } [data-theme="light"] { - --token: #666; - --comment: #999; - --keyword: #000; - --name: #333; - --highlight: #eaeaea; + --token: #666; + --comment: #999; + --keyword: #000; + --name: #333; + --highlight: #eaeaea; + + --bg: #fff; + --fg: #000; + --gray: #888; + --light-gray: #dedede; + --lighter-gray: #f5f5f5; + --lightest-gray: #fafafa; + --article-color: #212121; + --header-bg: rgba(255, 255, 255, 0.8); + --gray-alpha: rgba(19, 20, 21, 0.5); + --selection: rgba(0, 0, 0, 0.99); } * { - box-sizing: border-box; + box-sizing: border-box; } ::selection { - text-shadow: none; - background: var(--selection); + text-shadow: none; + background: var(--selection); } html, body { - padding: 0; - margin: 0; - font-size: 15px; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; + padding: 0; + margin: 0; + font-size: 15px; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } body { - min-height: 100vh; - font-family: var(--font-sans); - display: flex; - flex-direction: column; + min-height: 100vh; + font-family: var(--font-sans); + display: flex; + flex-direction: column; } p, li { - letter-spacing: -0.33px; - font-size: 1.125rem; + letter-spacing: -0.33px; + font-size: 1.125rem; } blockquote { - font-style: italic; - margin: 0; - padding-left: 1rem; - border-left: 3px solid var(--light-gray); + font-style: italic; + margin: 0; + padding-left: 1rem; + border-left: 3px solid var(--light-gray); } a.reset { - outline: none; - text-decoration: none; + outline: none; + text-decoration: none; } pre, code { - font-family: var(--font-mono) !important; + font-family: var(--font-mono) !important; } kbd { - font-family: var(--font-sans); - font-size: 1rem; - padding: 2px 7px; - font-weight: 600; - background: var(--lighter-gray); - border-radius: 5px; + font-family: var(--font-sans); + font-size: 1rem; + padding: 2px 7px; + font-weight: 600; + background: var(--lighter-gray); + border-radius: 5px; } @media print { - :root { - --bg: #fff; - --fg: #000; - --gray: #888; - --light-gray: #dedede; - --lighter-gray: #f5f5f5; - --lightest-gray: #fafafa; - --article-color: #212121; - --header-bg: rgba(255, 255, 255, 0.8); - --gray-alpha: rgba(19, 20, 21, 0.5); - --selection: rgba(0, 0, 0, 0.99); + :root { + --bg: #fff; + --fg: #000; + --gray: #888; + --light-gray: #dedede; + --lighter-gray: #f5f5f5; + --lightest-gray: #fafafa; + --article-color: #212121; + --header-bg: rgba(255, 255, 255, 0.8); + --gray-alpha: rgba(19, 20, 21, 0.5); + --selection: rgba(0, 0, 0, 0.99); - --token: #666; - --comment: #999; - --keyword: #000; - --name: #333; - --highlight: #eaeaea; - } + --token: #666; + --comment: #999; + --keyword: #000; + --name: #333; + --highlight: #eaeaea; + } - * { - text-shadow: none !important; - } + * { + text-shadow: none !important; + } } diff --git a/client/styles/markdown.css b/client/styles/markdown.css index 670d20d5..b95ae64d 100644 --- a/client/styles/markdown.css +++ b/client/styles/markdown.css @@ -1,129 +1,125 @@ article { - max-width: var(--main-content); - margin: 0 auto; - line-height: 1.9; + max-width: var(--main-content); + margin: 0 auto; + line-height: 1.9; } article > * + * { - margin-top: 2em; + margin-top: 2em; } article img { - max-width: 100%; - /* width: var(--main-content); */ - width: auto; - margin: auto; - display: block; - border-radius: var(--radius); + max-width: 100%; + margin: auto; } article [id]::before { - content: ""; - display: block; - height: 70px; - margin-top: -70px; - visibility: hidden; + content: ""; + display: block; + height: 70px; + margin-top: -70px; + visibility: hidden; } /* Lists */ article ul { - padding: 0; - list-style-position: inside; - list-style-type: circle; + padding: 0; + list-style-position: inside; + list-style-type: circle; } article ol { - padding: 0; - list-style-position: inside; + padding: 0; + list-style-position: inside; } article ul li.reset { - display: flex; - align-items: flex-start; + display: flex; + align-items: flex-start; - list-style-type: none; - margin-left: -0.5rem; + list-style-type: none; + margin-left: -0.5rem; } article ul li.reset .check { - display: flex; - align-items: center; - margin-right: 0.51rem; + display: flex; + align-items: center; + margin-right: 0.51rem; } /* Checkbox */ input[type="checkbox"] { - vertical-align: middle; - appearance: none; - display: inline-block; - background-origin: border-box; - user-select: none; - flex-shrink: 0; - height: 1rem; - width: 1rem; - background-color: var(--bg); - color: var(--fg); - border: 1px solid var(--fg); - border-radius: 3px; + vertical-align: middle; + appearance: none; + display: inline-block; + background-origin: border-box; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + background-color: var(--bg); + color: var(--fg); + border: 1px solid var(--fg); + border-radius: 3px; } input[type="checkbox"]:checked { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); - border-color: transparent; - background-color: currentColor; - background-size: 100% 100%; - background-position: center; - background-repeat: no-repeat; + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; } html[data-theme="light"] input[type="checkbox"]:checked { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M5.707 7.293a1 1 0 0 0-1.414 1.414l2 2a1 1 0 0 0 1.414 0l4-4a1 1 0 0 0-1.414-1.414L7 8.586 5.707 7.293z'/%3e%3c/svg%3e"); } input[type="checkbox"]:focus { - outline: none; - border-color: var(--fg); + outline: none; + border-color: var(--fg); } /* Code Snippets */ .token-line:not(:last-child) { - min-height: 1.4rem; + min-height: 1.4rem; } article *:not(pre) > code { - font-weight: 500; - font-family: var(--font-sans); + font-weight: 500; + font-family: var(--font-sans); } article li > p { - font-family: var(--font-mono); - display: inline-block; + font-family: var(--font-mono); + display: inline-block; } article pre { - overflow-x: auto; - border-radius: var(--inline-radius); - line-height: 1.8; - padding: 1rem; - font-size: 0.875rem; + overflow-x: auto; + border-radius: var(--inline-radius); + line-height: 1.8; + padding: 1rem; + font-size: 0.875rem; } /* Linkable Headers */ .header-link { - color: inherit; - text-decoration: none; + color: inherit; + text-decoration: none; } .header-link::after { - opacity: 0; - content: "#"; - margin-left: var(--gap-half); + opacity: 0; + content: "#"; + margin-left: var(--gap-half); } .header-link:hover::after { - opacity: 1; + opacity: 1; } diff --git a/client/yarn.lock b/client/yarn.lock index 80eced45..145dc566 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -2278,6 +2278,11 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" From 7ca0cbac4cc73de3c8086a56b42f6bb56fcbf9ab Mon Sep 17 00:00:00 2001 From: Max Leiter Date: Wed, 23 Mar 2022 16:42:56 -0700 Subject: [PATCH 53/63] client: set post title if text pasted w/o title set --- .../components/edit-document-list/index.tsx | 4 +++- .../edit-document/document.module.css | 4 ++-- client/components/edit-document/index.tsx | 14 +++++++---- client/components/new-post/index.tsx | 24 +++++++++++++++++-- .../components/view-document-list/index.tsx | 2 +- client/components/view-document/index.tsx | 11 +++++---- client/lib/render-markdown.tsx | 18 +++++++------- server/src/lib/render-markdown.tsx | 11 +++++++-- 8 files changed, 61 insertions(+), 27 deletions(-) diff --git a/client/components/edit-document-list/index.tsx b/client/components/edit-document-list/index.tsx index 49144473..9822b916 100644 --- a/client/components/edit-document-list/index.tsx +++ b/client/components/edit-document-list/index.tsx @@ -2,11 +2,12 @@ import type { Document } from "@lib/types" import DocumentComponent from "@components/edit-document" import { ChangeEvent, memo, useCallback } from "react" -const DocumentList = ({ docs, removeDoc, updateDocContent, updateDocTitle }: { +const DocumentList = ({ docs, removeDoc, updateDocContent, updateDocTitle, onPaste }: { docs: Document[], updateDocTitle: (i: number) => (title: string) => void updateDocContent: (i: number) => (content: string) => void removeDoc: (i: number) => () => void + onPaste: (e: any) => void }) => { const handleOnChange = useCallback((i) => (e: ChangeEvent) => { updateDocContent(i)(e.target.value) @@ -16,6 +17,7 @@ const DocumentList = ({ docs, removeDoc, updateDocContent, updateDocTitle }: { docs.map(({ content, id, title }, i) => { return ( void + onPaste?: (e: any) => void } -const Document = ({ remove, title, content, setTitle, setContent, initialTab = 'edit', skeleton, handleOnContentChange }: Props) => { +const Document = ({ onPaste, remove, title, content, setTitle, setContent, initialTab = 'edit', skeleton, handleOnContentChange }: Props) => { const codeEditorRef = useRef(null) const [tab, setTab] = useState(initialTab) // const height = editable ? "500px" : '100%' @@ -54,7 +55,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' if (skeleton) { return <> - +
{remove && } @@ -63,7 +64,7 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = '
- +
} @@ -90,8 +91,9 @@ const Document = ({ remove, title, content, setTitle, setContent, initialTab = ' {/* */} -
+
*/} -
+