Skip to content

Commit

Permalink
#3762: Add basic slate editor
Browse files Browse the repository at this point in the history
  • Loading branch information
anilsonmez-simsoft committed Aug 24, 2021
1 parent 3d24d23 commit fed3fcf
Show file tree
Hide file tree
Showing 2 changed files with 218 additions and 0 deletions.
112 changes: 112 additions & 0 deletions client/src/components/RichTextEditor/RichTextEditor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useCallback, useMemo, useState } from "react"
import { createEditor } from "slate"
import { withHistory } from "slate-history"
import { Editable, Slate, withReact } from "slate-react"
import Toolbar from "./Toolbar"

const SlateEditor = () => {
const editor = useMemo(() => withReact(withHistory(createEditor())), [])
const [value, setValue] = useState(initialValueSlate)

const renderElement = useCallback(props => <Element {...props} />, [])
const renderLeaf = useCallback(props => <Leaf {...props} />, [])

return (
<Slate
editor={editor}
value={value}
onChange={newValue => setValue(newValue)}
>
<Toolbar />
<Editable renderElement={renderElement} renderLeaf={renderLeaf} />
</Slate>
)
}

const Element = ({ attributes, children, element }) => {
switch (element.type) {
case "block-quote":
return <blockquote {...attributes}>{children}</blockquote>
case "bulleted-list":
return <ul {...attributes}>{children}</ul>
case "heading-one":
return <h1 {...attributes}>{children}</h1>
case "heading-two":
return <h2 {...attributes}>{children}</h2>
case "heading-three":
return <h3 {...attributes}>{children}</h3>
case "list-item":
return <li {...attributes}>{children}</li>
case "numbered-list":
return <ol {...attributes}>{children}</ol>
default:
return <p {...attributes}>{children}</p>
}
}

const Leaf = ({ attributes, children, leaf }) => {
if (leaf.bold) {
children = <strong>{children}</strong>
}

if (leaf.code) {
children = <code>{children}</code>
}

if (leaf.italic) {
children = <em>{children}</em>
}

if (leaf.underline) {
children = <u>{children}</u>
}

if (leaf.strikethrough) {
children = <strike>{children}</strike>
}

if (leaf.highlight) {
children = <span style={{ backgroundColor: "yellow" }}>{children}</span>
}

return <span {...attributes}>{children}</span>
}

const initialValueSlate = [
{
type: "paragraph",
children: [
{ text: "This is editable " },
{ text: "rich", bold: true },
{ text: " text, " },
{ text: "much", italic: true },
{ text: " better than a " },
{ text: "<textarea>", code: true },
{ text: "!" }
]
},
{
type: "paragraph",
children: [
{
text:
"Since it's rich text, you can do things like turn a selection of text "
},
{ text: "bold", bold: true },
{
text:
", or add a semantically rendered block quote in the middle of the page, like this:"
}
]
},
{
type: "block-quote",
children: [{ text: "A wise quote." }]
},
{
type: "paragraph",
children: [{ text: "Try it out for yourself!" }]
}
]

export default SlateEditor
106 changes: 106 additions & 0 deletions client/src/components/RichTextEditor/Toolbar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import PropTypes from "prop-types"
import React from "react"
import { Editor, Transforms } from "slate"
import { useSlate } from "slate-react"

const LIST_TYPES = ["bulleted-list", "numbered-list"]

const Toolbar = () => {
const editor = useSlate()
return (
<div>
<MarkButton editor={editor} format="bold" text="Bold" />
<MarkButton editor={editor} format="italic" text="Italic" />
<MarkButton editor={editor} format="underline" text="Underline" />
<MarkButton editor={editor} format="strikethrough" text="Strikethrough" />
<BlockButton editor={editor} format="block-quote" text="Blockquote" />
<BlockButton editor={editor} format="heading-one" text="H1" />
<BlockButton editor={editor} format="heading-two" text="H2" />
<BlockButton editor={editor} format="heading-three" text="H3" />
<BlockButton editor={editor} format="bulleted-list" text="UL" />
<BlockButton editor={editor} format="numbered-list" text="OL" />
</div>
)
}

function toggleBlock(editor, format) {
const isActive = isBlockActive(editor, format)
const isList = LIST_TYPES.includes(format)

Transforms.unwrapNodes(editor, {
match: n => LIST_TYPES.includes(n.type),
split: true
})

Transforms.setNodes(editor, {
type: isActive ? "paragraph" : isList ? "list-item" : format
})

if (!isActive && isList) {
const block = { type: format, children: [] }
Transforms.wrapNodes(editor, block)
}
}

function isBlockActive(editor, format) {
const [match] = Editor.nodes(editor, {
match: n => n.type === format
})

return !!match
}

function toggleMark(editor, format) {
if (isMarkActive(editor, format)) {
Editor.removeMark(editor, format)
} else {
Editor.addMark(editor, format, true)
}
}

function isMarkActive(editor, format) {
const marks = Editor.marks(editor)
return marks && marks[format] === true
}

const BlockButton = ({ editor, format, text }) => {
return (
<button
type="button"
onMouseDown={event => {
event.preventDefault()
toggleBlock(editor, format)
}}
>
{text}
</button>
)
}

BlockButton.propTypes = {
editor: PropTypes.object.isRequired,
format: PropTypes.string.isRequired,
text: PropTypes.string
}

const MarkButton = ({ editor, format, text }) => {
return (
<button
type="button"
onMouseDown={event => {
event.preventDefault()
toggleMark(editor, format)
}}
>
{text}
</button>
)
}

MarkButton.propTypes = {
editor: PropTypes.object.isRequired,
format: PropTypes.string.isRequired,
text: PropTypes.string
}

export default Toolbar

0 comments on commit fed3fcf

Please sign in to comment.