@flozy/editor 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. package/.eslintignore +4 -0
  2. package/.eslintrc.json +6 -0
  3. package/.github/workflows/npm-publish.yml +33 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.storybook/main.js +20 -0
  6. package/.storybook/preview.js +14 -0
  7. package/.vscode/extensions.json +7 -0
  8. package/.vscode/launch.json +15 -0
  9. package/.vscode/settings.json +22 -0
  10. package/README.md +2 -0
  11. package/craco.config.js +16 -0
  12. package/package.json +107 -0
  13. package/public/favicon.ico +0 -0
  14. package/public/index.html +43 -0
  15. package/public/logo192.png +0 -0
  16. package/public/logo512.png +0 -0
  17. package/public/manifest.json +25 -0
  18. package/public/robots.txt +3 -0
  19. package/src/components/Editor/CollaborativeEditor.js +119 -0
  20. package/src/components/Editor/CommonEditor.js +549 -0
  21. package/src/components/Editor/Editor.css +115 -0
  22. package/src/components/Editor/Elements/CodeToText/CodeToText.css +57 -0
  23. package/src/components/Editor/Elements/CodeToText/CodeToText.jsx +115 -0
  24. package/src/components/Editor/Elements/CodeToText/CodeToTextButton.jsx +16 -0
  25. package/src/components/Editor/Elements/CodeToText/HtmlCode.jsx +59 -0
  26. package/src/components/Editor/Elements/CodeToText/HtmlContextMenu.jsx +39 -0
  27. package/src/components/Editor/Elements/Color Picker/ColorPicker.css +38 -0
  28. package/src/components/Editor/Elements/Color Picker/ColorPicker.jsx +110 -0
  29. package/src/components/Editor/Elements/Color Picker/defaultColors.js +34 -0
  30. package/src/components/Editor/Elements/Embed/Embed.css +14 -0
  31. package/src/components/Editor/Elements/Embed/Embed.jsx +74 -0
  32. package/src/components/Editor/Elements/Embed/Image.jsx +82 -0
  33. package/src/components/Editor/Elements/Embed/Video.jsx +65 -0
  34. package/src/components/Editor/Elements/Equation/Equation.jsx +19 -0
  35. package/src/components/Editor/Elements/Equation/EquationButton.jsx +59 -0
  36. package/src/components/Editor/Elements/Equation/styles.css +4 -0
  37. package/src/components/Editor/Elements/Grid/Grid.js +48 -0
  38. package/src/components/Editor/Elements/Grid/GridButton.js +21 -0
  39. package/src/components/Editor/Elements/Grid/GridItem.js +57 -0
  40. package/src/components/Editor/Elements/ID/Id.jsx +56 -0
  41. package/src/components/Editor/Elements/Link/Link.jsx +24 -0
  42. package/src/components/Editor/Elements/Link/LinkButton.jsx +71 -0
  43. package/src/components/Editor/Elements/Link/styles.css +20 -0
  44. package/src/components/Editor/Elements/Mentions/Mentions.jsx +37 -0
  45. package/src/components/Editor/Elements/NewLine/NewLineButton.js +29 -0
  46. package/src/components/Editor/Elements/Table/Table.jsx +13 -0
  47. package/src/components/Editor/Elements/Table/TableSelector.css +18 -0
  48. package/src/components/Editor/Elements/Table/TableSelector.jsx +76 -0
  49. package/src/components/Editor/Elements/TableContextMenu/TableContextMenu.jsx +97 -0
  50. package/src/components/Editor/Elements/TableContextMenu/styles.css +18 -0
  51. package/src/components/Editor/RemoteCursorOverlay/Overlay.js +78 -0
  52. package/src/components/Editor/Toolbar/Toolbar.jsx +167 -0
  53. package/src/components/Editor/Toolbar/styles.css +28 -0
  54. package/src/components/Editor/Toolbar/toolbarGroups.js +167 -0
  55. package/src/components/Editor/Toolbar/toolbarIcons/align-center.svg +1 -0
  56. package/src/components/Editor/Toolbar/toolbarIcons/align-left.svg +1 -0
  57. package/src/components/Editor/Toolbar/toolbarIcons/align-right.svg +1 -0
  58. package/src/components/Editor/Toolbar/toolbarIcons/blockquote.svg +1 -0
  59. package/src/components/Editor/Toolbar/toolbarIcons/bold.png +0 -0
  60. package/src/components/Editor/Toolbar/toolbarIcons/fontColor.svg +4 -0
  61. package/src/components/Editor/Toolbar/toolbarIcons/headingOne.svg +3 -0
  62. package/src/components/Editor/Toolbar/toolbarIcons/headingTwo.svg +3 -0
  63. package/src/components/Editor/Toolbar/toolbarIcons/italic.png +0 -0
  64. package/src/components/Editor/Toolbar/toolbarIcons/link.svg +1 -0
  65. package/src/components/Editor/Toolbar/toolbarIcons/orderedList.svg +1 -0
  66. package/src/components/Editor/Toolbar/toolbarIcons/strikethrough.png +0 -0
  67. package/src/components/Editor/Toolbar/toolbarIcons/subscript.svg +1 -0
  68. package/src/components/Editor/Toolbar/toolbarIcons/superscript.svg +1 -0
  69. package/src/components/Editor/Toolbar/toolbarIcons/textColor.png +0 -0
  70. package/src/components/Editor/Toolbar/toolbarIcons/underline.png +0 -0
  71. package/src/components/Editor/Toolbar/toolbarIcons/unlink.svg +1 -0
  72. package/src/components/Editor/Toolbar/toolbarIcons/unorderedList.svg +1 -0
  73. package/src/components/Editor/YjsProvider.js +11 -0
  74. package/src/components/Editor/common/Button.jsx +12 -0
  75. package/src/components/Editor/common/Icon.jsx +82 -0
  76. package/src/components/Editor/common/MentionsPopup.jsx +56 -0
  77. package/src/components/Editor/hooks/useMentions.js +44 -0
  78. package/src/components/Editor/hooks/withCollaborative.js +15 -0
  79. package/src/components/Editor/hooks/withCommon.js +17 -0
  80. package/src/components/Editor/plugins/withEmbeds.js +36 -0
  81. package/src/components/Editor/plugins/withEquation.js +8 -0
  82. package/src/components/Editor/plugins/withLinks.js +9 -0
  83. package/src/components/Editor/plugins/withMentions.js +19 -0
  84. package/src/components/Editor/plugins/withTable.js +74 -0
  85. package/src/components/Editor/utils/SlateUtilityFunctions.js +273 -0
  86. package/src/components/Editor/utils/customHooks/useContextMenu.js +42 -0
  87. package/src/components/Editor/utils/customHooks/useFormat.js +26 -0
  88. package/src/components/Editor/utils/customHooks/usePopup.jsx +26 -0
  89. package/src/components/Editor/utils/customHooks/useResize.js +41 -0
  90. package/src/components/Editor/utils/draftToSlate.js +104 -0
  91. package/src/components/Editor/utils/embed.js +18 -0
  92. package/src/components/Editor/utils/equation.js +22 -0
  93. package/src/components/Editor/utils/events.js +56 -0
  94. package/src/components/Editor/utils/grid.js +12 -0
  95. package/src/components/Editor/utils/gridItem.js +19 -0
  96. package/src/components/Editor/utils/link.js +53 -0
  97. package/src/components/Editor/utils/mentions.js +11 -0
  98. package/src/components/Editor/utils/paragraph.js +4 -0
  99. package/src/components/Editor/utils/serializer.js +32 -0
  100. package/src/components/Editor/utils/table.js +151 -0
  101. package/src/components/index.js +5 -0
  102. package/src/index.js +1 -0
  103. package/src/stories/CollaborativeEditor.stories.js +30 -0
  104. package/src/stories/Editor.stories.js +24 -0
  105. package/src/stories/EditorSampleProps/ChatSample.js +43 -0
  106. package/src/stories/EditorSampleProps/LayoutOne.js +551 -0
