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 && } auto height={'36px'} width={'36px'} onClick={() => removeFile(remove)} />}
diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx
index 3e9e8d26..30c37ecd 100644
--- a/client/components/post-list/list-item.tsx
+++ b/client/components/post-list/list-item.tsx
@@ -30,17 +30,17 @@ const ListItem = ({ post }: { post: any }) => {
return (
-
-
-
+
+
+
{post.title}
- {time}
- {post.files.length === 1 ? "1 file" : `${post.files.length} files`}
+ {time}
+ {post.files.length === 1 ? "1 file" : `${post.files.length} files`}
diff --git a/client/components/preview/preview.module.css b/client/components/preview/preview.module.css
index 6250035c..bfd181da 100644
--- a/client/components/preview/preview.module.css
+++ b/client/components/preview/preview.module.css
@@ -87,8 +87,6 @@
border-radius: 3px;
white-space: pre-wrap;
word-wrap: break-word;
- color: initial;
- background-color: #f5f5f5;
padding: 2px 4px;
}
diff --git a/client/components/preview/react-markdown-preview.tsx b/client/components/preview/react-markdown-preview.tsx
index fc4e6416..92d9963d 100644
--- a/client/components/preview/react-markdown-preview.tsx
+++ b/client/components/preview/react-markdown-preview.tsx
@@ -36,7 +36,7 @@ const ReactMarkdownPreview = ({ content, height }: Props) => {
background: 'transparent'
}}
codeTagProps={{
- style: { background: 'transparent' }
+ style: { background: 'transparent', color: 'inherit' }
}}
style={themeType === 'dark' ? dark : light}
showLineNumbers={true}
diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx
index 115b69fb..0bd32738 100644
--- a/client/pages/post/[id].tsx
+++ b/client/pages/post/[id].tsx
@@ -8,7 +8,6 @@ import Header from "../../components/header";
import VisibilityBadge from "../../components/visibility-badge";
import { ThemeProps } from "../_app";
import PageSeo from "components/page-seo";
-import Head from "next/head";
import styles from './styles.module.css';
import Cookies from "js-cookie";
@@ -80,14 +79,18 @@ const Post = ({ theme, changeTheme }: ThemeProps) => {
+ {/* {!isLoading && } */}
+
{error && {error} }
- {/* {!error && (isLoading || !post?.files) && } */}
{!error && isLoading && <>
>}
{!isLoading && post && <>
-
{post.title}
+
+ {post.title}
+
+
Download as ZIP archive
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 &&
{/* fileRejections.splice(0, fileRejections.length)} auto iconRight={ }> */}
- There was a problem with some of your files.
+ There was a problem with one or more of your files.
{fileRejectionItems}
}
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 (
-
-
+
setTitle(e.target.value)} />
+
{
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) => void
title?: string
}
-const Title = ({ setTitle, title }: props) => {
+const Title = ({ handleChange, title }: props) => {
+
return (
Drift
setTitle(event.target.value)}
+ onChange={handleChange}
height={"55px"}
font={1.5}
label="Post title"
@@ -35,4 +36,4 @@ const Title = ({ setTitle, title }: props) => {
)
}
-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
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 (
-
setTitle(e.target.value)} />
+
{
- docs.map(({ id }) => {
- const doc = docs.find((doc) => doc.id === id)
+ docs.map(({ content, id, title }) => {
return (
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) => void
+ setTitle: (title: string) => void
title?: string
}
-const Title = ({ handleChange, title }: props) => {
+const Title = ({ setTitle, title }: props) => {
return (
Drift
@@ -26,7 +26,7 @@ const Title = ({ handleChange, title }: props) => {
setTitle(event.target.value)}
height={"55px"}
font={1.5}
label="Post title"
@@ -36,4 +36,4 @@ const Title = ({ handleChange, title }: props) => {
)
}
-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
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 (
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
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
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) => {
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 && setServerPassword(event.target.value)}
+ placeholder="Server Password"
+ required
+ scale={4 / 3}
+ />}
+
{signingIn ? 'Sign In' : 'Sign Up'}
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
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
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(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) => {
-
From abe419dabac3875955b20da691839aad08e85cfd Mon Sep 17 00:00:00 2001
From: Max Leiter
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
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(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([])
useEffect(() => {
@@ -42,7 +41,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => {
name: "Home",
href: "/",
icon: ,
- condition: true,
+ condition: !isSignedIn,
value: "home"
},
{
@@ -59,34 +58,9 @@ const Header = ({ changeTheme, theme }: DriftProps) => {
condition: isSignedIn,
value: "mine"
},
- // {
- // name: "Settings",
- // href: "/settings",
- // icon: ,
- // 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: ,
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 {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 (
@@ -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
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();
+ const [selectedTab, setSelectedTab] = useState(router.pathname === '/' ? 'home' : router.pathname.split('/')[1]);
const [expanded, setExpanded] = useState(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' ? : ,
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
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()
- const [docs, setDocs] = useState([{
+ const [docs, setDocs] = useState([{
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 (
- remove(id)}
key={id}
editable={true}
@@ -120,10 +148,12 @@ const Post = () => {
onSubmit('private')}>Create Private
onSubmit('public')} >Create Public
onSubmit('unlisted')} >Create Unlisted
+ onSubmit('protected')} >Create with Password
+ onSubmit('protected', password)} />
)
}
-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()
+ const [confirmPassword, setConfirmPassword] = useState()
+ const [error, setError] = useState()
+
+ const onSubmit = () => {
+ if (!password || !confirmPassword) {
+ setError('Please enter a password')
+ return
+ }
+
+ if (password !== confirmPassword) {
+ setError("Passwords do not match")
+ return
+ }
+
+ onSubmitAfterVerify(password)
+ }
+
+ return (<>
+ {
+ Enter a password
+
+ {!error &&
+ This doesn't protect your post from the server administrator.
+ }
+ {error &&
+ {error}
+ }
+
+ setPassword(e.target.value)} />
+ setConfirmPassword(e.target.value)} />
+
+ Cancel
+ Submit
+ }
+ >)
+}
+
+
+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 = {
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()
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) => (
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) => (
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
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()
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
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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
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((resolve, reject) => {
+ return new Promise((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 = {
pageProps: P;
@@ -22,11 +17,10 @@ type AppProps
= {
export type DriftProps = ThemeProps
function MyApp({ Component, pageProps }: AppProps) {
- const [themeType, setThemeType] = useSharedState('theme', 'light')
- const theme = useTheme();
+ const [themeType, setThemeType] = useSharedState('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()
+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 (
- {!isLoading && (
-
- )}
+
{/* {!isLoading && } */}
-
- {error && {error} }
- {!error && isLoading && <>
-
- >}
- {!isLoading && post && <>
-
-
- {post.title}
-
-
-
- Download as ZIP archive
-
+
+
+ {post.title}
+
- {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
-
- ))}
- >}
+
+ Download as ZIP archive
+
+
+ {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
+
+ ))}
)
}
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
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
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
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: ,
+ icon: ,
condition: isSignedIn,
value: "signout"
},
@@ -94,7 +104,7 @@ const Header = ({ changeTheme, theme }: DriftProps) => {
setSelectedTab('');
}
},
- icon: theme === 'light' ? : ,
+ icon: theme === 'light' ? : ,
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 = () => {
onSubmit('protected', password)} />
-
+
)
}
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('theme')
return (
{
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
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
+const MyPosts = ({ posts, error }: { posts: any, error: any }) => {
+ return
}
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 }) => {
-
+
{post.title}
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 (
-
+
)
}
+// 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) => {
@@ -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 (
+
+
+
+
+
+
+
+ {/* {!isLoading && } */}
+
+
+ {post.title}
+
+
+
+ Download as ZIP archive
+
+
+ {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
+
+ ))}
+
+
+ )
+}
+
+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
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
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
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(router.pathname === '/' ? 'home' : router.pathname.split('/')[1]);
const [expanded, setExpanded] = useState(false)
const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true })
const isMobile = useMediaQuery('xs', { match: 'down' })
- const isSignedIn = useSignedIn()
+ const { signedIn: isSignedIn } = useSignedIn()
const [pages, setPages] = useState([])
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()
const [confirmPassword, setConfirmPassword] = useState()
const [error, setError] = useState()
@@ -30,7 +31,7 @@ const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify }: Props
{
Enter a password
- {!error &&
+ {!error && warning &&
This doesn't protect your post from the server administrator.
}
{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 = {
pageProps: P;
} & Omit, "pageProps">;
-export type DriftProps = ThemeProps
function MyApp({ Component, pageProps }: AppProps) {
const [themeType, setThemeType] = useSharedState('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()
+ 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
+ }
+
+ return (
+
+
+
+
+
+
+ {/* {!isLoading && } */}
+
+
+ {post.title}
+
+
+
+ Download as ZIP archive
+
+
+ {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
+
+ ))}
+
+
+ )
+}
+
+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
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()
const [confirmPassword, setConfirmPassword] = useState()
const [error, setError] = useState()
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
{
Enter a password
- {!error && warning &&
+ {!error && creating &&
This doesn't protect your post from the server administrator.
}
{error &&
@@ -39,7 +39,7 @@ const PasswordModal = ({ isOpen, onClose, onSubmit: onSubmitAfterVerify, warning
}
setPassword(e.target.value)} />
- setConfirmPassword(e.target.value)} />
+ {creating && setConfirmPassword(e.target.value)} />}
Cancel
Submit
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
+ return
}
return (
From 266848e6b2d0dcf47de685c00473e7eb20c9e17c Mon Sep 17 00:00:00 2001
From: Max Leiter
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
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 = () => (
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 (
+
+
+
+
+
+
+
+ {/* {!isLoading && } */}
+
+
+ {post.title}
+
+
+
+ Download as ZIP archive
+
+
+ {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
+
+ ))}
+
+
+ )
+}
+
+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 = {
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 (
@@ -18,7 +15,7 @@ const New = ({ theme, changeTheme }: ThemeProps) => {
- {isSignedIn && }
+
)
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 (
-
-
-
-
-
-
-
- {/* {!isLoading && } */}
-
-
- {post.title}
-
-
-
- Download as ZIP archive
-
-
- {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
-
- ))}
-
-
- )
+const PostView = ({ post, theme, changeTheme }: PostProps) => {
+ return
}
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 (
-
-
-
-
-
-
-
- {/* {!isLoading && } */}
-
-
- {post.title}
-
-
-
- Download as ZIP archive
-
-
- {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
-
- ))}
-
-
- )
+ return ( )
}
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()
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
}
- return (
-
-
-
-
-
-
- {/* {!isLoading && } */}
-
-
- {post.title}
-
-
-
- Download as ZIP archive
-
-
- {post.files.map(({ id, content, title }: { id: any, content: string, title: string }) => (
-
- ))}
-
-
- )
+ return ( )
}
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) => (
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) => (
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
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
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('theme')
+
return (
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) => {
+ updateDocContent(i)(e.target.value)
+ }, [updateDocContent])
+
+ return (<>{
+ docs.map(({ content, id, title }, i) => {
+ return (
+
+ )
+ })
+ }
+ >)
+}
+
+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) => 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(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) => 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
>
}
+
+
return (
<>
@@ -122,7 +127,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init
) => 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 && } auto height={'36px'} width={'36px'} onClick={() => removeFile(remove)} />}
+ {remove && editable && } auto height={'36px'} width={'36px'} onClick={removeFile} />}
{tab === 'edit' && editable && }
- {rawLink && }
+ {rawLink && }
{/* */}
@@ -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
-
+
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) => {
+ 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 (
-
+
- {
- docs.map(({ content, id, title }) => {
- return (
-
remove(id)}
- key={id}
- editable={true}
- setContent={(content) => updateContent(content, id)}
- setTitle={(title) => updateTitle(title, id)}
- content={content}
- title={title}
- />
- )
- })
- }
-
+
void
+ onChange: (e: ChangeEvent) => void
title?: string
}
-const Title = ({ setTitle, title }: props) => {
-
+const Title = ({ onChange, title }: props) => {
return (
Drift
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 (
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
+
+
+ {text}
+
+
+ )
+}
+
+renderer.link = (href, _, text) => {
+ const isHrefLocal = href?.startsWith('/') || href?.startsWith('#')
+ if (isHrefLocal) {
+ return renderToStaticMarkup(
+
+ {text}
+
+ )
+ }
+ return `${text} `
+}
+
+renderer.image = function (href, _, text) {
+ return ` `
+}
+
+renderer.checkbox = () => ''
+renderer.listitem = (text, task, checked) => {
+ if (task) {
+ return ` ${text} `
+ }
+
+ return `${text} `
+}
+
+renderer.code = (code: string, language: string) => {
+ return renderToStaticMarkup(
+
+ {/* {title && {title}
} */}
+ {/* {language && title && {language}
} */}
+
+
+ )
+}
+
+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 (
+ <>
+
+ >
+ )
+
+ 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 (
+ <>
+
+ {({ className, style, tokens, getLineProps, getTokenProps }) => (
+
+ {
+ tokens.map((line, i) => (
+
+ {
+ line.map((token, key) => (
+
+ ))
+ }
+
+ ))}
+
+ )}
+
+ >
+ )
+}
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
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, keyof Props>
+type ButtonDropdownProps = Props & Attrs
+
+const ButtonDropdown: React.FC> = ({
+ type,
+ className,
+ disabled,
+ loading,
+ iconHeight = 24,
+ ...props
+}) => {
+ const [visible, setVisible] = useState(false)
+ const [dropdown, setDropdown] = useState(null)
+
+ const onClick = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ e.nativeEvent.stopImmediatePropagation()
+ setVisible(!visible)
+ }
+
+ const onBlur = () => {
+ setVisible(false)
+ }
+
+ const onMouseDown = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ e.nativeEvent.stopImmediatePropagation()
+ }
+
+ const onMouseUp = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ e.nativeEvent.stopImmediatePropagation()
+ }
+
+ const onMouseLeave = (e: React.MouseEvent) => {
+ e.stopPropagation()
+ e.nativeEvent.stopImmediatePropagation()
+ setVisible(false)
+ }
+
+ const onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Escape") {
+ setVisible(false)
+ }
+ }
+
+ const onClickOutside = useCallback(() => (e: React.MouseEvent) => {
+ 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 (
+
+
+ {props.children[0]}
+ setVisible(!visible)}>
+
+ {
+ visible && (
+
+
+ {props.children.slice(1)}
+
+
+
+ )
+ }
+
+ )
+
+
+
+}
+
+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 & {
+ children: React.ReactNode
+ buttonType?: 'primary' | 'secondary'
+ className?: string
+ onClick?: (e: React.MouseEvent) => void
+}
+
+// eslint-disable-next-line react/display-name
+const Button = forwardRef(
+ ({ children, onClick, className, buttonType = 'primary', type = 'button', disabled = false, ...props }, ref) => {
+ return (
+
+ {children}
+
+ )
+ }
+)
+
+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) => 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
{tab === 'edit' && editable &&
}
- {rawLink &&
}
+ {rawLink && id &&
}
{/* */}
-