diff --git a/front/components/Editor/Editor.tsx b/front/components/Editor/Editor.tsx index f3f39fcc..d90d5951 100644 --- a/front/components/Editor/Editor.tsx +++ b/front/components/Editor/Editor.tsx @@ -19,6 +19,7 @@ import ImageBlock from "./Extensions/Image/ImageBlock"; import Youtube from "@tiptap/extension-youtube"; import VideoBlock from "./Extensions/Video/VideoBlock"; import { Save } from "lucide-react"; +import MathEquationBlock from "./Extensions/MathEquation/MathEquationBlock"; interface Editor { content: string; @@ -54,6 +55,10 @@ function Editor(props: Editor) { editable: true, lecture: props.lecture, }), + MathEquationBlock.configure({ + editable: true, + lecture: props.lecture, + }), Youtube.configure({ controls: true, modestBranding: true, diff --git a/front/components/Editor/Extensions/MathEquation/MathEquationBlock.ts b/front/components/Editor/Extensions/MathEquation/MathEquationBlock.ts new file mode 100644 index 00000000..d35bfa54 --- /dev/null +++ b/front/components/Editor/Extensions/MathEquation/MathEquationBlock.ts @@ -0,0 +1,35 @@ +import { mergeAttributes, Node } from "@tiptap/core"; +import { ReactNodeViewRenderer } from "@tiptap/react"; + +import MathEquationBlockComponent from "./MathEquationBlockComponent"; + +export default Node.create({ + name: "blockMathEquation", + group: "block", + + atom: true, + + addAttributes() { + return { + math_equation: { + default: "", + }, + }; + }, + + parseHTML() { + return [ + { + tag: "block-math-equation", + }, + ]; + }, + + renderHTML({ HTMLAttributes }) { + return ["block-math-equation", mergeAttributes(HTMLAttributes), 0]; + }, + + addNodeView() { + return ReactNodeViewRenderer(MathEquationBlockComponent); + }, +}); diff --git a/front/components/Editor/Extensions/MathEquation/MathEquationBlockComponent.tsx b/front/components/Editor/Extensions/MathEquation/MathEquationBlockComponent.tsx new file mode 100644 index 00000000..102e00b2 --- /dev/null +++ b/front/components/Editor/Extensions/MathEquation/MathEquationBlockComponent.tsx @@ -0,0 +1,116 @@ +import { NodeViewWrapper } from "@tiptap/react"; +import React from "react"; +import styled from "styled-components"; +import "katex/dist/katex.min.css"; +import { InlineMath, BlockMath } from "react-katex"; +import { Edit, Save } from "lucide-react"; + +function MathEquationBlockComponent(props: any) { + const [equation, setEquation] = React.useState(props.node.attrs.math_equation); + const [isEditing, setIsEditing] = React.useState(false); + const isEditable = props.extension.options.editable; + + const handleEquationChange = (event: React.ChangeEvent) => { + setEquation(event.target.value); + props.updateAttributes({ + math_equation: equation, + }); + }; + + const saveEquation = () => { + props.updateAttributes({ + math_equation: equation, + }); + setIsEditing(false); + }; + + return ( + + + {isEditable && ( + + + + )} + {equation} + {isEditing && ( + + + + + )} + + + ); +} + +export default MathEquationBlockComponent; + +const MathEqWrapper = styled.div` + display: flex; + flex-direction: column; + background: #f9f9f9a2; + border-radius: 8px; + margin: 20px; + padding: 20px; + min-height: 74px; + border: ${(props) => (props.contentEditable ? "2px dashed #713f1117" : "none")}; +`; + +const MathEqTopMenu = styled.div` + display: flex; + justify-content: flex-end; + button { + margin-left: 10px; + cursor: pointer; + border: none; + background: none; + font-size: 14px; + color: #494949; + } +`; + +const EditBar = styled.div` + display: flex; + justify-content: flex-end; + margin-top: 10px; + background-color: white; + border-radius: 10px; + padding: 5px; + color: #5252528d; + align-items: center; + justify-content: space-between; + height: 50px; + border: solid 1px #52525224; + + button { + margin-left: 10px; + margin-right: 7px; + cursor: pointer; + border: none; + background: none; + font-size: 14px; + color: #494949; + } + + input { + border: none; + background: none; + font-size: 14px; + color: #494949; + width: 100%; + font-family: "DM Sans", sans-serif; + padding-left: 10px; + &:focus { + outline: none; + } + + &::placeholder { + color: #49494936; + } + } +`; diff --git a/front/components/Editor/Toolbar/ToolbarButtons.tsx b/front/components/Editor/Toolbar/ToolbarButtons.tsx index 87ea1c0d..24c58d98 100644 --- a/front/components/Editor/Toolbar/ToolbarButtons.tsx +++ b/front/components/Editor/Toolbar/ToolbarButtons.tsx @@ -1,6 +1,6 @@ import styled from "styled-components"; import { FontBoldIcon, FontItalicIcon, StrikethroughIcon, ArrowLeftIcon, ArrowRightIcon, OpacityIcon } from "@radix-ui/react-icons"; -import { AlertCircle, AlertTriangle, ImagePlus, Info, Video, Youtube } from "lucide-react"; +import { AlertCircle, AlertTriangle, ImagePlus, Info, Sigma, Video, Youtube } from "lucide-react"; export const ToolbarButtons = ({ editor }: any) => { if (!editor) { @@ -90,6 +90,19 @@ export const ToolbarButtons = ({ editor }: any) => { addYoutubeVideo()}> + + editor + .chain() + .focus() + .insertContent({ + type: "blockMathEquation", + }) + .run() + } + > + + ); }; diff --git a/front/components/LectureViews/DynamicCanva/DynamicCanva.tsx b/front/components/LectureViews/DynamicCanva/DynamicCanva.tsx index e6b71fc7..2dd76671 100644 --- a/front/components/LectureViews/DynamicCanva/DynamicCanva.tsx +++ b/front/components/LectureViews/DynamicCanva/DynamicCanva.tsx @@ -9,6 +9,7 @@ import Youtube from "@tiptap/extension-youtube"; import { EditorContentWrapper } from "@editor/Editor"; import VideoBlock from "@editor/Extensions/Video/VideoBlock"; import { styled } from "styled-components"; +import MathEquationBlock from "@components/Editor/Extensions/MathEquation/MathEquationBlock"; interface Editor { content: string; @@ -37,6 +38,10 @@ function Canva(props: Editor) { editable: true, lecture: props.lecture, }), + MathEquationBlock.configure({ + editable: false, + lecture: props.lecture, + }), Youtube.configure({ controls: true, modestBranding: true, diff --git a/front/package-lock.json b/front/package-lock.json index 57229587..db8ab2bd 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -23,6 +23,7 @@ "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-katex": "^3.0.1", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", "y-indexeddb": "^9.0.9", @@ -34,6 +35,7 @@ "@types/react": "18.0.20", "@types/react-beautiful-dnd": "^13.1.2", "@types/react-dom": "18.0.6", + "@types/react-katex": "^3.0.0", "@types/styled-components": "^5.1.26", "autoprefixer": "^10.4.12", "eslint": "8.23.1", @@ -3020,6 +3022,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-katex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/react-katex/-/react-katex-3.0.0.tgz", + "integrity": "sha512-AiHHXh71a2M7Z6z1wj6iA23SkiRF9r0neHUdu8zjU/cT3MyLxDefYHbcceKhV/gjDEZgF3YaiNHyPNtoGUjPvg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-redux": { "version": "7.1.24", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", @@ -5294,6 +5305,29 @@ "node": ">=4.0" } }, + "node_modules/katex": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.4.tgz", + "integrity": "sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.0.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -6083,6 +6117,18 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-katex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-katex/-/react-katex-3.0.1.tgz", + "integrity": "sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==", + "dependencies": { + "katex": "^0.16.0" + }, + "peerDependencies": { + "prop-types": "^15.8.1", + "react": ">=15.3.2 <=18" + } + }, "node_modules/react-redux": { "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", @@ -9227,6 +9273,15 @@ "@types/react": "*" } }, + "@types/react-katex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/react-katex/-/react-katex-3.0.0.tgz", + "integrity": "sha512-AiHHXh71a2M7Z6z1wj6iA23SkiRF9r0neHUdu8zjU/cT3MyLxDefYHbcceKhV/gjDEZgF3YaiNHyPNtoGUjPvg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/react-redux": { "version": "7.1.24", "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.24.tgz", @@ -10842,6 +10897,21 @@ "object.assign": "^4.1.3" } }, + "katex": { + "version": "0.16.4", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.4.tgz", + "integrity": "sha512-WudRKUj8yyBeVDI4aYMNxhx5Vhh2PjpzQw1GRu/LVGqL4m1AxwD1GcUp0IMbdJaf5zsjtj8ghP0DOQRYhroNkw==", + "requires": { + "commander": "^8.0.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + } + } + }, "language-subtag-registry": { "version": "0.3.22", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", @@ -11420,6 +11490,14 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "react-katex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/react-katex/-/react-katex-3.0.1.tgz", + "integrity": "sha512-wIUW1fU5dHlkKvq4POfDkHruQsYp3fM8xNb/jnc8dnQ+nNCnaj0sx5pw7E6UyuEdLRyFKK0HZjmXBo+AtXXy0A==", + "requires": { + "katex": "^0.16.0" + } + }, "react-redux": { "version": "7.2.9", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.9.tgz", diff --git a/front/package.json b/front/package.json index a310c591..325ba9c8 100644 --- a/front/package.json +++ b/front/package.json @@ -24,6 +24,7 @@ "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", + "react-katex": "^3.0.1", "styled-components": "^6.0.0-beta.9", "swr": "^2.0.1", "y-indexeddb": "^9.0.9", @@ -35,6 +36,7 @@ "@types/react": "18.0.20", "@types/react-beautiful-dnd": "^13.1.2", "@types/react-dom": "18.0.6", + "@types/react-katex": "^3.0.0", "@types/styled-components": "^5.1.26", "autoprefixer": "^10.4.12", "eslint": "8.23.1",