client: add textarea-markdown-editor package and replace current editor textarea

This commit is contained in:
Max Leiter 2022-04-11 22:39:35 -07:00
parent 481d4ae36c
commit f510813e4b
No known key found for this signature in database
GPG key ID: A3512F2F2F17EBDA
4 changed files with 73 additions and 127 deletions

View file

@ -5,124 +5,53 @@ 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" import { Button, ButtonGroup } from "@geist-ui/core"
import { TextareaMarkdownRef } from "textarea-markdown-editor"
// TODO: clean up // TODO: clean up
const FormattingIcons = ({ const FormattingIcons = ({
textareaRef, textareaRef,
setText
}: { }: {
textareaRef?: RefObject<HTMLTextAreaElement> textareaRef?: RefObject<TextareaMarkdownRef>
setText?: (text: string) => void
}) => { }) => {
// const { textBefore, textAfter, selectedText } = useMemo(() => {
// if (textareaRef && textareaRef.current) {
// const textarea = textareaRef.current
// const text = textareaRef.current.value
// const selectionStart = textarea.selectionStart
// const selectionEnd = textarea.selectionEnd
// const textBefore = text.substring(0, selectionStart)
// const textAfter = text.substring(selectionEnd)
// const selectedText = text.substring(selectionStart, selectionEnd)
// return { textBefore, textAfter, selectedText }
// }
// return { textBefore: '', textAfter: '' }
// }, [textareaRef,])
const handleBoldClick = useCallback(() => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
const newText = `${before}**${selectedText}**${after}`
setText(newText)
}
}, [setText, textareaRef])
const handleItalicClick = useCallback(() => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
const newText = `${before}*${selectedText}*${after}`
setText(newText)
}
}, [setText, textareaRef])
const handleLinkClick = useCallback(() => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
let formattedText = ""
if (selectedText.includes("http")) {
formattedText = `[](${selectedText})`
} else {
formattedText = `[${selectedText}](https://)`
}
const newText = `${before}${formattedText}${after}`
setText(newText)
}
}, [setText, textareaRef])
const handleImageClick = useCallback(() => {
if (textareaRef?.current && setText) {
const selectionStart = textareaRef.current.selectionStart
const selectionEnd = textareaRef.current.selectionEnd
const text = textareaRef.current.value
const before = text.substring(0, selectionStart)
const after = text.substring(selectionEnd)
const selectedText = text.substring(selectionStart, selectionEnd)
let formattedText = ""
if (selectedText.includes("http")) {
formattedText = `![](${selectedText})`
} else {
formattedText = `![${selectedText}](https://)`
}
const newText = `${before}${formattedText}${after}`
setText(newText)
}
}, [setText, textareaRef])
const formattingActions = useMemo( const formattingActions = useMemo(
() => [ () => {
{ const handleBoldClick = () => textareaRef?.current?.trigger("bold")
icon: <Bold />, const handleItalicClick = () => textareaRef?.current?.trigger("italic")
name: "bold", const handleLinkClick = () => textareaRef?.current?.trigger("link")
action: handleBoldClick const handleImageClick = () => textareaRef?.current?.trigger("image")
}, return [
{ {
icon: <Italic />, icon: <Bold />,
name: "italic", name: "bold",
action: handleItalicClick action: handleBoldClick
}, },
// { {
// icon: <Underline />, icon: <Italic />,
// name: 'underline', name: "italic",
// action: handleUnderlineClick action: handleItalicClick
// }, },
{ // {
icon: <Link />, // icon: <Underline />,
name: "hyperlink", // name: 'underline',
action: handleLinkClick // action: handleUnderlineClick
}, // },
{ {
icon: <ImageIcon />, icon: <Link />,
name: "image", name: "hyperlink",
action: handleImageClick action: handleLinkClick
} },
], {
[handleBoldClick, handleImageClick, handleItalicClick, handleLinkClick] icon: <ImageIcon />,
name: "image",
action: handleImageClick
}
]
},
[textareaRef]
) )
return ( return (

View file

@ -9,16 +9,14 @@ import {
import styles from "./document.module.css" import styles from "./document.module.css"
import Trash from "@geist-ui/icons/trash" 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 { import {
Button, Button,
ButtonGroup,
Card,
Input, Input,
Spacer, Spacer,
Tabs, Tabs,
Textarea, Textarea,
Tooltip
} from "@geist-ui/core" } from "@geist-ui/core"
import Preview from "@components/preview" import Preview from "@components/preview"
@ -27,7 +25,6 @@ type Props = {
title?: string title?: string
content?: string content?: string
setTitle?: (title: string) => void setTitle?: (title: string) => void
setContent?: (content: string) => void
handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void handleOnContentChange?: (e: ChangeEvent<HTMLTextAreaElement>) => void
initialTab?: "edit" | "preview" initialTab?: "edit" | "preview"
remove?: () => void remove?: () => void
@ -40,11 +37,10 @@ const Document = ({
title, title,
content, content,
setTitle, setTitle,
setContent,
initialTab = "edit", initialTab = "edit",
handleOnContentChange handleOnContentChange
}: Props) => { }: Props) => {
const codeEditorRef = useRef<HTMLTextAreaElement>(null) const codeEditorRef = useRef<TextareaMarkdownRef>(null)
const [tab, setTab] = useState(initialTab) const [tab, setTab] = useState(initialTab)
// const height = editable ? "500px" : '100%' // const height = editable ? "500px" : '100%'
const height = "100%" const height = "100%"
@ -126,7 +122,7 @@ const Document = ({
</div> </div>
<div className={styles.descriptionContainer}> <div className={styles.descriptionContainer}>
{tab === "edit" && ( {tab === "edit" && (
<FormattingIcons setText={setContent} textareaRef={codeEditorRef} /> <FormattingIcons textareaRef={codeEditorRef} />
)} )}
<Tabs <Tabs
onChange={handleTabChange} onChange={handleTabChange}
@ -143,18 +139,20 @@ const Document = ({
flexDirection: "column" flexDirection: "column"
}} }}
> >
<Textarea <TextareaMarkdown.Wrapper ref={codeEditorRef}>
onPaste={onPaste ? onPaste : undefined} <Textarea
ref={codeEditorRef} onPaste={onPaste ? onPaste : undefined}
placeholder="" ref={codeEditorRef}
value={content} placeholder=""
onChange={handleOnContentChange} value={content}
width="100%" onChange={handleOnContentChange}
// TODO: Textarea should grow to fill parent if height == 100% width="100%"
style={{ flex: 1, minHeight: 350 }} // TODO: Textarea should grow to fill parent if height == 100%
resize="vertical" style={{ flex: 1, minHeight: 350 }}
className={styles.textarea} resize="vertical"
/> className={styles.textarea}
/>
</TextareaMarkdown.Wrapper>
</div> </div>
</Tabs.Item> </Tabs.Item>
<Tabs.Item label="Preview" value="preview"> <Tabs.Item label="Preview" value="preview">

View file

@ -39,7 +39,8 @@
"rehype-raw": "^6.1.1", "rehype-raw": "^6.1.1",
"rehype-slug": "^5.0.1", "rehype-slug": "^5.0.1",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"swr": "^1.2.2" "swr": "^1.2.2",
"textarea-markdown-editor": "^0.1.13"
}, },
"devDependencies": { "devDependencies": {
"@next/bundle-analyzer": "^12.1.0", "@next/bundle-analyzer": "^12.1.0",

View file

@ -2876,6 +2876,11 @@ module-lookup-amd@^7.0.1:
requirejs "^2.3.5" requirejs "^2.3.5"
requirejs-config-file "^4.0.0" requirejs-config-file "^4.0.0"
mousetrap@^1.6.5:
version "1.6.5"
resolved "https://registry.yarnpkg.com/mousetrap/-/mousetrap-1.6.5.tgz#8a766d8c272b08393d5f56074e0b5ec183485bf9"
integrity sha512-QNo4kEepaIBwiT8CDhP98umTetp+JNfQYBWvC1pc6/OAibuXtRcxZ58Qz8skvEHYvURne/7R8T5VoOI7rDsEUA==
mri@^1.1.0: mri@^1.1.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
@ -3739,6 +3744,11 @@ react-syntax-highlighter@^15.4.5:
prismjs "^1.25.0" prismjs "^1.25.0"
refractor "^3.2.0" refractor "^3.2.0"
react-trigger-change@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/react-trigger-change/-/react-trigger-change-1.0.2.tgz#af573398ecef2475362b84f8c08c07fea23914c3"
integrity sha1-r1czmOzvJHU2K4T4wIwH/qI5FMM=
react@17.0.2: react@17.0.2:
version "17.0.2" version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@ -4246,6 +4256,14 @@ text-table@^0.2.0:
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
textarea-markdown-editor@^0.1.13:
version "0.1.13"
resolved "https://registry.yarnpkg.com/textarea-markdown-editor/-/textarea-markdown-editor-0.1.13.tgz#27aeda4bc95148a8ed69021f553753feb224325f"
integrity sha512-2r1gTPFA/wwAzt+Aa6LVZWjJNvL0aXfR6Z9T6eQBpJ1AK6gtPVCZgkO97KIrqpAmMcwgNCz0ToYj2AqPufdVeg==
dependencies:
mousetrap "^1.6.5"
react-trigger-change "^1.0.2"
to-regex-range@^5.0.1: to-regex-range@^5.0.1:
version "5.0.1" version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"