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;