client: overhaul markdown rendering (now server-side), refactor theming

This commit is contained in:
Max Leiter 2022-03-22 20:06:15 -07:00
parent d1ee9d857f
commit 34b1ab979f
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
41 changed files with 735 additions and 518 deletions

View file

@ -1,5 +1,5 @@
import type { LinkProps } from "@geist-ui/core" 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"; import { useRouter } from "next/router";
const Link = (props: LinkProps) => { const Link = (props: LinkProps) => {

View file

@ -4,6 +4,7 @@ import styles from './auth.module.css'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import Link from '../Link' import Link from '../Link'
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import useSignedIn from '@lib/hooks/use-signed-in'
const NO_EMPTY_SPACE_REGEX = /^\S*$/; const NO_EMPTY_SPACE_REGEX = /^\S*$/;
const ERROR_MESSAGE = "Provide a non empty username and a password with at least 6 characters"; 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 [errorMsg, setErrorMsg] = useState('');
const [requiresServerPassword, setRequiresServerPassword] = useState(false); const [requiresServerPassword, setRequiresServerPassword] = useState(false);
const signingIn = page === 'signin' const signingIn = page === 'signin'
const { signin } = useSignedIn();
useEffect(() => { useEffect(() => {
async function fetchRequiresPass() { async function fetchRequiresPass() {
if (!signingIn) { if (!signingIn) {
@ -37,7 +38,7 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
const handleJson = (json: any) => { const handleJson = (json: any) => {
Cookies.set('drift-token', json.token); signin(json.token)
Cookies.set('drift-userid', json.userId); Cookies.set('drift-userid', json.userId);
router.push('/') router.push('/')
@ -65,7 +66,6 @@ const Auth = ({ page }: { page: "signup" | "signin" }) => {
handleJson(json) handleJson(json)
} catch (err: any) { } catch (err: any) {
console.log(err)
setErrorMsg(err.message ?? "Something went wrong") setErrorMsg(err.message ?? "Something went wrong")
} }
} }

View file

@ -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;
}

View file

@ -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

View file

@ -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);
}

View file

@ -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

View file

@ -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 Bold from '@geist-ui/icons/bold'
import Italic from '@geist-ui/icons/italic' import Italic from '@geist-ui/icons/italic'
import Link from '@geist-ui/icons/link' import Link from '@geist-ui/icons/link'
import ImageIcon from '@geist-ui/icons/image' import ImageIcon from '@geist-ui/icons/image'
import { RefObject, useCallback, useMemo } from "react" import { RefObject, useCallback, useMemo } from "react"
import styles from '../document.module.css' import styles from '../document.module.css'
import { Button, ButtonGroup } from "@geist-ui/core"
// TODO: clean up // TODO: clean up

View file

@ -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 { ChangeEvent, memo, useCallback, useMemo, useRef, useState } from "react"
import styles from './document.module.css' import styles from './document.module.css'
@ -15,9 +8,8 @@ import ExternalLink from '@geist-ui/icons/externalLink'
import FormattingIcons from "./formatting-icons" import FormattingIcons from "./formatting-icons"
import Skeleton from "react-loading-skeleton" import Skeleton from "react-loading-skeleton"
import dynamic from "next/dynamic"; import { Button, ButtonGroup, Card, Input, Spacer, Tabs, Textarea, Tooltip } from "@geist-ui/core"
import Preview from "@components/preview"
const MarkdownPreview = dynamic(() => import("../preview"))
// import Link from "next/link" // import Link from "next/link"
type Props = { type Props = {
@ -74,13 +66,6 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init
setTab(newTab as 'edit' | 'preview') 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 onTitleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => setTitle ? setTitle(event.target.value) : null, [setTitle])
const removeFile = useCallback(() => (remove?: () => void) => { const removeFile = useCallback(() => (remove?: () => void) => {
@ -140,14 +125,14 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init
</div> </div>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
{tab === 'edit' && editable && <FormattingIcons setText={setContent} textareaRef={codeEditorRef} />} {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 onChange={handleTabChange} initialValue={initialTab} hideDivider leftSpace={0}>
<Tabs.Item label={editable ? "Edit" : "Raw"} value="edit"> <Tabs.Item label={editable ? "Edit" : "Raw"} value="edit">
{/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */} {/* <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 <Textarea
ref={codeEditorRef} ref={codeEditorRef}
placeholder="Type some contents..." placeholder=""
value={content} value={content}
onChange={handleOnContentChange} onChange={handleOnContentChange}
width="100%" width="100%"
@ -160,7 +145,7 @@ const Document = ({ remove, editable, title, content, setTitle, setContent, init
</div> </div>
</Tabs.Item> </Tabs.Item>
<Tabs.Item label="Preview" value="preview"> <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.Item>
</Tabs> </Tabs>

View file

@ -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;

View file

@ -1,19 +1,16 @@
import React from 'react' import React from 'react'
import MoonIcon from '@geist-ui/icons/moon' import MoonIcon from '@geist-ui/icons/moon'
import SunIcon from '@geist-ui/icons/sun' import SunIcon from '@geist-ui/icons/sun'
import Select from '@geist-ui/core/dist/select'
// import { useAllThemes, useTheme } from '@geist-ui/core' // import { useAllThemes, useTheme } from '@geist-ui/core'
import styles from './header.module.css' import styles from './header.module.css'
import { ThemeProps } from '@lib/types' import { ThemeProps } from '@lib/types'
import Cookies from 'js-cookie' import { Select } from '@geist-ui/core'
const Controls = ({ changeTheme, theme }: ThemeProps) => { const Controls = ({ changeTheme, theme }: ThemeProps) => {
const switchThemes = (type: string | string[]) => { const switchThemes = () => {
changeTheme() changeTheme()
Cookies.set('drift-theme', Array.isArray(type) ? type[0] : type)
} }
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<Select <Select

View file

@ -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

View file

@ -1,210 +1,8 @@
import Page from "@geist-ui/core/dist/page"; import dynamic from 'next/dynamic'
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"; const Header = dynamic(import('./header'), {
import styles from './header.module.css'; ssr: false,
import { useRouter } from "next/router"; // loading: () => <MenuSkeleton />,
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 >
)
}
export default Header 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 > * /}

View file

@ -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

View file

@ -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);
}
}

View file

@ -1,7 +1,4 @@
import Button from '@geist-ui/core/dist/button' import { Button, useToasts, ButtonDropdown } from '@geist-ui/core'
import useToasts from '@geist-ui/core/dist/use-toasts'
import ButtonDropdown from '@geist-ui/core/dist/button-dropdown'
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import generateUUID from '@lib/generate-uuid'; import generateUUID from '@lib/generate-uuid';

View file

@ -1,7 +1,5 @@
import Input from "@geist-ui/core/dist/input"
import Modal from "@geist-ui/core/dist/modal" import { Modal, Note, Spacer, Input } from "@geist-ui/core"
import Note from "@geist-ui/core/dist/note"
import Spacer from "@geist-ui/core/dist/spacer"
import { useState } from "react" import { useState } from "react"
type Props = { type Props = {

View file

@ -1,9 +1,9 @@
import { ChangeEvent, memo, useCallback } from 'react' import { ChangeEvent, memo, useCallback } from 'react'
import Text from '@geist-ui/core/dist/text' import { Text } from '@geist-ui/core'
import Input from '@geist-ui/core/dist/input'
import ShiftBy from '@components/shift-by' import ShiftBy from '@components/shift-by'
import styles from '../post.module.css' import styles from '../post.module.css'
import { Input } from '@geist-ui/core'
const titlePlaceholders = [ const titlePlaceholders = [
"How to...", "How to...",

View file

@ -1,4 +1,4 @@
import Text from "@geist-ui/core/dist/text" import { Text } from "@geist-ui/core"
import NextLink from "next/link" import NextLink from "next/link"
import Link from '../Link' import Link from '../Link'

View file

@ -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 Skeleton from "react-loading-skeleton";
import { Card, Divider, Grid, Spacer } from "@geist-ui/core";
const ListItemSkeleton = () => (<Card> const ListItemSkeleton = () => (<Card>
<Spacer height={1 / 2} /> <Spacer height={1 / 2} />

View file

@ -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 NextLink from "next/link"
import { useEffect, useMemo, useState } from "react" import { useEffect, useMemo, useState } from "react"
@ -13,6 +5,7 @@ import timeAgo from "@lib/time-ago"
import ShiftBy from "../shift-by" import ShiftBy from "../shift-by"
import VisibilityBadge from "../visibility-badge" import VisibilityBadge from "../visibility-badge"
import getPostPath from "@lib/get-post-path" 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 const FilenameInput = ({ title }: { title: string }) => <Input
value={title} value={title}

View file

@ -1,19 +1,18 @@
import Header from "@components/header" import Header from "@components/header/header"
import PageSeo from "@components/page-seo" import PageSeo from "@components/page-seo"
import VisibilityBadge from "@components/visibility-badge" 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 DocumentComponent from '@components/document'
import styles from './post-page.module.css' 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 post: Post
} }
const PostPage = ({ post, changeTheme, theme }: Props) => { const PostPage = ({ post }: Props) => {
const download = async () => { const download = async () => {
const downloadZip = (await import("client-zip")).downloadZip const downloadZip = (await import("client-zip")).downloadZip
const blob = await downloadZip(post.files.map((file: any) => { const blob = await downloadZip(post.files.map((file: any) => {
@ -39,9 +38,9 @@ const PostPage = ({ post, changeTheme, theme }: Props) => {
/> />
<Page.Header> <Page.Header>
<Header theme={theme} changeTheme={changeTheme} /> <Header />
</Page.Header> </Page.Header>
<Page.Content width={"var(--main-content-width)"} margin="auto"> <Page.Content className={homeStyles.main}>
{/* {!isLoading && <PostFileExplorer files={post.files} />} */} {/* {!isLoading && <PostFileExplorer files={post.files} />} */}
<div className={styles.header}> <div className={styles.header}>
<div className={styles.titleAndBadge}> <div className={styles.titleAndBadge}>

View file

@ -1,28 +1,55 @@
import useTheme from "@lib/hooks/use-theme"
import { memo, useEffect, useState } from "react" import { memo, useEffect, useState } from "react"
import ReactMarkdownPreview from "./react-markdown-preview"
type Props = { type Props = {
content?: string
height?: number | string height?: number | string
fileId?: string
content?: string
title?: string
// file extensions we can highlight // file extensions we can highlight
type?: string
} }
const MarkdownPreview = ({ content = '', height = 500, type = 'markdown' }: Props) => { const MarkdownPreview = ({ height = 500, fileId, content, title }: Props) => {
const [contentToRender, setContent] = useState(content) const [preview, setPreview] = useState<string>(content || "")
const [isLoading, setIsLoading] = useState<boolean>(true)
const { theme } = useTheme()
useEffect(() => { useEffect(() => {
// 'm' so it doesn't flash code when you change the type to md async function fetchPost() {
const renderAsMarkdown = ['m', 'markdown', 'md', 'mdown', 'mkdn', 'mkd', 'mdwn', 'mdtxt', 'mdtext', 'text', ''] if (fileId) {
if (!renderAsMarkdown.includes(type)) { const resp = await fetch(`/api/markdown/${fileId}`, {
setContent(`~~~${type} method: "GET",
${content} })
~~~ if (resp.ok) {
`) const res = await resp.text()
} else { setPreview(res)
setContent(content) setIsLoading(false)
} }
}, [type, content]) } else {
return (<ReactMarkdownPreview height={height} content={contentToRender} />) 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)
}
}
}
fetchPost()
}, [content, fileId, title])
return (<>
{isLoading ? <div>Loading...</div> : <div data-theme={theme} dangerouslySetInnerHTML={{ __html: preview }} style={{
height
}} />}
</>)
} }
export default memo(MarkdownPreview) export default memo(MarkdownPreview)

View file

@ -1,4 +1,4 @@
import Badge from "@geist-ui/core/dist/badge"; import { Badge } from "@geist-ui/core"
import type { PostVisibility } from "@lib/types" import type { PostVisibility } from "@lib/types"
type Props = { type Props = {

View file

@ -1,9 +1,14 @@
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import useSharedState from "./use-shared-state";
const useSignedIn = () => { 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 token = Cookies.get("drift-token")
const signin = (token: string) => {
setSignedIn(true);
Cookies.set("drift-token", token);
}
useEffect(() => { useEffect(() => {
if (token) { if (token) {
@ -11,9 +16,9 @@ const useSignedIn = () => {
} else { } else {
setSignedIn(false); setSignedIn(false);
} }
}, [token]); }, [setSignedIn, token]);
return { signedIn, token }; return { signedIn, signin, token };
} }
export default useSignedIn; export default useSignedIn;

View file

@ -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

View file

@ -1,4 +1,3 @@
import Link from '@components/Link'
import { marked } from 'marked' import { marked } from 'marked'
import Highlight, { defaultProps, Language } from 'prism-react-renderer' import Highlight, { defaultProps, Language } from 'prism-react-renderer'
import { renderToStaticMarkup } from 'react-dom/server' import { renderToStaticMarkup } from 'react-dom/server'

View file

@ -18,7 +18,9 @@
"cookie": "^0.4.2", "cookie": "^0.4.2",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"marked": "^4.0.12",
"next": "^12.1.1-canary.15", "next": "^12.1.1-canary.15",
"prism-react-renderer": "^1.3.1",
"react": "17.0.2", "react": "17.0.2",
"react-dom": "17.0.2", "react-dom": "17.0.2",
"react-dropzone": "^12.0.4", "react-dropzone": "^12.0.4",
@ -33,6 +35,7 @@
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.1.0", "@next/bundle-analyzer": "^12.1.0",
"@types/marked": "^4.0.3",
"@types/node": "17.0.21", "@types/node": "17.0.21",
"@types/react": "17.0.39", "@types/react": "17.0.39",
"@types/react-dom": "^17.0.14", "@types/react-dom": "^17.0.14",

View file

@ -1,46 +1,21 @@
import '@styles/globals.css' 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 type { AppProps as NextAppProps } from "next/app";
import useSharedState from '@lib/hooks/use-shared-state';
import 'react-loading-skeleton/dist/skeleton.css' import 'react-loading-skeleton/dist/skeleton.css'
import { SkeletonTheme } from 'react-loading-skeleton'; import { SkeletonTheme } from 'react-loading-skeleton';
import Head from 'next/head'; import Head from 'next/head';
import type { ThemeProps } from '@lib/types'; import useTheme from '@lib/hooks/use-theme';
import Cookies from 'js-cookie'; import { CssBaseline, GeistProvider } from '@geist-ui/core';
type AppProps<P = any> = { type AppProps<P = any> = {
pageProps: P; pageProps: P;
} & Omit<NextAppProps<P>, "pageProps">; } & Omit<NextAppProps<P>, "pageProps">;
function MyApp({ Component, pageProps }: AppProps<ThemeProps>) { function MyApp({ Component, pageProps }: AppProps) {
const [themeType, setThemeType] = useSharedState<string>('theme', Cookies.get('drift-theme') || 'light') const { theme } = useTheme()
const skeletonBaseColor = 'var(--light-gray)'
useEffect(() => { const skeletonHighlightColor = 'var(--lighter-gray)'
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])
return ( return (
<> <>
@ -58,10 +33,10 @@ function MyApp({ Component, pageProps }: AppProps<ThemeProps>) {
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
<title>Drift</title> <title>Drift</title>
</Head> </Head>
<GeistProvider themeType={themeType} > <GeistProvider themeType={theme} >
<SkeletonTheme baseColor={skeletonBaseColor} highlightColor={skeletonHighlightColor}> <SkeletonTheme baseColor={skeletonBaseColor} highlightColor={skeletonHighlightColor}>
<CssBaseline /> <CssBaseline />
<Component {...pageProps} theme={themeType || 'light'} changeTheme={changeTheme} /> <Component {...pageProps} />
</SkeletonTheme> </SkeletonTheme>
</GeistProvider> </GeistProvider>
</> </>

View file

@ -1,5 +1,5 @@
import { CssBaseline } from '@geist-ui/core'
import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document' import Document, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import CssBaseline from '@geist-ui/core/dist/css-baseline'
class MyDocument extends Document { class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) { static async getInitialProps(ctx: DocumentContext) {

View file

@ -12,17 +12,16 @@ const renderMarkdown: NextApiHandler = async (req, res) => {
} }
}) })
const json = await file.json() const json = await file.json()
const { content, title } = 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 fileType = () => {
const pathParts = title.split(".") const pathParts = title.split(".")
const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : "" const language = pathParts.length > 1 ? pathParts[pathParts.length - 1] : ""
return language return language
} }
const type = fileType() const type = fileType()
let contentToRender: string = content; let contentToRender: string = '\n' + content;
if (!renderAsMarkdown.includes(type)) { if (!renderAsMarkdown.includes(type)) {
contentToRender = `~~~${type} contentToRender = `~~~${type}

View file

@ -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

View file

@ -1,13 +1,10 @@
import styles from '@styles/Home.module.css' 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 Header from '@components/header'
import Document from '@components/document' import Document from '@components/document'
import Image from 'next/image' import Image from 'next/image'
import ShiftBy from '@components/shift-by' import ShiftBy from '@components/shift-by'
import PageSeo from '@components/page-seo' import PageSeo from '@components/page-seo'
import { ThemeProps } from '@lib/types' import { Page, Text, Spacer } from '@geist-ui/core'
export function getStaticProps() { export function getStaticProps() {
const introDoc = process.env.WELCOME_CONTENT const introDoc = process.env.WELCOME_CONTENT
@ -19,19 +16,19 @@ export function getStaticProps() {
} }
} }
type Props = ThemeProps & { type Props = {
introContent: string introContent: string
} }
const Home = ({ theme, changeTheme, introContent }: Props) => { const Home = ({ introContent }: Props) => {
return ( return (
<Page className={styles.container} width="100%"> <Page className={styles.container}>
<PageSeo /> <PageSeo />
<Page.Header> <Page.Header>
<Header theme={theme} changeTheme={changeTheme} /> <Header />
</Page.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' }}> <div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<ShiftBy y={-2}><Image src={'/assets/logo-optimized.svg'} width={'48px'} height={'48px'} alt="" /></ShiftBy> <ShiftBy y={-2}><Image src={'/assets/logo-optimized.svg'} width={'48px'} height={'48px'} alt="" /></ShiftBy>
<Spacer /> <Spacer />

View file

@ -1,19 +1,19 @@
import styles from '@styles/Home.module.css' import styles from '@styles/Home.module.css'
import Page from '@geist-ui/core/dist/page'
import Header from '@components/header' import Header from '@components/header'
import MyPosts from '@components/my-posts' import MyPosts from '@components/my-posts'
import cookie from "cookie"; import cookie from "cookie";
import type { GetServerSideProps } from 'next'; 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 ( return (
<Page className={styles.container} width="100%"> <Page className={styles.container}>
<Page.Header> <Page.Header>
<Header theme={theme} changeTheme={changeTheme} /> <Header />
</Page.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} /> <MyPosts error={error} posts={posts} />
</Page.Content> </Page.Content>
</Page > </Page >

View file

@ -1,20 +1,19 @@
import styles from '@styles/Home.module.css' import styles from '@styles/Home.module.css'
import NewPost from '@components/new-post' import NewPost from '@components/new-post'
import Page from '@geist-ui/core/dist/page'
import Header from '@components/header' import Header from '@components/header'
import PageSeo from '@components/page-seo' 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 ( return (
<Page className={styles.container} width="100%"> <Page className={styles.container} width="100%">
<PageSeo title="Drift - New" /> <PageSeo title="Drift - New" />
<Page.Header> <Page.Header>
<Header theme={theme} changeTheme={changeTheme} /> <Header />
</Page.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 /> <NewPost />
</Page.Content> </Page.Content>
</Page > </Page >

View file

@ -1,14 +1,14 @@
import type { GetStaticPaths, GetStaticProps } from "next"; 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"; import PostPage from "@components/post-page";
export type PostProps = ThemeProps & { export type PostProps = {
post: Post post: Post
} }
const PostView = ({ post, theme, changeTheme }: PostProps) => { const PostView = ({ post }: PostProps) => {
return <PostPage post={post} theme={theme} changeTheme={changeTheme} /> return <PostPage post={post} />
} }
export const getStaticPaths: GetStaticPaths = async () => { export const getStaticPaths: GetStaticPaths = async () => {

View file

@ -1,28 +1,14 @@
import cookie from "cookie"; import cookie from "cookie";
import type { GetServerSideProps } from "next"; import type { GetServerSideProps } from "next";
import { PostVisibility, ThemeProps } from "@lib/types"; import { Post } from "@lib/types";
import PostPage from "@components/post-page"; import PostPage from "@components/post-page";
type File = { export type PostProps = {
id: string post: Post
title: string
content: string
} }
type Files = File[] const Post = ({ post, }: PostProps) => {
return (<PostPage post={post} />)
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} />)
} }
export const getServerSideProps: GetServerSideProps = async (context) => { export const getServerSideProps: GetServerSideProps = async (context) => {

View file

@ -1,14 +1,13 @@
import useToasts from "@geist-ui/core/dist/use-toasts"; import { Page, useToasts } from '@geist-ui/core';
import Page from "@geist-ui/core/dist/page";
import type { Post, ThemeProps } from "@lib/types"; import type { Post } from "@lib/types";
import PasswordModal from "@components/new-post/password"; import PasswordModal from "@components/new-post/password";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Cookies from "js-cookie"; import Cookies from "js-cookie";
import PostPage from "@components/post-page"; import PostPage from "@components/post-page";
const Post = ({ theme, changeTheme }: ThemeProps) => { const Post = () => {
const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true); const [isPasswordModalOpen, setIsPasswordModalOpen] = useState(true);
const [post, setPost] = useState<Post>() const [post, setPost] = useState<Post>()
const router = useRouter() 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 <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 export default Post

View file

@ -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 PageSeo from "@components/page-seo";
import Auth from "@components/auth"; import Auth from "@components/auth";
import Header from "@components/header"; import Header from "@components/header/header";
import type { ThemeProps } from "@lib/types"; import styles from '@styles/Home.module.css'
const SignIn = () => (
const SignIn = ({ theme, changeTheme }: ThemeProps) => (
<Page width={"100%"}> <Page width={"100%"}>
<PageSeo title="Drift - Sign In" /> <PageSeo title="Drift - Sign In" />
<Page.Header> <Page.Header>
<Header theme={theme} changeTheme={changeTheme} /> <Header />
</Page.Header> </Page.Header>
<Page.Content paddingTop={"var(--gap)"} width={"var(--main-content-width)"} margin="auto"> <Page.Content className={styles.main}>
<Auth page="signin" /> <Auth page="signin" />
</Page.Content> </Page.Content>
</Page> </Page>

View file

@ -1,17 +1,17 @@
import Page from "@geist-ui/core/dist/page"; import { Page } from '@geist-ui/core';
import Auth from "@components/auth"; import Auth from "@components/auth";
import Header from "@components/header"; import Header from "@components/header/header";
import PageSeo from '@components/page-seo'; 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%"> <Page width="100%">
<PageSeo title="Drift - Sign Up" /> <PageSeo title="Drift - Sign Up" />
<Page.Header> <Page.Header>
<Header theme={theme} changeTheme={changeTheme} /> <Header />
</Page.Header> </Page.Header>
<Page.Content width={"var(--main-content-width)"} paddingTop={"var(--gap)"} margin="auto"> <Page.Content className={styles.main}>
<Auth page="signup" /> <Auth page="signup" />
</Page.Content> </Page.Content>
</Page> </Page>

View file

@ -25,29 +25,7 @@
--transition: 0.1s ease-in-out; --transition: 0.1s ease-in-out;
--transition-slow: 0.3s ease-in-out; --transition-slow: 0.3s ease-in-out;
/* Dark Mode Colors */ --page-nav-height: 64px;
--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; --token: #999;
--comment: #999; --comment: #999;
--keyword: #fff; --keyword: #fff;
@ -56,17 +34,6 @@
} }
[data-theme="light"] { [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; --token: #666;
--comment: #999; --comment: #999;
--keyword: #000; --keyword: #000;
@ -81,11 +48,6 @@
::selection { ::selection {
text-shadow: none; text-shadow: none;
background: var(--selection); background: var(--selection);
color: var(--bg);
}
html {
line-height: 1.5;
} }
html, html,
@ -93,8 +55,6 @@ body {
padding: 0; padding: 0;
margin: 0; margin: 0;
font-size: 15px; font-size: 15px;
background: var(--bg);
color: var(--fg);
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
@ -113,44 +73,6 @@ li {
font-size: 1.125rem; 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 { blockquote {
font-style: italic; font-style: italic;
margin: 0; margin: 0;
@ -158,52 +80,16 @@ blockquote {
border-left: 3px solid var(--light-gray); border-left: 3px solid var(--light-gray);
} }
button {
border: none;
padding: 0;
margin: 0;
line-height: inherit;
font-size: inherit;
}
p a,
a.reset { a.reset {
outline: none; outline: none;
color: var(--fg);
text-decoration: none; text-decoration: none;
} }
p a:hover,
p a:focus,
p a:active,
a.reset:hover,
a.reset:focus {
color: var(--gray);
}
pre, pre,
code { code {
font-family: var(--font-mono); 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 { kbd {
font-family: var(--font-sans); font-family: var(--font-sans);
font-size: 1rem; font-size: 1rem;
@ -213,18 +99,6 @@ kbd {
border-radius: 5px; 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 { @media print {
:root { :root {
--bg: #fff; --bg: #fff;

View file

@ -197,6 +197,11 @@
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= 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": "@types/mdast@^3.0.0":
version "3.0.10" version "3.0.10"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.10.tgz#4724244a82a4598884cbbe9bcfd73dff927ee8af" 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" resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.2.tgz#9b59eb2c1b22fe71954a65ff512887065a7bb57c"
integrity sha512-y8j3a5/DkJCmS5x4dMCQL+OR0+2EAq3DOtio1COSHsmW2BGXnNCK3v12hJt1LrUz5iZH5g0LmuYOjDdI+czghA== 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: mdast-util-definitions@^5.0.0:
version "5.1.0" version "5.1.0"
resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-5.1.0.tgz#b6d10ef00a3c4cf191e8d9a5fa58d7f4a366f817" 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" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
prism-react-renderer@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/prism-react-renderer/-/prism-react-renderer-1.3.1.tgz#88fc9d0df6bed06ca2b9097421349f8c2f24e30d"
integrity sha512-xUeDMEz074d0zc5y6rxiMp/dlC7C+5IDDlaEUlcBOFE2wddz7hz5PNupb087mPwTt7T9BrFmewObfCBuf/LKwQ==
prismjs@^1.25.0, prismjs@~1.27.0: prismjs@^1.25.0, prismjs@~1.27.0:
version "1.27.0" version "1.27.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057"