Custom tabs
This commit is contained in:
parent
45c2e59105
commit
3c5dcc24ac
16 changed files with 356 additions and 233 deletions
|
@ -14,10 +14,10 @@ type Props = {
|
||||||
const MarkdownPreview = ({
|
const MarkdownPreview = ({
|
||||||
height = 500,
|
height = 500,
|
||||||
fileId,
|
fileId,
|
||||||
content: initial = "",
|
content = "",
|
||||||
title
|
title
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [content, setPreview] = useState<string>(initial)
|
const [preview, setPreview] = useState<string>(content)
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(true)
|
const [isLoading, setIsLoading] = useState<boolean>(true)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPost() {
|
async function fetchPost() {
|
||||||
|
@ -28,7 +28,7 @@ const MarkdownPreview = ({
|
||||||
? undefined
|
? undefined
|
||||||
: JSON.stringify({
|
: JSON.stringify({
|
||||||
title: title || "",
|
title: title || "",
|
||||||
content: initial
|
content: content
|
||||||
})
|
})
|
||||||
|
|
||||||
const resp = await fetch(path, {
|
const resp = await fetch(path, {
|
||||||
|
@ -47,14 +47,14 @@ const MarkdownPreview = ({
|
||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
fetchPost()
|
fetchPost()
|
||||||
}, [initial, fileId, title])
|
}, [content, fileId, title])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<><Spinner /></>
|
<><Spinner /></>
|
||||||
) : (
|
) : (
|
||||||
<StaticPreview content={content} height={height} />
|
<StaticPreview preview={preview} height={height} />
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -63,16 +63,17 @@ const MarkdownPreview = ({
|
||||||
export default memo(MarkdownPreview)
|
export default memo(MarkdownPreview)
|
||||||
|
|
||||||
export const StaticPreview = ({
|
export const StaticPreview = ({
|
||||||
content,
|
preview,
|
||||||
height = 500
|
height = 500
|
||||||
}: {
|
}: {
|
||||||
content: string
|
preview: string
|
||||||
height: string | number
|
height: string | number
|
||||||
}) => {
|
}) => {
|
||||||
|
console.log("content", preview)
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
className={styles.markdownPreview}
|
className={styles.markdownPreview}
|
||||||
dangerouslySetInnerHTML={{ __html: content }}
|
dangerouslySetInnerHTML={{ __html: preview }}
|
||||||
style={{
|
style={{
|
||||||
height
|
height
|
||||||
}}
|
}}
|
||||||
|
|
84
client/app/(posts)/components/tabs/index.tsx
Normal file
84
client/app/(posts)/components/tabs/index.tsx
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
import * as RadixTabs from "@radix-ui/react-tabs"
|
||||||
|
import FormattingIcons from "app/(posts)/new/components/edit-document-list/edit-document/formatting-icons"
|
||||||
|
import { ChangeEvent, useRef } from "react"
|
||||||
|
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
|
||||||
|
import Preview, { StaticPreview } from "../preview"
|
||||||
|
import styles from "./tabs.module.css"
|
||||||
|
|
||||||
|
type Props = RadixTabs.TabsProps & {
|
||||||
|
isEditing: boolean
|
||||||
|
defaultTab: "preview" | "edit"
|
||||||
|
handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
|
||||||
|
onPaste?: (e: any) => void
|
||||||
|
title?: string
|
||||||
|
content?: string
|
||||||
|
preview?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DocumentTabs({
|
||||||
|
isEditing,
|
||||||
|
defaultTab,
|
||||||
|
handleOnContentChange,
|
||||||
|
onPaste,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
preview,
|
||||||
|
...props
|
||||||
|
}: Props) {
|
||||||
|
const codeEditorRef = useRef<TextareaMarkdownRef>(null)
|
||||||
|
|
||||||
|
const handleTabChange = (newTab: string) => {
|
||||||
|
if (newTab === "preview") {
|
||||||
|
codeEditorRef.current?.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RadixTabs.Root
|
||||||
|
{...props}
|
||||||
|
onValueChange={handleTabChange}
|
||||||
|
className={styles.root}
|
||||||
|
defaultValue={defaultTab}
|
||||||
|
>
|
||||||
|
<RadixTabs.List className={styles.list}>
|
||||||
|
<RadixTabs.Trigger value="edit" className={styles.trigger}>
|
||||||
|
{isEditing ? "Edit" : "Raw"}
|
||||||
|
</RadixTabs.Trigger>
|
||||||
|
<RadixTabs.Trigger value="preview" className={styles.trigger}>
|
||||||
|
{isEditing ? "Preview" : "Rendered"}
|
||||||
|
</RadixTabs.Trigger>
|
||||||
|
</RadixTabs.List>
|
||||||
|
<RadixTabs.Content value="edit">
|
||||||
|
<FormattingIcons textareaRef={codeEditorRef} />
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
marginTop: 6,
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextareaMarkdown.Wrapper ref={codeEditorRef}>
|
||||||
|
<textarea
|
||||||
|
readOnly={!isEditing}
|
||||||
|
onPaste={onPaste ? onPaste : undefined}
|
||||||
|
ref={codeEditorRef}
|
||||||
|
placeholder=""
|
||||||
|
value={content}
|
||||||
|
onChange={handleOnContentChange}
|
||||||
|
// TODO: Textarea should grow to fill parent if height == 100%
|
||||||
|
style={{ flex: 1, minHeight: 350 }}
|
||||||
|
// className={styles.textarea}
|
||||||
|
/>
|
||||||
|
</TextareaMarkdown.Wrapper>
|
||||||
|
</div>
|
||||||
|
</RadixTabs.Content>
|
||||||
|
<RadixTabs.Content value="preview">
|
||||||
|
{isEditing ? (
|
||||||
|
<Preview height={"100%"} title={title} content={content} />
|
||||||
|
) : (
|
||||||
|
<StaticPreview height={"100%"} preview={preview || ""} />
|
||||||
|
)}
|
||||||
|
</RadixTabs.Content>
|
||||||
|
</RadixTabs.Root>
|
||||||
|
)
|
||||||
|
}
|
42
client/app/(posts)/components/tabs/tabs.module.css
Normal file
42
client/app/(posts)/components/tabs/tabs.module.css
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger {
|
||||||
|
width: 80px;
|
||||||
|
height: 30px;
|
||||||
|
margin: 4px 0;
|
||||||
|
font-family: inherit;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:hover {
|
||||||
|
background-color: var(--lighter-gray);
|
||||||
|
color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger:last-child {
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger[data-state="active"] {
|
||||||
|
color: var(--darkest-gray);
|
||||||
|
box-shadow: inset 0 -1px 0 0 currentColor, 0 1px 0 0 currentColor;
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
|
import Input from "@components/input"
|
||||||
import { ChangeEvent, memo } from "react"
|
import { ChangeEvent, memo } from "react"
|
||||||
import { Input } from "@geist-ui/core/dist"
|
|
||||||
|
|
||||||
import styles from "../post.module.css"
|
import styles from "../post.module.css"
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,17 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
border-width: 2px;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
border-style: dashed;
|
border: 2px dashed var(--border) !important;
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: all 0.24s ease-in-out;
|
transition: all 0.24s ease-in-out;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropzone:hover {
|
||||||
|
border-color: var(--gray) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.dropzone:focus {
|
.dropzone:focus {
|
||||||
box-shadow: 0 0 4px 1px rgba(124, 124, 124, 0.5);
|
box-shadow: 0 0 4px 1px rgba(124, 124, 124, 0.5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,11 @@ import Trash from "@geist-ui/icons/trash"
|
||||||
import FormattingIcons from "./formatting-icons"
|
import FormattingIcons from "./formatting-icons"
|
||||||
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
|
import TextareaMarkdown, { TextareaMarkdownRef } from "textarea-markdown-editor"
|
||||||
|
|
||||||
import { Input, Tabs, Textarea } from "@geist-ui/core/dist"
|
import { Tabs } from "@geist-ui/core/dist"
|
||||||
import Preview from "../../../../components/preview"
|
import Preview from "../../../../components/preview"
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
|
import Input from "@components/input"
|
||||||
|
import DocumentTabs from "app/(posts)/components/tabs"
|
||||||
|
|
||||||
// import Link from "next/link"
|
// import Link from "next/link"
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -35,18 +37,9 @@ const Document = ({
|
||||||
initialTab = "edit",
|
initialTab = "edit",
|
||||||
handleOnContentChange
|
handleOnContentChange
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const codeEditorRef = useRef<TextareaMarkdownRef>(null)
|
|
||||||
const [tab, setTab] = useState(initialTab)
|
|
||||||
// const height = editable ? "500px" : '100%'
|
// const height = editable ? "500px" : '100%'
|
||||||
const height = "100%"
|
const height = "100%"
|
||||||
|
|
||||||
const handleTabChange = (newTab: string) => {
|
|
||||||
if (newTab === "edit") {
|
|
||||||
codeEditorRef.current?.focus()
|
|
||||||
}
|
|
||||||
setTab(newTab as "edit" | "preview")
|
|
||||||
}
|
|
||||||
|
|
||||||
const onTitleChange = useCallback(
|
const onTitleChange = useCallback(
|
||||||
(event: ChangeEvent<HTMLInputElement>) =>
|
(event: ChangeEvent<HTMLInputElement>) =>
|
||||||
setTitle ? setTitle(event.target.value) : null,
|
setTitle ? setTitle(event.target.value) : null,
|
||||||
|
@ -71,22 +64,6 @@ const Document = ({
|
||||||
[content]
|
[content]
|
||||||
)
|
)
|
||||||
|
|
||||||
// if (skeleton) {
|
|
||||||
// return <>
|
|
||||||
// <Spacer height={1} />
|
|
||||||
// <div className={styles.card}>
|
|
||||||
// <div className={styles.fileNameContainer}>
|
|
||||||
// <Skeleton width={275} height={36} />
|
|
||||||
// {remove && <Skeleton width={36} height={36} />}
|
|
||||||
// </div>
|
|
||||||
// <div className={styles.descriptionContainer}>
|
|
||||||
// <div style={{ flexDirection: 'row', display: 'flex' }}><Skeleton width={125} height={36} /></div>
|
|
||||||
// <Skeleton width={'100%'} height={350} />
|
|
||||||
// </div >
|
|
||||||
// </div>
|
|
||||||
// </>
|
|
||||||
// }
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
|
@ -95,63 +72,38 @@ const Document = ({
|
||||||
placeholder="MyFile.md"
|
placeholder="MyFile.md"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={onTitleChange}
|
onChange={onTitleChange}
|
||||||
marginTop="var(--gap-double)"
|
|
||||||
size={1.2}
|
|
||||||
font={1.2}
|
|
||||||
label="Filename"
|
label="Filename"
|
||||||
width={"100%"}
|
width={"100%"}
|
||||||
id={title}
|
id={title}
|
||||||
|
style={{
|
||||||
|
borderTopRightRadius: remove ? 0 : "var(--radius)",
|
||||||
|
borderBottomRightRadius: remove ? 0 : "var(--radius)"
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
{remove && (
|
{remove && (
|
||||||
<Button
|
<Button
|
||||||
iconLeft={<Trash />}
|
iconLeft={<Trash />}
|
||||||
height={"36px"}
|
height={"39px"}
|
||||||
width={"48px"}
|
width={"48px"}
|
||||||
padding={0}
|
padding={0}
|
||||||
margin={0}
|
margin={0}
|
||||||
onClick={() => removeFile(remove)}
|
onClick={() => removeFile(remove)}
|
||||||
|
style={{
|
||||||
|
borderTopLeftRadius: 0,
|
||||||
|
borderBottomLeftRadius: 0
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.descriptionContainer}>
|
<div className={styles.descriptionContainer}>
|
||||||
{tab === "edit" && <FormattingIcons textareaRef={codeEditorRef} />}
|
<DocumentTabs
|
||||||
<Tabs
|
isEditing={true}
|
||||||
onChange={handleTabChange}
|
defaultTab={"edit"}
|
||||||
initialValue={initialTab}
|
handleOnContentChange={handleOnContentChange}
|
||||||
hideDivider
|
onPaste={onPaste}
|
||||||
leftSpace={0}
|
title={title}
|
||||||
>
|
content={content}
|
||||||
<Tabs.Item label={"Edit"} value="edit">
|
|
||||||
{/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: 6,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TextareaMarkdown.Wrapper ref={codeEditorRef}>
|
|
||||||
<Textarea
|
|
||||||
onPaste={onPaste ? onPaste : undefined}
|
|
||||||
ref={codeEditorRef}
|
|
||||||
placeholder=""
|
|
||||||
value={content}
|
|
||||||
onChange={handleOnContentChange}
|
|
||||||
width="100%"
|
|
||||||
// TODO: Textarea should grow to fill parent if height == 100%
|
|
||||||
style={{ flex: 1, minHeight: 350 }}
|
|
||||||
resize="vertical"
|
|
||||||
className={styles.textarea}
|
|
||||||
/>
|
/>
|
||||||
</TextareaMarkdown.Wrapper>
|
|
||||||
</div>
|
|
||||||
</Tabs.Item>
|
|
||||||
<Tabs.Item label="Preview" value="preview">
|
|
||||||
<div>
|
|
||||||
<Preview height={height} title={title} content={content} />
|
|
||||||
</div>
|
|
||||||
</Tabs.Item>
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useToasts, Input, ButtonDropdown } from "@geist-ui/core/dist"
|
import { useToasts, ButtonDropdown } from "@geist-ui/core/dist"
|
||||||
import { useRouter } from "next/navigation"
|
import { useRouter } from "next/navigation"
|
||||||
import { useCallback, useState } from "react"
|
import { useCallback, useState } from "react"
|
||||||
import generateUUID from "@lib/generate-uuid"
|
import generateUUID from "@lib/generate-uuid"
|
||||||
|
@ -15,6 +15,7 @@ import PasswordModal from "../../../components/password-modal"
|
||||||
import Title from "./title"
|
import Title from "./title"
|
||||||
import FileDropzone from "./drag-and-drop"
|
import FileDropzone from "./drag-and-drop"
|
||||||
import Button from "@components/button"
|
import Button from "@components/button"
|
||||||
|
import Input from "@components/input"
|
||||||
const emptyDoc = {
|
const emptyDoc = {
|
||||||
title: "",
|
title: "",
|
||||||
content: "",
|
content: "",
|
||||||
|
@ -287,7 +288,7 @@ const Post = ({
|
||||||
<DatePicker
|
<DatePicker
|
||||||
onChange={onChangeExpiration}
|
onChange={onChangeExpiration}
|
||||||
customInput={
|
customInput={
|
||||||
<Input label="Expires at" clearable width="100%" height="40px" />
|
<Input label="Expires at" width="100%" height="40px" />
|
||||||
}
|
}
|
||||||
placeholderText="Won't expire"
|
placeholderText="Won't expire"
|
||||||
selected={expiresAt}
|
selected={expiresAt}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { ChangeEvent, memo, useEffect, useState } from "react"
|
import { ChangeEvent, memo, useEffect, useState } from "react"
|
||||||
import { Text } from "@geist-ui/core/dist"
|
|
||||||
|
|
||||||
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/dist"
|
import Input from "@components/input"
|
||||||
|
|
||||||
const titlePlaceholders = [
|
const titlePlaceholders = [
|
||||||
"How to...",
|
"How to...",
|
||||||
|
@ -30,20 +29,15 @@ const Title = ({ onChange, title }: props) => {
|
||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
<div className={styles.title}>
|
<div className={styles.title}>
|
||||||
<Text h1 width={"150px"} className={styles.drift}>
|
<h1 className={styles.drift}>Drift</h1>
|
||||||
Drift
|
|
||||||
</Text>
|
|
||||||
<ShiftBy y={-3}>
|
|
||||||
<Input
|
<Input
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={title || ""}
|
value={title || ""}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
height={"55px"}
|
height={"55px"}
|
||||||
font={1.5}
|
|
||||||
label="Post title"
|
label="Post title"
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%", fontSize: 18 }}
|
||||||
/>
|
/>
|
||||||
</ShiftBy>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,17 +5,13 @@ import ExternalLink from "@geist-ui/icons/externalLink"
|
||||||
import Skeleton from "@components/skeleton"
|
import Skeleton from "@components/skeleton"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
|
||||||
import {
|
import { Tabs, Textarea, Tag, Spacer } from "@geist-ui/core/dist"
|
||||||
Button,
|
|
||||||
ButtonGroup,
|
|
||||||
Spacer,
|
|
||||||
Tabs,
|
|
||||||
Textarea,
|
|
||||||
Tag
|
|
||||||
} from "@geist-ui/core/dist"
|
|
||||||
import { StaticPreview } from "app/(posts)/components/preview"
|
import { StaticPreview } from "app/(posts)/components/preview"
|
||||||
import FadeIn from "@components/fade-in"
|
import FadeIn from "@components/fade-in"
|
||||||
import Tooltip from "@components/tooltip"
|
import Tooltip from "@components/tooltip"
|
||||||
|
import Button from "@components/button"
|
||||||
|
import ButtonGroup from "@components/button-group"
|
||||||
|
import DocumentTabs from "app/(posts)/components/tabs"
|
||||||
|
|
||||||
// import Link from "next/link"
|
// import Link from "next/link"
|
||||||
type Props = {
|
type Props = {
|
||||||
|
@ -37,22 +33,13 @@ const DownloadButton = ({ rawLink }: { rawLink?: string }) => {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<Button
|
<Button iconRight={<Download />} aria-label="Download" />
|
||||||
scale={2 / 3}
|
|
||||||
px={0.6}
|
|
||||||
icon={<Download />}
|
|
||||||
auto
|
|
||||||
aria-label="Download"
|
|
||||||
/>
|
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip content="Open raw in new tab">
|
<Tooltip content="Open raw in new tab">
|
||||||
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
|
<Link href={rawLink || ""} target="_blank" rel="noopener noreferrer">
|
||||||
<Button
|
<Button
|
||||||
scale={2 / 3}
|
iconRight={<ExternalLink />}
|
||||||
px={0.6}
|
|
||||||
icon={<ExternalLink />}
|
|
||||||
auto
|
|
||||||
aria-label="Open raw file in new tab"
|
aria-label="Open raw file in new tab"
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -70,15 +57,6 @@ const Document = ({
|
||||||
skeleton,
|
skeleton,
|
||||||
id
|
id
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const codeEditorRef = useRef<HTMLTextAreaElement>(null)
|
|
||||||
const height = "100%"
|
|
||||||
|
|
||||||
const handleTabChange = (newTab: string) => {
|
|
||||||
if (newTab === "edit") {
|
|
||||||
codeEditorRef.current?.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawLink = () => {
|
const rawLink = () => {
|
||||||
if (id) {
|
if (id) {
|
||||||
return `/file/raw/${id}`
|
return `/file/raw/${id}`
|
||||||
|
@ -105,8 +83,7 @@ const Document = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FadeIn>
|
<>
|
||||||
<Spacer height={1} />
|
|
||||||
<div className={styles.card}>
|
<div className={styles.card}>
|
||||||
<Link href={`#${title}`} className={styles.fileNameContainer}>
|
<Link href={`#${title}`} className={styles.fileNameContainer}>
|
||||||
<Tag
|
<Tag
|
||||||
|
@ -120,47 +97,15 @@ const Document = ({
|
||||||
</Link>
|
</Link>
|
||||||
<div className={styles.descriptionContainer}>
|
<div className={styles.descriptionContainer}>
|
||||||
<DownloadButton rawLink={rawLink()} />
|
<DownloadButton rawLink={rawLink()} />
|
||||||
<Tabs
|
<DocumentTabs
|
||||||
onChange={handleTabChange}
|
defaultTab={initialTab}
|
||||||
initialValue={initialTab}
|
preview={preview}
|
||||||
hideDivider
|
content={content}
|
||||||
leftSpace={0}
|
isEditing={false}
|
||||||
>
|
|
||||||
<Tabs.Item label={"Raw"} value="edit">
|
|
||||||
{/* <textarea className={styles.lineCounter} wrap='off' readOnly ref={lineNumberRef}>1.</textarea> */}
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
marginTop: 6,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Textarea
|
|
||||||
readOnly
|
|
||||||
ref={codeEditorRef}
|
|
||||||
value={content}
|
|
||||||
width="100%"
|
|
||||||
// TODO: Textarea should grow to fill parent if height == 100%
|
|
||||||
style={{ flex: 1, minHeight: 350 }}
|
|
||||||
resize="vertical"
|
|
||||||
className={styles.textarea}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Tabs.Item>
|
|
||||||
<Tabs.Item label="Preview" value="preview">
|
|
||||||
<div>
|
|
||||||
<StaticPreview
|
|
||||||
height={height}
|
|
||||||
// fileId={id}
|
|
||||||
content={preview}
|
|
||||||
// title={title}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</Tabs.Item>
|
</>
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</FadeIn>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,3 +11,9 @@
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* all buttons on the inside should have no border radius */
|
||||||
|
|
||||||
|
.button-group :global(button) {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
"use client"
|
"use client"
|
||||||
import ShiftBy from "@components/shift-by"
|
import { Tabs, Textarea } from "@geist-ui/core/dist"
|
||||||
import { Spacer, Tabs, Textarea, Text } from "@geist-ui/core/dist"
|
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import styles from "./home.module.css"
|
import styles from "./home.module.css"
|
||||||
// TODO:components/new-post/ move these styles
|
// TODO:components/new-post/ move these styles
|
||||||
import markdownStyles from "app/(posts)/components/preview/preview.module.css";
|
import markdownStyles from "app/(posts)/components/preview/preview.module.css"
|
||||||
import Card from "./card"
|
import Card from "./card"
|
||||||
const Home = ({
|
const Home = ({
|
||||||
introTitle,
|
introTitle,
|
||||||
|
@ -20,7 +19,6 @@ const Home = ({
|
||||||
<div
|
<div
|
||||||
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
style={{ display: "flex", flexDirection: "row", alignItems: "center" }}
|
||||||
>
|
>
|
||||||
<ShiftBy y={-2}>
|
|
||||||
<Image
|
<Image
|
||||||
src={"/assets/logo-optimized.svg"}
|
src={"/assets/logo-optimized.svg"}
|
||||||
width={48}
|
width={48}
|
||||||
|
@ -28,11 +26,7 @@ const Home = ({
|
||||||
alt=""
|
alt=""
|
||||||
priority
|
priority
|
||||||
/>
|
/>
|
||||||
</ShiftBy>
|
<h1 style={{ marginLeft: "var(--gap)" }}>{introTitle}</h1>
|
||||||
<Spacer />
|
|
||||||
<Text style={{ display: "inline" }} h1>
|
|
||||||
{introTitle}
|
|
||||||
</Text>
|
|
||||||
</div>
|
</div>
|
||||||
<Card>
|
<Card>
|
||||||
<Tabs initialValue={"preview"} hideDivider leftSpace={0}>
|
<Tabs initialValue={"preview"} hideDivider leftSpace={0}>
|
||||||
|
@ -45,13 +39,11 @@ const Home = ({
|
||||||
flexDirection: "column"
|
flexDirection: "column"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Textarea
|
<textarea
|
||||||
readOnly
|
readOnly
|
||||||
value={introContent}
|
value={introContent}
|
||||||
width="100%"
|
|
||||||
// TODO: Textarea should grow to fill parent if height == 100%
|
// TODO: Textarea should grow to fill parent if height == 100%
|
||||||
style={{ flex: 1, minHeight: 350 }}
|
style={{ flex: 1, minHeight: 350 }}
|
||||||
resize="vertical"
|
|
||||||
className={styles.textarea}
|
className={styles.textarea}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,19 +3,32 @@ import styles from "./input.module.css"
|
||||||
|
|
||||||
type Props = React.HTMLProps<HTMLInputElement> & {
|
type Props = React.HTMLProps<HTMLInputElement> & {
|
||||||
label?: string
|
label?: string
|
||||||
fontSize?: number | string
|
width?: number | string
|
||||||
|
height?: number | string
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
const Input = React.forwardRef<HTMLInputElement, Props>(
|
const Input = React.forwardRef<HTMLInputElement, Props>(
|
||||||
({ label, className, ...props }, ref) => {
|
({ label, className, width, height, ...props }, ref) => {
|
||||||
|
const classes = [styles.input, styles.withLabel, className].join(" ")
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div
|
||||||
|
className={styles.wrapper}
|
||||||
|
style={{
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}}
|
||||||
|
>
|
||||||
{label && <label className={styles.label}>{label}</label>}
|
{label && <label className={styles.label}>{label}</label>}
|
||||||
<input
|
<input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={className ? `${styles.input} ${className}` : styles.input}
|
className={classes}
|
||||||
{...props}
|
{...props}
|
||||||
|
style={{
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
...(props.style || {})
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
font-size: 1rem;
|
/* font-size: 1rem; */
|
||||||
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input {
|
.input {
|
||||||
|
@ -11,7 +12,7 @@
|
||||||
border-radius: var(--inline-radius);
|
border-radius: var(--inline-radius);
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
border: 1px solid var(--light-gray);
|
border: 1px solid var(--border);
|
||||||
padding: 0 var(--gap-half);
|
padding: 0 var(--gap-half);
|
||||||
outline: none;
|
outline: none;
|
||||||
transition: border-color var(--transition);
|
transition: border-color var(--transition);
|
||||||
|
@ -21,12 +22,18 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.withLabel {
|
||||||
|
/* if with label, then left border should be flat */
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.input::placeholder {
|
.input::placeholder {
|
||||||
font-size: 1rem;
|
/* font-size: 1rem; */
|
||||||
}
|
}
|
||||||
|
|
||||||
.input:focus {
|
.input:focus {
|
||||||
border-color: var(--input-border-focus);
|
border-color: var(--light-gray);
|
||||||
}
|
}
|
||||||
|
|
||||||
.label {
|
.label {
|
||||||
|
@ -37,14 +44,13 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 var(--gap-half);
|
padding: 0 var(--gap-half);
|
||||||
color: var(--fg);
|
color: var(--darker-gray);
|
||||||
background-color: var(--light-gray);
|
background-color: var(--lighter-gray);
|
||||||
border-top-left-radius: var(--radius);
|
border-top-left-radius: var(--radius);
|
||||||
border-bottom-left-radius: var(--radius);
|
border-bottom-left-radius: var(--radius);
|
||||||
border-top: 1px solid var(--input-border);
|
border-top: 1px solid var(--input-border);
|
||||||
border-left: 1px solid var(--input-border);
|
border-left: 1px solid var(--input-border);
|
||||||
border-bottom: 1px solid var(--input-border);
|
border-bottom: 1px solid var(--input-border);
|
||||||
font-size: inherit;
|
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,10 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*:focus-visible {
|
||||||
|
outline: 1px solid var(--gray);
|
||||||
|
}
|
||||||
|
|
||||||
::selection {
|
::selection {
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
background: var(--fg) !important;
|
background: var(--fg) !important;
|
||||||
|
@ -111,7 +115,7 @@ input,
|
||||||
button,
|
button,
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select {
|
||||||
border: var(--border);
|
border: none;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
background: var(--bg);
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
@ -175,3 +179,17 @@ main {
|
||||||
padding-top: 0 !important;
|
padding-top: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
padding: var(--gap-half);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"@next-auth/prisma-adapter": "^1.0.5",
|
"@next-auth/prisma-adapter": "^1.0.5",
|
||||||
"@prisma/client": "^4.6.1",
|
"@prisma/client": "^4.6.1",
|
||||||
"@radix-ui/react-popover": "^1.0.2",
|
"@radix-ui/react-popover": "^1.0.2",
|
||||||
|
"@radix-ui/react-tabs": "^1.0.1",
|
||||||
"@radix-ui/react-tooltip": "^1.0.2",
|
"@radix-ui/react-tooltip": "^1.0.2",
|
||||||
"@wcj/markdown-to-html": "^2.1.2",
|
"@wcj/markdown-to-html": "^2.1.2",
|
||||||
"bcrypt": "^5.1.0",
|
"bcrypt": "^5.1.0",
|
||||||
|
|
|
@ -7,6 +7,7 @@ specifiers:
|
||||||
'@next/bundle-analyzer': 12.1.6
|
'@next/bundle-analyzer': 12.1.6
|
||||||
'@prisma/client': ^4.6.1
|
'@prisma/client': ^4.6.1
|
||||||
'@radix-ui/react-popover': ^1.0.2
|
'@radix-ui/react-popover': ^1.0.2
|
||||||
|
'@radix-ui/react-tabs': ^1.0.1
|
||||||
'@radix-ui/react-tooltip': ^1.0.2
|
'@radix-ui/react-tooltip': ^1.0.2
|
||||||
'@types/bcrypt': ^5.0.0
|
'@types/bcrypt': ^5.0.0
|
||||||
'@types/node': 17.0.23
|
'@types/node': 17.0.23
|
||||||
|
@ -48,6 +49,7 @@ dependencies:
|
||||||
'@next-auth/prisma-adapter': 1.0.5_2pl3b2nwmjya7el2zbe6cwkney
|
'@next-auth/prisma-adapter': 1.0.5_2pl3b2nwmjya7el2zbe6cwkney
|
||||||
'@prisma/client': 4.6.1_prisma@4.6.1
|
'@prisma/client': 4.6.1_prisma@4.6.1
|
||||||
'@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
'@radix-ui/react-popover': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||||
|
'@radix-ui/react-tabs': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
'@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
'@radix-ui/react-tooltip': 1.0.2_jbvntnid6ohjelon6ccj5dhg2u
|
||||||
'@wcj/markdown-to-html': 2.1.2
|
'@wcj/markdown-to-html': 2.1.2
|
||||||
bcrypt: 5.1.0
|
bcrypt: 5.1.0
|
||||||
|
@ -1024,6 +1026,21 @@ packages:
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-collection/1.0.1_biqbaboplfbrettd7655fr4n2y:
|
||||||
|
resolution: {integrity: sha512-uuiFbs+YCKjn3X1DTSx9G7BHApu4GHbi3kgiwsnFUbOKCrwejAJv4eE4Vc8C0Oaxt9T0aV4ox0WCOdx+39Xo+g==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.20.1
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-context': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
'@radix-ui/react-slot': 1.0.1_react@18.2.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-compose-refs/1.0.0_react@18.2.0:
|
/@radix-ui/react-compose-refs/1.0.0_react@18.2.0:
|
||||||
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==}
|
resolution: {integrity: sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1042,6 +1059,15 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-direction/1.0.0_react@18.2.0:
|
||||||
|
resolution: {integrity: sha512-2HV05lGUgYcA6xgLQ4BKPDmtL+QbIZYH5fCOTAOOcJ5O0QbWS3i9lKaurLzliYUDhORI2Qr3pyjhJh44lKA3rQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.20.1
|
||||||
|
react: 18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-dismissable-layer/1.0.2_biqbaboplfbrettd7655fr4n2y:
|
/@radix-ui/react-dismissable-layer/1.0.2_biqbaboplfbrettd7655fr4n2y:
|
||||||
resolution: {integrity: sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==}
|
resolution: {integrity: sha512-WjJzMrTWROozDqLB0uRWYvj4UuXsM/2L19EmQ3Au+IJWqwvwq9Bwd+P8ivo0Deg9JDPArR1I6MbWNi1CmXsskg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1178,6 +1204,26 @@ packages:
|
||||||
react-dom: 18.2.0_react@18.2.0
|
react-dom: 18.2.0_react@18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-roving-focus/1.0.1_biqbaboplfbrettd7655fr4n2y:
|
||||||
|
resolution: {integrity: sha512-TB76u5TIxKpqMpUAuYH2VqMhHYKa+4Vs1NHygo/llLvlffN6mLVsFhz0AnSFlSBAvTBYVHYAkHAyEt7x1gPJOA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.20.1
|
||||||
|
'@radix-ui/primitive': 1.0.0
|
||||||
|
'@radix-ui/react-collection': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
'@radix-ui/react-compose-refs': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-context': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-direction': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-id': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
'@radix-ui/react-use-callback-ref': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-slot/1.0.1_react@18.2.0:
|
/@radix-ui/react-slot/1.0.1_react@18.2.0:
|
||||||
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
|
resolution: {integrity: sha512-avutXAFL1ehGvAXtPquu0YK5oz6ctS474iM3vNGQIkswrVhdrS52e3uoMQBzZhNRAIE0jBnUyXWNmSjGHhCFcw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -1188,6 +1234,25 @@ packages:
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-tabs/1.0.1_biqbaboplfbrettd7655fr4n2y:
|
||||||
|
resolution: {integrity: sha512-mVNEwHwgjy2G9F7b39f9VY+jF0QUZykTm0Sdv+Uz6KC4KOEIa4HLDiHU8MeEZluRtZE3aqGYDhl93O7QbJDwhg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.20.1
|
||||||
|
'@radix-ui/primitive': 1.0.0
|
||||||
|
'@radix-ui/react-context': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-direction': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-id': 1.0.0_react@18.2.0
|
||||||
|
'@radix-ui/react-presence': 1.0.0_biqbaboplfbrettd7655fr4n2y
|
||||||
|
'@radix-ui/react-primitive': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
'@radix-ui/react-roving-focus': 1.0.1_biqbaboplfbrettd7655fr4n2y
|
||||||
|
'@radix-ui/react-use-controllable-state': 1.0.0_react@18.2.0
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0_react@18.2.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/react-tooltip/1.0.2_jbvntnid6ohjelon6ccj5dhg2u:
|
/@radix-ui/react-tooltip/1.0.2_jbvntnid6ohjelon6ccj5dhg2u:
|
||||||
resolution: {integrity: sha512-11gUlok2rv5mu+KBtxniOKKNKjqC/uTbgFHWoQdbF46vMV+zjDaBvCtVDK9+MTddlpmlisGPGvvojX7Qm0yr+g==}
|
resolution: {integrity: sha512-11gUlok2rv5mu+KBtxniOKKNKjqC/uTbgFHWoQdbF46vMV+zjDaBvCtVDK9+MTddlpmlisGPGvvojX7Qm0yr+g==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
Loading…
Reference in a new issue