From 09da11990f9ed96c93332cddea900b3eb680c16b Mon Sep 17 00:00:00 2001 From: zerosoul Date: Wed, 23 Mar 2022 16:32:11 +0800 Subject: [PATCH] chore: icons --- src/assets/icons/add.solid.svg | 3 + src/assets/icons/markdown.svg | 3 + src/common/component/RichInput/Element.js | 33 +++++ src/common/component/RichInput/index.js | 118 +++++++++++++++++ .../component/RichInput/initialValue.js | 49 +++++++ src/common/component/RichInput/styled.js | 66 ++++++++++ .../component/RichInput/withShortcuts.js | 121 ++++++++++++++++++ 7 files changed, 393 insertions(+) create mode 100644 src/assets/icons/add.solid.svg create mode 100644 src/assets/icons/markdown.svg create mode 100644 src/common/component/RichInput/Element.js create mode 100644 src/common/component/RichInput/index.js create mode 100644 src/common/component/RichInput/initialValue.js create mode 100644 src/common/component/RichInput/styled.js create mode 100644 src/common/component/RichInput/withShortcuts.js diff --git a/src/assets/icons/add.solid.svg b/src/assets/icons/add.solid.svg new file mode 100644 index 00000000..68e88b42 --- /dev/null +++ b/src/assets/icons/add.solid.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/icons/markdown.svg b/src/assets/icons/markdown.svg new file mode 100644 index 00000000..0832a420 --- /dev/null +++ b/src/assets/icons/markdown.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/common/component/RichInput/Element.js b/src/common/component/RichInput/Element.js new file mode 100644 index 00000000..9b8cec75 --- /dev/null +++ b/src/common/component/RichInput/Element.js @@ -0,0 +1,33 @@ +const Element = ({ attributes, children, element }) => { + console.log("element type", element); + switch (element.type) { + case "del": + return {children}; + case "italic": + return {children}; + case "bold": + return {children}; + case "blockquote": + return
{children}
; + case "bulleted-list": + return ; + case "heading-one": + return

{children}

; + case "heading-two": + return

{children}

; + case "heading-three": + return

{children}

; + case "heading-four": + return

{children}

; + case "heading-five": + return
{children}
; + case "heading-six": + return
{children}
; + case "list-item": + return
  • {children}
  • ; + default: + return

    {children}

    ; + } +}; + +export default Element; diff --git a/src/common/component/RichInput/index.js b/src/common/component/RichInput/index.js new file mode 100644 index 00000000..eff90b07 --- /dev/null +++ b/src/common/component/RichInput/index.js @@ -0,0 +1,118 @@ +// import React from "react"; +// import styled from 'styled-components'; +import AutoReplace from "slate-auto-replace"; +import { useState, useRef, useCallback, useMemo, useEffect } from "react"; +import { serialize } from "remark-slate"; +import { Slate, Editable, withReact } from "slate-react"; +import { createEditor, Transforms, Editor } from "slate"; +import { withHistory } from "slate-history"; +import { useKey } from "rooks"; +import Styled from "./styled"; + +import initialValue, { emptyValue } from "./initialValue"; +import Element from "./Element"; +// import withShortcuts from "./withShortcuts"; + +// Add the plugin to your set of plugins... +const plugins = [ + AutoReplace({ + trigger: "space", + before: /^(>)$/, + change: (change, e, matches) => { + return change.setBlocks({ type: "blockquote" }); + }, + }), +]; + +export default function RichInput({ + setEditor, + placeholder = "Write some markdown...", + updateMarkdown, + updatePureText, + sendMessage, +}) { + const editableRef = useRef(null); + const [shift, setShift] = useState(false); + const [value, setValue] = useState(emptyValue); + // const renderElement = useCallback((props) => , []); + const editor = useMemo(() => withReact(withHistory(createEditor())), []); + // const editor = useMemo( + // () => withShortcuts(withReact(withHistory(createEditor()))), + // [] + // ); + useEffect(() => { + setEditor(editor); + }, []); + + useKey( + "Enter", + (evt) => { + evt.preventDefault(); + + sendMessage(); + Transforms.delete(editor, { + at: { + anchor: Editor.start(editor, []), + focus: Editor.end(editor, []), + }, + }); + }, + { + target: editableRef, + when: !shift, + } + ); + useKey( + "Shift", + (e) => { + console.log("shift", e.type); + setShift(e.type == "keydown"); + }, + { eventTypes: ["keydown", "keyup"], target: editableRef } + ); + const handleChange = useCallback( + (nextValue) => { + setValue(nextValue); + // serialize slate state to a markdown string + const md = nextValue.map((v) => serialize(v)).join("\n"); + updateMarkdown(md); + console.log("serialized", nextValue, md); + + const nonParagraphs = nextValue.filter((v) => v.type != "paragraph"); + if (nonParagraphs.length == 0) { + const nonEmptyPs = nextValue.filter((v) => !!v.children[0].text); + // 全部是P + const pureText = nonEmptyPs + .map((v) => { + return v.children.map(({ text }) => text).join(""); + }) + .join("\n"); + console.log("pure text", nonEmptyPs, pureText); + updatePureText(pureText); + } else { + updatePureText(""); + } + }, + [updateMarkdown, updatePureText] + ); + + console.log("slate value", value); + return ( + + + + + + ); +} diff --git a/src/common/component/RichInput/initialValue.js b/src/common/component/RichInput/initialValue.js new file mode 100644 index 00000000..856f28aa --- /dev/null +++ b/src/common/component/RichInput/initialValue.js @@ -0,0 +1,49 @@ +const initialValue = [ + { + type: "paragraph", + children: [ + { + text: + 'The editor gives you full control over the logic you can add. For example, it\'s fairly common to want to add markdown-like shortcuts to editors. So that, when you start a line with "> " you get a blockquote that looks like this:', + }, + ], + }, + { + type: "block-quote", + children: [{ text: "A wise quote." }], + }, + { + type: "paragraph", + children: [ + { + text: + 'Order when you start a line with "## " you get a level-two heading, like this:', + }, + ], + }, + { + type: "heading-two", + children: [{ text: "Try it out!" }], + }, + { + type: "paragraph", + children: [ + { + text: + 'Try it out for yourself! Try starting a new line with ">", "-", or "#"s.', + }, + ], + }, +]; +export const emptyValue = [ + { + type: "paragraph", + children: [ + { + text: "", + }, + ], + }, +]; + +export default initialValue; diff --git a/src/common/component/RichInput/styled.js b/src/common/component/RichInput/styled.js new file mode 100644 index 00000000..5ed9a904 --- /dev/null +++ b/src/common/component/RichInput/styled.js @@ -0,0 +1,66 @@ +import styled from "styled-components"; +const Styled = styled.div` + max-height: 50vh; + overflow: auto; + p { + font-weight: 400; + font-size: 14px; + /* line-height: 20px; */ + line-height: 22px; + color: #475467; + /* margin-bottom: 10px; */ + } + i { + font-style: italic; + } + /* del{ + font-style: italic; + } */ + ul, + ol { + font-weight: 400; + font-size: 14px; + line-height: 20px; + color: #475467; + list-style-position: inside; + } + ul { + list-style-type: disc; + } + ol { + /* list-style-type:; */ + } + strong { + font-weight: 700; + } + h1, + h2, + h3 { + font-weight: 700; + color: #475467; + } + h1 { + font-size: 24px; + line-height: 32px; + } + h2 { + font-size: 20px; + line-height: 30px; + } + h3 { + font-size: 16px; + line-height: 24px; + } + blockquote { + margin-bottom: 10px; + color: #98a2b3; + opacity: 0.8; + box-shadow: inset 2px 0px 0px #a8b0bd; + font-weight: 400; + font-size: 14px; + line-height: 20px; + padding: 16px; + } +`; + +export default Styled; diff --git a/src/common/component/RichInput/withShortcuts.js b/src/common/component/RichInput/withShortcuts.js new file mode 100644 index 00000000..e892d527 --- /dev/null +++ b/src/common/component/RichInput/withShortcuts.js @@ -0,0 +1,121 @@ +import { + Editor, + Transforms, + Range, + Point, + Element as SlateElement, +} from "slate"; +const SHORTCUTS = { + "-": "list-item", + "+": "list-item", + "~~": "del", + _: "italic", + __: "bold", + ">": "blockquote", + "#": "heading-one", + "##": "heading-two", + "###": "heading-three", + "####": "heading-four", + "#####": "heading-five", + "######": "heading-six", +}; +const withShortcuts = (editor) => { + const { deleteBackward, insertText } = editor; + + editor.insertText = (text) => { + const { selection } = editor; + console.log("insert text", text, selection, Range.isCollapsed(selection)); + + if (text === " " && selection && Range.isCollapsed(selection)) { + const { anchor } = selection; + const block = Editor.above(editor, { + match: (n) => Editor.isBlock(editor, n), + }); + const path = block ? block[1] : []; + const start = Editor.start(editor, path); + const range = { anchor, focus: start }; + const beforeText = Editor.string(editor, range); + const type = SHORTCUTS[beforeText]; + console.log( + "insert text parsed", + block, + path, + start, + range, + beforeText, + type + ); + if (type) { + Transforms.select(editor, range); + Transforms.delete(editor); + const newProperties = { + type, + }; + Transforms.setNodes(editor, newProperties, { + match: (n) => Editor.isBlock(editor, n), + }); + + if (type === "list-item") { + const list = { + type: "bulleted-list", + children: [], + }; + Transforms.wrapNodes(editor, list, { + match: (n) => + !Editor.isEditor(n) && + SlateElement.isElement(n) && + n.type === "list-item", + }); + } + + return; + } + } + + insertText(text); + }; + + editor.deleteBackward = (...args) => { + const { selection } = editor; + if (selection && Range.isCollapsed(selection)) { + const match = Editor.above(editor, { + match: (n) => Editor.isBlock(editor, n), + }); + + if (match) { + const [block, path] = match; + const start = Editor.start(editor, path); + + if ( + !Editor.isEditor(block) && + SlateElement.isElement(block) && + block.type !== "paragraph" && + Point.equals(selection.anchor, start) + ) { + const newProperties = { + type: "paragraph", + }; + Transforms.setNodes(editor, newProperties); + + if (block.type === "list-item") { + Transforms.unwrapNodes(editor, { + match: (n) => + !Editor.isEditor(n) && + SlateElement.isElement(n) && + n.type === "bulleted-list", + split: true, + }); + } + + return; + } + } + + deleteBackward(...args); + } + }; + + return editor; +}; + +export default withShortcuts;