{
@@ -230,14 +235,17 @@ function TextFormatFloatingToolbar({
aria-label="Insert code block"
icon={}
/> */}
- { configuration.toolbar.showInsertLink && <>
- } />
- > }
+ {configuration.toolbar.showInsertLink && (
+ <>
+ }
+ />
+ >
+ )}
>
)}
@@ -282,13 +290,13 @@ function useFloatingTextFormatToolbar(editor, anchorElem, configuration) {
const node = getSelectedNode(selection);
// Update text format
- setIsBold(selection.hasFormat('bold'));
- setIsItalic(selection.hasFormat('italic'));
- setIsUnderline(selection.hasFormat('underline'));
- setIsStrikethrough(selection.hasFormat('strikethrough'));
- setIsSubscript(selection.hasFormat('subscript'));
- setIsSuperscript(selection.hasFormat('superscript'));
- setIsCode(selection.hasFormat('code'));
+ setIsBold(selection.hasFormat("bold"));
+ setIsItalic(selection.hasFormat("italic"));
+ setIsUnderline(selection.hasFormat("underline"));
+ setIsStrikethrough(selection.hasFormat("strikethrough"));
+ setIsSubscript(selection.hasFormat("subscript"));
+ setIsSuperscript(selection.hasFormat("superscript"));
+ setIsCode(selection.hasFormat("code"));
// Update links
const parent = node.getParent();
@@ -300,15 +308,15 @@ function useFloatingTextFormatToolbar(editor, anchorElem, configuration) {
if (
!$isCodeHighlightNode(selection.anchor.getNode()) &&
- selection.getTextContent() !== ''
+ selection.getTextContent() !== ""
) {
setIsText($isTextNode(node) || $isParagraphNode(node));
} else {
setIsText(false);
}
- const rawTextContent = selection.getTextContent().replace(/\n/g, '');
- if (!selection.isCollapsed() && rawTextContent === '') {
+ const rawTextContent = selection.getTextContent().replace(/\n/g, "");
+ if (!selection.isCollapsed() && rawTextContent === "") {
setIsText(false);
return;
}
@@ -316,9 +324,9 @@ function useFloatingTextFormatToolbar(editor, anchorElem, configuration) {
}, [editor]);
useEffect(() => {
- document.addEventListener('selectionchange', updatePopup);
+ document.addEventListener("selectionchange", updatePopup);
return () => {
- document.removeEventListener('selectionchange', updatePopup);
+ document.removeEventListener("selectionchange", updatePopup);
};
}, [updatePopup]);
@@ -331,7 +339,7 @@ function useFloatingTextFormatToolbar(editor, anchorElem, configuration) {
if (editor.getRootElement() === null) {
setIsText(false);
}
- }),
+ })
);
}, [editor, updatePopup]);
@@ -353,13 +361,13 @@ function useFloatingTextFormatToolbar(editor, anchorElem, configuration) {
isUnderline={isUnderline}
isCode={isCode}
/>,
- anchorElem,
+ anchorElem
);
}
export default function FloatingTextFormatToolbarPlugin({
anchorElem = document.body,
- configuration
+ configuration,
}) {
const [editor] = useLexicalComposerContext();
return useFloatingTextFormatToolbar(editor, anchorElem, configuration);
diff --git a/src/plugins/controlledValuePlugin.js b/src/plugins/controlledValuePlugin.js
index e0ce28b..c9a2cbc 100644
--- a/src/plugins/controlledValuePlugin.js
+++ b/src/plugins/controlledValuePlugin.js
@@ -3,20 +3,27 @@ import React, { useEffect } from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
-import { $convertFromMarkdownString, $convertToMarkdownString, TRANSFORMERS } from "@lexical/markdown";
+import {
+ $convertFromMarkdownString,
+ $convertToMarkdownString,
+ TRANSFORMERS,
+} from "@lexical/markdown";
-export const ControlledValuePlugin = ({ value, onChange, isRichtext, format }) => {
+export const ControlledValuePlugin = ({
+ value,
+ onChange,
+ isRichtext,
+ format,
+}) => {
useAdoptPlaintextValue(value, isRichtext, format);
const handleChange = (editorState, editor) => {
editorState.read(() => {
if (format === "markdown") {
- editor.update(() => {
- const markdown = $convertToMarkdownString(TRANSFORMERS);
- if (onChange) {
- onChange(markdown);
- }
- });
+ const markdown = $convertToMarkdownString(TRANSFORMERS);
+ if (onChange) {
+ onChange(markdown);
+ }
} else {
const editorState = editor.getEditorState();
const json = editorState.toJSON();
@@ -34,22 +41,26 @@ export const useAdoptPlaintextValue = (value, isRichText, format) => {
const [editor] = useLexicalComposerContext();
useEffect(() => {
- if (value)
- {
- if (isRichText)
- {
+ if (value) {
+ if (isRichText) {
editor.update(() => {
if (format === "markdown") {
$convertFromMarkdownString(value, TRANSFORMERS);
} else {
- const editorState = editor.parseEditorState(value)
+ const editorState = editor.parseEditorState(value);
editor.setEditorState(editorState);
}
});
- }
- else {
- // TODO: implement plain text updates
- }
+ } else {
+ editor.update(() => {
+ const root = $getRoot();
+ const selection = $getSelection();
+ const paragraphNode = $createParagraphNode();
+ const textNode = $createTextNode(value);
+ paragraphNode.append(textNode);
+ root.append(paragraphNode);
+ });
+ }
}
}, [editor, value]);
};
diff --git a/src/plugins/draggableBlockPlugin/index.css b/src/plugins/draggableBlockPlugin/index.css
index 6f8cdd5..712e4ef 100644
--- a/src/plugins/draggableBlockPlugin/index.css
+++ b/src/plugins/draggableBlockPlugin/index.css
@@ -9,10 +9,16 @@
will-change: transform;
}
+.rtl .draggable-block-menu {
+ left: auto;
+ right: 0;
+}
+
.draggable-block-menu .icon {
width: 16px;
height: 16px;
opacity: 0.3;
+ background-image: url('data:image/svg+xml;utf8,');
}
.draggable-block-menu:active {
@@ -33,3 +39,9 @@
opacity: 0;
will-change: transform;
}
+
+
+.rtl .draggable-block-target-line {
+ left: auto;
+ right: 0;
+}
diff --git a/src/plugins/draggableBlockPlugin/index.js b/src/plugins/draggableBlockPlugin/index.js
index c61ae85..d0b77de 100644
--- a/src/plugins/draggableBlockPlugin/index.js
+++ b/src/plugins/draggableBlockPlugin/index.js
@@ -1,8 +1,8 @@
-import './index.css';
+import "./index.css";
-import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
-import {eventFiles} from '@lexical/rich-text';
-import {mergeRegister} from '@lexical/utils';
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import { eventFiles } from "@lexical/rich-text";
+import { mergeRegister } from "@lexical/utils";
import {
$getNearestNodeFromDOMNode,
$getNodeByKey,
@@ -11,20 +11,20 @@ import {
COMMAND_PRIORITY_LOW,
DRAGOVER_COMMAND,
DROP_COMMAND,
-} from 'lexical';
-import * as React from 'react';
-import {useEffect, useRef, useState} from 'react';
-import {createPortal} from 'react-dom';
+} from "lexical";
+import * as React from "react";
+import { useEffect, useRef, useState } from "react";
+import { createPortal } from "react-dom";
-import {isHTMLElement} from '../../utils/guard';
-import {Point} from '../../utils/point';
-import {Rect} from '../../utils/rect';
-import Icons from '../../icons';
+import { isHTMLElement } from "../../utils/guard";
+import { Point } from "../../utils/point";
+import { Rect } from "../../utils/rect";
+import Icons from "../../icons";
const SPACE = 4;
const TARGET_LINE_HALF_HEIGHT = 2;
-const DRAGGABLE_BLOCK_MENU_CLASSNAME = 'draggable-block-menu';
-const DRAG_DATA_FORMAT = 'application/x-lexical-drag-block';
+const DRAGGABLE_BLOCK_MENU_CLASSNAME = "draggable-block-menu";
+const DRAG_DATA_FORMAT = "application/x-lexical-drag-block";
const TEXT_BOX_HORIZONTAL_PADDING = 28;
const Downward = 1;
@@ -49,36 +49,31 @@ function getTopLevelNodeKeys(editor) {
}
function getCollapsedMargins(elem) {
- const getMargin = ( element, margin) =>
+ const getMargin = (element, margin) =>
element ? parseFloat(window.getComputedStyle(element)[margin]) : 0;
- const {marginTop, marginBottom} = window.getComputedStyle(elem);
+ const { marginTop, marginBottom } = window.getComputedStyle(elem);
const prevElemSiblingMarginBottom = getMargin(
elem.previousElementSibling,
- 'marginBottom',
+ "marginBottom"
);
const nextElemSiblingMarginTop = getMargin(
elem.nextElementSibling,
- 'marginTop',
+ "marginTop"
);
const collapsedTopMargin = Math.max(
parseFloat(marginTop),
- prevElemSiblingMarginBottom,
+ prevElemSiblingMarginBottom
);
const collapsedBottomMargin = Math.max(
parseFloat(marginBottom),
- nextElemSiblingMarginTop,
+ nextElemSiblingMarginTop
);
- return {marginBottom: collapsedBottomMargin, marginTop: collapsedTopMargin};
+ return { marginBottom: collapsedBottomMargin, marginTop: collapsedTopMargin };
}
-function getBlockElement(
- anchorElem,
- editor,
- event,
- useEdgeAsDefault = false,
-) {
+function getBlockElement(anchorElem, editor, event, useEdgeAsDefault = false) {
const anchorElementRect = anchorElem.getBoundingClientRect();
const topLevelNodeKeys = getTopLevelNodeKeys(editor);
@@ -120,7 +115,7 @@ function getBlockElement(
}
const point = new Point(event.x, event.y);
const domRect = Rect.fromDOM(elem);
- const {marginTop, marginBottom} = getCollapsedMargins(elem);
+ const { marginTop, marginBottom } = getCollapsedMargins(elem);
const rect = domRect.generateNewRect({
bottom: domRect.bottom + marginBottom,
@@ -131,7 +126,7 @@ function getBlockElement(
const {
result,
- reason: {isOnTopSide, isOnBottomSide},
+ reason: { isOnTopSide, isOnBottomSide },
} = rect.contains(point);
if (result) {
@@ -162,14 +157,14 @@ function isOnMenu(element) {
return !!element.closest(`.${DRAGGABLE_BLOCK_MENU_CLASSNAME}`);
}
-function setMenuPosition(
- targetElem,
- floatingElem,
- anchorElem,
-) {
+function setMenuPosition(targetElem, floatingElem, anchorElem, isRtl) {
if (!targetElem) {
- floatingElem.style.opacity = '0';
- floatingElem.style.transform = 'translate(-10000px, -10000px)';
+ floatingElem.style.opacity = "0";
+ if (isRtl) {
+ floatingElem.style.transform = "translate(10000px, -10000px)";
+ } else {
+ floatingElem.style.transform = "translate(-10000px, -10000px)";
+ }
return;
}
@@ -185,15 +180,15 @@ function setMenuPosition(
const left = SPACE;
- floatingElem.style.opacity = '1';
+ floatingElem.style.opacity = "1";
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
}
function setDragImage(dataTransfer, draggableBlockElem) {
- const {transform} = draggableBlockElem.style;
+ const { transform } = draggableBlockElem.style;
// Remove dragImage borders
- draggableBlockElem.style.transform = 'translateZ(0)';
+ draggableBlockElem.style.transform = "translateZ(0)";
dataTransfer.setDragImage(draggableBlockElem, 0, 0);
setTimeout(() => {
@@ -201,13 +196,13 @@ function setDragImage(dataTransfer, draggableBlockElem) {
});
}
-function setTargetLine( targetLineElem, targetBlockElem, mouseY, anchorElem ) {
- const {top: targetBlockElemTop, height: targetBlockElemHeight} =
+function setTargetLine(targetLineElem, targetBlockElem, mouseY, anchorElem) {
+ const { top: targetBlockElemTop, height: targetBlockElemHeight } =
targetBlockElem.getBoundingClientRect();
- const {top: anchorTop, width: anchorWidth} =
+ const { top: anchorTop, width: anchorWidth } =
anchorElem.getBoundingClientRect();
- const {marginTop, marginBottom} = getCollapsedMargins(targetBlockElem);
+ const { marginTop, marginBottom } = getCollapsedMargins(targetBlockElem);
let lineTop = targetBlockElemTop;
if (mouseY >= targetBlockElemTop) {
lineTop += targetBlockElemHeight + marginBottom / 2;
@@ -222,17 +217,21 @@ function setTargetLine( targetLineElem, targetBlockElem, mouseY, anchorElem ) {
targetLineElem.style.width = `${
anchorWidth - (TEXT_BOX_HORIZONTAL_PADDING - SPACE) * 2
}px`;
- targetLineElem.style.opacity = '.4';
+ targetLineElem.style.opacity = ".4";
}
-function hideTargetLine(targetLineElem) {
+function hideTargetLine(targetLineElem, isRtl) {
if (targetLineElem) {
- targetLineElem.style.opacity = '0';
- targetLineElem.style.transform = 'translate(-10000px, -10000px)';
+ targetLineElem.style.opacity = "0";
+ if (isRtl) {
+ targetLineElem.style.transform = "translate(10000px, -10000px)";
+ } else {
+ targetLineElem.style.transform = "translate(-10000px, -10000px)";
+ }
}
}
-function useDraggableBlockMenu(editor, anchorElem, isEditable) {
+function useDraggableBlockMenu(editor, anchorElem, isEditable, isRtl) {
const scrollerElem = anchorElem.parentElement;
const menuRef = useRef(null);
@@ -261,18 +260,18 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
setDraggableBlockElem(null);
}
- scrollerElem?.addEventListener('mousemove', onMouseMove);
- scrollerElem?.addEventListener('mouseleave', onMouseLeave);
+ scrollerElem?.addEventListener("mousemove", onMouseMove);
+ scrollerElem?.addEventListener("mouseleave", onMouseLeave);
return () => {
- scrollerElem?.removeEventListener('mousemove', onMouseMove);
- scrollerElem?.removeEventListener('mouseleave', onMouseLeave);
+ scrollerElem?.removeEventListener("mousemove", onMouseMove);
+ scrollerElem?.removeEventListener("mouseleave", onMouseLeave);
};
}, [scrollerElem, anchorElem, editor]);
useEffect(() => {
if (menuRef.current) {
- setMenuPosition(draggableBlockElem, menuRef.current, anchorElem);
+ setMenuPosition(draggableBlockElem, menuRef.current, anchorElem, isRtl);
}
}, [anchorElem, draggableBlockElem]);
@@ -285,7 +284,7 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
if (isFileTransfer) {
return false;
}
- const {pageY, target} = event;
+ const { pageY, target } = event;
if (!isHTMLElement(target)) {
return false;
}
@@ -308,8 +307,8 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
if (isFileTransfer) {
return false;
}
- const {target, dataTransfer, pageY} = event;
- const dragData = dataTransfer?.getData(DRAG_DATA_FORMAT) || '';
+ const { target, dataTransfer, pageY } = event;
+ const dragData = dataTransfer?.getData(DRAG_DATA_FORMAT) || "";
const draggedNode = $getNodeByKey(dragData);
if (!draggedNode) {
return false;
@@ -345,15 +344,15 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
(event) => {
return onDragover(event);
},
- COMMAND_PRIORITY_LOW,
+ COMMAND_PRIORITY_LOW
),
editor.registerCommand(
DROP_COMMAND,
(event) => {
return onDrop(event);
},
- COMMAND_PRIORITY_HIGH,
- ),
+ COMMAND_PRIORITY_HIGH
+ )
);
}, [anchorElem, editor]);
@@ -363,7 +362,7 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
return;
}
setDragImage(dataTransfer, draggableBlockElem);
- let nodeKey = '';
+ let nodeKey = "";
editor.update(() => {
const node = $getNearestNodeFromDOMNode(draggableBlockElem);
if (node) {
@@ -376,7 +375,7 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
function onDragEnd() {
isDraggingBlockRef.current = false;
- hideTargetLine(targetLineRef.current);
+ hideTargetLine(targetLineRef.current, isRtl);
}
return createPortal(
@@ -386,20 +385,20 @@ function useDraggableBlockMenu(editor, anchorElem, isEditable) {
ref={menuRef}
draggable={true}
onDragStart={onDragStart}
- onDragEnd={onDragEnd}>
-
-
-
+ onDragEnd={onDragEnd}
+ >
+
>,
- anchorElem,
+ anchorElem
);
}
export default function DraggableBlockPlugin({
anchorElem = document.body,
+ isRtl,
}) {
const [editor] = useLexicalComposerContext();
- return useDraggableBlockMenu(editor, anchorElem, editor._editable);
+ return useDraggableBlockMenu(editor, anchorElem, editor._editable, isRtl);
}
diff --git a/src/plugins/floatingLinkEditorPlugin/index.js b/src/plugins/floatingLinkEditorPlugin/index.js
index bc24c9b..fb04146 100644
--- a/src/plugins/floatingLinkEditorPlugin/index.js
+++ b/src/plugins/floatingLinkEditorPlugin/index.js
@@ -1,12 +1,12 @@
-import './index.css';
+import "./index.css";
import {
$createLinkNode,
$isAutoLinkNode,
$isLinkNode,
TOGGLE_LINK_COMMAND,
-} from '@lexical/link';
-import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
-import {$findMatchingParent, mergeRegister} from '@lexical/utils';
+} from "@lexical/link";
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import { $findMatchingParent, mergeRegister } from "@lexical/utils";
import {
$getSelection,
$isLineBreakNode,
@@ -17,16 +17,16 @@ import {
COMMAND_PRIORITY_LOW,
KEY_ESCAPE_COMMAND,
SELECTION_CHANGE_COMMAND,
-} from 'lexical';
-import {useCallback, useEffect, useRef, useState} from 'react';
-import * as React from 'react';
-import {createPortal} from 'react-dom';
+} from "lexical";
+import { useCallback, useEffect, useRef, useState } from "react";
+import * as React from "react";
+import { createPortal } from "react-dom";
-import {getSelectedNode} from '../../utils/getSelectedNode';
-import {setFloatingElemPositionForLinkEditor} from '../../utils/setFloatingElemPositionForLinkEditor';
-import {sanitizeUrl} from '../../utils/url';
-import { Button, Input } from 'antd';
-import Icons from '../../icons';
+import { getSelectedNode } from "../../utils/getSelectedNode";
+import { setFloatingElemPositionForLinkEditor } from "../../utils/setFloatingElemPositionForLinkEditor";
+import { sanitizeUrl } from "../../utils/url";
+import { Button, Input } from "antd";
+import Icons from "../../icons";
function FloatingLinkEditor({
editor,
@@ -35,14 +35,13 @@ function FloatingLinkEditor({
anchorElem,
isLinkEditMode,
setIsLinkEditMode,
+ isRtl,
}) {
const editorRef = useRef(null);
const inputRef = useRef(null);
- const [linkUrl, setLinkUrl] = useState('');
- const [editedLinkUrl, setEditedLinkUrl] = useState('https://');
- const [lastSelection, setLastSelection] = useState(
- null,
- );
+ const [linkUrl, setLinkUrl] = useState("");
+ const [editedLinkUrl, setEditedLinkUrl] = useState("https://");
+ const [lastSelection, setLastSelection] = useState(null);
const updateLinkEditor = useCallback(() => {
const selection = $getSelection();
@@ -55,7 +54,7 @@ function FloatingLinkEditor({
} else if ($isLinkNode(node)) {
setLinkUrl(node.getURL());
} else {
- setLinkUrl('');
+ setLinkUrl("");
}
if (isLinkEditMode) {
setEditedLinkUrl(linkUrl);
@@ -78,19 +77,30 @@ function FloatingLinkEditor({
rootElement.contains(nativeSelection.anchorNode) &&
editor.isEditable()
) {
- const domRect = nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
+ const domRect =
+ nativeSelection.focusNode?.parentElement?.getBoundingClientRect();
if (domRect) {
domRect.y += 40;
- setFloatingElemPositionForLinkEditor(domRect, editorElem, anchorElem);
+ setFloatingElemPositionForLinkEditor(
+ domRect,
+ editorElem,
+ anchorElem,
+ isRtl
+ );
}
setLastSelection(selection);
- } else if (!activeElement || activeElement.className !== 'link-input') {
+ } else if (!activeElement || activeElement.className !== "link-input") {
if (rootElement !== null) {
- setFloatingElemPositionForLinkEditor(null, editorElem, anchorElem);
+ setFloatingElemPositionForLinkEditor(
+ null,
+ editorElem,
+ anchorElem,
+ isRtl
+ );
}
setLastSelection(null);
setIsLinkEditMode(false);
- setLinkUrl('');
+ setLinkUrl("");
}
return true;
@@ -105,24 +115,24 @@ function FloatingLinkEditor({
});
};
- window.addEventListener('resize', update);
+ window.addEventListener("resize", update);
if (scrollerElem) {
- scrollerElem.addEventListener('scroll', update);
+ scrollerElem.addEventListener("scroll", update);
}
return () => {
- window.removeEventListener('resize', update);
+ window.removeEventListener("resize", update);
if (scrollerElem) {
- scrollerElem.removeEventListener('scroll', update);
+ scrollerElem.removeEventListener("scroll", update);
}
};
}, [anchorElem.parentElement, editor, updateLinkEditor]);
useEffect(() => {
return mergeRegister(
- editor.registerUpdateListener(({editorState}) => {
+ editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
updateLinkEditor();
});
@@ -134,7 +144,7 @@ function FloatingLinkEditor({
updateLinkEditor();
return true;
},
- COMMAND_PRIORITY_LOW,
+ COMMAND_PRIORITY_LOW
),
editor.registerCommand(
KEY_ESCAPE_COMMAND,
@@ -145,8 +155,8 @@ function FloatingLinkEditor({
}
return false;
},
- COMMAND_PRIORITY_HIGH,
- ),
+ COMMAND_PRIORITY_HIGH
+ )
);
}, [editor, updateLinkEditor, setIsLink, isLink]);
@@ -163,10 +173,10 @@ function FloatingLinkEditor({
}, [isLinkEditMode, isLink]);
const monitorInputInteraction = (event) => {
- if (event.key === 'Enter') {
+ if (event.key === "Enter") {
event.preventDefault();
handleLinkSubmission();
- } else if (event.key === 'Escape') {
+ } else if (event.key === "Escape") {
event.preventDefault();
setIsLinkEditMode(false);
}
@@ -174,7 +184,7 @@ function FloatingLinkEditor({
const handleLinkSubmission = () => {
if (lastSelection !== null) {
- if (linkUrl !== '') {
+ if (linkUrl !== "") {
editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));
editor.update(() => {
const selection = $getSelection();
@@ -200,16 +210,26 @@ function FloatingLinkEditor({
{!isLink ? null : isLinkEditMode ? (
-
) : (
-
- {
- setEditedLinkUrl(linkUrl);
- setIsLinkEditMode(true);
- }}
- onMouseDown={(event) => event.preventDefault()}
- icon={}/>
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)}
- onMouseDown={(event) => event.preventDefault()}
- icon={}/>
- >}
+
+ {
+ setEditedLinkUrl(linkUrl);
+ setIsLinkEditMode(true);
+ }}
+ onMouseDown={(event) => event.preventDefault()}
+ icon={}
+ />
+
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, null)
+ }
+ onMouseDown={(event) => event.preventDefault()}
+ icon={}
+ />
+ >
+ }
ref={inputRef}
value={editedLinkUrl}
onChange={(event) => {
@@ -242,9 +276,9 @@ function FloatingLinkEditor({
}}
onKeyDown={(event) => {
monitorInputInteraction(event);
- }} />
-
-
+ }}
+ />
+
)}
);
@@ -255,6 +289,7 @@ function useFloatingLinkEditorToolbar(
anchorElem,
isLinkEditMode,
setIsLinkEditMode,
+ isRtl
) {
const [activeEditor, setActiveEditor] = useState(editor);
const [isLink, setIsLink] = useState(false);
@@ -267,7 +302,7 @@ function useFloatingLinkEditorToolbar(
const focusLinkNode = $findMatchingParent(focusNode, $isLinkNode);
const focusAutoLinkNode = $findMatchingParent(
focusNode,
- $isAutoLinkNode,
+ $isAutoLinkNode
);
if (!(focusLinkNode || focusAutoLinkNode)) {
setIsLink(false);
@@ -294,7 +329,7 @@ function useFloatingLinkEditorToolbar(
}
}
return mergeRegister(
- editor.registerUpdateListener(({editorState}) => {
+ editor.registerUpdateListener(({ editorState }) => {
editorState.read(() => {
updateToolbar();
});
@@ -306,7 +341,7 @@ function useFloatingLinkEditorToolbar(
setActiveEditor(newEditor);
return false;
},
- COMMAND_PRIORITY_CRITICAL,
+ COMMAND_PRIORITY_CRITICAL
),
editor.registerCommand(
CLICK_COMMAND,
@@ -316,14 +351,14 @@ function useFloatingLinkEditorToolbar(
const node = getSelectedNode(selection);
const linkNode = $findMatchingParent(node, $isLinkNode);
if ($isLinkNode(linkNode) && (payload.metaKey || payload.ctrlKey)) {
- window.open(linkNode.getURL(), '_blank');
+ window.open(linkNode.getURL(), "_blank");
return true;
}
}
return false;
},
- COMMAND_PRIORITY_LOW,
- ),
+ COMMAND_PRIORITY_LOW
+ )
);
}, [editor]);
@@ -335,8 +370,9 @@ function useFloatingLinkEditorToolbar(
setIsLink={setIsLink}
isLinkEditMode={isLinkEditMode}
setIsLinkEditMode={setIsLinkEditMode}
+ isRtl={isRtl}
/>,
- anchorElem,
+ anchorElem
);
}
@@ -344,6 +380,7 @@ export default function FloatingLinkEditorPlugin({
anchorElem = document.body,
isLinkEditMode,
setIsLinkEditMode,
+ isRtl,
}) {
const [editor] = useLexicalComposerContext();
return useFloatingLinkEditorToolbar(
@@ -351,5 +388,6 @@ export default function FloatingLinkEditorPlugin({
anchorElem,
isLinkEditMode,
setIsLinkEditMode,
+ isRtl
);
}
diff --git a/src/plugins/savePlugin.js b/src/plugins/savePlugin.js
index e542cb3..3bf898d 100644
--- a/src/plugins/savePlugin.js
+++ b/src/plugins/savePlugin.js
@@ -1,21 +1,19 @@
-import {useEffect, useCallback} from 'react';
+import { useEffect, useCallback } from "react";
// ------------------------------------------------------
-import { $convertToMarkdownString, TRANSFORMERS } from '@lexical/markdown';
-import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
-import {
- COMMAND_PRIORITY_LOW,
-} from 'lexical';
+import { $convertToMarkdownString, TRANSFORMERS } from "@lexical/markdown";
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import { COMMAND_PRIORITY_LOW } from "lexical";
// ------------------------------------------------------
-import { SAVE_COMMAND } from '../commands/saveCommand';
+import { SAVE_COMMAND } from "../commands/saveCommand";
// ------------------------------------------------------
function SavePlugin({ format, onSave }) {
const [editor] = useLexicalComposerContext();
- const saveCallback = () => {
+ const saveCallback = useCallback(() => {
if (format === "markdown") {
editor.update(() => {
const markdown = $convertToMarkdownString(TRANSFORMERS);
- if (onSave) {
+ if (onSave) {
onSave(markdown);
}
});
@@ -26,16 +24,14 @@ function SavePlugin({ format, onSave }) {
onSave(json);
}
}
- };
+ }, [format, onSave]);
useEffect(() => {
- editor.registerCommand(
- SAVE_COMMAND,
- saveCallback,
- COMMAND_PRIORITY_LOW,
- );
-
- }, [editor]);
+ if (editor._commands.has(SAVE_COMMAND)) {
+ editor._commands.delete(SAVE_COMMAND);
+ }
+ editor.registerCommand(SAVE_COMMAND, saveCallback, COMMAND_PRIORITY_LOW);
+ }, [editor, format, saveCallback]);
return null;
}
diff --git a/src/plugins/toolbarPlugin/fontDropdown.js b/src/plugins/toolbarPlugin/fontDropdown.js
index 36fce79..e271d7d 100644
--- a/src/plugins/toolbarPlugin/fontDropdown.js
+++ b/src/plugins/toolbarPlugin/fontDropdown.js
@@ -1,6 +1,6 @@
-import React from 'react';
+import React from "react";
import { Button, Dropdown, Select, Space } from "antd";
-import Icons from '../../icons';
+import Icons from "../../icons";
// --------------------------------------------------
@@ -14,39 +14,44 @@ const FONT_FAMILY_OPTIONS = [
];
export const defaultFont = (configuration) => {
- var fonts = configuration?.toolbar?.fonts || FONT_FAMILY_OPTIONS;
- return configuration?.toolbar?.defaultFont
- ? fonts.find(x => x.value === configuration?.toolbar?.defaultFont) || fonts[0]
- : fonts[0];
-}
-
+ var fonts = configuration?.toolbar?.fonts ?? FONT_FAMILY_OPTIONS;
+ var retVal = configuration?.toolbar?.defaultFont
+ ? fonts.find((x) => x.value === configuration?.toolbar?.defaultFont) ||
+ fonts[0]
+ : fonts[0];
+ return retVal;
+};
// --------------------------------------------------
const FontDropDown = ({ fonts, value, onChange = () => {} }) => {
- const configuredFonts = (fonts && fonts.length > 0 ? fonts : FONT_FAMILY_OPTIONS);
- const selected = () => value && configuredFonts.find(x => x.value === value) || configuredFonts[0];
+ const configuredFonts =
+ fonts && fonts.length > 0 ? fonts : FONT_FAMILY_OPTIONS;
+ const selected = () =>
+ (value && configuredFonts.find((x) => x.value === value)) ||
+ configuredFonts[0];
const onFontSelect = (item) => {
onChange(item.key);
- }
- const items = configuredFonts
- .map(i => ({
+ };
+ const items = configuredFonts.map((i) => ({
key: i.value,
label: i.label,
}));
return (
-
+
- { selected().label }
+ {selected().label}
-
+
);
};
diff --git a/src/plugins/toolbarPlugin/index.css b/src/plugins/toolbarPlugin/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/plugins/toolbarPlugin/index.js b/src/plugins/toolbarPlugin/index.js
index e96055e..545b1c2 100644
--- a/src/plugins/toolbarPlugin/index.js
+++ b/src/plugins/toolbarPlugin/index.js
@@ -1,6 +1,6 @@
-import React from 'react';
+import React from "react";
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
-import useLexicalEditable from '@lexical/react/useLexicalEditable';
+import useLexicalEditable from "@lexical/react/useLexicalEditable";
import {
$getSelection,
$isRangeSelection,
@@ -16,15 +16,9 @@ import {
KEY_MODIFIER_COMMAND,
$getRoot,
} from "lexical";
-import {
- $isCodeNode,
- CODE_LANGUAGE_MAP,
-} from '@lexical/code';
-import {
- $isListNode,
- ListNode,
-} from '@lexical/list';
-import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link';
+import { $isCodeNode, CODE_LANGUAGE_MAP } from "@lexical/code";
+import { $isListNode, ListNode } from "@lexical/list";
+import { $isLinkNode, TOGGLE_LINK_COMMAND } from "@lexical/link";
import {
$getSelectionStyleValueForProperty,
$isParentElementRTL,
@@ -34,26 +28,27 @@ import {
$findMatchingParent,
$getNearestNodeOfType,
mergeRegister,
-} from '@lexical/utils';
-import {$isTableNode} from '@lexical/table';
-import { $isHeadingNode } from '@lexical/rich-text';
+} from "@lexical/utils";
+import { $isTableNode } from "@lexical/table";
+import { $isHeadingNode } from "@lexical/rich-text";
import { useCallback, useEffect, useState } from "react";
// -----------------------------------------------------------
import { Button, Divider, InputNumber, Tooltip } from "antd";
// -----------------------------------------------------------
-import { sanitizeUrl } from '../../utils/url';
-import { getSelectedNode } from '../../utils/getSelectedNode';
+import { sanitizeUrl } from "../../utils/url";
+import { getSelectedNode } from "../../utils/getSelectedNode";
import FontDropDown, { defaultFont } from "./fontDropdown";
-import BlockFormatDropDown, { blockTypeToBlockName } from './blockFormatDropDown';
+import BlockFormatDropDown, {
+ blockTypeToBlockName,
+} from "./blockFormatDropDown";
import InsertDropDown from "./insertDropDown";
import ToolsDropdown from "./toolsDropDown";
-import Icons from '../../icons'
+import Icons from "../../icons";
import CheckButton from "../../components/checkButton";
import AlignFormatDropDown from "./alignFormatDropDown";
-import styles from "../../styles.module.css";
-import { SAVE_COMMAND } from '../../commands/saveCommand';
+import { SAVE_COMMAND } from "../../commands/saveCommand";
// -----------------------------------------------------------
const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
@@ -67,28 +62,29 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
const [isSubscript, setIsSubscript] = useState(false);
const [isSuperscript, setIsSuperscript] = useState(false);
const [isCode, setIsCode] = useState(false);
- const [rootType, setRootType] = useState('root');
- const [blockType, setBlockType] = useState('paragraph');
+ const [rootType, setRootType] = useState("root");
+ const [blockType, setBlockType] = useState("paragraph");
const [selectedElementKey, setSelectedElementKey] = useState(null);
const [canUndo, setCanUndo] = useState(false);
const [canRedo, setCanRedo] = useState(false);
const [fontSize, setFontSize] = useState(15);
const [fontColor, setFontColor] = useState("#000");
const [bgColor, setBgColor] = useState("#fff");
- const [fontFamily, setFontFamily] = useState(configuration.toolbar.defaultFont);
- const [codeLanguage, setCodeLanguage] = useState('');
+ const [fontFamily, setFontFamily] = useState(
+ configuration.toolbar.defaultFont ?? defaultFont(configuration)
+ );
+ const [codeLanguage, setCodeLanguage] = useState("");
const [isLink, setIsLink] = useState(false);
const isEditable = useLexicalEditable();
- // TODO: Set Default Font
useEffect(() => {
editor.update(() => {
if (!editor || !configuration) return;
- $getRoot()?.getChildAtIndex(0)?.select();
+ $getRoot().select();
const selection = $getSelection();
if (selection) {
$patchStyleText(selection, {
- 'font-family': defaultFont(configuration?.toolbar?.fonts),
+ "font-family": fontFamily,
});
}
});
@@ -99,7 +95,7 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
if ($isRangeSelection(selection)) {
const anchorNode = selection.anchor.getNode();
let element =
- anchorNode.getKey() === 'root'
+ anchorNode.getKey() === "root"
? anchorNode
: $findMatchingParent(anchorNode, (e) => {
const parent = e.getParent();
@@ -114,13 +110,13 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
const elementDOM = activeEditor.getElementByKey(elementKey);
// Update text format
- setIsBold(selection.hasFormat('bold'));
- setIsItalic(selection.hasFormat('italic'));
- setIsUnderline(selection.hasFormat('underline'));
- setIsStrikethrough(selection.hasFormat('strikethrough'));
- setIsSubscript(selection.hasFormat('subscript'));
- setIsSuperscript(selection.hasFormat('superscript'));
- setIsCode(selection.hasFormat('code'));
+ setIsBold(selection.hasFormat("bold"));
+ setIsItalic(selection.hasFormat("italic"));
+ setIsUnderline(selection.hasFormat("underline"));
+ setIsStrikethrough(selection.hasFormat("strikethrough"));
+ setIsSubscript(selection.hasFormat("subscript"));
+ setIsSuperscript(selection.hasFormat("superscript"));
+ setIsCode(selection.hasFormat("code"));
setIsRTL($isParentElementRTL(selection));
// Update links
@@ -134,18 +130,15 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
const tableNode = $findMatchingParent(node, $isTableNode);
if ($isTableNode(tableNode)) {
- setRootType('table');
+ setRootType("table");
} else {
- setRootType('root');
+ setRootType("root");
}
if (elementDOM !== null) {
setSelectedElementKey(elementKey);
if ($isListNode(element)) {
- const parentList = $getNearestNodeOfType(
- anchorNode,
- ListNode,
- );
+ const parentList = $getNearestNodeOfType(anchorNode, ListNode);
const type = parentList
? parentList.getListType()
: element.getListType();
@@ -154,14 +147,13 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
const type = $isHeadingNode(element)
? element.getTag()
: element.getType();
- if (type in blockTypeToBlockName ) {
+ if (type in blockTypeToBlockName) {
setBlockType(type);
}
if ($isCodeNode(element)) {
- const language =
- element.getLanguage();
+ const language = element.getLanguage();
setCodeLanguage(
- language ? CODE_LANGUAGE_MAP[language] || language : '',
+ language ? CODE_LANGUAGE_MAP[language] || language : ""
);
return;
}
@@ -170,20 +162,26 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
// Handle buttons
setFontSize(
- parseInt($getSelectionStyleValueForProperty(selection, 'font-size', '15px').replace('px',''))
+ parseInt(
+ $getSelectionStyleValueForProperty(
+ selection,
+ "font-size",
+ "15px"
+ ).replace("px", "")
+ )
);
setFontColor(
- $getSelectionStyleValueForProperty(selection, 'color', '#000'),
+ $getSelectionStyleValueForProperty(selection, "color", "#000")
);
setBgColor(
$getSelectionStyleValueForProperty(
selection,
- 'background-color',
- '#fff',
- ),
+ "background-color",
+ "#fff"
+ )
);
setFontFamily(
- $getSelectionStyleValueForProperty(selection, 'font-family', defaultFont({ configuration }).value)
+ $getSelectionStyleValueForProperty(selection, "font-family", fontFamily)
);
}
}, [activeEditor]);
@@ -231,18 +229,18 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
KEY_MODIFIER_COMMAND,
(payload) => {
const event = payload;
- const {code, ctrlKey, metaKey} = event;
+ const { code, ctrlKey, metaKey } = event;
- if (code === 'KeyK' && (ctrlKey || metaKey)) {
+ if (code === "KeyK" && (ctrlKey || metaKey)) {
event.preventDefault();
return activeEditor.dispatchCommand(
TOGGLE_LINK_COMMAND,
- sanitizeUrl('https://'),
+ sanitizeUrl("https://")
);
}
return false;
},
- COMMAND_PRIORITY_NORMAL,
+ COMMAND_PRIORITY_NORMAL
);
}, [activeEditor, isLink]);
@@ -253,7 +251,7 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
$patchStyleText(selection, {
"font-family": font,
});
- }
+ }
});
};
@@ -271,7 +269,7 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
const insertLink = useCallback(() => {
if (!isLink) {
setIsLinkEditMode(true);
- editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl('https://'));
+ editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl("https://"));
} else {
setIsLinkEditMode(false);
editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);
@@ -279,59 +277,169 @@ const ToolbarPlugin = ({ configuration, setIsLinkEditMode, locale }) => {
}, [editor, isLink]);
return (
-
- { configuration.toolbar.showSave &&
- editor.dispatchCommand(SAVE_COMMAND) }
- icon={ } />
- }
- { configuration.toolbar.showUndoRedo && <>
-
- activeEditor.dispatchCommand(UNDO_COMMAND, undefined)} disabled={!canUndo}
- icon={ locale.isRtl ? : } />
-
-
- activeEditor.dispatchCommand(REDO_COMMAND, undefined)} disabled={!canRedo}
- icon={locale.isRtl ? : } />
-
-
> }
- { configuration.toolbar.showBlockFormat && <>
-
+ {configuration.toolbar.showSave && (
+
+ editor.dispatchCommand(SAVE_COMMAND)}
+ icon={}
+ />
+
+ )}
+ {configuration.toolbar.showUndoRedo && (
+ <>
+
+
+ activeEditor.dispatchCommand(UNDO_COMMAND, undefined)
+ }
+ disabled={!canUndo}
+ icon={locale.isRtl ? : }
+ />
+
+
+
+ activeEditor.dispatchCommand(REDO_COMMAND, undefined)
+ }
+ disabled={!canRedo}
+ icon={locale.isRtl ? : }
+ />
+
+
+ >
+ )}
+ {configuration.toolbar.showBlockFormat && (
+ <>
+
+
+ >
+ )}
+
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")
+ }
+ icon={}
+ />
+
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic")
+ }
+ icon={}
+ />
+
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline")
+ }
+ icon={}
+ />
+ {configuration.toolbar.showExtraFormat && (
+ <>
+
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")
+ }
+ icon={}
+ />
+
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "subscript")
+ }
+ icon={}
+ />
+
+ activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "superscript")
+ }
+ icon={}
+ />
+ >
+ )}
+ {configuration.toolbar.showInsertLink && (
+ }
/>
-
- > }
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "bold")} icon={} />
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "italic")} icon={} />
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "underline")} icon={} />
- { configuration.toolbar.showExtraFormat && <>
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "strikethrough")} icon={} />
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "subscript")} icon={} />
- activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, "superscript")} icon={} />
- > }
- { configuration.toolbar.showInsertLink && } />}
- { configuration.toolbar.showFontFormat && <>
-
-
-
+
+
+
- > }
- { configuration.toolbar.showAlignment && <>> }
- { configuration.toolbar.showInsert && <>> }
- { configuration.spellchecker.enabled && <>
-
-
+ />
>
- }
+ )}
+ {configuration.toolbar.showAlignment && (
+ <>
+
+
+ >
+ )}
+ {configuration.toolbar.showInsert && (
+ <>
+
+
+ >
+ )}
+ {configuration.spellchecker.enabled && (
+ <>
+
+
+ >
+ )}
);
};
diff --git a/src/styles.module.css b/src/styles.css
similarity index 96%
rename from src/styles.module.css
rename to src/styles.css
index 8d8809d..67f4b41 100644
--- a/src/styles.module.css
+++ b/src/styles.css
@@ -24,20 +24,21 @@ code {
}
.editorPlaceholder {
+ font-size: 15px;
color: #999;
overflow: hidden;
position: absolute;
text-overflow: ellipsis;
- top: 64px;
- left: 18px;
- font-size: 15px;
+ top: 8px;
+ left: 28px;
+ right: 28px;
user-select: none;
+ white-space: nowrap;
display: inline-block;
pointer-events: none;
}
.rtl .editorPlaceholder {
- right: 18px;
left: auto;
}
diff --git a/src/utils/setFloatingElemPosition.js b/src/utils/setFloatingElemPosition.js
index 436b560..f5afcad 100644
--- a/src/utils/setFloatingElemPosition.js
+++ b/src/utils/setFloatingElemPosition.js
@@ -8,12 +8,17 @@ export function setFloatingElemPosition(
isLink,
verticalGap,
horizontalOffset,
+ isRtl
) {
const scrollerElem = anchorElem.parentElement;
if (targetRect === null || !scrollerElem) {
- floatingElem.style.opacity = '0';
- floatingElem.style.transform = 'translate(-10000px, -10000px)';
+ floatingElem.style.opacity = "0";
+ if (isRtl) {
+ floatingElem.style.transform = "translate(10000px, -10000px)";
+ } else {
+ floatingElem.style.transform = "translate(-10000px, -10000px)";
+ }
return;
}
@@ -39,6 +44,6 @@ export function setFloatingElemPosition(
top -= anchorElementRect.top;
left -= anchorElementRect.left;
- floatingElem.style.opacity = '1';
+ floatingElem.style.opacity = "1";
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
}
diff --git a/src/utils/setFloatingElemPositionForLinkEditor.js b/src/utils/setFloatingElemPositionForLinkEditor.js
index 514d8f8..01006d1 100644
--- a/src/utils/setFloatingElemPositionForLinkEditor.js
+++ b/src/utils/setFloatingElemPositionForLinkEditor.js
@@ -7,12 +7,17 @@ export function setFloatingElemPositionForLinkEditor(
anchorElem,
verticalGap = VERTICAL_GAP,
horizontalOffset = HORIZONTAL_OFFSET,
+ isRtl
) {
const scrollerElem = anchorElem.parentElement;
if (targetRect === null || !scrollerElem) {
- floatingElem.style.opacity = '0';
- floatingElem.style.transform = 'translate(-10000px, -10000px)';
+ floatingElem.style.opacity = "0";
+ if (isRtl) {
+ floatingElem.style.transform = "translate(10000px, -10000px)";
+ } else {
+ floatingElem.style.transform = "translate(-10000px, -10000px)";
+ }
return;
}
@@ -34,6 +39,6 @@ export function setFloatingElemPositionForLinkEditor(
top -= anchorElementRect.top;
left -= anchorElementRect.left;
- floatingElem.style.opacity = '1';
+ floatingElem.style.opacity = "1";
floatingElem.style.transform = `translate(${left}px, ${top}px)`;
}