@@ -0,0 +1,115 @@
1
+ import React, { useEffect, useRef } from "react";
2
+ import "./CodeToText.css";
3
+ import Icon from "../../common/Icon";
4
+ import { Interweave } from "interweave";
5
+ import { Transforms } from "slate";
6
+ import { useSlateStatic } from "slate-react";
7
+ const CodeToText = (props) => {
8
+ const { html, action, location, handleCodeToText } = props;
9
+ const codeToTextRef = useRef();
10
+ const wrapperRef = useRef();
11
+
12
+ const editor = useSlateStatic();
13
+ const checkClick = (e) => {
14
+ const clickedComponent = e.target;
15
+ if (
16
+ wrapperRef?.current?.contains(clickedComponent) &&
17
+ !codeToTextRef?.current?.contains(clickedComponent)
18
+ ) {
19
+ let partialState = {
20
+ showInput: false,
21
+ };
22
+ if (html) {
23
+ partialState.html = action === "update" ? "" : html;
24
+ }
25
+ handleCodeToText(partialState);
26
+ }
27
+ };
28
+ useEffect(() => {
29
+ document.addEventListener("click", checkClick);
30
+ return () => {
31
+ document.removeEventListener("click", checkClick);
32
+ };
33
+ }, []);
34
+
35
+ const codeOnChange = async (e) => {
36
+ // e.preventDefault();
37
+ handleCodeToText({ html: e.target.value });
38
+ };
39
+ const addHtml = () => {
40
+ if (html) {
41
+ if (action === "update") {
42
+ Transforms.setNodes(
43
+ editor,
44
+ {
45
+ html,
46
+ },
47
+ {
48
+ at: location,
49
+ }
50
+ );
51
+ } else {
52
+ Transforms.insertNodes(
53
+ editor,
54
+ {
55
+ type: "htmlCode",
56
+ html: html,
57
+ children: [{ text: "" }],
58
+ },
59
+ {
60
+ select: true,
61
+ }
62
+ );
63
+ Transforms.insertNodes(editor, {
64
+ type: "paragraph",
65
+ children: [{ text: "" }],
66
+ });
67
+ }
68
+ }
69
+ handleCodeToText({
70
+ showInput: false,
71
+ html: "",
72
+ });
73
+ };
74
+ const clearHtml = () => {
75
+ handleCodeToText({ html: "" });
76
+ };
77
+ return (
78
+ <div className="code-wrapper" ref={wrapperRef}>
79
+ <div ref={codeToTextRef} className="codeToTextWrapper">
80
+ <div className="codeToText">
81
+ <textarea
82
+ name=""
83
+ id=""
84
+ value={html}
85
+ onChange={codeOnChange}
86
+ placeholder="Write html here..."
87
+ ></textarea>
88
+ <div
89
+ style={{
90
+ display: "flex",
91
+ alignItems: "center",
92
+ justifyContent: "center",
93
+ color: "white",
94
+ }}
95
+ >
96
+ <Icon icon="arrowRight" />
97
+ </div>
98
+ <div className="textOutput">
99
+ <Interweave content={html} />
100
+ </div>
101
+ </div>
102
+ <div>
103
+ <button onClick={addHtml} className="done">
104
+ Done
105
+ </button>
106
+ <button className="clear" onClick={clearHtml}>
107
+ Clear
108
+ </button>
109
+ </div>
110
+ </div>
111
+ </div>
112
+ );
113
+ };
114
+
115
+ export default CodeToText;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import Button from '../../common/Button'
3
+ import Icon from '../../common/Icon'
4
+ const CodeToTextButton = (props) => {
5
+ const {handleButtonClick} = props
6
+
7
+ return (
8
+ <>
9
+ <Button format='insert Html' onClick={() => handleButtonClick({showInput:true,action:'insert'})}>
10
+ <Icon icon='insertHtml'/>
11
+ </Button>
12
+ </>
13
+ )
14
+ }
15
+
16
+ export default CodeToTextButton
@@ -0,0 +1,59 @@
1
+ import { Interweave } from "interweave";
2
+ import React, { useEffect } from "react";
3
+ import { Transforms, Path } from "slate";
4
+ import { useSelected, useFocused, useSlateStatic } from "slate-react";
5
+ import useFormat from "../../utils/customHooks/useFormat";
6
+
7
+ const HtmlCode = (props) => {
8
+ const { attributes, element, children } = props;
9
+ const selected = useSelected();
10
+ const focused = useFocused();
11
+ const editor = useSlateStatic();
12
+
13
+ const isHtmlEmbed = useFormat(editor, "htmlCode");
14
+
15
+ const handleKeyUp = (e) => {
16
+ if (!isHtmlEmbed) return;
17
+ if (e.keyCode === 13) {
18
+ const parentPath = Path.parent(editor.selection.focus.path);
19
+ const nextPath = Path.next(parentPath);
20
+ Transforms.insertNodes(
21
+ editor,
22
+ {
23
+ type: "paragraph",
24
+ children: [{ text: "" }],
25
+ },
26
+ {
27
+ at: nextPath,
28
+ select: true, // Focus on this node once inserted
29
+ }
30
+ );
31
+ } else if (e.keyCode === 8) {
32
+ Transforms.removeNodes(editor);
33
+ }
34
+ // console.log(e);
35
+ };
36
+ useEffect(() => {
37
+ document.addEventListener("keyup", handleKeyUp);
38
+ return () => {
39
+ document.removeEventListener("keyup", handleKeyUp);
40
+ };
41
+ }, [isHtmlEmbed]);
42
+ return (
43
+ <div
44
+ {...attributes}
45
+ {...element.attr}
46
+ style={{
47
+ boxShadow: selected && focused && "0 0 3px 3px lightgray",
48
+ marginRight: "20px",
49
+ }}
50
+ >
51
+ <div contentEditable={false}>
52
+ <Interweave content={element.html} />
53
+ </div>
54
+ {children}
55
+ </div>
56
+ );
57
+ };
58
+
59
+ export default HtmlCode;
@@ -0,0 +1,39 @@
1
+ import React, { useState } from "react";
2
+ import useContextMenu from "../../utils/customHooks/useContextMenu.js";
3
+ import Icon from "../../common/Icon.jsx";
4
+ import { Transforms, Node, Path } from "slate";
5
+
6
+ const HtmlContextMenu = (props) => {
7
+ const { editor, handleCodeToText } = props;
8
+ const [selection, setSelection] = useState();
9
+ const [showMenu, { top, left }] = useContextMenu(
10
+ editor,
11
+ "htmlCode",
12
+ setSelection
13
+ );
14
+
15
+ const handleEditHtml = () => {
16
+ Transforms.select(editor, selection);
17
+ const parentPath = Path.parent(selection.focus.path);
18
+ const htmlNode = Node.get(editor, parentPath);
19
+ handleCodeToText({
20
+ showInput: true,
21
+ html: htmlNode.html,
22
+ action: "update",
23
+ location: selection,
24
+ });
25
+ };
26
+
27
+ return (
28
+ showMenu && (
29
+ <div className="contextMenu" style={{ top, left }}>
30
+ <div className="menuOption" onClick={handleEditHtml}>
31
+ <Icon icon="pen" />
32
+ <span>Edit HTML</span>
33
+ </div>
34
+ </div>
35
+ )
36
+ );
37
+ };
38
+
39
+ export default HtmlContextMenu;
@@ -0,0 +1,38 @@
1
+
2
+ .color-options{
3
+ display: grid;
4
+ grid-template-columns: auto auto auto auto auto auto auto;
5
+ align-items: center;
6
+ gap: 5px;
7
+ }
8
+ .clicked{
9
+ border: 1px solid lightgray;
10
+ border-bottom: none;
11
+ }
12
+ .option,.hexPreview{
13
+ width: 16px;
14
+ height: 16px;
15
+ background-color: #000000;
16
+
17
+ }
18
+ .color-picker form{
19
+ display: flex;
20
+ align-items: center;
21
+ column-gap: 5px;
22
+ width: 100%;
23
+ }
24
+ .color-picker input{
25
+ width: 65%;
26
+ height:1.3em;
27
+ border:1px solid lightgray;
28
+ border-radius: 5px;
29
+ padding-left:5px
30
+ }
31
+ .color-picker button{
32
+ margin:0;
33
+ padding:0;
34
+ cursor: pointer;
35
+ }
36
+ .color-picker input:focus{
37
+ outline: none;
38
+ }
@@ -0,0 +1,110 @@
1
+ import React, { useRef, useState } from "react";
2
+ import { MdFormatColorText, MdFormatColorFill, MdCheck } from "react-icons/md";
3
+ import "./ColorPicker.css";
4
+ import { colors } from "./defaultColors.js";
5
+ import { addMarkData, activeMark } from "../../utils/SlateUtilityFunctions.js";
6
+ import { Transforms } from "slate";
7
+ import usePopup from "../../utils/customHooks/usePopup";
8
+ import { ReactEditor } from "slate-react";
9
+
10
+ const logo = {
11
+ color: <MdFormatColorText size={20} />,
12
+ bgColor: <MdFormatColorFill size={20} />,
13
+ };
14
+ const ColorPicker = ({ format, editor }) => {
15
+ const [selection, setSelection] = useState();
16
+ const [hexValue, setHexValue] = useState("");
17
+ const [validHex, setValidHex] = useState();
18
+ const colorPickerRef = useRef(null);
19
+ const [showOptions, setShowOptions] = usePopup(colorPickerRef);
20
+
21
+ const isValideHexSix = new RegExp("^#[0-9A-Za-z]{6}");
22
+ const isValideHexThree = new RegExp("^#[0-9A-Za-z]{3}");
23
+
24
+ const changeColor = (e) => {
25
+ const clickedColor = e.target.getAttribute("data-value");
26
+ selection && Transforms.select(editor, selection);
27
+ selection && ReactEditor.focus(editor);
28
+
29
+ addMarkData(editor, { format, value: clickedColor });
30
+ setShowOptions(false);
31
+ };
32
+ const toggleOption = () => {
33
+ setSelection(editor.selection);
34
+ selection && ReactEditor.focus(editor);
35
+
36
+ setShowOptions((prev) => !prev);
37
+ };
38
+ const handleFormSubmit = (e) => {
39
+ e.preventDefault();
40
+ if (!validHex) return;
41
+ selection && Transforms.select(editor, selection);
42
+
43
+ addMarkData(editor, { format, value: hexValue });
44
+ setShowOptions(false);
45
+ setValidHex("");
46
+ setHexValue("");
47
+ selection && ReactEditor.focus(editor);
48
+ };
49
+ const handleHexChange = (e) => {
50
+ e.preventDefault();
51
+ const newHex = e.target.value;
52
+ setValidHex(isValideHexSix.test(newHex) || isValideHexThree.test(newHex));
53
+ setHexValue(newHex);
54
+ };
55
+ return (
56
+ <div className="color-picker popup-wrapper" ref={colorPickerRef}>
57
+ <button
58
+ style={{
59
+ color: showOptions ? "black" : activeMark(editor, format),
60
+ opacity: "1",
61
+ }}
62
+ className={showOptions ? "clicked" : ""}
63
+ onClick={toggleOption}
64
+ >
65
+ {logo[format]}
66
+ </button>
67
+ {showOptions && (
68
+ <div className="popup">
69
+ <div className="color-options">
70
+ {colors.map((color, index) => {
71
+ return (
72
+ <div
73
+ key={index}
74
+ data-value={color}
75
+ onClick={changeColor}
76
+ className="option"
77
+ style={{ background: color }}
78
+ ></div>
79
+ );
80
+ })}
81
+ </div>
82
+ <p style={{ textAlign: "center", opacity: "0.7", width: "100%" }}>
83
+ OR
84
+ </p>
85
+ <form onSubmit={handleFormSubmit}>
86
+ <div
87
+ className="hexPreview"
88
+ style={{ background: validHex ? hexValue : "#000000" }}
89
+ ></div>
90
+ <input
91
+ type="text"
92
+ placeholder="#000000"
93
+ value={hexValue}
94
+ onChange={handleHexChange}
95
+ style={{
96
+ border:
97
+ validHex === false ? "1px solid red" : "1px solid lightgray",
98
+ }}
99
+ />
100
+ <button style={{ color: validHex ? "green" : "" }} type={"submit"}>
101
+ <MdCheck size={20} />
102
+ </button>
103
+ </form>
104
+ </div>
105
+ )}
106
+ </div>
107
+ );
108
+ };
109
+
110
+ export default ColorPicker;
@@ -0,0 +1,34 @@
1
+ export const colors = ["#000000","#e60000",
2
+ "#ff9900",
3
+ "#ffff00",
4
+ "#008a00",
5
+ "#0066cc",
6
+ "#9933ff",
7
+ "#ffffff",
8
+ "#facccc",
9
+ "#ffebcc",
10
+ "#ffffcc",
11
+ "#cce8cc",
12
+ "#cce0f5",
13
+ "#ebd6ff",
14
+ "#bbbbbb",
15
+ "#f06666",
16
+ "#ffc266",
17
+ "#ffff66",
18
+ "#66b966",
19
+ "#66a3e0",
20
+ "#c285ff",
21
+ "#888888",
22
+ "#a10000",
23
+ "#b26b00",
24
+ "#b2b200",
25
+ "#006100",
26
+ "#0047b2",
27
+ "#6b24b2",
28
+ "#444444",
29
+ "#5c0000",
30
+ "#663d00",
31
+ "#666600",
32
+ "#003700",
33
+ "#002966",
34
+ "#3d1466"]
@@ -0,0 +1,14 @@
1
+ .embed{
2
+ width: fit-content;
3
+ position: relative;
4
+ margin-right: 20px;
5
+ }
6
+ .embed img,.embed iframe{
7
+ width: 100%;
8
+ height:100%;
9
+ }
10
+ .embed button{
11
+ position: absolute;
12
+ bottom: -6px;
13
+ right: 0;
14
+ }
@@ -0,0 +1,74 @@
1
+ import React, {useRef, useState} from 'react';
2
+ import Button from '../../common/Button'
3
+ import Icon from '../../common/Icon'
4
+ import {isBlockActive} from '../../utils/SlateUtilityFunctions'
5
+ import usePopup from '../../utils/customHooks/usePopup'
6
+ import {insertEmbed } from '../../utils/embed.js'
7
+ import { Transforms } from 'slate';
8
+ import {ReactEditor} from 'slate-react'
9
+
10
+ import './Embed.css'
11
+ const Embed = ({editor,format}) =>{
12
+ const urlInputRef = useRef();
13
+ const [showInput,setShowInput] = usePopup(urlInputRef);
14
+ const [formData,setFormData] = useState({
15
+ url:'',
16
+ alt:''
17
+ })
18
+ const [selection,setSelection] = useState();
19
+ const handleButtonClick = (e)=>{
20
+ e.preventDefault();
21
+ setSelection(editor.selection);
22
+ selection && ReactEditor.focus(editor);
23
+
24
+ setShowInput(prev =>!prev);
25
+ }
26
+ const handleFormSubmit = (e)=>{
27
+ e.preventDefault();
28
+
29
+ selection && Transforms.select(editor,selection);
30
+ selection && ReactEditor.focus(editor);
31
+
32
+ insertEmbed(editor,{...formData},format);
33
+ setShowInput(false);
34
+ setFormData({
35
+ url:'',
36
+ alt:''
37
+ })
38
+ }
39
+ const handleImageUpload = ()=>{
40
+ setShowInput(false)
41
+ }
42
+ return (
43
+ <div ref={urlInputRef} className='popup-wrapper'>
44
+ <Button active={isBlockActive(editor,format)} style={{border: showInput?'1px solid lightgray':'',borderBottom: 'none'}} format={format} onClick={handleButtonClick}>
45
+ <Icon icon={format}/>
46
+ </Button>
47
+ {
48
+ showInput&&
49
+ <div className='popup'>
50
+ {
51
+ format === 'image' &&
52
+ <div>
53
+ <div style={{display:'flex',gap:'10px'}} onClick={handleImageUpload}>
54
+ <Icon icon='upload'/>
55
+ <span>Upload</span>
56
+ </div>
57
+ <p style={{textAlign:'center',opacity:'0.7',width:'100%'}}>OR</p>
58
+
59
+ </div>
60
+ }
61
+ <form onSubmit={handleFormSubmit}>
62
+ <input type="text" placeholder='Enter url' value={formData.url} onChange={e=>setFormData(prev =>({...prev,url:e.target.value}))}/>
63
+ <input type="text" placeholder='Enter alt' value={formData.alt} onChange={e=>setFormData(prev =>({...prev,alt:e.target.value}))}/>
64
+
65
+
66
+ <Button type='submit'>Save</Button>
67
+ </form>
68
+ </div>
69
+ }
70
+ </div>
71
+ )
72
+ }
73
+
74
+ export default Embed;
@@ -0,0 +1,82 @@
1
+ import React, { useEffect, useState } from "react";
2
+ import {
3
+ useSlateStatic,
4
+ useSelected,
5
+ useFocused,
6
+ ReactEditor,
7
+ } from "slate-react";
8
+ import { Node, Transforms } from "slate";
9
+ import Icon from "../../common/Icon";
10
+ import useResize from "../../utils/customHooks/useResize.js";
11
+
12
+ const Image = ({ attributes, element, children }) => {
13
+ const { url, alt } = element;
14
+ const editor = useSlateStatic();
15
+ const selected = useSelected();
16
+ const focused = useFocused();
17
+ const [parentDOM, setParentDOM] = useState(null);
18
+ const [size, onMouseDown, resizing, onLoad] = useResize({
19
+ parentDOM,
20
+ size: element?.size,
21
+ });
22
+ const arr = Array.from(Node.elements(editor));
23
+ const ele = arr.find(([elem]) => element === elem);
24
+
25
+ useEffect(() => {
26
+ if (editor && ele[1] !== undefined) {
27
+ const dom = ReactEditor.toDOMNode(editor, Node.get(editor, ele[1]));
28
+ setParentDOM(dom);
29
+ onLoad(dom);
30
+ }
31
+ }, []);
32
+
33
+ useEffect(() => {
34
+ if (!resizing) {
35
+ Transforms.setNodes(editor, {
36
+ size: size,
37
+ });
38
+ }
39
+ }, [resizing]);
40
+
41
+ return (
42
+ <div
43
+ {...attributes}
44
+ className="embed"
45
+ style={{
46
+ display: "flex",
47
+ width: "100%",
48
+ maxWidth: "100%",
49
+ boxShadow: selected && focused && "0 0 3px 3px lightgray",
50
+ }}
51
+ {...element.attr}
52
+ >
53
+ <div
54
+ contentEditable={false}
55
+ style={{
56
+ position: "relative",
57
+ width: size.widthInPercent
58
+ ? `${size.widthInPercent}%`
59
+ : `${size.width}px`,
60
+ height: `${size.height}px`,
61
+ }}
62
+ >
63
+ <img alt={alt} src={url} />
64
+ {selected && (
65
+ <button
66
+ onPointerDown={onMouseDown}
67
+ style={{
68
+ width: "15px",
69
+ height: "15px",
70
+ opacity: 1,
71
+ background: "transparent",
72
+ }}
73
+ >
74
+ <Icon icon="resize" />
75
+ </button>
76
+ )}
77
+ </div>
78
+ {children}
79
+ </div>
80
+ );
81
+ };
82
+ export default Image;
@@ -0,0 +1,65 @@
1
+ import React from "react";
2
+ import { useSelected, useFocused } from "slate-react";
3
+ import Icon from "../../common/Icon";
4
+ import useResize from "../../utils/customHooks/useResize.js";
5
+ // import "./Video.css";
6
+
7
+ const Video = ({ attributes, element, children }) => {
8
+ const { url, alt } = element;
9
+ const [size, onMouseDown, resizing] = useResize();
10
+ const selected = useSelected();
11
+ const focused = useFocused();
12
+ return (
13
+ <div
14
+ {...attributes}
15
+ className="embed"
16
+ style={{
17
+ display: "flex",
18
+ boxShadow: selected && focused && "0 0 3px 3px lightgray",
19
+ }}
20
+ {...element.attr}
21
+ >
22
+ <div
23
+ contentEditable={false}
24
+ style={{ width: `${size.width}px`, height: `${size.height}px` }}
25
+ >
26
+ {
27
+ // The iframe reloads on each re-render and hence it stutters and the document doesn't detect mouse-up event leading to unwanted behaviour
28
+ // So during resize replace the iframe with a simple div
29
+ resizing ? (
30
+ <div
31
+ style={{
32
+ width: "100%",
33
+ height: "100%",
34
+ border: "2px dashed black",
35
+ display: "flex",
36
+ justifyContent: "center",
37
+ alignItems: "center",
38
+ }}
39
+ >
40
+ <Icon icon="videoPlayer" />
41
+ </div>
42
+ ) : (
43
+ <iframe src={url} frameBorder="0" title={alt} />
44
+ )
45
+ }
46
+
47
+ {selected && (
48
+ <button
49
+ onMouseDown={onMouseDown}
50
+ style={{
51
+ width: "15px",
52
+ height: "15px",
53
+ opacity: 1,
54
+ background: "transparent",
55
+ }}
56
+ >
57
+ <Icon icon="resize" />
58
+ </button>
59
+ )}
60
+ </div>
61
+ {children}
62
+ </div>
63
+ );
64
+ };
65
+ export default Video;
@@ -0,0 +1,19 @@
1
+ import React from "react";
2
+ import { InlineMath, BlockMath } from "react-katex";
3
+
4
+ import "./styles.css";
5
+ const Equation = ({ attributes, element, children }) => {
6
+ const { inline, math } = element;
7
+ return (
8
+ <div className={inline ? "equation-inline" : ""}>
9
+ <span {...attributes} {...element.attr}>
10
+ <span contentEditable={false}>
11
+ {inline ? <InlineMath math={math} /> : <BlockMath math={math} />}
12
+ </span>
13
+ {children}
14
+ </span>
15
+ </div>
16
+ );
17
+ };
18
+
19
+ export default Equation;