From 0119811a67a675c35b2abd3bb38d3ae34e849f4c Mon Sep 17 00:00:00 2001
From: Ajay Bura <32841439+ajbura@users.noreply.github.com>
Date: Sun, 5 Mar 2023 16:23:14 +0530
Subject: [PATCH] Add custom editor
---
package-lock.json | 97 +++++++++++++-
package.json | 5 +-
src/app/components/editor/Editor.css.ts | 44 +++++++
src/app/components/editor/Editor.preview.tsx | 80 +++++++++++
src/app/components/editor/Editor.tsx | 85 ++++++++++++
src/app/components/editor/Elements.css.ts | 55 ++++++++
src/app/components/editor/Elements.tsx | 108 +++++++++++++++
src/app/components/editor/Toolbar.tsx | 132 +++++++++++++++++++
src/app/components/editor/common.ts | 96 ++++++++++++++
src/app/components/editor/keyboard.ts | 48 +++++++
src/app/components/editor/slate.d.ts | 106 +++++++++++++++
src/app/organisms/navigation/Navigation.jsx | 4 +-
tsconfig.json | 2 +-
13 files changed, 854 insertions(+), 8 deletions(-)
create mode 100644 src/app/components/editor/Editor.css.ts
create mode 100644 src/app/components/editor/Editor.preview.tsx
create mode 100644 src/app/components/editor/Editor.tsx
create mode 100644 src/app/components/editor/Elements.css.ts
create mode 100644 src/app/components/editor/Elements.tsx
create mode 100644 src/app/components/editor/Toolbar.tsx
create mode 100644 src/app/components/editor/common.ts
create mode 100644 src/app/components/editor/keyboard.ts
create mode 100644 src/app/components/editor/slate.d.ts
diff --git a/package-lock.json b/package-lock.json
index 43b48108..8bddc4df 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,10 +25,11 @@
"file-saver": "2.0.5",
"flux": "4.0.3",
"focus-trap-react": "10.0.2",
- "folds": "1.0.4",
+ "folds": "1.0.5",
"formik": "2.2.9",
"html-react-parser": "3.0.4",
"immer": "9.0.16",
+ "is-hotkey": "0.2.0",
"jotai": "1.12.0",
"katex": "0.16.4",
"linkify-html": "4.0.2",
@@ -44,6 +45,8 @@
"react-google-recaptcha": "2.1.0",
"react-modal": "3.16.1",
"sanitize-html": "2.8.0",
+ "slate": "0.90.0",
+ "slate-react": "0.90.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2"
},
@@ -922,6 +925,11 @@
"@jridgewell/sourcemap-codec": "1.4.14"
}
},
+ "node_modules/@juggle/resize-observer": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz",
+ "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA=="
+ },
"node_modules/@khanacademy/simple-markdown": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/@khanacademy/simple-markdown/-/simple-markdown-0.8.6.tgz",
@@ -1032,6 +1040,11 @@
"resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz",
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g=="
},
+ "node_modules/@types/is-hotkey": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/@types/is-hotkey/-/is-hotkey-0.1.7.tgz",
+ "integrity": "sha512-yB5C7zcOM7idwYZZ1wKQ3pTfjA9BbvFqRWvKB46GFddxnJtHwi/b9y84ykQtxQPg5qhdpg4Q/kWU3EGoCTmLzQ=="
+ },
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@@ -1044,6 +1057,11 @@
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
+ "node_modules/@types/lodash": {
+ "version": "4.14.191",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.191.tgz",
+ "integrity": "sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ=="
+ },
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
@@ -1904,6 +1922,11 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
},
+ "node_modules/compute-scroll-into-view": {
+ "version": "1.0.20",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.20.tgz",
+ "integrity": "sha512-UCB0ioiyj8CRjtrvaceBLqqhZCVP+1B8+NWQhmdsm0VXOJtobBCf1dBQmebCCo34qZmUwZfIH2MZLqNHazrfjg=="
+ },
"node_modules/computed-style": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/computed-style/-/computed-style-0.1.4.tgz",
@@ -2071,6 +2094,18 @@
"node": ">=8"
}
},
+ "node_modules/direction": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/direction/-/direction-1.0.4.tgz",
+ "integrity": "sha512-GYqKi1aH7PJXxdhTeZBFrg8vUBeKXi+cNprXsC1kpJcbcVnV9wBsrOu1cQEdG0WeQwlfHiy3XvnKfIrJ2R0NzQ==",
+ "bin": {
+ "direction": "cli.js"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
"node_modules/dnd-core": {
"version": "15.1.2",
"resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-15.1.2.tgz",
@@ -3022,9 +3057,9 @@
}
},
"node_modules/folds": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/folds/-/folds-1.0.4.tgz",
- "integrity": "sha512-oFtFutLaTW3CBvNlWOK8GDOWAw9CARZ/XVxGSHp6gucZRs0tNhD9bBy/oH0qbwzYfNIm4CmlBUdV5nMEaOnZsw==",
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/folds/-/folds-1.0.5.tgz",
+ "integrity": "sha512-j5QTgWL0+fpQ6v2SymealBgsMmt0BmXUbMXzXY2NO9SWgbvBbekRyEzNZfRUpX63vnLgQ1kK7s9Np/s/UrsDRw==",
"peerDependencies": {
"@vanilla-extract/css": "^1.9.2",
"@vanilla-extract/recipes": "^0.3.0",
@@ -3548,6 +3583,11 @@
"node": ">=0.10.0"
}
},
+ "node_modules/is-hotkey": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.2.0.tgz",
+ "integrity": "sha512-UknnZK4RakDmTgz4PI1wIph5yxSs/mvChWs9ifnlXsKuXgWmOkY/hAE0H/k2MIqH0RlRye0i1oC07MCRSD28Mw=="
+ },
"node_modules/is-negative-zero": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
@@ -4882,6 +4922,14 @@
"object-assign": "^4.1.1"
}
},
+ "node_modules/scroll-into-view-if-needed": {
+ "version": "2.2.31",
+ "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-2.2.31.tgz",
+ "integrity": "sha512-dGCXy99wZQivjmjIqihaBQNjryrz5rueJY7eHfTdyWEiR4ttYpsajb14rn9s5d4DY4EcY6+4+U/maARBXJedkA==",
+ "dependencies": {
+ "compute-scroll-into-view": "^1.0.20"
+ }
+ },
"node_modules/sdp-transform": {
"version": "2.14.1",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.1.tgz",
@@ -4946,6 +4994,42 @@
"node": ">=8"
}
},
+ "node_modules/slate": {
+ "version": "0.90.0",
+ "resolved": "https://registry.npmjs.org/slate/-/slate-0.90.0.tgz",
+ "integrity": "sha512-dv8idv0JjYyHiAJcVKf5yWKPDMTDi+PSZyfjsnquEI8VB5nmTVGjeJab06lc3o69O7aN05ROwO9/OY8mU1IUPA==",
+ "dependencies": {
+ "immer": "^9.0.6",
+ "is-plain-object": "^5.0.0",
+ "tiny-warning": "^1.0.3"
+ }
+ },
+ "node_modules/slate-react": {
+ "version": "0.90.0",
+ "resolved": "https://registry.npmjs.org/slate-react/-/slate-react-0.90.0.tgz",
+ "integrity": "sha512-z6pGd6jjU5VazLxlDi6zL3a6yaPBPJ+A2VyIlE/h/rvDywaLYGvk0xcrA9NrK71Dr47HK5ZN2zFEZNleh6wlPA==",
+ "dependencies": {
+ "@juggle/resize-observer": "^3.4.0",
+ "@types/is-hotkey": "^0.1.1",
+ "@types/lodash": "^4.14.149",
+ "direction": "^1.0.3",
+ "is-hotkey": "^0.1.6",
+ "is-plain-object": "^5.0.0",
+ "lodash": "^4.17.4",
+ "scroll-into-view-if-needed": "^2.2.20",
+ "tiny-invariant": "1.0.6"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0",
+ "slate": ">=0.65.3"
+ }
+ },
+ "node_modules/slate-react/node_modules/is-hotkey": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/is-hotkey/-/is-hotkey-0.1.8.tgz",
+ "integrity": "sha512-qs3NZ1INIS+H+yeo7cD9pDfwYV/jqRh1JG9S9zYrNudkoUQg7OL7ziXqRKu+InFjUIDoP2o6HIkLYMh1pcWgyQ=="
+ },
"node_modules/source-map-js": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
@@ -5084,6 +5168,11 @@
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true
},
+ "node_modules/tiny-invariant": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.6.tgz",
+ "integrity": "sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA=="
+ },
"node_modules/tiny-warning": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
diff --git a/package.json b/package.json
index 26ed0add..c6869614 100644
--- a/package.json
+++ b/package.json
@@ -35,10 +35,11 @@
"file-saver": "2.0.5",
"flux": "4.0.3",
"focus-trap-react": "10.0.2",
- "folds": "1.0.4",
+ "folds": "1.0.5",
"formik": "2.2.9",
"html-react-parser": "3.0.4",
"immer": "9.0.16",
+ "is-hotkey": "0.2.0",
"jotai": "1.12.0",
"katex": "0.16.4",
"linkify-html": "4.0.2",
@@ -54,6 +55,8 @@
"react-google-recaptcha": "2.1.0",
"react-modal": "3.16.1",
"sanitize-html": "2.8.0",
+ "slate": "0.90.0",
+ "slate-react": "0.90.0",
"tippy.js": "6.3.7",
"twemoji": "14.0.2"
},
diff --git a/src/app/components/editor/Editor.css.ts b/src/app/components/editor/Editor.css.ts
new file mode 100644
index 00000000..ece59ff2
--- /dev/null
+++ b/src/app/components/editor/Editor.css.ts
@@ -0,0 +1,44 @@
+import { style } from '@vanilla-extract/css';
+import { color, config, DefaultReset, toRem } from 'folds';
+
+export const Editor = style([
+ DefaultReset,
+ {
+ backgroundColor: color.SurfaceVariant.Container,
+ border: `${config.borderWidth.B300} solid ${color.SurfaceVariant.ContainerLine}`,
+ borderRadius: config.radii.R400,
+ },
+]);
+
+export const EditorOptions = style([
+ DefaultReset,
+ {
+ padding: config.space.S200,
+ },
+]);
+
+export const EditorTextareaScroll = style({});
+
+export const EditorTextarea = style([
+ DefaultReset,
+ {
+ flexGrow: 1,
+ height: '100%',
+ padding: `${toRem(13)} 0`,
+ selectors: {
+ [`${EditorTextareaScroll}:first-child &`]: {
+ paddingLeft: toRem(13),
+ },
+ [`${EditorTextareaScroll}:last-child &`]: {
+ paddingRight: toRem(13),
+ },
+ },
+ },
+]);
+
+export const EditorToolbar = style([
+ DefaultReset,
+ {
+ padding: config.space.S100,
+ },
+]);
diff --git a/src/app/components/editor/Editor.preview.tsx b/src/app/components/editor/Editor.preview.tsx
new file mode 100644
index 00000000..48202523
--- /dev/null
+++ b/src/app/components/editor/Editor.preview.tsx
@@ -0,0 +1,80 @@
+import React, { useState } from 'react';
+import FocusTrap from 'focus-trap-react';
+import {
+ config,
+ Icon,
+ IconButton,
+ Icons,
+ Line,
+ Modal,
+ Overlay,
+ OverlayBackdrop,
+ OverlayCenter,
+} from 'folds';
+
+import { CustomEditor } from './Editor';
+import { Toolbar } from './Toolbar';
+
+export function EditorPreview() {
+ const [open, setOpen] = useState(false);
+ const [toolbar, setToolbar] = useState(false);
+
+ return (
+ <>
+
+ {child}
+
+ );
+
+ if (child !== children) return child;
+
+ return {child};
+}
diff --git a/src/app/components/editor/Toolbar.tsx b/src/app/components/editor/Toolbar.tsx
new file mode 100644
index 00000000..16ddb371
--- /dev/null
+++ b/src/app/components/editor/Toolbar.tsx
@@ -0,0 +1,132 @@
+import FocusTrap from 'focus-trap-react';
+import { Box, config, Icon, IconButton, Icons, IconSrc, Line, Menu, PopOut, toRem } from 'folds';
+import React, { useState } from 'react';
+import { useSlate } from 'slate-react';
+import { isBlockActive, isMarkActive, toggleBlock, toggleMark } from './common';
+import * as css from './Editor.css';
+import { BlockType, MarkType } from './Elements';
+import { HeadingLevel } from './slate';
+
+type MarkButtonProps = { format: MarkType; icon: IconSrc };
+export function MarkButton({ format, icon }: MarkButtonProps) {
+ const editor = useSlate();
+
+ return (
+