diff --git a/packages/blocks-editor/src/Editor/BlocksEditor.tsx b/packages/blocks-editor/src/Editor/BlocksEditor.tsx index 8f926f687..9ad407bd9 100644 --- a/packages/blocks-editor/src/Editor/BlocksEditor.tsx +++ b/packages/blocks-editor/src/Editor/BlocksEditor.tsx @@ -93,10 +93,9 @@ export const BlocksEditor: FunctionComponent = ({ return ( <> - {children} +
= ({ )} + {children} ) } diff --git a/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.css b/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.css index fce6d7a48..c45b46279 100644 --- a/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.css +++ b/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.css @@ -13,21 +13,6 @@ will-change: transform; } -@media screen and (max-width: 768px) { - .floating-text-format-popup { - top: auto; - bottom: 0; - width: 100%; - overflow-x: scroll; - opacity: 1; - } - .floating-text-format-popup::-webkit-scrollbar { - width: 0px; - height: 0px; - background: transparent; - } -} - .floating-text-format-popup button.popup-item { border: 0; display: flex; diff --git a/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx b/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx index 63fd6d9f5..68714eca6 100644 --- a/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx +++ b/packages/blocks-editor/src/Lexical/Plugins/FloatingTextFormatToolbarPlugin/index.tsx @@ -316,7 +316,7 @@ function TextFormatFloatingToolbar({ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLElement): JSX.Element | null { const [activeEditor, setActiveEditor] = useState(editor) - const [isVisible, setIsVisible] = useState(false) + const [isText, setIsText] = useState(false) const [isLink, setIsLink] = useState(false) const [isBold, setIsBold] = useState(false) const [isItalic, setIsItalic] = useState(false) @@ -339,15 +339,15 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle const isMobile = window.matchMedia('(max-width: 768px)').matches + if (isMobile) { + return + } + if ( nativeSelection !== null && (!$isRangeSelection(selection) || rootElement === null || !rootElement.contains(nativeSelection.anchorNode)) ) { - if (isMobile) { - setIsVisible(true) - } else { - setIsVisible(false) - } + setIsText(false) return } @@ -404,11 +404,9 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle } if (!$isCodeHighlightNode(selection.anchor.getNode()) && selection.getTextContent() !== '') { - setIsVisible($isTextNode(node)) - } else if (isMobile) { - setIsVisible(true) + setIsText($isTextNode(node)) } else { - setIsVisible(false) + setIsText(false) } }) }, [editor, activeEditor]) @@ -432,13 +430,13 @@ function useFloatingTextFormatToolbar(editor: LexicalEditor, anchorElem: HTMLEle }), editor.registerRootListener(() => { if (editor.getRootElement() === null) { - setIsVisible(false) + setIsText(false) } }), ) }, [editor, updatePopup]) - if (!isVisible || isLink) { + if (!isText || isLink) { return null } diff --git a/packages/blocks-editor/src/Lexical/Utils/setFloatingElemPosition.ts b/packages/blocks-editor/src/Lexical/Utils/setFloatingElemPosition.ts index 76fe1ffbf..4f501182b 100644 --- a/packages/blocks-editor/src/Lexical/Utils/setFloatingElemPosition.ts +++ b/packages/blocks-editor/src/Lexical/Utils/setFloatingElemPosition.ts @@ -17,13 +17,6 @@ export function setFloatingElemPosition( ): void { const scrollerElem = anchorElem.parentElement - const isMobileScreen = window.innerWidth < 768 - - if (isMobileScreen) { - floatingElem.style.opacity = '1' - return - } - if (targetRect === null || !scrollerElem) { floatingElem.style.opacity = '0' floatingElem.style.transform = 'translate(-10000px, -10000px)' diff --git a/packages/icons/src/Lexical/type-h1.svg b/packages/icons/src/Lexical/type-h1.svg index 244e79848..d51545813 100755 --- a/packages/icons/src/Lexical/type-h1.svg +++ b/packages/icons/src/Lexical/type-h1.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/packages/icons/src/Lexical/type-h2.svg b/packages/icons/src/Lexical/type-h2.svg index 0347e5e88..f9c8f501a 100755 --- a/packages/icons/src/Lexical/type-h2.svg +++ b/packages/icons/src/Lexical/type-h2.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/packages/icons/src/Lexical/type-h3.svg b/packages/icons/src/Lexical/type-h3.svg index f37188ea6..80dec892f 100755 --- a/packages/icons/src/Lexical/type-h3.svg +++ b/packages/icons/src/Lexical/type-h3.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/packages/mobile/src/MobileWebAppContainer.tsx b/packages/mobile/src/MobileWebAppContainer.tsx index bc4a1ec21..a9a7ec4b1 100644 --- a/packages/mobile/src/MobileWebAppContainer.tsx +++ b/packages/mobile/src/MobileWebAppContainer.tsx @@ -51,6 +51,22 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo const keyboardShowListener = Keyboard.addListener('keyboardWillShow', () => { device.reloadStatusBarStyle(false) + webViewRef.current?.postMessage( + JSON.stringify({ + reactNativeEvent: ReactNativeToWebEvent.KeyboardWillShow, + messageType: 'event', + }), + ) + }) + + const keyboardWillHideListener = Keyboard.addListener('keyboardWillHide', () => { + device.reloadStatusBarStyle(false) + webViewRef.current?.postMessage( + JSON.stringify({ + reactNativeEvent: ReactNativeToWebEvent.KeyboardWillHide, + messageType: 'event', + }), + ) }) const keyboardHideListener = Keyboard.addListener('keyboardDidHide', () => { @@ -85,6 +101,7 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo keyboardHideListener.remove() keyboardWillChangeFrame.remove() keyboardDidChangeFrame.remove() + keyboardWillHideListener.remove() } }, [webViewRef, stateService, device, androidBackHandlerService, colorSchemeService]) @@ -275,6 +292,7 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo onRenderProcessGone={() => { webViewRef.current?.reload() }} + hideKeyboardAccessoryView={true} onShouldStartLoadWithRequest={onShouldStartLoadWithRequest} allowFileAccess={true} allowUniversalAccessFromFileURLs={true} diff --git a/packages/snjs/lib/Client/ReactNativeToWebEvent.ts b/packages/snjs/lib/Client/ReactNativeToWebEvent.ts index aea9733ac..a67454003 100644 --- a/packages/snjs/lib/Client/ReactNativeToWebEvent.ts +++ b/packages/snjs/lib/Client/ReactNativeToWebEvent.ts @@ -7,4 +7,6 @@ export enum ReactNativeToWebEvent { ColorSchemeChanged = 'ColorSchemeChanged', KeyboardFrameWillChange = 'KeyboardFrameWillChange', KeyboardFrameDidChange = 'KeyboardFrameDidChange', + KeyboardWillShow = 'KeyboardWillShow', + KeyboardWillHide = 'KeyboardWillHide', } diff --git a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx index 5bbd12b36..7155f3f7a 100644 --- a/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx +++ b/packages/web/src/javascripts/Components/ApplicationView/ApplicationView.tsx @@ -1,5 +1,5 @@ import { ApplicationGroup } from '@/Application/ApplicationGroup' -import { getPlatformString } from '@/Utils' +import { getPlatformString, isIOS } from '@/Utils' import { ApplicationEvent, Challenge, removeFromArray, WebAppEvent } from '@standardnotes/snjs' import { alertDialog, RouteType } from '@standardnotes/ui-services' import { WebApplication } from '@/Application/Application' @@ -29,6 +29,7 @@ import PanesSystemComponent from '../Panes/PanesSystemComponent' import DotOrgNotice from './DotOrgNotice' import LinkingControllerProvider from '@/Controllers/LinkingControllerProvider' import ImportModal from '../ImportModal/ImportModal' +import IosKeyboardClose from '../IosKeyboardClose/IosKeyboardClose' type Props = { application: WebApplication @@ -235,6 +236,7 @@ const ApplicationView: FunctionComponent = ({ application, mainApplicatio {application.routeService.isDotOrg && } + {isIOS() && }
diff --git a/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx b/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx index a1be869a0..c8d382ade 100644 --- a/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx +++ b/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx @@ -2,6 +2,10 @@ import * as icons from '@standardnotes/icons' export const IconNameToSvgMapping = { 'account-circle': icons.AccountCircleIcon, + 'align-center': icons.FormatAlignCenterIcon, + 'align-justify': icons.FormatAlignJustifyIcon, + 'align-left': icons.FormatAlignLeftIcon, + 'align-right': icons.FormatAlignRightIcon, 'arrow-down': icons.ArrowDownIcon, 'arrow-left': icons.ArrowLeftIcon, 'arrow-right': icons.ArrowRightIcon, @@ -11,8 +15,8 @@ export const IconNameToSvgMapping = { 'arrows-vertical': icons.ArrowsVerticalIcon, 'attachment-file': icons.AttachmentFileIcon, 'check-bold': icons.CheckBoldIcon, - 'check-circle': icons.CheckCircleIcon, 'check-circle-filled': icons.CheckCircleFilledIcon, + 'check-circle': icons.CheckCircleIcon, 'chevron-down': icons.ChevronDownIcon, 'chevron-left': icons.ChevronLeftIcon, 'chevron-right': icons.ChevronRightIcon, @@ -36,8 +40,10 @@ export const IconNameToSvgMapping = { 'format-align-right': icons.FormatAlignRightIcon, 'fullscreen-exit': icons.FullscreenExitIcon, 'hashtag-off': icons.HashtagOffIcon, + 'keyboard-close': icons.KeyboardCloseIcon, 'link-off': icons.LinkOffIcon, 'list-bulleted': icons.ListBulleted, + 'list-numbered': icons.ListNumbered, 'lock-filled': icons.LockFilledIcon, 'menu-arrow-down-alt': icons.MenuArrowDownAlt, 'menu-arrow-down': icons.MenuArrowDownIcon, @@ -63,9 +69,11 @@ export const IconNameToSvgMapping = { 'user-switch': icons.UserSwitch, accessibility: icons.AccessibilityIcon, add: icons.AddIcon, + aegis: icons.AegisIcon, archive: icons.ArchiveIcon, asterisk: icons.AsteriskIcon, authenticator: icons.AuthenticatorIcon, + bold: icons.BoldIcon, camera: icons.CameraIcon, check: icons.CheckIcon, close: icons.CloseIcon, @@ -77,13 +85,16 @@ export const IconNameToSvgMapping = { drag: icons.DragIcon, editor: icons.EditorIcon, email: icons.EmailIcon, + evernote: icons.EvernoteIcon, eye: icons.EyeIcon, file: icons.FileIcon, folder: icons.FolderIcon, + gkeep: icons.GoogleKeepIcon, hashtag: icons.HashtagIcon, help: icons.HelpIcon, history: icons.HistoryIcon, info: icons.InfoIcon, + italic: icons.ItalicIcon, keyboard: icons.KeyboardIcon, link: icons.LinkIcon, listed: icons.ListedIcon, @@ -91,6 +102,7 @@ export const IconNameToSvgMapping = { markdown: icons.MarkdownIcon, more: icons.MoreIcon, notes: icons.NotesIcon, + paragraph: icons.TextParagraphLongIcon, password: icons.PasswordIcon, pencil: icons.PencilIcon, pin: icons.PinIcon, @@ -102,23 +114,24 @@ export const IconNameToSvgMapping = { share: icons.ShareIcon, signIn: icons.SignInIcon, signOut: icons.SignOutIcon, + simplenote: icons.SimplenoteIcon, spreadsheets: icons.SpreadsheetsIcon, star: icons.StarIcon, + strikethrough: icons.StrikethroughIcon, + subscript: icons.SubscriptIcon, subtract: icons.SubtractIcon, + superscript: icons.SuperscriptIcon, sync: icons.SyncIcon, tasks: icons.TasksIcon, themes: icons.ThemesIcon, trash: icons.TrashIcon, tune: icons.TuneIcon, unarchive: icons.UnarchiveIcon, + underline: icons.UnderlineIcon, unpin: icons.UnpinIcon, upload: icons.UploadIcon, user: icons.UserIcon, view: icons.ViewIcon, warning: icons.WarningIcon, window: icons.WindowIcon, - evernote: icons.EvernoteIcon, - gkeep: icons.GoogleKeepIcon, - simplenote: icons.SimplenoteIcon, - aegis: icons.AegisIcon, } diff --git a/packages/web/src/javascripts/Components/IosKeyboardClose/IosKeyboardClose.tsx b/packages/web/src/javascripts/Components/IosKeyboardClose/IosKeyboardClose.tsx new file mode 100644 index 000000000..057a642ab --- /dev/null +++ b/packages/web/src/javascripts/Components/IosKeyboardClose/IosKeyboardClose.tsx @@ -0,0 +1,53 @@ +import { classNames, ReactNativeToWebEvent } from '@standardnotes/snjs' +import { useEffect, useState } from 'react' +import { useApplication } from '../ApplicationProvider' +import Icon from '../Icon/Icon' + +const IosKeyboardClose = () => { + const application = useApplication() + const [isVisible, setIsVisible] = useState(false) + const [isFocusInSuperEditor, setIsFocusInSuperEditor] = useState( + () => !!document.activeElement?.closest('#blocks-editor'), + ) + + useEffect(() => { + return application.addNativeMobileEventListener((event) => { + if (event === ReactNativeToWebEvent.KeyboardWillShow) { + setIsVisible(true) + } else if (event === ReactNativeToWebEvent.KeyboardWillHide) { + setIsVisible(false) + } + }) + }, [application]) + + useEffect(() => { + const handleFocusChange = () => { + setIsFocusInSuperEditor(!!document.activeElement?.closest('#blocks-editor')) + } + + document.addEventListener('focusin', handleFocusChange) + document.addEventListener('focusout', handleFocusChange) + + return () => { + document.removeEventListener('focusin', handleFocusChange) + document.removeEventListener('focusout', handleFocusChange) + } + }, []) + + if (!isVisible) { + return null + } + + return ( + + ) +} + +export default IosKeyboardClose diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx index eead04620..af102fcc7 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/BlockPickerPlugin.tsx @@ -6,25 +6,25 @@ import useModal from '@standardnotes/blocks-editor/src/Lexical/Hooks/useModal' import { InsertTableDialog } from '@standardnotes/blocks-editor/src/Lexical/Plugins/TablePlugin' import { BlockPickerOption } from './BlockPickerOption' import { BlockPickerMenuItem } from './BlockPickerMenuItem' -import { GetNumberedListBlock } from './Blocks/NumberedList' -import { GetBulletedListBlock } from './Blocks/BulletedList' -import { GetChecklistBlock } from './Blocks/Checklist' -import { GetDividerBlock } from './Blocks/Divider' -import { GetCollapsibleBlock } from './Blocks/Collapsible' -import { GetDynamicPasswordBlocks, GetPasswordBlocks } from './Blocks/Password' -import { GetParagraphBlock } from './Blocks/Paragraph' -import { GetHeadingsBlocks } from './Blocks/Headings' -import { GetQuoteBlock } from './Blocks/Quote' -import { GetAlignmentBlocks } from './Blocks/Alignment' -import { GetCodeBlock } from './Blocks/Code' -import { GetEmbedsBlocks } from './Blocks/Embeds' -import { GetDynamicTableBlocks, GetTableBlock } from './Blocks/Table' +import { GetNumberedListBlockOption } from './Options/NumberedList' +import { GetBulletedListBlockOption } from './Options/BulletedList' +import { GetChecklistBlockOption } from './Options/Checklist' +import { GetDividerBlockOption } from './Options/Divider' +import { GetCollapsibleBlockOption } from './Options/Collapsible' +import { GetDynamicPasswordBlocks, GetPasswordBlockOption } from './Options/Password' +import { GetParagraphBlockOption } from './Options/Paragraph' +import { GetHeadingsBlockOptions } from './Options/Headings' +import { GetQuoteBlockOption } from './Options/Quote' +import { GetAlignmentBlockOptions } from './Options/Alignment' +import { GetCodeBlockOption } from './Options/Code' +import { GetEmbedsBlockOptions } from './Options/Embeds' +import { GetDynamicTableBlocks, GetTableBlockOption } from './Options/Table' import Popover from '@/Components/Popover/Popover' import { PopoverClassNames } from '../ClassNames' -import { GetDatetimeBlocks } from './Blocks/DateTime' +import { GetDatetimeBlockOptions } from './Options/DateTime' import { isMobileScreen } from '@/Utils' import { useApplication } from '@/Components/ApplicationProvider' -import { GetIndentOutdentBlocks } from './Blocks/IndentOutdent' +import { GetIndentOutdentBlockOptions } from './Options/IndentOutdent' export default function BlockPickerMenuPlugin(): JSX.Element { const [editor] = useLexicalComposerContext() @@ -37,26 +37,26 @@ export default function BlockPickerMenuPlugin(): JSX.Element { }) const options = useMemo(() => { - const indentOutdentOptions = application.isNativeMobileWeb() ? GetIndentOutdentBlocks(editor) : [] + const indentOutdentOptions = application.isNativeMobileWeb() ? GetIndentOutdentBlockOptions(editor) : [] const baseOptions = [ - GetParagraphBlock(editor), - ...GetHeadingsBlocks(editor), + GetParagraphBlockOption(editor), + ...GetHeadingsBlockOptions(editor), ...indentOutdentOptions, - GetTableBlock(() => + GetTableBlockOption(() => showModal('Insert Table', (onClose) => ), ), - GetNumberedListBlock(editor), - GetBulletedListBlock(editor), - GetChecklistBlock(editor), - GetQuoteBlock(editor), - GetCodeBlock(editor), - GetDividerBlock(editor), - ...GetDatetimeBlocks(editor), - ...GetAlignmentBlocks(editor), - ...GetPasswordBlocks(editor), - GetCollapsibleBlock(editor), - ...GetEmbedsBlocks(editor), + GetNumberedListBlockOption(editor), + GetBulletedListBlockOption(editor), + GetChecklistBlockOption(editor), + GetQuoteBlockOption(editor), + GetCodeBlockOption(editor), + GetDividerBlockOption(editor), + ...GetDatetimeBlockOptions(editor), + ...GetAlignmentBlockOptions(editor), + GetPasswordBlockOption(editor), + GetCollapsibleBlockOption(editor), + ...GetEmbedsBlockOptions(editor), ] const dynamicOptions = [ diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Alignment.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Alignment.tsx deleted file mode 100644 index 172b2ed29..000000000 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Alignment.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { BlockPickerOption } from '../BlockPickerOption' -import { FORMAT_ELEMENT_COMMAND, LexicalEditor, ElementFormatType } from 'lexical' -import { LexicalIconName } from '@/Components/Icon/LexicalIcons' - -export function GetAlignmentBlocks(editor: LexicalEditor) { - return ['left', 'center', 'right', 'justify'].map( - (alignment) => - new BlockPickerOption(`Align ${alignment}`, { - iconName: `align-${alignment}` as LexicalIconName, - keywords: ['align', 'justify', alignment], - onSelect: () => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment as ElementFormatType), - }), - ) -} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Embeds.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Embeds.tsx deleted file mode 100644 index c565d093c..000000000 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Embeds.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { BlockPickerOption } from '../BlockPickerOption' -import { LexicalEditor } from 'lexical' -import { INSERT_EMBED_COMMAND } from '@lexical/react/LexicalAutoEmbedPlugin' -import { EmbedConfigs } from '@standardnotes/blocks-editor/src/Lexical/Plugins/AutoEmbedPlugin' -import { LexicalIconName } from '@/Components/Icon/LexicalIcons' - -export function GetEmbedsBlocks(editor: LexicalEditor) { - return EmbedConfigs.map( - (embedConfig) => - new BlockPickerOption(`Embed ${embedConfig.contentName}`, { - iconName: embedConfig.iconName as LexicalIconName, - keywords: [...embedConfig.keywords, 'embed'], - onSelect: () => editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type), - }), - ) -} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Headings.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Headings.tsx deleted file mode 100644 index 9d212b0b3..000000000 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Headings.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { BlockPickerOption } from '../BlockPickerOption' -import { $wrapNodes } from '@lexical/selection' -import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical' -import { $createHeadingNode, HeadingTagType } from '@lexical/rich-text' -import { LexicalIconName } from '@/Components/Icon/LexicalIcons' - -export function GetHeadingsBlocks(editor: LexicalEditor) { - return Array.from({ length: 3 }, (_, i) => i + 1).map( - (n) => - new BlockPickerOption(`Heading ${n}`, { - iconName: `h${n}` as LexicalIconName, - keywords: ['heading', 'header', `h${n}`], - onSelect: () => - editor.update(() => { - const selection = $getSelection() - if ($isRangeSelection(selection)) { - $wrapNodes(selection, () => $createHeadingNode(`h${n}` as HeadingTagType)) - } - }), - }), - ) -} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Alignment.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Alignment.tsx new file mode 100644 index 000000000..3e249b630 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Alignment.tsx @@ -0,0 +1,14 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetAlignmentBlocks } from '../../Blocks/Alignment' + +export function GetAlignmentBlockOptions(editor: LexicalEditor) { + return GetAlignmentBlocks(editor).map( + (block) => + new BlockPickerOption(block.name, { + iconName: block.iconName, + keywords: block.keywords, + onSelect: block.onSelect, + }), + ) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/BulletedList.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/BulletedList.tsx new file mode 100644 index 000000000..ad83f4b46 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/BulletedList.tsx @@ -0,0 +1,13 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetBulletedListBlock } from '../../Blocks/BulletedList' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetBulletedListBlockOption(editor: LexicalEditor) { + const block = GetBulletedListBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Checklist.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Checklist.tsx new file mode 100644 index 000000000..c72decafd --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Checklist.tsx @@ -0,0 +1,13 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetChecklistBlock } from '../../Blocks/Checklist' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetChecklistBlockOption(editor: LexicalEditor) { + const block = GetChecklistBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Code.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Code.tsx new file mode 100644 index 000000000..06b7ce191 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Code.tsx @@ -0,0 +1,12 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetCodeBlock } from '../../Blocks/Code' + +export function GetCodeBlockOption(editor: LexicalEditor) { + const block = GetCodeBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Collapsible.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Collapsible.tsx new file mode 100644 index 000000000..65bd126a7 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Collapsible.tsx @@ -0,0 +1,12 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetCollapsibleBlock } from '../../Blocks/Collapsible' + +export function GetCollapsibleBlockOption(editor: LexicalEditor) { + const block = GetCollapsibleBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/DateTime.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/DateTime.tsx new file mode 100644 index 000000000..2ac4aeac0 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/DateTime.tsx @@ -0,0 +1,15 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetDatetimeBlocks } from '../../Blocks/DateTime' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetDatetimeBlockOptions(editor: LexicalEditor) { + return GetDatetimeBlocks(editor).map( + (block) => + new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }), + ) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Divider.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Divider.tsx new file mode 100644 index 000000000..bbbe1e828 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Divider.tsx @@ -0,0 +1,13 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetDividerBlock } from '../../Blocks/Divider' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetDividerBlockOption(editor: LexicalEditor) { + const block = GetDividerBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Embeds.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Embeds.tsx new file mode 100644 index 000000000..b1329f081 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Embeds.tsx @@ -0,0 +1,15 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' +import { GetEmbedsBlocks } from '../../Blocks/Embeds' + +export function GetEmbedsBlockOptions(editor: LexicalEditor) { + return GetEmbedsBlocks(editor).map( + (block) => + new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }), + ) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Headings.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Headings.tsx new file mode 100644 index 000000000..06baa66ff --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Headings.tsx @@ -0,0 +1,15 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' +import { GetHeadingsBlocks } from '../../Blocks/Headings' + +export function GetHeadingsBlockOptions(editor: LexicalEditor) { + return GetHeadingsBlocks(editor).map( + (block) => + new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }), + ) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/IndentOutdent.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/IndentOutdent.tsx new file mode 100644 index 000000000..9d5109f50 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/IndentOutdent.tsx @@ -0,0 +1,15 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetIndentOutdentBlocks } from '../../Blocks/IndentOutdent' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetIndentOutdentBlockOptions(editor: LexicalEditor) { + return GetIndentOutdentBlocks(editor).map( + (block) => + new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }), + ) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/NumberedList.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/NumberedList.tsx new file mode 100644 index 000000000..63a456352 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/NumberedList.tsx @@ -0,0 +1,13 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetNumberedListBlock } from '../../Blocks/NumberedList' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetNumberedListBlockOption(editor: LexicalEditor) { + const block = GetNumberedListBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Paragraph.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Paragraph.tsx new file mode 100644 index 000000000..d88cc275d --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Paragraph.tsx @@ -0,0 +1,13 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetParagraphBlock } from '../../Blocks/Paragraph' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetParagraphBlockOption(editor: LexicalEditor) { + const block = GetParagraphBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Password.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Password.tsx similarity index 67% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Password.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Password.tsx index 06c560c0a..e23c7db40 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Password.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Password.tsx @@ -1,18 +1,18 @@ import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_PASSWORD_COMMAND } from '../../Commands' +import { GetPasswordBlock } from '../../Blocks/Password' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' -const DEFAULT_PASSWORD_LENGTH = 16 const MIN_PASSWORD_LENGTH = 8 -export function GetPasswordBlocks(editor: LexicalEditor) { - return [ - new BlockPickerOption('Generate cryptographically secure password', { - iconName: 'password', - keywords: ['password', 'secure'], - onSelect: () => editor.dispatchCommand(INSERT_PASSWORD_COMMAND, String(DEFAULT_PASSWORD_LENGTH)), - }), - ] +export function GetPasswordBlockOption(editor: LexicalEditor) { + const block = GetPasswordBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) } export function GetDynamicPasswordBlocks(editor: LexicalEditor, queryString: string) { diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Quote.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Quote.tsx new file mode 100644 index 000000000..8a1671c4c --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Quote.tsx @@ -0,0 +1,13 @@ +import { BlockPickerOption } from '../BlockPickerOption' +import { LexicalEditor } from 'lexical' +import { GetQuoteBlock } from '../../Blocks/Quote' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetQuoteBlockOption(editor: LexicalEditor) { + const block = GetQuoteBlock(editor) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, + }) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Table.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Table.tsx similarity index 79% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Table.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Table.tsx index 65ca0c058..cfa090336 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Table.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Options/Table.tsx @@ -1,12 +1,15 @@ import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_TABLE_COMMAND } from '@lexical/table' +import { GetTableBlock } from '../../Blocks/Table' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' -export function GetTableBlock(onSelect: () => void) { - return new BlockPickerOption('Table', { - iconName: 'table', - keywords: ['table', 'grid', 'spreadsheet', 'rows', 'columns'], - onSelect, +export function GetTableBlockOption(onSelect: () => void) { + const block = GetTableBlock(onSelect) + return new BlockPickerOption(block.name, { + iconName: block.iconName as LexicalIconName, + keywords: block.keywords, + onSelect: block.onSelect, }) } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Alignment.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Alignment.tsx new file mode 100644 index 000000000..878b52c7d --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Alignment.tsx @@ -0,0 +1,11 @@ +import { FORMAT_ELEMENT_COMMAND, LexicalEditor, ElementFormatType } from 'lexical' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetAlignmentBlocks(editor: LexicalEditor) { + return ['left', 'center', 'right', 'justify'].map((alignment) => ({ + name: `Align ${alignment}`, + iconName: `align-${alignment}` as LexicalIconName, + keywords: ['align', 'justify', alignment], + onSelect: () => editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment as ElementFormatType), + })) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/BulletedList.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/BulletedList.tsx similarity index 69% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/BulletedList.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/BulletedList.tsx index 90f2a5df7..cbbe19350 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/BulletedList.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/BulletedList.tsx @@ -1,11 +1,11 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_UNORDERED_LIST_COMMAND } from '@lexical/list' export function GetBulletedListBlock(editor: LexicalEditor) { - return new BlockPickerOption('Bulleted List', { - iconName: 'list-ul', + return { + name: 'Bulleted List', + iconName: 'list-bulleted', keywords: ['bulleted list', 'unordered list', 'ul'], onSelect: () => editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Checklist.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Checklist.tsx similarity index 73% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Checklist.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Checklist.tsx index 56940414d..11652a73c 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Checklist.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Checklist.tsx @@ -1,11 +1,11 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_CHECK_LIST_COMMAND } from '@lexical/list' export function GetChecklistBlock(editor: LexicalEditor) { - return new BlockPickerOption('Check List', { + return { + name: 'Check List', iconName: 'check', keywords: ['check list', 'todo list'], onSelect: () => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Code.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Code.tsx similarity index 84% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Code.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Code.tsx index 3d3dd581c..cc5265a7e 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Code.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Code.tsx @@ -1,11 +1,12 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { $wrapNodes } from '@lexical/selection' import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical' import { $createCodeNode } from '@lexical/code' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' export function GetCodeBlock(editor: LexicalEditor) { - return new BlockPickerOption('Code', { - iconName: 'lexical-code', + return { + name: 'Code', + iconName: 'code' as LexicalIconName, keywords: ['javascript', 'python', 'js', 'codeblock'], onSelect: () => editor.update(() => { @@ -21,5 +22,5 @@ export function GetCodeBlock(editor: LexicalEditor) { } } }), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Collapsible.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Collapsible.tsx similarity index 69% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Collapsible.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Collapsible.tsx index 21a02959f..8a65e3880 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Collapsible.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Collapsible.tsx @@ -1,11 +1,12 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_COLLAPSIBLE_COMMAND } from '@standardnotes/blocks-editor/src/Lexical/Plugins/CollapsiblePlugin' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' export function GetCollapsibleBlock(editor: LexicalEditor) { - return new BlockPickerOption('Collapsible', { - iconName: 'caret-right-fill', + return { + name: 'Collapsible', + iconName: 'caret-right-fill' as LexicalIconName, keywords: ['collapse', 'collapsible', 'toggle'], onSelect: () => editor.dispatchCommand(INSERT_COLLAPSIBLE_COMMAND, undefined), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/DateTime.tsx similarity index 71% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/DateTime.tsx index f52b15635..66b328523 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/DateTime.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/DateTime.tsx @@ -1,23 +1,25 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' -import { INSERT_DATETIME_COMMAND, INSERT_DATE_COMMAND, INSERT_TIME_COMMAND } from '../../Commands' +import { INSERT_DATETIME_COMMAND, INSERT_DATE_COMMAND, INSERT_TIME_COMMAND } from '../Commands' export function GetDatetimeBlocks(editor: LexicalEditor) { return [ - new BlockPickerOption('Current date and time', { + { + name: 'Current date and time', iconName: 'authenticator', keywords: ['date', 'current'], onSelect: () => editor.dispatchCommand(INSERT_DATETIME_COMMAND, 'datetime'), - }), - new BlockPickerOption('Current time', { + }, + { + name: 'Current time', iconName: 'authenticator', keywords: ['time', 'current'], onSelect: () => editor.dispatchCommand(INSERT_TIME_COMMAND, 'datetime'), - }), - new BlockPickerOption('Current date', { + }, + { + name: 'Current date', iconName: 'authenticator', keywords: ['date', 'current'], onSelect: () => editor.dispatchCommand(INSERT_DATE_COMMAND, 'datetime'), - }), + }, ] } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Divider.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Divider.tsx similarity index 77% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Divider.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Divider.tsx index 5a30ee8fd..a1e0c2020 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Divider.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Divider.tsx @@ -1,11 +1,11 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode' export function GetDividerBlock(editor: LexicalEditor) { - return new BlockPickerOption('Divider', { + return { + name: 'Divider', iconName: 'horizontal-rule', keywords: ['horizontal rule', 'divider', 'hr'], onSelect: () => editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Embeds.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Embeds.tsx new file mode 100644 index 000000000..fff1d076b --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Embeds.tsx @@ -0,0 +1,13 @@ +import { LexicalEditor } from 'lexical' +import { INSERT_EMBED_COMMAND } from '@lexical/react/LexicalAutoEmbedPlugin' +import { EmbedConfigs } from '@standardnotes/blocks-editor/src/Lexical/Plugins/AutoEmbedPlugin' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetEmbedsBlocks(editor: LexicalEditor) { + return EmbedConfigs.map((embedConfig) => ({ + name: `Embed ${embedConfig.contentName}`, + iconName: embedConfig.iconName as LexicalIconName, + keywords: [...embedConfig.keywords, 'embed'], + onSelect: () => editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type), + })) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Headings.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Headings.tsx new file mode 100644 index 000000000..f8c9fd76f --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Headings.tsx @@ -0,0 +1,19 @@ +import { $wrapNodes } from '@lexical/selection' +import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical' +import { $createHeadingNode, HeadingTagType } from '@lexical/rich-text' +import { LexicalIconName } from '@/Components/Icon/LexicalIcons' + +export function GetHeadingsBlocks(editor: LexicalEditor) { + return Array.from({ length: 3 }, (_, i) => i + 1).map((n) => ({ + name: `Heading ${n}`, + iconName: `h${n}` as LexicalIconName, + keywords: ['heading', 'header', `h${n}`], + onSelect: () => + editor.update(() => { + const selection = $getSelection() + if ($isRangeSelection(selection)) { + $wrapNodes(selection, () => $createHeadingNode(`h${n}` as HeadingTagType)) + } + }), + })) +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/IndentOutdent.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/IndentOutdent.tsx similarity index 75% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/IndentOutdent.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/IndentOutdent.tsx index b5a26fcdb..1035401f0 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/IndentOutdent.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/IndentOutdent.tsx @@ -1,17 +1,18 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { INDENT_CONTENT_COMMAND, OUTDENT_CONTENT_COMMAND, LexicalEditor } from 'lexical' export function GetIndentOutdentBlocks(editor: LexicalEditor) { return [ - new BlockPickerOption('Indent', { + { + name: 'Indent', iconName: 'arrow-right', keywords: ['indent'], onSelect: () => editor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined), - }), - new BlockPickerOption('Outdent', { + }, + { + name: 'Outdent', iconName: 'arrow-left', keywords: ['outdent'], onSelect: () => editor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined), - }), + }, ] } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/NumberedList.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/NumberedList.tsx similarity index 68% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/NumberedList.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/NumberedList.tsx index 1e70827be..a0f16fe8d 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/NumberedList.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/NumberedList.tsx @@ -1,11 +1,11 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { LexicalEditor } from 'lexical' import { INSERT_ORDERED_LIST_COMMAND } from '@lexical/list' export function GetNumberedListBlock(editor: LexicalEditor) { - return new BlockPickerOption('Numbered List', { - iconName: 'list-ol', + return { + name: 'Numbered List', + iconName: 'list-numbered', keywords: ['numbered list', 'ordered list', 'ol'], onSelect: () => editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Paragraph.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Paragraph.tsx similarity index 82% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Paragraph.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Paragraph.tsx index c5b582d84..ab79d22c6 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Paragraph.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Paragraph.tsx @@ -1,9 +1,9 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { $wrapNodes } from '@lexical/selection' import { $createParagraphNode, $getSelection, $isRangeSelection, LexicalEditor } from 'lexical' export function GetParagraphBlock(editor: LexicalEditor) { - return new BlockPickerOption('Paragraph', { + return { + name: 'Paragraph', iconName: 'paragraph', keywords: ['normal', 'paragraph', 'p', 'text'], onSelect: () => @@ -13,5 +13,5 @@ export function GetParagraphBlock(editor: LexicalEditor) { $wrapNodes(selection, () => $createParagraphNode()) } }), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Password.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Password.tsx new file mode 100644 index 000000000..316b6e06b --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Password.tsx @@ -0,0 +1,13 @@ +import { LexicalEditor } from 'lexical' +import { INSERT_PASSWORD_COMMAND } from '../Commands' + +const DEFAULT_PASSWORD_LENGTH = 16 + +export function GetPasswordBlock(editor: LexicalEditor) { + return { + name: 'Generate cryptographically secure password', + iconName: 'password', + keywords: ['password', 'secure'], + onSelect: () => editor.dispatchCommand(INSERT_PASSWORD_COMMAND, String(DEFAULT_PASSWORD_LENGTH)), + } +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Quote.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Quote.tsx similarity index 82% rename from packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Quote.tsx rename to packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Quote.tsx index f648871d4..5115f5695 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/BlockPickerPlugin/Blocks/Quote.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Quote.tsx @@ -1,10 +1,10 @@ -import { BlockPickerOption } from '../BlockPickerOption' import { $wrapNodes } from '@lexical/selection' import { $getSelection, $isRangeSelection, LexicalEditor } from 'lexical' import { $createQuoteNode } from '@lexical/rich-text' export function GetQuoteBlock(editor: LexicalEditor) { - return new BlockPickerOption('Quote', { + return { + name: 'Quote', iconName: 'quote', keywords: ['block quote'], onSelect: () => @@ -14,5 +14,5 @@ export function GetQuoteBlock(editor: LexicalEditor) { $wrapNodes(selection, () => $createQuoteNode()) } }), - }) + } } diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Table.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Table.tsx new file mode 100644 index 000000000..7f803cdb5 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/Blocks/Table.tsx @@ -0,0 +1,3 @@ +export function GetTableBlock(onSelect: () => void) { + return { name: 'Table', iconName: 'table', keywords: ['table', 'grid', 'spreadsheet', 'rows', 'columns'], onSelect } +} diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx new file mode 100644 index 000000000..07b9f0e86 --- /dev/null +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/Plugins/MobileToolbarPlugin/MobileToolbarPlugin.tsx @@ -0,0 +1,180 @@ +import Icon from '@/Components/Icon/Icon' +import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' +import useModal from '@standardnotes/blocks-editor/src/Lexical/Hooks/useModal' +import { InsertTableDialog } from '@standardnotes/blocks-editor/src/Lexical/Plugins/TablePlugin' +import { getSelectedNode } from '@standardnotes/blocks-editor/src/Lexical/Utils/getSelectedNode' +import { sanitizeUrl } from '@standardnotes/blocks-editor/src/Lexical/Utils/sanitizeUrl' +import { $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical' +import { $isLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { GetAlignmentBlocks } from '../Blocks/Alignment' +import { GetBulletedListBlock } from '../Blocks/BulletedList' +import { GetChecklistBlock } from '../Blocks/Checklist' +import { GetCodeBlock } from '../Blocks/Code' +import { GetCollapsibleBlock } from '../Blocks/Collapsible' +import { GetDatetimeBlocks } from '../Blocks/DateTime' +import { GetDividerBlock } from '../Blocks/Divider' +import { GetEmbedsBlocks } from '../Blocks/Embeds' +import { GetHeadingsBlocks } from '../Blocks/Headings' +import { GetIndentOutdentBlocks } from '../Blocks/IndentOutdent' +import { GetNumberedListBlock } from '../Blocks/NumberedList' +import { GetParagraphBlock } from '../Blocks/Paragraph' +import { GetPasswordBlock } from '../Blocks/Password' +import { GetQuoteBlock } from '../Blocks/Quote' +import { GetTableBlock } from '../Blocks/Table' +import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery' +import { classNames } from '@standardnotes/snjs' + +const MobileToolbarPlugin = () => { + const [editor] = useLexicalComposerContext() + const [modal, showModal] = useModal() + + const [isInEditor, setIsInEditor] = useState(false) + const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm) + + const insertLink = useCallback(() => { + const selection = $getSelection() + if (!$isRangeSelection(selection)) { + return + } + const node = getSelectedNode(selection) + const parent = node.getParent() + const isLink = $isLinkNode(parent) || $isLinkNode(node) + if (!isLink) { + editor.update(() => { + const selection = $getSelection() + const textContent = selection?.getTextContent() + if (!textContent) { + editor.dispatchCommand(TOGGLE_LINK_COMMAND, 'https://') + return + } + editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(textContent)) + }) + } else { + editor.dispatchCommand(TOGGLE_LINK_COMMAND, null) + } + }, [editor]) + + const items = useMemo( + () => [ + { + name: 'Bold', + iconName: 'bold', + onSelect: () => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold') + }, + }, + { + name: 'Italic', + iconName: 'italic', + onSelect: () => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic') + }, + }, + { + name: 'Underline', + iconName: 'underline', + onSelect: () => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline') + }, + }, + { + name: 'Strikethrough', + iconName: 'strikethrough', + onSelect: () => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough') + }, + }, + { + name: 'Subscript', + iconName: 'subscript', + onSelect: () => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript') + }, + }, + { + name: 'Superscript', + iconName: 'superscript', + onSelect: () => { + editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript') + }, + }, + { + name: 'Link', + iconName: 'link', + onSelect: insertLink, + }, + GetParagraphBlock(editor), + ...GetHeadingsBlocks(editor), + ...GetIndentOutdentBlocks(editor), + GetTableBlock(() => + showModal('Insert Table', (onClose) => ), + ), + GetNumberedListBlock(editor), + GetBulletedListBlock(editor), + GetChecklistBlock(editor), + GetQuoteBlock(editor), + GetCodeBlock(editor), + GetDividerBlock(editor), + ...GetDatetimeBlocks(editor), + ...GetAlignmentBlocks(editor), + ...[GetPasswordBlock(editor)], + GetCollapsibleBlock(editor), + ...GetEmbedsBlocks(editor), + ], + [editor, insertLink, showModal], + ) + + useEffect(() => { + const rootElement = editor.getRootElement() + + if (!rootElement) { + return + } + + const handleFocus = () => setIsInEditor(true) + const handleBlur = () => setIsInEditor(false) + + rootElement.addEventListener('focus', handleFocus) + rootElement.addEventListener('blur', handleBlur) + + return () => { + rootElement.removeEventListener('focus', handleFocus) + rootElement.removeEventListener('blur', handleBlur) + } + }, [editor]) + + if (!isMobile || !isInEditor) { + return null + } + + return ( + <> + {modal} +
+
+ {items.map((item) => { + return ( + + ) + })} +
+ +
+ + ) +} + +export default MobileToolbarPlugin diff --git a/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx b/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx index 4c8a58b56..0539a91bd 100644 --- a/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx +++ b/packages/web/src/javascripts/Components/NoteView/SuperEditor/SuperEditor.tsx @@ -41,6 +41,7 @@ import ReadonlyPlugin from './Plugins/ReadonlyPlugin/ReadonlyPlugin' import { SuperSearchContextProvider } from './Plugins/SearchPlugin/Context' import { SearchPlugin } from './Plugins/SearchPlugin/SearchPlugin' import ModalOverlay from '@/Components/Modal/ModalOverlay' +import MobileToolbarPlugin from './Plugins/MobileToolbarPlugin/MobileToolbarPlugin' import { SuperEditorNodes } from './SuperEditorNodes' const NotePreviewCharLimit = 160 @@ -164,7 +165,7 @@ export const SuperEditor: FunctionComponent = ({ }, [reloadPreferences, application]) return ( -
+
@@ -203,6 +204,7 @@ export const SuperEditor: FunctionComponent = ({ +