diff --git a/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx b/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx index 81f4c1314..3fc4fd9ab 100644 --- a/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx +++ b/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx @@ -11,7 +11,11 @@ import './index.css'; import {$isCodeHighlightNode} from '@lexical/code'; import {$isLinkNode, TOGGLE_LINK_COMMAND} from '@lexical/link'; import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext'; -import {mergeRegister} from '@lexical/utils'; +import { + mergeRegister, + $findMatchingParent, + $getNearestNodeOfType, +} from '@lexical/utils'; import { $getSelection, $isRangeSelection, @@ -20,7 +24,17 @@ import { FORMAT_TEXT_COMMAND, LexicalEditor, SELECTION_CHANGE_COMMAND, + $isRootOrShadowRoot, + COMMAND_PRIORITY_CRITICAL, } from 'lexical'; +import {$isHeadingNode} from '@lexical/rich-text'; +import { + INSERT_UNORDERED_LIST_COMMAND, + REMOVE_LIST_COMMAND, + $isListNode, + ListNode, + INSERT_ORDERED_LIST_COMMAND, +} from '@lexical/list'; import {useCallback, useEffect, useRef, useState} from 'react'; import {createPortal} from 'react-dom'; @@ -36,9 +50,26 @@ import { LinkIcon, SuperscriptIcon, SubscriptIcon, + ListBulleted, + ListNumbered, } from '@standardnotes/icons'; import {IconComponent} from '../../Theme/IconComponent'; +const blockTypeToBlockName = { + bullet: 'Bulleted List', + check: 'Check List', + code: 'Code Block', + h1: 'Heading 1', + h2: 'Heading 2', + h3: 'Heading 3', + h4: 'Heading 4', + h5: 'Heading 5', + h6: 'Heading 6', + number: 'Numbered List', + paragraph: 'Normal', + quote: 'Quote', +}; + const IconSize = 15; function TextFormatFloatingToolbar({ @@ -52,6 +83,8 @@ function TextFormatFloatingToolbar({ isStrikethrough, isSubscript, isSuperscript, + isBulletedList, + isNumberedList, }: { editor: LexicalEditor; anchorElem: HTMLElement; @@ -63,6 +96,8 @@ function TextFormatFloatingToolbar({ isSubscript: boolean; isSuperscript: boolean; isUnderline: boolean; + isBulletedList: boolean; + isNumberedList: boolean; }): JSX.Element { const popupCharStylesEditorRef = useRef(null); @@ -74,6 +109,22 @@ function TextFormatFloatingToolbar({ } }, [editor, isLink]); + const formatBulletList = useCallback(() => { + if (!isBulletedList) { + editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined); + } else { + editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined); + } + }, [isBulletedList]); + + const formatNumberedList = useCallback(() => { + if (!isNumberedList) { + editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined); + } else { + editor.dispatchCommand(REMOVE_LIST_COMMAND, undefined); + } + }, [isNumberedList]); + const updateTextFormatFloatingToolbar = useCallback(() => { const selection = $getSelection(); @@ -226,6 +277,22 @@ function TextFormatFloatingToolbar({ + + )} @@ -236,6 +303,7 @@ function useFloatingTextFormatToolbar( editor: LexicalEditor, anchorElem: HTMLElement, ): JSX.Element | null { + const [activeEditor, setActiveEditor] = useState(editor); const [isText, setIsText] = useState(false); const [isLink, setIsLink] = useState(false); const [isBold, setIsBold] = useState(false); @@ -245,6 +313,8 @@ function useFloatingTextFormatToolbar( const [isSubscript, setIsSubscript] = useState(false); const [isSuperscript, setIsSuperscript] = useState(false); const [isCode, setIsCode] = useState(false); + const [blockType, setBlockType] = + useState('paragraph'); const updatePopup = useCallback(() => { editor.getEditorState().read(() => { @@ -270,6 +340,42 @@ function useFloatingTextFormatToolbar( return; } + const anchorNode = selection.anchor.getNode(); + let element = + anchorNode.getKey() === 'root' + ? anchorNode + : $findMatchingParent(anchorNode, (e) => { + const parent = e.getParent(); + return parent !== null && $isRootOrShadowRoot(parent); + }); + + if (element === null) { + element = anchorNode.getTopLevelElementOrThrow(); + } + + const elementKey = element.getKey(); + const elementDOM = activeEditor.getElementByKey(elementKey); + + if (elementDOM !== null) { + if ($isListNode(element)) { + const parentList = $getNearestNodeOfType( + anchorNode, + ListNode, + ); + const type = parentList + ? parentList.getListType() + : element.getListType(); + setBlockType(type); + } else { + const type = $isHeadingNode(element) + ? element.getTag() + : element.getType(); + if (type in blockTypeToBlockName) { + setBlockType(type as keyof typeof blockTypeToBlockName); + } + } + } + const node = getSelectedNode(selection); // Update text format @@ -298,14 +404,19 @@ function useFloatingTextFormatToolbar( setIsText(false); } }); - }, [editor]); + }, [editor, activeEditor]); useEffect(() => { - document.addEventListener('selectionchange', updatePopup); - return () => { - document.removeEventListener('selectionchange', updatePopup); - }; - }, [updatePopup]); + return editor.registerCommand( + SELECTION_CHANGE_COMMAND, + (_payload, newEditor) => { + setActiveEditor(newEditor); + updatePopup(); + return false; + }, + COMMAND_PRIORITY_CRITICAL, + ); + }, [editor, updatePopup]); useEffect(() => { return mergeRegister( @@ -336,6 +447,8 @@ function useFloatingTextFormatToolbar( isSuperscript={isSuperscript} isUnderline={isUnderline} isCode={isCode} + isBulletedList={blockType === 'bullet'} + isNumberedList={blockType === 'number'} />, anchorElem, ); diff --git a/packages/icons/src/Icons/ic-list-bulleted.svg b/packages/icons/src/Icons/ic-list-bulleted.svg index 98c5a4333..685152dee 100644 --- a/packages/icons/src/Icons/ic-list-bulleted.svg +++ b/packages/icons/src/Icons/ic-list-bulleted.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/icons/src/Icons/ic-list-numbered.svg b/packages/icons/src/Icons/ic-list-numbered.svg new file mode 100644 index 000000000..4566d975c --- /dev/null +++ b/packages/icons/src/Icons/ic-list-numbered.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/packages/icons/src/Icons/ic-more-vert.svg b/packages/icons/src/Icons/ic-more-vert.svg index 6da703741..633464c75 100644 --- a/packages/icons/src/Icons/ic-more-vert.svg +++ b/packages/icons/src/Icons/ic-more-vert.svg @@ -1,3 +1,3 @@ - + diff --git a/packages/icons/src/Icons/index.ts b/packages/icons/src/Icons/index.ts index d91ae5186..c502e3a2f 100644 --- a/packages/icons/src/Icons/index.ts +++ b/packages/icons/src/Icons/index.ts @@ -112,6 +112,7 @@ import LineWidthIcon from './ic-line-width.svg' import LinkIcon from './ic-link.svg' import LinkOffIcon from './ic-link-off.svg' import ListBulleted from './ic-list-bulleted.svg' +import ListNumbered from './ic-list-numbered.svg' import ListedFilledIcon from './ic-listed-filled.svg' import ListedIcon from './ic-listed.svg' import LockFilledIcon from './ic-lock-filled.svg' @@ -318,6 +319,7 @@ export { ListBulleted, ListedFilledIcon, ListedIcon, + ListNumbered, LockFilledIcon, LockIcon, MarkdownIcon, diff --git a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx index 81d731ec1..06d982ba0 100644 --- a/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx +++ b/packages/web/src/javascripts/Components/Preferences/Panes/Account/ChangeEmail/ChangeEmail.tsx @@ -8,7 +8,6 @@ import { WebApplication } from '@/Application/Application' import { useBeforeUnload } from '@/Hooks/useBeforeUnload' import ChangeEmailForm from './ChangeEmailForm' import ChangeEmailSuccess from './ChangeEmailSuccess' -import { isEmailValid } from '@/Utils' enum SubmitButtonTitles { Default = 'Continue', @@ -57,18 +56,6 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = return success } - const validateNewEmail = async () => { - if (!isEmailValid(newEmail)) { - applicationAlertService - .alert('The email you entered has an invalid format. Please review your input and try again.') - .catch(console.error) - - return false - } - - return true - } - const resetProgressState = () => { setSubmitButtonTitle(SubmitButtonTitles.Default) setIsContinuing(false) @@ -110,7 +97,7 @@ const ChangeEmail: FunctionComponent = ({ onCloseDialog, application }) = setIsContinuing(true) setSubmitButtonTitle(SubmitButtonTitles.GeneratingKeys) - const valid = (await validateCurrentPassword()) && (await validateNewEmail()) + const valid = await validateCurrentPassword() if (!valid) { resetProgressState()