From 34b1ab979f9dcef6a529623c4de4e7d8dc3583f8 Mon Sep 17 00:00:00 2001 From: Max Leiter <maxwell.leiter@gmail.com> Date: Tue, 22 Mar 2022 20:06:15 -0700 Subject: [PATCH] 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 9f2e376..e96f89b 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 43b1d28..5f48f3c 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 0000000..dd03da0 --- /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 0000000..7000059 --- /dev/null +++ b/client/components/button-dropdown/index.tsx @@ -0,0 +1,116 @@ +import Button from "@components/button" +import React, { useCallback, useEffect } from "react" +import { useState } from "react" +import styles from './dropdown.module.css' +import DownIcon from '@geist-ui/icons/arrowDown' +type Props = { + type?: "primary" | "secondary" + loading?: boolean + disabled?: boolean + className?: string + iconHeight?: number +} + +type Attrs = Omit<React.HTMLAttributes<any>, keyof Props> +type ButtonDropdownProps = Props & Attrs + +const ButtonDropdown: React.FC<React.PropsWithChildren<ButtonDropdownProps>> = ({ + type, + className, + disabled, + loading, + iconHeight = 24, + ...props +}) => { + const [visible, setVisible] = useState(false) + const [dropdown, setDropdown] = useState<HTMLDivElement | null>(null) + + const onClick = (e: React.MouseEvent<HTMLButtonElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + setVisible(!visible) + } + + const onBlur = () => { + setVisible(false) + } + + const onMouseDown = (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + } + + const onMouseUp = (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + } + + const onMouseLeave = (e: React.MouseEvent<HTMLDivElement>) => { + e.stopPropagation() + e.nativeEvent.stopImmediatePropagation() + setVisible(false) + } + + const onKeyDown = (e: React.KeyboardEvent<HTMLDivElement>) => { + if (e.key === "Escape") { + setVisible(false) + } + } + + const onClickOutside = useCallback(() => (e: React.MouseEvent<HTMLDivElement>) => { + if (dropdown && !dropdown.contains(e.target as Node)) { + setVisible(false) + } + }, [dropdown]) + + useEffect(() => { + if (visible) { + document.addEventListener("mousedown", onClickOutside) + } else { + document.removeEventListener("mousedown", onClickOutside) + } + + return () => { + document.removeEventListener("mousedown", onClickOutside) + } + }, [visible, onClickOutside]) + + if (!Array.isArray(props.children)) { + return null + } + + return ( + <div + className={`${styles.main} ${className}`} + onMouseDown={onMouseDown} + onMouseUp={onMouseUp} + onMouseLeave={onMouseLeave} + onKeyDown={onKeyDown} + onBlur={onBlur} + > + <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'flex-end' }}> + {props.children[0]} + <Button style={{ height: iconHeight, width: iconHeight }} className={styles.icon} onClick={() => setVisible(!visible)}><DownIcon /></Button> + </div> + { + visible && ( + <div + className={`${styles.dropdown}`} + > + <div + className={`${styles.dropdownContent}`} + > + {props.children.slice(1)} + + </div> + </div> + ) + } + </div > + ) + + + +} + +export default ButtonDropdown \ No newline at end of file diff --git a/client/components/button/button.module.css b/client/components/button/button.module.css new file mode 100644 index 0000000..381f396 --- /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 0000000..0e85a79 --- /dev/null +++ b/client/components/button/index.tsx @@ -0,0 +1,28 @@ +import styles from './button.module.css' +import { forwardRef, Ref } from 'react' + +type Props = React.HTMLProps<HTMLButtonElement> & { + children: React.ReactNode + buttonType?: 'primary' | 'secondary' + className?: string + onClick?: (e: React.MouseEvent<HTMLButtonElement>) => void +} + +// eslint-disable-next-line react/display-name +const Button = forwardRef<HTMLButtonElement, Props>( + ({ children, onClick, className, buttonType = 'primary', type = 'button', disabled = false, ...props }, ref) => { + return ( + <button + ref={ref} + className={`${styles.button} ${styles[type]} ${className}`} + disabled={disabled} + onClick={onClick} + {...props} + > + {children} + </button> + ) + } +) + +export default Button diff --git a/client/components/document/formatting-icons/index.tsx b/client/components/document/formatting-icons/index.tsx index 279162d..1ef79c8 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 b34f1b1..d8200c8 100644 --- a/client/components/document/index.tsx +++ b/client/components/document/index.tsx @@ -1,11 +1,4 @@ -import Button from "@geist-ui/core/dist/button" -import Card from "@geist-ui/core/dist/card" -import ButtonGroup from "@geist-ui/core/dist/button-group" -import Input from "@geist-ui/core/dist/input" -import Spacer from "@geist-ui/core/dist/spacer" -import Tabs from "@geist-ui/core/dist/tabs" -import Textarea from "@geist-ui/core/dist/textarea" -import Tooltip from "@geist-ui/core/dist/tooltip" + import { ChangeEvent, memo, useCallback, useMemo, useRef, useState } from "react" import styles from './document.module.css' @@ -15,9 +8,8 @@ import ExternalLink from '@geist-ui/icons/externalLink' import FormattingIcons from "./formatting-icons" import Skeleton from "react-loading-skeleton" -import dynamic from "next/dynamic"; - -const MarkdownPreview = dynamic(() => import("../preview")) +import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } from "@geist-ui/core" +import Preview from "@components/preview" // import Link from "next/link" type Props = { @@ -74,13 +66,6 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init setTab(newTab as 'edit' | 'preview') } - const getType = useCallback(() => { - if (!title) return - const pathParts = title.split(".") - const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" - return language - }, [title]) - const onTitleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null, [setTitle]) const removeFile = useCallback(() => (remove?: () => void) => { @@ -140,14 +125,14 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </div> <div className={styles.descriptionContainer}> {tab === 'edit' && editable && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />} - {rawLink && <DownloadButton rawLink={rawLink()} />} + {rawLink && id && <DownloadButton rawLink={rawLink()} />} <Tabs onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}> <Tabs.Item label={editable ? "Edit" : "Raw"} value="edit"> {/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */} - <div style={{ display: 'flex', flexDirection: 'column' }}> + <div style={{ marginTop: 'var(--gap)', display: 'flex', flexDirection: 'column' }}> <Textarea ref={codeEditorRef} - placeholder="Type some contents..." + placeholder="" value={content} onChange={handleOnContentChange} width="100%" @@ -160,7 +145,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init </div> </Tabs.Item> <Tabs.Item label="Preview" value="preview"> - <MarkdownPreview height={height} content={content} type={getType()} /> + <Preview height={height} fileId={id} title={title} content={content} /> </Tabs.Item> </Tabs> diff --git a/client/components/head/index.tsx b/client/components/head/index.tsx new file mode 100644 index 0000000..1bc53d0 --- /dev/null +++ b/client/components/head/index.tsx @@ -0,0 +1,27 @@ +import Head from "next/head"; +import React from "react"; + +type PageSeoProps = { + title?: string; + description?: string; + isLoading?: boolean; + isPrivate?: boolean +}; + +const PageSeo = ({ + title = 'Drift', + description = "A self-hostable clone of GitHub Gist", + isPrivate = false +}: PageSeoProps) => { + + return ( + <> + <Head> + <title>{title}</title> + {!isPrivate && <meta name="description" content={description} />} + </Head> + </> + ); +}; + +export default PageSeo; diff --git a/client/components/header/controls.tsx b/client/components/header/controls.tsx index 6d4ee88..09b4afa 100644 --- a/client/components/header/controls.tsx +++ b/client/components/header/controls.tsx @@ -1,19 +1,16 @@ import React from 'react' import MoonIcon from '@geist-ui/icons/moon' import SunIcon from '@geist-ui/icons/sun' -import Select from '@geist-ui/core/dist/select' // import { useAllThemes, useTheme } from '@geist-ui/core' import styles from './header.module.css' import { ThemeProps } from '@lib/types' -import Cookies from 'js-cookie' +import { Select } from '@geist-ui/core' const Controls = ({ changeTheme, theme }: ThemeProps) => { - const switchThemes = (type: string | string[]) => { + const switchThemes = () => { changeTheme() - Cookies.set('drift-theme', Array.isArray(type) ? type[0] : type) } - return ( <div className={styles.wrapper}> <Select diff --git a/client/components/header/header.tsx b/client/components/header/header.tsx new file mode 100644 index 0000000..4f07b54 --- /dev/null +++ b/client/components/header/header.tsx @@ -0,0 +1,179 @@ + +import { ButtonGroup, Page, Spacer, Tabs, useBodyScroll, useMediaQuery, } from "@geist-ui/core"; + +import { useCallback, 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'; +import type { ThemeProps } from "@lib/types"; +import useTheme from "@lib/hooks/use-theme"; +import { Button } from "@geist-ui/core"; + +type Tab = { + name: string + icon: JSX.Element + condition?: boolean + value: string + onClick?: () => void + href?: string +} + + +const Header = () => { + const router = useRouter(); + const [selectedTab, setSelectedTab] = useState<string>(router.pathname === '/' ? 'home' : router.pathname.split('/')[1]); + const [expanded, setExpanded] = useState<boolean>(false) + const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) + const isMobile = useMediaQuery('xs', { match: 'down' }) + const { signedIn: isSignedIn } = useSignedIn() + const [pages, setPages] = useState<Tab[]>([]) + const { changeTheme, theme } = useTheme() + useEffect(() => { + setBodyHidden(expanded) + }, [expanded, setBodyHidden]) + + useEffect(() => { + if (!isMobile) { + setExpanded(false) + } + }, [isMobile]) + + useEffect(() => { + const pageList: Tab[] = [ + { + name: "Home", + href: "/", + icon: <HomeIcon />, + condition: !isSignedIn, + value: "home" + }, + { + name: "New", + href: "/new", + icon: <NewIcon />, + condition: isSignedIn, + value: "new" + }, + { + name: "Yours", + href: "/mine", + icon: <YourIcon />, + condition: isSignedIn, + value: "mine" + }, + { + name: "Sign out", + href: "/signout", + icon: <SignOutIcon />, + condition: isSignedIn, + value: "signout" + }, + { + name: "Sign in", + href: "/signin", + icon: <SignInIcon />, + condition: !isSignedIn, + value: "signin" + }, + { + name: "Sign up", + href: "/signup", + icon: <SignUpIcon />, + condition: !isSignedIn, + value: "signup" + }, + { + name: isMobile ? "GitHub" : "", + href: "https://github.com/maxleiter/drift", + icon: <GitHubIcon />, + condition: true, + value: "github" + }, + { + name: isMobile ? "Change theme" : "", + onClick: function () { + if (typeof window !== 'undefined') { + changeTheme(); + } + }, + icon: theme === 'light' ? <MoonIcon /> : <SunIcon />, + condition: true, + value: "theme", + } + ] + + setPages(pageList.filter(page => page.condition)) + }, [changeTheme, isMobile, isSignedIn, theme]) + + + const onTabChange = useCallback((tab: string) => { + if (typeof window === 'undefined') return + const match = pages.find(page => page.value === tab) + if (match?.onClick) { + match.onClick() + } else { + router.push(match?.href || '/') + } + }, [pages, router]) + + + return ( + <Page.Header height={'var(--page-nav-height)'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"}> + <div className={styles.tabs}> + <Tabs + value={selectedTab} + leftSpace={0} + align="center" + hideDivider + hideBorder + onChange={onTabChange}> + {pages.map((tab) => { + return <Tabs.Item + font="14px" + label={<>{tab.icon} {tab.name}</>} + value={tab.value} + key={`${tab.value}`} + /> + })} + </Tabs> + </div> + <div className={styles.controls}> + <Button + auto + type="abort" + onClick={() => setExpanded(!expanded)} + aria-label="Menu" + > + <Spacer height={5 / 6} width={0} /> + <MenuIcon /> + </Button> + </div> + {isMobile && expanded && (<div className={styles.mobile}> + <ButtonGroup vertical> + {pages.map((tab, index) => { + return <Button + key={`${tab.name}-${index}`} + onClick={() => onTabChange(tab.value)} + icon={tab.icon} + > + {tab.name} + </Button> + })} + </ButtonGroup> + </div>)} + </Page.Header > + ) +} + +export default Header diff --git a/client/components/header/index.tsx b/client/components/header/index.tsx index 7dec087..b8fe948 100644 --- a/client/components/header/index.tsx +++ b/client/components/header/index.tsx @@ -1,210 +1,8 @@ -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 dynamic from 'next/dynamic' -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'; -import type { ThemeProps } from "@lib/types"; - -type Tab = { - name: string - icon: JSX.Element - condition?: boolean - value: string - onClick?: () => void - href?: string -} - - -const Header = ({ changeTheme, theme }: ThemeProps) => { - const router = useRouter(); - const [selectedTab, setSelectedTab] = useState<string>(router.pathname === '/' ? 'home' : router.pathname.split('/')[1]); - const [expanded, setExpanded] = useState<boolean>(false) - const [, setBodyHidden] = useBodyScroll(null, { scrollLayer: true }) - const isMobile = useMediaQuery('xs', { match: 'down' }) - const { signedIn: isSignedIn } = useSignedIn() - const [pages, setPages] = useState<Tab[]>([]) - - useEffect(() => { - setBodyHidden(expanded) - }, [expanded, setBodyHidden]) - - useEffect(() => { - if (!isMobile) { - setExpanded(false) - } - }, [isMobile]) - - useEffect(() => { - const pageList: Tab[] = [ - { - name: "Home", - href: "/", - icon: <HomeIcon />, - condition: !isSignedIn, - value: "home" - }, - { - name: "New", - href: "/new", - icon: <NewIcon />, - condition: isSignedIn, - value: "new" - }, - { - name: "Yours", - href: "/mine", - icon: <YourIcon />, - condition: isSignedIn, - value: "mine" - }, - { - name: "Sign out", - href: "/signout", - icon: <SignOutIcon />, - condition: isSignedIn, - value: "signout" - }, - { - name: "Sign in", - href: "/signin", - icon: <SignInIcon />, - condition: !isSignedIn, - value: "signin" - }, - { - name: "Sign up", - href: "/signup", - icon: <SignUpIcon />, - condition: !isSignedIn, - value: "signup" - }, - { - name: isMobile ? "GitHub" : "", - href: "https://github.com/maxleiter/drift", - icon: <GitHubIcon />, - condition: true, - value: "github" - }, - { - name: isMobile ? "Change theme" : "", - onClick: function () { - if (typeof window !== 'undefined') { - changeTheme(); - setSelectedTab(''); - } - }, - icon: theme === 'light' ? <MoonIcon /> : <SunIcon />, - condition: true, - value: "theme", - } - ] - - setPages(pageList.filter(page => page.condition)) - }, [changeTheme, isMobile, isSignedIn, theme]) - - // useEffect(() => { - // setSelectedTab(pages.find((page) => { - // console.log(page.href, router.asPath) - // if (page.href && page.href === router.asPath) { - // return true - // } - // })?.href) - // }, [pages, router, router.pathname]) - - const onTabChange = (tab: string) => { - const match = pages.find(page => page.value === tab) - if (match?.onClick) { - match.onClick() - } else if (match?.href) { - router.push(`${match.href}`) - } - } - - return ( - <Page.Header height={'var(--page-nav-height)'} margin={0} paddingBottom={0} paddingTop={"var(--gap)"}> - <div className={styles.tabs}> - <Tabs - value={selectedTab} - leftSpace={0} - align="center" - hideDivider - hideBorder - onChange={onTabChange}> - {pages.map((tab) => { - return <Tabs.Item - font="14px" - label={<>{tab.icon} {tab.name}</>} - value={tab.value} - key={`${tab.value}`} - /> - })} - </Tabs> - </div> - <div className={styles.controls}> - <Button - auto - type="abort" - onClick={() => setExpanded(!expanded)} - aria-label="Menu" - > - <Spacer height={5 / 6} width={0} /> - <MenuIcon /> - </Button> - </div> - {isMobile && expanded && (<div className={styles.mobile}> - <ButtonGroup vertical> - {pages.map((tab, index) => { - return <Button - key={`${tab.name}-${index}`} - onClick={() => onTabChange(tab.value)} - icon={tab.icon} - > - {tab.name} - </Button> - })} - </ButtonGroup> - </div>)} - </Page.Header > - ) -} +const Header = dynamic(import('./header'), { + ssr: false, + // loading: () => <MenuSkeleton />, +}) export default Header - - -// {/* {/* <ButtonGroup> -// <Button onClick={() => { - -// }}><Link href="/signin">Sign out</Link></Button> -// <Button> -// <Link href="/mine"> -// Yours -// </Link> -// </Button> -// <Button> -// {/* TODO: Link outside Button, but seems to break ButtonGroup */} -// <Link href="/new"> -// New -// </Link> -// </Button > -// <Button onClick={() => changeTheme()}> -// <ShiftBy y={6}>{theme.type === 'light' ? <Moon /> : <Sun />}</ShiftBy> -// </Button> -// </ButtonGroup > * /} \ No newline at end of file diff --git a/client/components/input/index.tsx b/client/components/input/index.tsx new file mode 100644 index 0000000..29c7ae9 --- /dev/null +++ b/client/components/input/index.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import styles from './input.module.css' + +type Props = React.HTMLProps<HTMLInputElement> & { + label?: string + fontSize?: number | string +} + +// eslint-disable-next-line react/display-name +const Input = React.forwardRef<HTMLInputElement, Props>(({ label, className, ...props }, ref) => { + return (<div className={styles.wrapper}> + {label && <label className={styles.label}>{label}</label>} + <input + + ref={ref} + className={className ? `${styles.input} ${className}` : styles.input} + {...props} + /> + </div> + + ) +}) + +export default Input diff --git a/client/components/input/input.module.css b/client/components/input/input.module.css new file mode 100644 index 0000000..2a5cf7f --- /dev/null +++ b/client/components/input/input.module.css @@ -0,0 +1,57 @@ +.wrapper { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + height: 100%; + font-size: 1rem; +} + +.input { + height: 2.5rem; + border-radius: var(--inline-radius); + background: var(--bg); + color: var(--fg); + border: 1px solid var(--light-gray); + padding: 0 var(--gap-half); + outline: none; + transition: border-color var(--transition); + display: flex; + justify-content: center; + margin: 0; + width: 100%; +} + +.input::placeholder { + font-size: 1.5rem; +} + +.input:focus { + border-color: var(--input-border-focus); +} + +.label { + display: inline-flex; + width: initial; + height: 100%; + align-items: center; + pointer-events: none; + margin: 0; + padding: 0 var(--gap-half); + color: var(--fg); + background-color: var(--light-gray); + border-top-left-radius: var(--radius); + border-bottom-left-radius: var(--radius); + border-top: 1px solid var(--input-border); + border-left: 1px solid var(--input-border); + border-bottom: 1px solid var(--input-border); + font-size: inherit; + line-height: 1; + white-space: nowrap; +} + +@media screen and (max-width: 768px) { + .wrapper { + margin-bottom: var(--gap); + } +} diff --git a/client/components/new-post/index.tsx b/client/components/new-post/index.tsx index 95bda11..c28ce16 100644 --- a/client/components/new-post/index.tsx +++ b/client/components/new-post/index.tsx @@ -1,7 +1,4 @@ -import Button from '@geist-ui/core/dist/button' -import useToasts from '@geist-ui/core/dist/use-toasts' -import ButtonDropdown from '@geist-ui/core/dist/button-dropdown' - +import { Button, useToasts, ButtonDropdown } from '@geist-ui/core' import { useRouter } from 'next/router'; import { useCallback, useState } from 'react' import generateUUID from '@lib/generate-uuid'; diff --git a/client/components/new-post/password/index.tsx b/client/components/new-post/password/index.tsx index 8615a30..9ab7a8f 100644 --- a/client/components/new-post/password/index.tsx +++ b/client/components/new-post/password/index.tsx @@ -1,7 +1,5 @@ -import Input from "@geist-ui/core/dist/input" -import Modal from "@geist-ui/core/dist/modal" -import Note from "@geist-ui/core/dist/note" -import Spacer from "@geist-ui/core/dist/spacer" + +import { Modal, Note, Spacer, Input } from "@geist-ui/core" import { useState } from "react" type Props = { diff --git a/client/components/new-post/title/index.tsx b/client/components/new-post/title/index.tsx index 89d7968..e5b5f82 100644 --- a/client/components/new-post/title/index.tsx +++ b/client/components/new-post/title/index.tsx @@ -1,9 +1,9 @@ import { ChangeEvent, memo, useCallback } from 'react' -import Text from '@geist-ui/core/dist/text' -import Input from '@geist-ui/core/dist/input' +import { Text } from '@geist-ui/core' import ShiftBy from '@components/shift-by' import styles from '../post.module.css' +import { Input } from '@geist-ui/core' const titlePlaceholders = [ "How to...", diff --git a/client/components/post-list/index.tsx b/client/components/post-list/index.tsx index 39af6d3..915d772 100644 --- a/client/components/post-list/index.tsx +++ b/client/components/post-list/index.tsx @@ -1,4 +1,4 @@ -import Text from "@geist-ui/core/dist/text" +import { Text } from "@geist-ui/core" import NextLink from "next/link" import Link from '../Link' diff --git a/client/components/post-list/list-item-skeleton.tsx b/client/components/post-list/list-item-skeleton.tsx index 150c4ee..60610e3 100644 --- a/client/components/post-list/list-item-skeleton.tsx +++ b/client/components/post-list/list-item-skeleton.tsx @@ -1,9 +1,7 @@ -import Card from "@geist-ui/core/dist/card"; -import Spacer from "@geist-ui/core/dist/spacer"; -import Grid from "@geist-ui/core/dist/grid"; -import Divider from "@geist-ui/core/dist/divider"; + import Skeleton from "react-loading-skeleton"; +import { Card, Divider, Grid, Spacer } from "@geist-ui/core"; const ListItemSkeleton = () => (<Card> <Spacer height={1 / 2} /> diff --git a/client/components/post-list/list-item.tsx b/client/components/post-list/list-item.tsx index 36b2616..72082b7 100644 --- a/client/components/post-list/list-item.tsx +++ b/client/components/post-list/list-item.tsx @@ -1,11 +1,3 @@ -import Card from "@geist-ui/core/dist/card" -import Spacer from "@geist-ui/core/dist/spacer" -import Grid from "@geist-ui/core/dist/grid" -import Divider from "@geist-ui/core/dist/divider" -import Link from "@geist-ui/core/dist/link" -import Text from "@geist-ui/core/dist/text" -import Input from "@geist-ui/core/dist/input" -import Tooltip from "@geist-ui/core/dist/tooltip" import NextLink from "next/link" import { useEffect, useMemo, useState } from "react" @@ -13,6 +5,7 @@ import timeAgo from "@lib/time-ago" import ShiftBy from "../shift-by" import VisibilityBadge from "../visibility-badge" import getPostPath from "@lib/get-post-path" +import { Input, Link, Text, Card, Spacer, Grid, Tooltip, Divider } from "@geist-ui/core" const FilenameInput = ({ title }: { title: string }) => <Input value={title} diff --git a/client/components/post-page/index.tsx b/client/components/post-page/index.tsx index 9e66ce0..4182277 100644 --- a/client/components/post-page/index.tsx +++ b/client/components/post-page/index.tsx @@ -1,19 +1,18 @@ -import Header from "@components/header" +import Header from "@components/header/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 styles from './post-page.module.css' +import homeStyles from '@styles/Home.module.css' -import type { Post, ThemeProps } from "@lib/types" +import type { Post } from "@lib/types" +import { Page, Button, Text } from "@geist-ui/core" -type Props = ThemeProps & { +type Props = { post: Post } -const PostPage = ({ post, changeTheme, theme }: Props) => { +const PostPage = ({ post }: Props) => { const download = async () => { const downloadZip = (await import("client-zip")).downloadZip const blob = await downloadZip(post.files.map((file: any) => { @@ -39,9 +38,9 @@ const PostPage = ({ post, changeTheme, theme }: Props) => { /> <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> + <Header /> </Page.Header> - <Page.Content width={"var(--main-content-width)"} margin="auto"> + <Page.Content className={homeStyles.main}> {/* {!isLoading && <PostFileExplorer files={post.files} />} */} <div className={styles.header}> <div className={styles.titleAndBadge}> diff --git a/client/components/preview/index.tsx b/client/components/preview/index.tsx index 02170b6..3ad4be6 100644 --- a/client/components/preview/index.tsx +++ b/client/components/preview/index.tsx @@ -1,28 +1,55 @@ +import useTheme from "@lib/hooks/use-theme" import { memo, useEffect, useState } from "react" -import ReactMarkdownPreview from "./react-markdown-preview" type Props = { - content?: string height?: number | string + fileId?: string + content?: string + title?: string // file extensions we can highlight - type?: string } -const MarkdownPreview = ({ content = '', height = 500, type = 'markdown' }: Props) => { - const [contentToRender, setContent] = useState(content) +const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => { + const [preview, setPreview] = useState<string>(content || "") + const [isLoading, setIsLoading] = useState<boolean>(true) + const { theme } = useTheme() useEffect(() => { - // 'm' so it doesn't flash code when you change the type to md - const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] - if (!renderAsMarkdown.includes(type)) { - setContent(`~~~${type} -${content} -~~~ -`) - } else { - setContent(content) + async function fetchPost() { + if (fileId) { + const resp = await fetch(`/api/markdown/${fileId}`, { + method: "GET", + }) + if (resp.ok) { + const res = await resp.text() + setPreview(res) + setIsLoading(false) + } + } else { + const resp = await fetch(`/api/render-markdown`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + title, + content, + }), + }) + if (resp.ok) { + const res = await resp.text() + setPreview(res) + setIsLoading(false) + } + } } - }, [type, content]) - return (<ReactMarkdownPreview height={height} content={contentToRender} />) + fetchPost() + }, [content, fileId, title]) + return (<> + {isLoading ? <div>Loading...</div> : <div data-theme={theme} dangerouslySetInnerHTML={{ __html: preview }} style={{ + height + }} />} + </>) + } export default memo(MarkdownPreview) diff --git a/client/components/visibility-badge/index.tsx b/client/components/visibility-badge/index.tsx index e534450..d907fda 100644 --- a/client/components/visibility-badge/index.tsx +++ b/client/components/visibility-badge/index.tsx @@ -1,4 +1,4 @@ -import Badge from "@geist-ui/core/dist/badge"; +import { Badge } from "@geist-ui/core" import type { PostVisibility } from "@lib/types" type Props = { diff --git a/client/lib/hooks/use-signed-in.ts b/client/lib/hooks/use-signed-in.ts index 3288424..e6da5bd 100644 --- a/client/lib/hooks/use-signed-in.ts +++ b/client/lib/hooks/use-signed-in.ts @@ -1,9 +1,14 @@ import Cookies from "js-cookie"; import { useEffect, useState } from "react"; +import useSharedState from "./use-shared-state"; const useSignedIn = () => { - const [signedIn, setSignedIn] = useState(typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); + const [signedIn, setSignedIn] = useSharedState('signedIn', typeof window === 'undefined' ? false : !!Cookies.get("drift-token")); const token = Cookies.get("drift-token") + const signin = (token: string) => { + setSignedIn(true); + Cookies.set("drift-token", token); + } useEffect(() => { if (token) { @@ -11,9 +16,9 @@ const useSignedIn = () => { } else { setSignedIn(false); } - }, [token]); + }, [setSignedIn, token]); - return { signedIn, token }; + return { signedIn, signin, token }; } export default useSignedIn; diff --git a/client/lib/hooks/use-theme.ts b/client/lib/hooks/use-theme.ts new file mode 100644 index 0000000..0a33332 --- /dev/null +++ b/client/lib/hooks/use-theme.ts @@ -0,0 +1,27 @@ +import { useCallback, useEffect } from "react" +import useSharedState from "./use-shared-state" + +const useTheme = () => { + const isClient = typeof window === "object" + const [themeType, setThemeType] = useSharedState<string>('theme', 'light') + + useEffect(() => { + if (!isClient) return + const storedTheme = localStorage.getItem('drift-theme') + if (storedTheme) { + setThemeType(storedTheme) + } + }, [isClient, setThemeType]) + + const changeTheme = useCallback(() => { + setThemeType(last => { + const newTheme = last === 'dark' ? 'light' : 'dark' + localStorage.setItem('drift-theme', newTheme) + return newTheme + }) + }, [setThemeType]) + + return { theme: themeType, changeTheme } +} + +export default useTheme \ No newline at end of file diff --git a/client/lib/render-markdown.tsx b/client/lib/render-markdown.tsx index 5f6f467..b92569e 100644 --- a/client/lib/render-markdown.tsx +++ b/client/lib/render-markdown.tsx @@ -1,4 +1,3 @@ -import Link from '@components/Link' import { marked } from 'marked' import Highlight, { defaultProps, Language } from 'prism-react-renderer' import { renderToStaticMarkup } from 'react-dom/server' diff --git a/client/package.json b/client/package.json index b17c82c..332b25a 100644 --- a/client/package.json +++ b/client/package.json @@ -18,7 +18,9 @@ "cookie": "^0.4.2", "dotenv": "^16.0.0", "js-cookie": "^3.0.1", + "marked": "^4.0.12", "next": "^12.1.1-canary.15", + "prism-react-renderer": "^1.3.1", "react": "17.0.2", "react-dom": "17.0.2", "react-dropzone": "^12.0.4", @@ -33,6 +35,7 @@ }, "devDependencies": { "@next/bundle-analyzer": "^12.1.0", + "@types/marked": "^4.0.3", "@types/node": "17.0.21", "@types/react": "17.0.39", "@types/react-dom": "^17.0.14", diff --git a/client/pages/_app.tsx b/client/pages/_app.tsx index f29abeb..2a11ca6 100644 --- a/client/pages/_app.tsx +++ b/client/pages/_app.tsx @@ -1,46 +1,21 @@ import '@styles/globals.css' -import GeistProvider from '@geist-ui/core/dist/geist-provider' -import CssBaseline from '@geist-ui/core/dist/css-baseline' -import useTheme from '@geist-ui/core/dist/use-theme' - -import { useEffect, useMemo, useState } from 'react' import type { AppProps as NextAppProps } from "next/app"; -import useSharedState from '@lib/hooks/use-shared-state'; import 'react-loading-skeleton/dist/skeleton.css' import { SkeletonTheme } from 'react-loading-skeleton'; import Head from 'next/head'; -import type { ThemeProps } from '@lib/types'; -import Cookies from 'js-cookie'; +import useTheme from '@lib/hooks/use-theme'; +import { CssBaseline, GeistProvider } from '@geist-ui/core'; type AppProps<P = any> = { pageProps: P; } & Omit<NextAppProps<P>, "pageProps">; -function MyApp({ Component, pageProps }: AppProps<ThemeProps>) { - const [themeType, setThemeType] = useSharedState<string>('theme', Cookies.get('drift-theme') || 'light') - - useEffect(() => { - const storedTheme = Cookies.get('drift-theme') - if (storedTheme) setThemeType(storedTheme) - // TODO: useReducer? - }, [setThemeType, themeType]) - - const changeTheme = () => { - const newTheme = themeType === 'dark' ? 'light' : 'dark' - localStorage.setItem('drift-theme', newTheme) - setThemeType(last => (last === 'dark' ? 'light' : 'dark')) - } - - const skeletonBaseColor = useMemo(() => { - if (themeType === 'dark') return '#333' - return '#eee' - }, [themeType]) - const skeletonHighlightColor = useMemo(() => { - if (themeType === 'dark') return '#555' - return '#ddd' - }, [themeType]) +function MyApp({ Component, pageProps }: AppProps) { + const { theme } = useTheme() + const skeletonBaseColor = 'var(--light-gray)' + const skeletonHighlightColor = 'var(--lighter-gray)' return ( <> @@ -58,10 +33,10 @@ function MyApp({ Component, pageProps }: AppProps<ThemeProps>) { <meta name="theme-color" content="#ffffff" /> <title>Drift</title> </Head> - <GeistProvider themeType={themeType} > + <GeistProvider themeType={theme} > <SkeletonTheme baseColor={skeletonBaseColor} highlightColor={skeletonHighlightColor}> <CssBaseline /> - <Component {...pageProps} theme={themeType || 'light'} changeTheme={changeTheme} /> + <Component {...pageProps} /> </SkeletonTheme> </GeistProvider> </> diff --git a/client/pages/_document.tsx b/client/pages/_document.tsx index c732c7a..4f43e76 100644 --- a/client/pages/_document.tsx +++ b/client/pages/_document.tsx @@ -1,5 +1,5 @@ +import { CssBaseline } from '@geist-ui/core' import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document' -import CssBaseline from '@geist-ui/core/dist/css-baseline' class MyDocument extends Document { static async getInitialProps(ctx: DocumentContext) { diff --git a/client/pages/api/markdown/[id].tsx b/client/pages/api/markdown/[id].ts similarity index 87% rename from client/pages/api/markdown/[id].tsx rename to client/pages/api/markdown/[id].ts index 359c33e..14d34d9 100644 --- a/client/pages/api/markdown/[id].tsx +++ b/client/pages/api/markdown/[id].ts @@ -12,17 +12,16 @@ const renderMarkdown: NextApiHandler = async (req, res) => { } }) - const json = await file.json() const { content, title } = json - const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] + const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] const fileType = () => { const pathParts = title.split(".") const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" return language } const type = fileType() - let contentToRender: string = content; + let contentToRender: string = '\n' + content; if (!renderAsMarkdown.includes(type)) { contentToRender = `~~~${type} diff --git a/client/pages/api/render-markdown.ts b/client/pages/api/render-markdown.ts new file mode 100644 index 0000000..a9e4162 --- /dev/null +++ b/client/pages/api/render-markdown.ts @@ -0,0 +1,30 @@ +import type { NextApiHandler } from "next"; + +import markdown from "@lib/render-markdown"; + +const renderMarkdown: NextApiHandler = async (req, res) => { + const { content, title } = req.body + const renderAsMarkdown = ['markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] + const fileType = () => { + const pathParts = title.split(".") + const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" + return language + } + const type = fileType() + let contentToRender: string = '\n' + (content || ''); + + if (!renderAsMarkdown.includes(type)) { + contentToRender = `~~~${type} +${content} +~~~` + } + + if (typeof contentToRender !== 'string') { + res.status(400).send('content must be a string') + return + } + res.status(200).write(markdown(contentToRender)) + res.end() +} + +export default renderMarkdown diff --git a/client/pages/index.tsx b/client/pages/index.tsx index 0c24fe5..5d96533 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -1,13 +1,10 @@ import styles from '@styles/Home.module.css' -import Page from '@geist-ui/core/dist/page' -import Spacer from '@geist-ui/core/dist/spacer' -import Text from '@geist-ui/core/dist/text' import Header from '@components/header' import Document from '@components/document' import Image from 'next/image' import ShiftBy from '@components/shift-by' import PageSeo from '@components/page-seo' -import { ThemeProps } from '@lib/types' +import { Page, Text, Spacer } from '@geist-ui/core' export function getStaticProps() { const introDoc = process.env.WELCOME_CONTENT @@ -19,19 +16,19 @@ export function getStaticProps() { } } -type Props = ThemeProps & { +type Props = { introContent: string } -const Home = ({ theme, changeTheme, introContent }: Props) => { +const Home = ({ introContent }: Props) => { return ( - <Page className={styles.container} width="100%"> + <Page className={styles.container}> <PageSeo /> <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> + <Header /> </Page.Header> - <Page.Content width={"var(--main-content-width)"} margin="auto" paddingTop={"var(--gap)"}> + <Page.Content className={styles.main}> <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}> <ShiftBy y={-2}><Image src={'/assets/logo-optimized.svg'} width={'48px'} height={'48px'} alt="" /></ShiftBy> <Spacer /> diff --git a/client/pages/mine.tsx b/client/pages/mine.tsx index 6cb50e8..6e5eeed 100644 --- a/client/pages/mine.tsx +++ b/client/pages/mine.tsx @@ -1,19 +1,19 @@ import styles from '@styles/Home.module.css' -import Page from '@geist-ui/core/dist/page' import Header from '@components/header' import MyPosts from '@components/my-posts' import cookie from "cookie"; import type { GetServerSideProps } from 'next'; -import type { ThemeProps } from '@lib/types'; +import { Post } from '@lib/types'; +import { Page } from '@geist-ui/core'; -const Home = ({ posts, error, theme, changeTheme }: ThemeProps & { posts: any; error: any; }) => { +const Home = ({ posts, error }: { posts: Post[]; error: any; }) => { return ( - <Page className={styles.container} width="100%"> + <Page className={styles.container}> <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> + <Header /> </Page.Header> - <Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}> + <Page.Content className={styles.main}> <MyPosts error={error} posts={posts} /> </Page.Content> </Page > diff --git a/client/pages/new.tsx b/client/pages/new.tsx index 3eb521e..14e4bce 100644 --- a/client/pages/new.tsx +++ b/client/pages/new.tsx @@ -1,20 +1,19 @@ import styles from '@styles/Home.module.css' import NewPost from '@components/new-post' -import Page from '@geist-ui/core/dist/page' import Header from '@components/header' import PageSeo from '@components/page-seo' -import type { ThemeProps } from '@lib/types' +import { Page } from '@geist-ui/core' -const New = ({ theme, changeTheme }: ThemeProps) => { +const New = () => { return ( <Page className={styles.container} width="100%"> <PageSeo title="Drift - New" /> <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> + <Header /> </Page.Header> - <Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="0 auto" className={styles.main}> + <Page.Content className={styles.main}> <NewPost /> </Page.Content> </Page > diff --git a/client/pages/post/[id].tsx b/client/pages/post/[id].tsx index d2eb31c..3327ce2 100644 --- a/client/pages/post/[id].tsx +++ b/client/pages/post/[id].tsx @@ -1,14 +1,14 @@ import type { GetStaticPaths, GetStaticProps } from "next"; -import type { Post, ThemeProps } from "@lib/types"; +import type { Post } from "@lib/types"; import PostPage from "@components/post-page"; -export type PostProps = ThemeProps & { +export type PostProps = { post: Post } -const PostView = ({ post, theme, changeTheme }: PostProps) => { - return <PostPage post={post} theme={theme} changeTheme={changeTheme} /> +const PostView = ({ post }: PostProps) => { + return <PostPage post={post} /> } export const getStaticPaths: GetStaticPaths = async () => { diff --git a/client/pages/post/private/[id].tsx b/client/pages/post/private/[id].tsx index c93f717..f3c7d1e 100644 --- a/client/pages/post/private/[id].tsx +++ b/client/pages/post/private/[id].tsx @@ -1,28 +1,14 @@ import cookie from "cookie"; import type { GetServerSideProps } from "next"; -import { PostVisibility, ThemeProps } from "@lib/types"; +import { Post } from "@lib/types"; import PostPage from "@components/post-page"; -type File = { - id: string - title: string - content: string +export type PostProps = { + post: Post } -type Files = File[] - -export type PostProps = ThemeProps & { - post: { - id: string - title: string - description: string - visibility: PostVisibility - files: Files - } -} - -const Post = ({ post, theme, changeTheme }: PostProps) => { - return (<PostPage post={post} changeTheme={changeTheme} theme={theme} />) +const Post = ({ post, }: PostProps) => { + return (<PostPage post={post} />) } export const getServerSideProps: GetServerSideProps = async (context) => { diff --git a/client/pages/post/protected/[id].tsx b/client/pages/post/protected/[id].tsx index 787042b..d34e970 100644 --- a/client/pages/post/protected/[id].tsx +++ b/client/pages/post/protected/[id].tsx @@ -1,14 +1,13 @@ -import useToasts from "@geist-ui/core/dist/use-toasts"; -import Page from "@geist-ui/core/dist/page"; +import { Page, useToasts } from '@geist-ui/core'; -import type { Post, ThemeProps } from "@lib/types"; +import type { Post } from "@lib/types"; import PasswordModal from "@components/new-post/password"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Cookies from "js-cookie"; import PostPage from "@components/post-page"; -const Post = ({ theme, changeTheme }: ThemeProps) => { +const Post = () => { const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true); const [post, setPost] = useState<Post>() const router = useRouter() @@ -74,7 +73,7 @@ const Post = ({ theme, changeTheme }: ThemeProps) => { return <Page><PasswordModal creating={false} onClose={onClose} onSubmit={onSubmit} isOpen={isPasswordModalOpen} /></Page> } - return (<PostPage post={post} changeTheme={changeTheme} theme={theme} />) + return (<PostPage post={post} />) } export default Post diff --git a/client/pages/signin.tsx b/client/pages/signin.tsx index e36776d..4e1ed7c 100644 --- a/client/pages/signin.tsx +++ b/client/pages/signin.tsx @@ -1,17 +1,16 @@ -import Page from "@geist-ui/core/dist/page"; +import { Page } from '@geist-ui/core'; import PageSeo from "@components/page-seo"; import Auth from "@components/auth"; -import Header from "@components/header"; -import type { ThemeProps } from "@lib/types"; - -const SignIn = ({ theme, changeTheme }: ThemeProps) => ( +import Header from "@components/header/header"; +import styles from '@styles/Home.module.css' +const SignIn = () => ( <Page width={"100%"}> <PageSeo title="Drift - Sign In" /> <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> + <Header /> </Page.Header> - <Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="auto"> + <Page.Content className={styles.main}> <Auth page="signin" /> </Page.Content> </Page> diff --git a/client/pages/signup.tsx b/client/pages/signup.tsx index d2ac510..383f461 100644 --- a/client/pages/signup.tsx +++ b/client/pages/signup.tsx @@ -1,17 +1,17 @@ -import Page from "@geist-ui/core/dist/page"; +import { Page } from '@geist-ui/core'; import Auth from "@components/auth"; -import Header from "@components/header"; +import Header from "@components/header/header"; import PageSeo from '@components/page-seo'; -import type { ThemeProps } from "@lib/types"; +import styles from '@styles/Home.module.css' -const SignUp = ({ theme, changeTheme }: ThemeProps) => ( +const SignUp = () => ( <Page width="100%"> <PageSeo title="Drift - Sign Up" /> <Page.Header> - <Header theme={theme} changeTheme={changeTheme} /> + <Header /> </Page.Header> - <Page.Content width={"var(--main-content-width)"} paddingTop={"var(--gap)"} margin="auto"> + <Page.Content className={styles.main}> <Auth page="signup" /> </Page.Content> </Page> diff --git a/client/styles/globals.css b/client/styles/globals.css index 2e93b83..63af768 100644 --- a/client/styles/globals.css +++ b/client/styles/globals.css @@ -25,29 +25,7 @@ --transition: 0.1s ease-in-out; --transition-slow: 0.3s ease-in-out; - /* Dark Mode Colors */ - --bg: #131415; - --fg: #fafbfc; - --gray: #666; - --light-gray: #444; - --lighter-gray: #222; - --lightest-gray: #1a1a1a; - --article-color: #eaeaea; - --header-bg: rgba(19, 20, 21, 0.45); - --gray-alpha: rgba(255, 255, 255, 0.5); - --selection: rgba(255, 255, 255, 0.99); - - /* Forms */ - --input-height: 2.5rem; - --input-border: 1px solid var(--light-gray); - --input-border-focus: 1px solid var(--gray); - --input-border-error: 1px solid var(--red); - --input-bg: var(--bg); - --input-fg: var(--fg); - --input-placeholder-fg: var(--light-gray); - --input-bg-hover: var(--lightest-gray); - - /* Syntax Highlighting */ + --page-nav-height: 64px; --token: #999; --comment: #999; --keyword: #fff; @@ -56,17 +34,6 @@ } [data-theme="light"] { - --bg: #fff; - --fg: #000; - --gray: #888; - --light-gray: #dedede; - --lighter-gray: #f5f5f5; - --lightest-gray: #fafafa; - --article-color: #212121; - --header-bg: rgba(255, 255, 255, 0.8); - --gray-alpha: rgba(19, 20, 21, 0.5); - --selection: rgba(0, 0, 0, 0.99); - --token: #666; --comment: #999; --keyword: #000; @@ -81,11 +48,6 @@ ::selection { text-shadow: none; background: var(--selection); - color: var(--bg); -} - -html { - line-height: 1.5; } html, @@ -93,8 +55,6 @@ body { padding: 0; margin: 0; font-size: 15px; - background: var(--bg); - color: var(--fg); text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -113,44 +73,6 @@ li { font-size: 1.125rem; } -h1, -h2, -h3, -h4, -h5, -h6 { - font-family: var(--font-sans); - font-weight: 600; - line-height: 1.75; -} - -h1 { - font-size: 2.5rem; - font-weight: 600; - line-height: 1.25; - letter-spacing: -0.89px; -} - -h2 { - font-size: 2rem; - letter-spacing: -0.69px; -} - -h3 { - font-size: 1.5rem; - letter-spacing: -0.47px; -} - -h4 { - font-size: 1.25rem; - letter-spacing: -0.33px; -} - -hr { - border: none; - border-bottom: 1px solid var(--light-gray); -} - blockquote { font-style: italic; margin: 0; @@ -158,52 +80,16 @@ blockquote { border-left: 3px solid var(--light-gray); } -button { - border: none; - padding: 0; - margin: 0; - line-height: inherit; - font-size: inherit; -} - -p a, a.reset { outline: none; - color: var(--fg); text-decoration: none; } -p a:hover, -p a:focus, -p a:active, -a.reset:hover, -a.reset:focus { - color: var(--gray); -} - pre, code { font-family: var(--font-mono); } -.clamp { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 1; - overflow: hidden; -} - -.clamp-2 { - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; - overflow: hidden; -} - -.flex { - display: flex; -} - kbd { font-family: var(--font-sans); font-size: 1rem; @@ -213,18 +99,6 @@ kbd { border-radius: 5px; } -summary { - cursor: pointer; - outline: none; -} - -details { - border-radius: var(--radius); - background: var(--lightest-gray); - padding: 1rem; - border-radius: var(--radius); -} - @media print { :root { --bg: #fff; diff --git a/client/yarn.lock b/client/yarn.lock index 112e3aa..69809e3 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -197,6 +197,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= +"@types/marked@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.0.3.tgz#2098f4a77adaba9ce881c9e0b6baf29116e5acc4" + integrity sha512-HnMWQkLJEf/PnxZIfbm0yGJRRZYYMhb++O9M36UCTA9z53uPvVoSlAwJr3XOpDEryb7Hwl1qAx/MV6YIW1RXxg== + "@types/mdast@^3.0.0": version "3.0.10" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" @@ -1804,6 +1809,11 @@ markdown-table@^3.0.0: resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c" integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== +marked@^4.0.12: + version "4.0.12" + resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d" + integrity sha512-hgibXWrEDNBWgGiK18j/4lkS6ihTe9sxtV4Q1OQppb/0zzyPSzoFANBa5MfsG/zgsWklmNnhm0XACZOH/0HBiQ== + mdast-util-definitions@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817" @@ -2559,6 +2569,11 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== +prism-react-renderer@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d" + integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ== + prismjs@^1.25.0, prismjs@~1.27.0: version "1.27.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"