Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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 (
<div className={"w-full h-12 bg-gray-50 border-b-2 border-gray px-5 py-2 flex items-center gap-1"}>

{/* Text style */}

<BoldToolbarTab t={toolbarTheme} e={editor}/>
<ItalicToolbarTab t={toolbarTheme} e={editor}/>
<UnderlineToolbarTab t={toolbarTheme} e={editor}/>

<Separator.Root decorative orientation="vertical" className={'mx-2 w-0.5 h-full bg-gray-500'} />

{/* Text direction */}

<AlignLeftToolbarTab t={toolbarTheme} e={editor}/>
<AlignCenterToolbarTab t={toolbarTheme} e={editor}/>
<AlignRightToolbarTab t={toolbarTheme} e={editor}/>
<AlignJustifyToolbarTab t={toolbarTheme} e={editor}/>

</div>
)
}
Original file line number Diff line number Diff line change
@@ -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 <button
className={clsx('h-8 w-8 flex items-center justify-center text-center text-gray-700 rounded hover:bg-gray-300', isActive ? 'bg-gray-300' : 'bg-white')}
onClick={dispatcher}
aria-label={label}
>
<FontAwesomeIcon icon={fontAwesomeIcon}/>
</button>
}
Original file line number Diff line number Diff line change
@@ -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
]

}
94 changes: 80 additions & 14 deletions components/shared/extendedForms/CustomEditor/index.tsx
Original file line number Diff line number Diff line change
@@ -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<CustomEditorProps> = ({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<CustomEditorProps> = ({
blocks = {},
readonly = false,
...reactEditorOptions
}: CustomEditorProps) => {

const ReactEditorJS: any = createReactEditorJS()
// const ReactEditorJS: any = createReactEditorJS()

const initialConfig = {
theme,
onError,
nodes: [
HeadingNode,
QuoteNode,
]
};

return (
<div className={"text-left bg-white custom-editor pt-3"}>
<Twemoji options={{ className: 'twemoji' }} >
<ReactEditorJS defaultValue={blocks} tools={EDITOR_JS_TOOLS} readOnly={readonly}
placeholder={"Que souhaites-tu raconter ?"} {...reactEditorOptions} />
</Twemoji>
</div>
)
<LexicalComposer initialConfig={initialConfig}>
<div className={'bg-white rounded-2xl overflow-hidden'}>

{/* Toolbar */}
<CustomEditorToolbar />

<div className={'no-child-outline relative p-3 h-64'}>

<LexicalRichTextPlugin
contentEditable={<LexicalContentEditable />}
placeholder={<div
className={'top-3 left-3 pointer-events-none absolute overflow-hidden text-gray-400'}>Enter
some text...</div>}
/>
{/*<LexicalOnChangePlugin/>*/}
<HistoryPlugin/>
</div>
</div>

</LexicalComposer>
);


// return (
// <div className={"text-left bg-white custom-editor pt-3"}>
// <Twemoji options={{ className: 'twemoji' }} >
// <ReactEditorJS defaultValue={blocks} tools={EDITOR_JS_TOOLS} readOnly={readonly}
// placeholder={"Que souhaites-tu raconter ?"} {...reactEditorOptions} />
// </Twemoji>
// </div>
// )
}

export default CustomEditor;
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
28 changes: 28 additions & 0 deletions styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Loading