diff --git a/components/shared/extendedForms/CustomEditor/editor-components/CustomEditorToolbar.tsx b/components/shared/extendedForms/CustomEditor/editor-components/CustomEditorToolbar.tsx new file mode 100644 index 0000000..d89b04f --- /dev/null +++ b/components/shared/extendedForms/CustomEditor/editor-components/CustomEditorToolbar.tsx @@ -0,0 +1,114 @@ +import {useLexicalComposerContext} from "@lexical/react/LexicalComposerContext"; +import { + faAlignCenter, faAlignJustify, + faAlignLeft, + faAlignRight, + faBold, + faItalic, + faUnderline +} from "@fortawesome/free-solid-svg-icons"; +import { + FORMAT_ELEMENT_COMMAND, + FORMAT_TEXT_COMMAND, + LexicalEditor, + SELECTION_CHANGE_COMMAND +} from "lexical"; +import {useEffect} from "react"; +import {mergeRegister} from "@lexical/utils"; +import {ToolbarTheme, useEditorToolbarTheme} from "./useEditorToolbarTheme"; +import {ToolbarTab} from "./ToolbarTab"; +import * as Separator from '@radix-ui/react-separator'; + + +type DefinedToolBarTabProps = { t: ToolbarTheme, e: LexicalEditor }; + + +const BoldToolbarTab = ({t, e}: DefinedToolBarTabProps) => ToolbarTab({ + isActive: t.isBold, + fontAwesomeIcon: faBold, + dispatcher: () => e.dispatchCommand(FORMAT_TEXT_COMMAND, "bold"), + label: 'Texte gras' +}); + +const ItalicToolbarTab = ({t, e}: DefinedToolBarTabProps) => ToolbarTab({ + isActive: t.isItalic, + fontAwesomeIcon: faItalic, + dispatcher: () => e.dispatchCommand(FORMAT_TEXT_COMMAND, "italic"), + label: 'Texte italique' +}); + +const UnderlineToolbarTab = ({t, e}: DefinedToolBarTabProps) => ToolbarTab({ + isActive: t.isUnderline, + fontAwesomeIcon: faUnderline, + dispatcher: () => e.dispatchCommand(FORMAT_TEXT_COMMAND, "underline"), + label: 'Texte souligné' +}); + +const AlignLeftToolbarTab = ({e}: DefinedToolBarTabProps) => ToolbarTab({ + fontAwesomeIcon: faAlignLeft, + dispatcher: () => e.dispatchCommand(FORMAT_ELEMENT_COMMAND, "left"), + label: 'Texte à gauche' +}); + +const AlignRightToolbarTab = ({e}: DefinedToolBarTabProps) => ToolbarTab({ + fontAwesomeIcon: faAlignRight, + dispatcher: () => e.dispatchCommand(FORMAT_ELEMENT_COMMAND, "right"), + label: 'Texte à droite' +}); + +const AlignCenterToolbarTab = ({e}: DefinedToolBarTabProps) => ToolbarTab({ + fontAwesomeIcon: faAlignCenter, + dispatcher: () => e.dispatchCommand(FORMAT_ELEMENT_COMMAND, "center"), + label: 'Texte centré' +}); + +const AlignJustifyToolbarTab = ({e}: DefinedToolBarTabProps) => ToolbarTab({ + fontAwesomeIcon: faAlignJustify, + dispatcher: () => e.dispatchCommand(FORMAT_ELEMENT_COMMAND, "justify"), + label: 'Texte justifié' +}); + + +export const CustomEditorToolbar = () => { + const [editor] = useLexicalComposerContext(); + const [toolbarTheme, updateToolbar] = useEditorToolbarTheme(); + + useEffect(() => { + return mergeRegister( + editor.registerUpdateListener(({editorState}) => { + editorState.read(() => { + updateToolbar(); + + }) + }), + editor.registerCommand( + SELECTION_CHANGE_COMMAND, + () => { + return true; + }, + 1 + ) + ); + }, [editor, updateToolbar]) + + return ( +
+ + {/* Text style */} + + + + + + + + {/* Text direction */} + + + + + + +
+ ) +} \ No newline at end of file diff --git a/components/shared/extendedForms/CustomEditor/editor-components/ToolbarTab.tsx b/components/shared/extendedForms/CustomEditor/editor-components/ToolbarTab.tsx new file mode 100644 index 0000000..d52db29 --- /dev/null +++ b/components/shared/extendedForms/CustomEditor/editor-components/ToolbarTab.tsx @@ -0,0 +1,20 @@ +import {IconProp} from "@fortawesome/fontawesome-svg-core"; +import clsx from "clsx"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; + +interface ToolbarTabOptions { + isActive?: boolean, + fontAwesomeIcon: IconProp, + dispatcher: () => void, + label: string +} + +export const ToolbarTab = ({isActive = false, fontAwesomeIcon, dispatcher, label}: ToolbarTabOptions) => { + return +} \ No newline at end of file diff --git a/components/shared/extendedForms/CustomEditor/editor-components/useEditorToolbarTheme.ts b/components/shared/extendedForms/CustomEditor/editor-components/useEditorToolbarTheme.ts new file mode 100644 index 0000000..c8c3bb6 --- /dev/null +++ b/components/shared/extendedForms/CustomEditor/editor-components/useEditorToolbarTheme.ts @@ -0,0 +1,36 @@ +import {useState} from "react"; +import {$getSelection, $isRangeSelection, GridSelection, NodeSelection, RangeSelection} from "lexical"; + +export interface ToolbarTheme { + isBold: boolean, + isItalic: boolean, + isUnderline: boolean +} + +function isRangeSelection (selection: RangeSelection | NodeSelection | GridSelection | null): selection is RangeSelection { + return $isRangeSelection(selection) +} + +export function useEditorToolbarTheme(): [ToolbarTheme, () => void] { + const [isBold, setIsBold] = useState(false); + const [isItalic, setIsItalic] = useState(false); + const [isUnderline, setIsUnderline] = useState(false); + + const updateToolbar = () => { + const selection = $getSelection(); + + if (!selection) return; + + if (isRangeSelection(selection)){ + setIsBold(selection.hasFormat("bold")); + setIsItalic(selection.hasFormat("italic")); + setIsUnderline(selection.hasFormat("underline")); + } + } + + return [ + {isBold, isItalic, isUnderline}, + updateToolbar + ] + +} \ No newline at end of file diff --git a/components/shared/extendedForms/CustomEditor/index.tsx b/components/shared/extendedForms/CustomEditor/index.tsx index c2abccb..f18a3bb 100644 --- a/components/shared/extendedForms/CustomEditor/index.tsx +++ b/components/shared/extendedForms/CustomEditor/index.tsx @@ -1,28 +1,94 @@ import React from "react"; -import {createReactEditorJS} from 'react-editor-js' -import {EDITOR_JS_TOOLS} from "./tools"; -import Twemoji from "react-twemoji"; - +import LexicalComposer from '@lexical/react/LexicalComposer'; +import LexicalRichTextPlugin from '@lexical/react/LexicalRichTextPlugin'; +import LexicalContentEditable from '@lexical/react/LexicalContentEditable'; +import {HistoryPlugin} from '@lexical/react/LexicalHistoryPlugin'; +import {EditorThemeClasses} from 'lexical'; +import {HeadingNode, QuoteNode} from "@lexical/rich-text"; +import {CustomEditorToolbar} from "./editor-components/CustomEditorToolbar"; export interface CustomEditorProps { blocks: any, readonly: boolean, - [key:string]: any + + [key: string]: any +} + +const theme: EditorThemeClasses = { + ltr: 'ltr', + rtl: 'rtl', + placeholder: 'editor-placeholder', + paragraph: 'editor-paragraph', + text: { + bold: 'font-bold', + italic: 'italic', + underline: 'underline' + } } -export const CustomEditor: React.VFC = ({blocks = {}, readonly = false, ...reactEditorOptions}: CustomEditorProps) => { +function onError(error: any) { + console.error(error); +} + +// When the editor changes, you can get notified via the +// LexicalOnChangePlugin! +// function onChange(editorState: any) { +// editorState.read(() => { +// // Read the contents of the EditorState here. +// const root = $getRoot(); +// const selection = $getSelection(); +// }); +// } + +export const CustomEditor: React.VFC = ({ + blocks = {}, + readonly = false, + ...reactEditorOptions + }: CustomEditorProps) => { - const ReactEditorJS: any = createReactEditorJS() + // const ReactEditorJS: any = createReactEditorJS() + const initialConfig = { + theme, + onError, + nodes: [ + HeadingNode, + QuoteNode, + ] + }; return ( -
- - - -
- ) + +
+ + {/* Toolbar */} + + +
+ + } + placeholder={
Enter + some text...
} + /> + {/**/} + +
+
+ +
+ ); + + + // return ( + //
+ // + // + // + //
+ // ) } export default CustomEditor; diff --git a/package.json b/package.json index dc65a49..c88f8c9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,9 @@ "@fortawesome/free-solid-svg-icons": "^5.15.4", "@fortawesome/react-fontawesome": "^0.1.16", "@google-cloud/storage": "^5.18.0", + "@lexical/react": "^0.2.1", "@radix-ui/react-popover": "^0.1.6", + "@radix-ui/react-separator": "^0.1.4", "@tailwindcss/forms": "^0.3.4", "@urql/exchange-auth": "^0.1.6", "add": "^2.0.6", @@ -43,6 +45,7 @@ "graphql": "^16.0.1", "graphql-tag": "^2.12.6", "jsonwebtoken": "^8.5.1", + "lexical": "^0.2.1", "next": "12.0.4", "next-auth": "^4.1.0", "next-i18next": "^9.2.0", @@ -77,12 +80,12 @@ "@storybook/builder-webpack5": "^6.4.0", "@storybook/manager-webpack5": "^6.4.0", "@storybook/react": "^6.4.0", - "@types/react-twemoji": "^0.4.0", "@storybook/theming": "^6.4.0", "@types/emoji-mart": "^3.0.9", "@types/formidable": "^2.0.4", "@types/node": "16.11.9", "@types/react": "17.0.35", + "@types/react-twemoji": "^0.4.0", "@types/uuid": "^8.3.4", "babel-loader": "^8.2.3", "eslint": "7", diff --git a/styles/globals.css b/styles/globals.css index d7e8e6b..b026dd6 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -137,4 +137,32 @@ h6.ce-header { .twemoji { height: 1em; display: inline-block; +} + +.ltr { + text-align: left; +} + +.rtl { + text-align: right; +} + +.editor-placeholder { + color: #999; + overflow: hidden; + position: absolute; + top: 15px; + left: 15px; + user-select: none; + pointer-events: none; +} + +.editor-paragraph { + margin: 0 0 15px 0; + position: relative; + outline: none; +} + +.no-child-outline * { + outline: none; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ec20ff9..4a8b6e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1908,6 +1908,141 @@ "@types/yargs" "^15.0.0" chalk "^4.0.0" +"@lexical/clipboard@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/clipboard/-/clipboard-0.2.1.tgz#0e3bc64dfa7fe891c7043c8683af2e0895577246" + integrity sha512-r2r8TwYLnM0i+//oQlUSz9IK/FrDrMi+mW3K2L9enjimV+HqXhdSqxQQspMjZ8ULE/lVRzf3aDFgP2p61Fn7nQ== + dependencies: + "@lexical/selection" "0.2.1" + "@lexical/utils" "0.2.1" + +"@lexical/code@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/code/-/code-0.2.1.tgz#f030df3ed25a46045e0fb13261bfce4c51dfa3c1" + integrity sha512-Z76KwRoI/eAKvpqPI0uHljQuY/DE0+fNqWDvl01nTKjMz5taU0a2LptzOxdRR9juRvKMAgVihKMBoXaC41QlVw== + dependencies: + "@lexical/utils" "0.2.1" + prismjs "^1.27.0" + +"@lexical/dragon@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/dragon/-/dragon-0.2.1.tgz#a0e61c0c5fe70eca841f11e992296a3f4079928e" + integrity sha512-je0uq9S6JGLBChwJrmSLOM04QOl5cCzC4zPNh8D7fGUFuw7W15uo9cdqmSEIgXsi3u1AkbTfAsf/QUzqHPLriQ== + +"@lexical/hashtag@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/hashtag/-/hashtag-0.2.1.tgz#d83d06ad08a294a93e88b65aa6ff4526062c0985" + integrity sha512-tDPU5ViCzNDCqhPZFKZwoBeuxjv20hc8usLv582ouGgpBHK9B+SF8IPepklAvYNhbt33JQDczmyHbcw4kognbg== + dependencies: + "@lexical/utils" "0.2.1" + +"@lexical/history@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/history/-/history-0.2.1.tgz#4bbbf8b5e90fa1c6e65b84a76ec33ec00be509f9" + integrity sha512-6IZc89QnY4hZw8xbdPEcwKBPJeSpqLR1f6nGnSyu3MgcHc4DcE0vDtdwPnvOON0mWpseWdFiYFWaudFFqBeyAQ== + dependencies: + "@lexical/utils" "0.2.1" + +"@lexical/link@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/link/-/link-0.2.1.tgz#7a53f36276230074a93e989c7d8fc144e7dcd060" + integrity sha512-91eID3LUVj07epPI98MhSVXlqMhbmPHFi/khaekYfx+STtMKMVyiPf9txxsVUKIgajfCopWSVUqu9ZdT+R7ZnQ== + dependencies: + "@lexical/utils" "0.2.1" + +"@lexical/list@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/list/-/list-0.2.1.tgz#fa9daa66932cda8acd373f6f225e3b9696c50115" + integrity sha512-jRly4P0ClYZtKDXzmhPaDBFXYlTqXc2XooqKjdYff/0TLoVaMdZJJL4YrbBsFSIFhwuMTeb4qJNRaFLca49xhg== + dependencies: + "@lexical/utils" "0.2.1" + +"@lexical/markdown@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/markdown/-/markdown-0.2.1.tgz#af9ba36fb741ce2f0011e4d5be275f7a8d029969" + integrity sha512-CcEW6FfbBPLS/SSlR7vPs6OQjY0V9IbRwW2mBc02mA5xnwwNWr+SaiK7yoC2g6geX65PgTdEnz6jjmH6h86CwA== + dependencies: + "@lexical/code" "0.2.1" + "@lexical/link" "0.2.1" + "@lexical/list" "0.2.1" + "@lexical/rich-text" "0.2.1" + "@lexical/text" "0.2.1" + "@lexical/utils" "0.2.1" + +"@lexical/offset@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/offset/-/offset-0.2.1.tgz#37eb0ff339e1c76cf39f1dfc210dc9a4ee9455f2" + integrity sha512-gHKc8cWtc2GuBeZKC7DtCgIo/eYGI0VZ8WghNNB38m0JxUXw2PSoJ2ma8ih+TLSZDiw1XEzYxs2f9Ai1YbGR3w== + +"@lexical/overflow@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/overflow/-/overflow-0.2.1.tgz#32e71dc79eaaeb292bc740de967efeab8c599233" + integrity sha512-WJdX/a6Jd6flZEbBnKpb6srsLjaE7H2kigu1t0CVphoMRGIomSgwvGv6VFWE6ttNyOmHbqVPefcfpQZN3eZFag== + +"@lexical/plain-text@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/plain-text/-/plain-text-0.2.1.tgz#687637a706585f984185eede724c26b941c00164" + integrity sha512-OP3GwRbjNSihrCF5D/TWL83YeNMuLI3y+Hk+Ezgl/Zc30uyCVSeKYn8zozu6cillVtSrOQDtv8D96AM0VytKnw== + +"@lexical/react@^0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/react/-/react-0.2.1.tgz#1c5931cb0afd6e75f757d701fad77d7979c14892" + integrity sha512-urCOmdvHh5sLgo+ctN5WzoHNTc5e9xoyoYH0gy8Dcw1OCQJoyxRibigcrXIlRhC/X7bwrY59PDpvnIDLLouqHg== + dependencies: + "@lexical/clipboard" "0.2.1" + "@lexical/code" "0.2.1" + "@lexical/dragon" "0.2.1" + "@lexical/hashtag" "0.2.1" + "@lexical/history" "0.2.1" + "@lexical/link" "0.2.1" + "@lexical/list" "0.2.1" + "@lexical/markdown" "0.2.1" + "@lexical/overflow" "0.2.1" + "@lexical/plain-text" "0.2.1" + "@lexical/rich-text" "0.2.1" + "@lexical/selection" "0.2.1" + "@lexical/table" "0.2.1" + "@lexical/text" "0.2.1" + "@lexical/utils" "0.2.1" + "@lexical/yjs" "0.2.1" + +"@lexical/rich-text@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/rich-text/-/rich-text-0.2.1.tgz#302bb167d9fb8e8de9de3eeb70a3822ee4cf3537" + integrity sha512-YJYnm6LEeiduRb+nNrrKj2g4nKtFTd3v0s0MiJB+/8id5OY0VQWWIUZym1g/cfG1obXOMSGEsqgUR+l/14TjOA== + +"@lexical/selection@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/selection/-/selection-0.2.1.tgz#1a5d442060d40ba14e9196082c70aae5bfc8adac" + integrity sha512-WsulHJV8lQRCDb4oTWlPLF2/C0SLc38h+5WHag85A9/xFwLOpl8xxKYZkhbAmda9jUsr7h2/SY/1FS4xTxk0Dg== + +"@lexical/table@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/table/-/table-0.2.1.tgz#04d828c47b6f35378bfb4b9c9c7d9eb18f0acd63" + integrity sha512-fLwaZQBwkNWTMcMUf7xdBTAv/iwsRhGlqHqXPGELf7nr31H/RcWis7VMtFFFC6bXm2l8AaqwHk9tmGKlzlspnA== + dependencies: + "@lexical/utils" "0.2.1" + +"@lexical/text@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/text/-/text-0.2.1.tgz#6c7115d98f1b18d1f3aed61f0282c83977e8ba4d" + integrity sha512-5x3TvlXw7HJjHLbgYcwkJPc9TVs755BhGH8k9C6lFFUlk28wEzSRBFkna9CchrVqF52jDjJKLu6+hkIGNdsYbg== + +"@lexical/utils@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/utils/-/utils-0.2.1.tgz#b7aefa1e2d947df6a73e01378528c759cdabe710" + integrity sha512-P1O0uN7U3vVTk4pqaobP+MF9RkXtSCseIEyLZMgBgoPAS+TcelXeVgbhTjP2H67yss9cNRlCseqF3/anexPCVg== + dependencies: + "@lexical/list" "0.2.1" + "@lexical/table" "0.2.1" + +"@lexical/yjs@0.2.1": + version "0.2.1" + resolved "https://registry.yarnpkg.com/@lexical/yjs/-/yjs-0.2.1.tgz#5a8ebff3ff21e84fc4f9642ad4ff6a7311c93748" + integrity sha512-aRV4W68PVjVF/FdcvokN+6bZoql6dsy9BTwopcggpc2wqLTCQn0cd8knZ81zmR00D+1achxnCVpoQzRdik6cJA== + dependencies: + "@lexical/offset" "0.2.1" + "@mdx-js/loader@^1.6.22": version "1.6.22" resolved "https://registry.npmjs.org/@mdx-js/loader/-/loader-1.6.22.tgz" @@ -2268,6 +2403,14 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-slot" "0.1.2" +"@radix-ui/react-separator@^0.1.4": + version "0.1.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-0.1.4.tgz#383ad0f82b364d9982a978d752084af3598e4090" + integrity sha512-ct2qE072ydvMyFiPwgO8xGE4iLGR9BsSk2tIan23TYSiNTeQ89anzkuax5vNGjey0el2vnwUcReFS0Gy7Lk2VQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "0.1.4" + "@radix-ui/react-slot@0.1.2": version "0.1.2" resolved "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-0.1.2.tgz" @@ -9750,6 +9893,11 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lexical@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/lexical/-/lexical-0.2.1.tgz#da32570ed50c27f0cfa7ed0724e0fe2692b56b3f" + integrity sha512-6GwyP+KByy6aMuS1Ri1fFD/7NHENnhEFutdT+4SGIZCau0RkzlUYz0izA7FPQHEn663w23h0op477UHvjzrJHQ== + lilconfig@^2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz" @@ -11651,6 +11799,11 @@ prismjs@^1.21.0, prismjs@~1.25.0: resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.25.0.tgz" integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== +prismjs@^1.27.0: + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== + process-nextick-args@~2.0.0: version "2.0.1" resolved "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz"