Skip to content

Commit

Permalink
Merge pull request #245 from gnosis/issue-#236
Browse files Browse the repository at this point in the history
Implement Markdown parsing in useHandlePastedText
  • Loading branch information
juliopavila authored Jul 25, 2023
2 parents 0c01f4f + 2b19ac6 commit b3d2576
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 13 deletions.
2 changes: 2 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"is-ipfs": "^6.0.2",
"lodash": "^4.17.21",
"markdown-to-jsx": "^7.1.7",
"marked": "^5.1.2",
"moment": "^2.29.2",
"notistack": "^2.0.8",
"react": "^17.0.2",
Expand Down Expand Up @@ -89,6 +90,7 @@
"@types/draft-convert": "^2.1.4",
"@types/draft-js": "^0.11.10",
"@types/lodash": "^4.14.180",
"@types/marked": "^5.0.1",
"@types/turndown": "^5.0.1",
"prettier": "^2.5.1"
},
Expand Down
89 changes: 81 additions & 8 deletions packages/app/src/components/commons/Editor/EditorRichText.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled from "@emotion/styled"
import { Box, Divider, Grid, Portal, Stack, Tooltip, Typography } from "@mui/material"
import React, { useEffect, useLayoutEffect, useState, useRef } from "react"
import React, { useEffect, useLayoutEffect, useState, useRef, createRef } from "react"

import AddIcon from "@mui/icons-material/Add"
import { ReactComponent as ParagraphIcon } from "../../../assets/images/paragraphIcon.svg"
Expand Down Expand Up @@ -74,6 +74,7 @@ type RichTextItemProps = {
label?: string
color?: string
icon: React.ReactNode
selected?: boolean
}

type RichTextProps = {
Expand Down Expand Up @@ -189,7 +190,7 @@ const DragTooltipContent = () => {
)
}

const RichTextItem: React.FC<RichTextItemProps> = ({ label, icon, color }) => {
const RichTextItem: React.FC<RichTextItemProps> = ({ label, icon, color, selected }) => {
return (
<Grid
container
Expand All @@ -198,6 +199,7 @@ const RichTextItem: React.FC<RichTextItemProps> = ({ label, icon, color }) => {
alignItems="center"
sx={{
cursor: "pointer",
"& .rich-text-icon": { backgroundColor: selected ? palette.grays[100] : palette.grays[50] },
"&:hover": {
"& .rich-text-icon": { backgroundColor: palette.grays[100] },
},
Expand Down Expand Up @@ -225,9 +227,13 @@ const RichTextItem: React.FC<RichTextItemProps> = ({ label, icon, color }) => {

const EditorRichText: React.FC<RichTextProps> = ({ onRichTextSelected, showCommand, onDelete, onAdd }) => {
const { setShowBlockTypePopup } = useArticleContext()

const containerRef = useRef<Element | (() => Element | null) | null>(null)
const richTextRef = useRef<HTMLDivElement | null>(null)
const ref = useRef<HTMLDivElement | null>(null)
const headerOptionRefs = HEADER_OPTIONS.map(() => createRef<HTMLDivElement>())
const optionRefs = OPTIONS.map(() => createRef<HTMLDivElement>())
const [selectedIndex, setSelectedIndex] = useState<number>(0)

const [show, setShow] = useState<boolean>(false)
const [top, setTop] = useState<number>()
Expand Down Expand Up @@ -260,6 +266,67 @@ const EditorRichText: React.FC<RichTextProps> = ({ onRichTextSelected, showComma
}
}, [topOffset])

useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
const richTextOption = HEADER_OPTIONS.concat(OPTIONS)
switch (event.key) {
case "ArrowUp":
event.preventDefault()
setSelectedIndex((prevIndex) => Math.max(prevIndex - 1, 0))
break

case "ArrowDown":
event.preventDefault()
setSelectedIndex((prevIndex) => Math.min(prevIndex + 1, richTextOption.length - 1))
break

case "Enter":
event.preventDefault()
setSelectedIndex((currentIndex) => {
console.log("currentIndex", currentIndex)
const selectedOption = richTextOption[currentIndex]
if (selectedOption) {
handleSelection(selectedOption.value)
}
return currentIndex
})
break

default:
break
}
}

if (show) {
window.addEventListener("keydown", handleKeyDown)
} else {
window.removeEventListener("keydown", handleKeyDown)
}

return () => {
window.removeEventListener("keydown", handleKeyDown)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show])

useEffect(() => {
const selectedOptionRef = selectedIndex < 6 ? headerOptionRefs[selectedIndex] : optionRefs[selectedIndex - 6]

setTimeout(() => {
if (selectedOptionRef && selectedOptionRef.current && richTextRef.current) {
const selectedOptionRect = selectedOptionRef.current.getBoundingClientRect()
const optionsContainerRect = richTextRef.current.getBoundingClientRect()

if (
selectedOptionRect.bottom > optionsContainerRect.bottom ||
selectedOptionRect.top < optionsContainerRect.top
) {
richTextRef.current.scrollTop = selectedOptionRef.current.offsetTop - 10
}
}
}, 0)
}, [headerOptionRefs, optionRefs, selectedIndex])

useLayoutEffect(() => {
function updatePosition() {
if (richTextRef.current) {
Expand Down Expand Up @@ -293,6 +360,12 @@ const EditorRichText: React.FC<RichTextProps> = ({ onRichTextSelected, showComma
onDelete()
}

useEffect(() => {
if (show && headerOptionRefs[0].current) {
headerOptionRefs[0].current.focus()
}
}, [headerOptionRefs, show])

return (
<>
<Stack direction="row" spacing={0.5} ref={richTextRef}>
Expand Down Expand Up @@ -336,9 +409,9 @@ const EditorRichText: React.FC<RichTextProps> = ({ onRichTextSelected, showComma
<Grid item>
<Grid container spacing={0.25}>
{HEADER_OPTIONS.map(({ icon, value }, index) => (
<Grid item key={`-${index}`}>
<div onClick={() => handleSelection(value)}>
<RichTextItem icon={icon} />
<Grid item key={`-${index}`} ref={headerOptionRefs[index]}>
<div onClick={() => value && handleSelection(value)} tabIndex={0}>
<RichTextItem icon={icon} selected={index === selectedIndex} />
</div>
</Grid>
))}
Expand All @@ -348,9 +421,9 @@ const EditorRichText: React.FC<RichTextProps> = ({ onRichTextSelected, showComma
</Grid>

{OPTIONS.map(({ label, icon, value }, index) => (
<Grid item key={`${label}-${index}`}>
<div onClick={() => value && handleSelection(value)}>
<RichTextItem label={label} icon={icon} />
<Grid item key={`${label}-${index}`} ref={optionRefs[index]}>
<div onClick={() => value && handleSelection(value)} tabIndex={0}>
<RichTextItem label={label} icon={icon} selected={index + 6 === selectedIndex} />
</div>
</Grid>
))}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,44 @@
import { EditorState, DraftHandleValue, Modifier } from "draft-js"
import { EditorState, DraftHandleValue, Modifier, convertFromHTML, ContentState } from "draft-js"
import { marked } from "marked"

const useHandlePastedText = (
editorState: EditorState,
setEditorState: React.Dispatch<React.SetStateAction<EditorState>>,
) => {
const isMarkdown = (text: string) => {
// These are just some of the characters commonly used in markdown.
const markdownCharacters = ["#", "*", "_", "~", "[", "]", "(", ")", "|", "`", ">", "-"]

//markdown text
for (let character of markdownCharacters) {
if (text.includes(character)) {
return true
}
}

//If none of the markdown characters are found, we assume it is not markdown.
return false
}

return (text: string, html: string | undefined, editorState: EditorState): DraftHandleValue => {
// We obtain the current content and selection;
const contentState = editorState.getCurrentContent()
const selectionState = editorState.getSelection()

// Replace the text selected with the copied text and we use it as one block
const newContentState = Modifier.replaceText(contentState, selectionState, text)
const newEditorState = EditorState.push(editorState, newContentState, "insert-fragment")

let newContentState: ContentState

if (isMarkdown(text)) {
// you need to implement isMarkdown function
// If the pasted text is Markdown, treat it as such
const markdownHTML = marked(text) // Convert Markdown to HTML
const contentBlockArray = convertFromHTML(markdownHTML) // Convert HTML to Draft.js blocks
newContentState = ContentState.createFromBlockArray(contentBlockArray.contentBlocks, contentBlockArray.entityMap)
} else {
// If the pasted text is not Markdown, handle it as plain text
newContentState = Modifier.replaceText(contentState, selectionState, text)
}

const newEditorState = EditorState.push(editorState, newContentState, "insert-characters")

setEditorState(newEditorState)
return "handled"
Expand Down
10 changes: 10 additions & 0 deletions packages/app/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2826,6 +2826,11 @@
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a"
integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==

"@types/marked@^5.0.1":
version "5.0.1"
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-5.0.1.tgz#15acd796d722b91bf00738c8c8539aaf5034f0c6"
integrity sha512-Y3pAUzHKh605fN6fvASsz5FDSWbZcs/65Q6xYRmnIP9ZIYz27T4IOmXfH9gWJV1dpi7f1e7z7nBGUTx/a0ptpA==

"@types/minimatch@*":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-5.1.2.tgz#07508b45797cb81ec3f273011b054cd0755eddca"
Expand Down Expand Up @@ -10962,6 +10967,11 @@ markdown-to-jsx@^7.1.7:
resolved "https://registry.yarnpkg.com/markdown-to-jsx/-/markdown-to-jsx-7.1.9.tgz#1ffae0cda07c189163d273bd57a5b8f8f8745586"
integrity sha512-x4STVIKIJR0mGgZIZ5RyAeQD7FEZd5tS8m/htbcVGlex32J+hlSLj+ExrHCxP6nRKF1EKbcO7i6WhC1GtOpBlA==

marked@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/marked/-/marked-5.1.2.tgz#62b5ccfc75adf72ca3b64b2879b551d89e77677f"
integrity sha512-ahRPGXJpjMjwSOlBoTMZAK7ATXkli5qCPxZ21TG44rx1KEo44bii4ekgTDQPNRQ4Kh7JMb9Ub1PVk1NxRSsorg==

match-sorter@^6.0.2:
version "6.3.1"
resolved "https://registry.yarnpkg.com/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"
Expand Down

0 comments on commit b3d2576

Please sign in to comment.