chore: consolidate super toolbar items and remove need for scroll [skip e2e]
This commit is contained in:
3
packages/icons/src/Icons/ic-list-check.svg
Normal file
3
packages/icons/src/Icons/ic-list-check.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<path fill="currentColor" fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zM3.854 2.146a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 3.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 1 1 .708-.708L2 7.293l1.146-1.147a.5.5 0 0 1 .708 0zm0 4a.5.5 0 0 1 0 .708l-1.5 1.5a.5.5 0 0 1-.708 0l-.5-.5a.5.5 0 0 1 .708-.708l.146.147l1.146-1.147a.5.5 0 0 1 .708 0z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 660 B |
@@ -35,6 +35,7 @@ import CheckBoldIcon from './ic-check-bold.svg'
|
|||||||
import CheckCircleFilledIcon from './ic-check-circle-filled.svg'
|
import CheckCircleFilledIcon from './ic-check-circle-filled.svg'
|
||||||
import CheckCircleIcon from './ic-check-circle.svg'
|
import CheckCircleIcon from './ic-check-circle.svg'
|
||||||
import CheckIcon from './ic-check.svg'
|
import CheckIcon from './ic-check.svg'
|
||||||
|
import CheckListIcon from './ic-list-check.svg'
|
||||||
import ChevronDownIcon from './ic-chevron-down.svg'
|
import ChevronDownIcon from './ic-chevron-down.svg'
|
||||||
import ChevronLeftIcon from './ic-chevron-left.svg'
|
import ChevronLeftIcon from './ic-chevron-left.svg'
|
||||||
import ChevronRightIcon from './ic-chevron-right.svg'
|
import ChevronRightIcon from './ic-chevron-right.svg'
|
||||||
@@ -254,6 +255,7 @@ export {
|
|||||||
CheckCircleFilledIcon,
|
CheckCircleFilledIcon,
|
||||||
CheckCircleIcon,
|
CheckCircleIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
|
CheckListIcon,
|
||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
ChevronLeftIcon,
|
ChevronLeftIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ export const IconNameToSvgMapping = {
|
|||||||
'link-off': icons.LinkOffIcon,
|
'link-off': icons.LinkOffIcon,
|
||||||
'list-bulleted': icons.ListBulleted,
|
'list-bulleted': icons.ListBulleted,
|
||||||
'list-numbered': icons.ListNumbered,
|
'list-numbered': icons.ListNumbered,
|
||||||
|
'list-check': icons.CheckListIcon,
|
||||||
'lock-filled': icons.LockFilledIcon,
|
'lock-filled': icons.LockFilledIcon,
|
||||||
'menu-arrow-down-alt': icons.MenuArrowDownAlt,
|
'menu-arrow-down-alt': icons.MenuArrowDownAlt,
|
||||||
'menu-arrow-down': icons.MenuArrowDownIcon,
|
'menu-arrow-down': icons.MenuArrowDownIcon,
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
CSSProperties,
|
ComponentPropsWithoutRef,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
KeyboardEventHandler,
|
KeyboardEventHandler,
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
useRef,
|
useRef,
|
||||||
@@ -12,15 +11,11 @@ import { useListKeyboardNavigation } from '@/Hooks/useListKeyboardNavigation'
|
|||||||
import { mergeRefs } from '@/Hooks/mergeRefs'
|
import { mergeRefs } from '@/Hooks/mergeRefs'
|
||||||
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
import { MutuallyExclusiveMediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
|
||||||
|
|
||||||
type MenuProps = {
|
interface MenuProps extends ComponentPropsWithoutRef<'menu'> {
|
||||||
className?: string
|
|
||||||
style?: CSSProperties | undefined
|
|
||||||
a11yLabel: string
|
a11yLabel: string
|
||||||
children: ReactNode
|
|
||||||
closeMenu?: () => void
|
closeMenu?: () => void
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
initialFocus?: number
|
initialFocus?: number
|
||||||
onKeyDown?: KeyboardEventHandler<HTMLMenuElement>
|
|
||||||
shouldAutoFocus?: boolean
|
shouldAutoFocus?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +30,7 @@ const Menu = forwardRef(
|
|||||||
initialFocus,
|
initialFocus,
|
||||||
onKeyDown,
|
onKeyDown,
|
||||||
shouldAutoFocus = true,
|
shouldAutoFocus = true,
|
||||||
|
...props
|
||||||
}: MenuProps,
|
}: MenuProps,
|
||||||
forwardedRef,
|
forwardedRef,
|
||||||
) => {
|
) => {
|
||||||
@@ -73,6 +69,7 @@ const Menu = forwardRef(
|
|||||||
ref={mergeRefs([menuElementRef, forwardedRef])}
|
ref={mergeRefs([menuElementRef, forwardedRef])}
|
||||||
style={style}
|
style={style}
|
||||||
aria-label={a11yLabel}
|
aria-label={a11yLabel}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</menu>
|
</menu>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { PlatformedKeyboardShortcut } from '@standardnotes/ui-services'
|
|||||||
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
import { KeyboardShortcutIndicator } from '../KeyboardShortcutIndicator/KeyboardShortcutIndicator'
|
||||||
import MenuListItem from './MenuListItem'
|
import MenuListItem from './MenuListItem'
|
||||||
|
|
||||||
type MenuItemProps = {
|
export interface MenuItemProps extends ComponentPropsWithoutRef<'button'> {
|
||||||
children: ReactNode
|
children: ReactNode
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>
|
onClick?: MouseEventHandler<HTMLButtonElement>
|
||||||
onBlur?: (event: { relatedTarget: EventTarget | null }) => void
|
onBlur?: (event: { relatedTarget: EventTarget | null }) => void
|
||||||
@@ -17,7 +17,7 @@ type MenuItemProps = {
|
|||||||
tabIndex?: number
|
tabIndex?: number
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
shortcut?: PlatformedKeyboardShortcut
|
shortcut?: PlatformedKeyboardShortcut
|
||||||
} & ComponentPropsWithoutRef<'button'>
|
}
|
||||||
|
|
||||||
const MenuItem = forwardRef(
|
const MenuItem = forwardRef(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const BulletedListBlock = {
|
|||||||
|
|
||||||
export const ChecklistBlock = {
|
export const ChecklistBlock = {
|
||||||
name: 'Check List',
|
name: 'Check List',
|
||||||
iconName: 'check' as LexicalIconName,
|
iconName: 'list-check' as LexicalIconName,
|
||||||
keywords: ['check list', 'todo list'],
|
keywords: ['check list', 'todo list'],
|
||||||
onSelect: (editor: LexicalEditor) => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
|
onSelect: (editor: LexicalEditor) => editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const CodeOptionsPlugin = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="absolute right-6 top-2 rounded border border-border bg-default p-2">
|
<div className="absolute right-6 top-13 rounded border border-border bg-default p-2">
|
||||||
<Dropdown
|
<Dropdown
|
||||||
label="Change code block language"
|
label="Change code block language"
|
||||||
items={CODE_LANGUAGE_OPTIONS.map(([value, label]) => ({
|
items={CODE_LANGUAGE_OPTIONS.map(([value, label]) => ({
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export const SearchDialog = ({ open, closeDialog }: { open: boolean; closeDialog
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="absolute right-6 top-4 z-10 flex select-none rounded border border-border bg-default"
|
className="absolute right-6 top-13 z-10 flex select-none rounded border border-border bg-default"
|
||||||
ref={setElement}
|
ref={setElement}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -25,7 +25,16 @@ import { mergeRegister, $findMatchingParent, $getNearestNodeOfType } from '@lexi
|
|||||||
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
import { $isLinkNode, $isAutoLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'
|
||||||
import { $isListNode, ListNode } from '@lexical/list'
|
import { $isListNode, ListNode } from '@lexical/list'
|
||||||
import { $isHeadingNode } from '@lexical/rich-text'
|
import { $isHeadingNode } from '@lexical/rich-text'
|
||||||
import { ComponentPropsWithoutRef, ForwardedRef, forwardRef, useCallback, useEffect, useRef, useState } from 'react'
|
import {
|
||||||
|
ComponentPropsWithoutRef,
|
||||||
|
ForwardedRef,
|
||||||
|
ReactNode,
|
||||||
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import { CenterAlignBlock, JustifyAlignBlock, LeftAlignBlock, RightAlignBlock } from '../Blocks/Alignment'
|
import { CenterAlignBlock, JustifyAlignBlock, LeftAlignBlock, RightAlignBlock } from '../Blocks/Alignment'
|
||||||
import { BulletedListBlock, ChecklistBlock, NumberedListBlock } from '../Blocks/List'
|
import { BulletedListBlock, ChecklistBlock, NumberedListBlock } from '../Blocks/List'
|
||||||
import { CodeBlock } from '../Blocks/Code'
|
import { CodeBlock } from '../Blocks/Code'
|
||||||
@@ -48,9 +57,10 @@ import { $isLinkTextNode } from './ToolbarLinkTextEditor'
|
|||||||
import Popover from '@/Components/Popover/Popover'
|
import Popover from '@/Components/Popover/Popover'
|
||||||
import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'
|
import LexicalTableOfContents from '@lexical/react/LexicalTableOfContents'
|
||||||
import Menu from '@/Components/Menu/Menu'
|
import Menu from '@/Components/Menu/Menu'
|
||||||
import MenuItem from '@/Components/Menu/MenuItem'
|
import MenuItem, { MenuItemProps } from '@/Components/Menu/MenuItem'
|
||||||
import { remToPx } from '@/Utils'
|
import { remToPx } from '@/Utils'
|
||||||
import FloatingLinkEditor from './FloatingLinkEditor'
|
import FloatingLinkEditor from './FloatingLinkEditor'
|
||||||
|
import MenuItemSeparator from '@/Components/Menu/MenuItemSeparator'
|
||||||
|
|
||||||
const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand<string | null>('TOGGLE_LINK_AND_EDIT_COMMAND')
|
const TOGGLE_LINK_AND_EDIT_COMMAND = createCommand<string | null>('TOGGLE_LINK_AND_EDIT_COMMAND')
|
||||||
|
|
||||||
@@ -69,16 +79,32 @@ const blockTypeToBlockName = {
|
|||||||
quote: 'Quote',
|
quote: 'Quote',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const blockTypeToIconName = {
|
||||||
|
bullet: 'list-bulleted',
|
||||||
|
check: 'list-check',
|
||||||
|
code: 'code',
|
||||||
|
h1: 'h1',
|
||||||
|
h2: 'h2',
|
||||||
|
h3: 'h3',
|
||||||
|
h4: 'h4',
|
||||||
|
h5: 'h5',
|
||||||
|
h6: 'h6',
|
||||||
|
number: 'list-numbered',
|
||||||
|
paragraph: 'paragraph',
|
||||||
|
quote: 'quote',
|
||||||
|
}
|
||||||
|
|
||||||
interface ToolbarButtonProps extends ComponentPropsWithoutRef<'button'> {
|
interface ToolbarButtonProps extends ComponentPropsWithoutRef<'button'> {
|
||||||
name: string
|
name: string
|
||||||
active?: boolean
|
active?: boolean
|
||||||
iconName: string
|
iconName?: string
|
||||||
|
children?: ReactNode
|
||||||
onSelect: () => void
|
onSelect: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const ToolbarButton = forwardRef(
|
const ToolbarButton = forwardRef(
|
||||||
(
|
(
|
||||||
{ name, active, iconName, onSelect, disabled, ...props }: ToolbarButtonProps,
|
{ name, active, iconName, children, onSelect, disabled, ...props }: ToolbarButtonProps,
|
||||||
ref: ForwardedRef<HTMLButtonElement>,
|
ref: ForwardedRef<HTMLButtonElement>,
|
||||||
) => {
|
) => {
|
||||||
const [editor] = useLexicalComposerContext()
|
const [editor] = useLexicalComposerContext()
|
||||||
@@ -105,11 +131,15 @@ const ToolbarButton = forwardRef(
|
|||||||
active && 'bg-info text-info-contrast',
|
active && 'bg-info text-info-contrast',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Icon
|
{children ? (
|
||||||
type={iconName}
|
children
|
||||||
size="custom"
|
) : iconName ? (
|
||||||
className="h-4 w-4 !text-current md:h-3.5 md:w-3.5 [&>path]:!text-current"
|
<Icon
|
||||||
/>
|
type={iconName}
|
||||||
|
size="custom"
|
||||||
|
className="h-4 w-4 !text-current md:h-3.5 md:w-3.5 [&>path]:!text-current"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
</StyledTooltip>
|
</StyledTooltip>
|
||||||
@@ -117,6 +147,24 @@ const ToolbarButton = forwardRef(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
interface ToolbarMenuItemProps extends Omit<MenuItemProps, 'children'> {
|
||||||
|
name: string
|
||||||
|
iconName: string
|
||||||
|
active?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToolbarMenuItem = ({ name, iconName, active, ...props }: ToolbarMenuItemProps) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
className={classNames('overflow-hidden md:py-2', active ? '!bg-info text-info-contrast' : 'hover:bg-contrast')}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<Icon type={iconName} className="-mt-px mr-2.5 flex-shrink-0" />
|
||||||
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{name}</span>
|
||||||
|
</MenuItem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const ToolbarPlugin = () => {
|
const ToolbarPlugin = () => {
|
||||||
const application = useApplication()
|
const application = useApplication()
|
||||||
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
const isMobile = useMediaQuery(MutuallyExclusiveMediaQueryBreakpoints.sm)
|
||||||
@@ -149,6 +197,18 @@ const ToolbarPlugin = () => {
|
|||||||
const [isTOCOpen, setIsTOCOpen] = useState(false)
|
const [isTOCOpen, setIsTOCOpen] = useState(false)
|
||||||
const tocAnchorRef = useRef<HTMLButtonElement>(null)
|
const tocAnchorRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
const [isTextFormatMenuOpen, setIsTextFormatMenuOpen] = useState(false)
|
||||||
|
const textFormatAnchorRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
const [isTextStyleMenuOpen, setIsTextStyleMenuOpen] = useState(false)
|
||||||
|
const textStyleAnchorRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
const [isAlignmentMenuOpen, setIsAlignmentMenuOpen] = useState(false)
|
||||||
|
const alignmentAnchorRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
const [isInsertMenuOpen, setIsInsertMenuOpen] = useState(false)
|
||||||
|
const insertAnchorRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
const [canUndo, setCanUndo] = useState(false)
|
const [canUndo, setCanUndo] = useState(false)
|
||||||
const [canRedo, setCanRedo] = useState(false)
|
const [canRedo, setCanRedo] = useState(false)
|
||||||
|
|
||||||
@@ -400,7 +460,7 @@ const ToolbarPlugin = () => {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'bg-contrast',
|
'bg-contrast',
|
||||||
'md:w-full md:border-b md:border-border md:px-2 md:py-1 md:translucent-ui:border-[--popover-border-color] md:translucent-ui:bg-[--popover-background-color] md:translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)]',
|
'md:w-full md:border-b md:border-border md:px-1 md:py-1 md:translucent-ui:border-[--popover-border-color] md:translucent-ui:bg-[--popover-background-color] md:translucent-ui:[backdrop-filter:var(--popover-backdrop-filter)]',
|
||||||
!canShowToolbar || !isEditable ? 'hidden' : '',
|
!canShowToolbar || !isEditable ? 'hidden' : '',
|
||||||
)}
|
)}
|
||||||
id="super-mobile-toolbar"
|
id="super-mobile-toolbar"
|
||||||
@@ -420,7 +480,7 @@ const ToolbarPlugin = () => {
|
|||||||
)}
|
)}
|
||||||
<div className="flex w-full flex-shrink-0 border-t border-border md:border-0">
|
<div className="flex w-full flex-shrink-0 border-t border-border md:border-0">
|
||||||
<Toolbar
|
<Toolbar
|
||||||
className="super-toolbar flex items-center gap-1 overflow-x-auto px-1"
|
className="super-toolbar flex items-center gap-1 overflow-x-auto px-1 md:flex-wrap"
|
||||||
ref={toolbarRef}
|
ref={toolbarRef}
|
||||||
store={toolbarStore}
|
store={toolbarStore}
|
||||||
>
|
>
|
||||||
@@ -431,6 +491,11 @@ const ToolbarPlugin = () => {
|
|||||||
onSelect={() => setIsTOCOpen(!isTOCOpen)}
|
onSelect={() => setIsTOCOpen(!isTOCOpen)}
|
||||||
ref={tocAnchorRef}
|
ref={tocAnchorRef}
|
||||||
/>
|
/>
|
||||||
|
<ToolbarButton
|
||||||
|
name="Search"
|
||||||
|
iconName="search"
|
||||||
|
onSelect={() => application.keyboardService.triggerCommand(SUPER_TOGGLE_SEARCH)}
|
||||||
|
/>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name="Undo"
|
name="Undo"
|
||||||
iconName="undo"
|
iconName="undo"
|
||||||
@@ -461,12 +526,6 @@ const ToolbarPlugin = () => {
|
|||||||
active={isUnderline}
|
active={isUnderline}
|
||||||
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')}
|
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline')}
|
||||||
/>
|
/>
|
||||||
<ToolbarButton
|
|
||||||
name="Highlight"
|
|
||||||
iconName="draw"
|
|
||||||
active={isHighlight}
|
|
||||||
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'highlight')}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name="Link"
|
name="Link"
|
||||||
iconName="link"
|
iconName="link"
|
||||||
@@ -475,24 +534,6 @@ const ToolbarPlugin = () => {
|
|||||||
editor.dispatchCommand(TOGGLE_LINK_AND_EDIT_COMMAND, '')
|
editor.dispatchCommand(TOGGLE_LINK_AND_EDIT_COMMAND, '')
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ToolbarButton
|
|
||||||
name="Strikethrough"
|
|
||||||
iconName="strikethrough"
|
|
||||||
active={isStrikethrough}
|
|
||||||
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name="Subscript"
|
|
||||||
iconName="subscript"
|
|
||||||
active={isSubscript}
|
|
||||||
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript')}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name="Superscript"
|
|
||||||
iconName="superscript"
|
|
||||||
active={isSuperscript}
|
|
||||||
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript')}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name="Inline Code"
|
name="Inline Code"
|
||||||
iconName="code-tags"
|
iconName="code-tags"
|
||||||
@@ -500,127 +541,45 @@ const ToolbarPlugin = () => {
|
|||||||
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code')}
|
onSelect={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'code')}
|
||||||
/>
|
/>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name="Search"
|
name="Formatting options"
|
||||||
iconName="search"
|
onSelect={() => {
|
||||||
onSelect={() => application.keyboardService.triggerCommand(SUPER_TOGGLE_SEARCH)}
|
setIsTextFormatMenuOpen(!isTextFormatMenuOpen)
|
||||||
/>
|
}}
|
||||||
|
ref={textFormatAnchorRef}
|
||||||
|
>
|
||||||
|
<Icon type="text" size="custom" className="h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
|
<Icon type="chevron-down" size="custom" className="ml-1 h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
|
</ToolbarButton>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name={ParagraphBlock.name}
|
name="Text style"
|
||||||
iconName={ParagraphBlock.iconName}
|
onSelect={() => {
|
||||||
active={blockType === 'paragraph'}
|
setIsTextStyleMenuOpen(!isTextStyleMenuOpen)
|
||||||
onSelect={() => ParagraphBlock.onSelect(editor)}
|
}}
|
||||||
/>
|
ref={textStyleAnchorRef}
|
||||||
|
>
|
||||||
|
<Icon type={blockTypeToIconName[blockType]} size="custom" className="h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
|
<Icon type="chevron-down" size="custom" className="ml-2 h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
|
</ToolbarButton>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name={H1Block.name}
|
name="Alignment"
|
||||||
iconName={H1Block.iconName}
|
onSelect={() => {
|
||||||
active={blockType === 'h1'}
|
setIsAlignmentMenuOpen(!isAlignmentMenuOpen)
|
||||||
onSelect={() => H1Block.onSelect(editor)}
|
}}
|
||||||
/>
|
ref={alignmentAnchorRef}
|
||||||
|
>
|
||||||
|
<Icon type="align-left" size="custom" className="h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
|
<Icon type="chevron-down" size="custom" className="ml-2 h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
|
</ToolbarButton>
|
||||||
<ToolbarButton
|
<ToolbarButton
|
||||||
name={H2Block.name}
|
name="Insert"
|
||||||
iconName={H2Block.iconName}
|
onSelect={() => {
|
||||||
active={blockType === 'h2'}
|
setIsInsertMenuOpen(!isInsertMenuOpen)
|
||||||
onSelect={() => H2Block.onSelect(editor)}
|
}}
|
||||||
/>
|
ref={insertAnchorRef}
|
||||||
<ToolbarButton
|
>
|
||||||
name={H3Block.name}
|
<Icon type="add" size="custom" className="h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
iconName={H3Block.iconName}
|
<Icon type="chevron-down" size="custom" className="ml-2 h-4 w-4 md:h-3.5 md:w-3.5" />
|
||||||
active={blockType === 'h3'}
|
</ToolbarButton>
|
||||||
onSelect={() => H3Block.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={IndentBlock.name}
|
|
||||||
iconName={IndentBlock.iconName}
|
|
||||||
onSelect={() => IndentBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={OutdentBlock.name}
|
|
||||||
iconName={OutdentBlock.iconName}
|
|
||||||
onSelect={() => OutdentBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={LeftAlignBlock.name}
|
|
||||||
iconName={LeftAlignBlock.iconName}
|
|
||||||
active={elementFormat === 'left'}
|
|
||||||
onSelect={() => LeftAlignBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={CenterAlignBlock.name}
|
|
||||||
iconName={CenterAlignBlock.iconName}
|
|
||||||
active={elementFormat === 'center'}
|
|
||||||
onSelect={() => CenterAlignBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={RightAlignBlock.name}
|
|
||||||
iconName={RightAlignBlock.iconName}
|
|
||||||
active={elementFormat === 'right'}
|
|
||||||
onSelect={() => RightAlignBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={JustifyAlignBlock.name}
|
|
||||||
iconName={JustifyAlignBlock.iconName}
|
|
||||||
active={elementFormat === 'justify'}
|
|
||||||
onSelect={() => JustifyAlignBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={BulletedListBlock.name}
|
|
||||||
iconName={BulletedListBlock.iconName}
|
|
||||||
active={blockType === 'bullet'}
|
|
||||||
onSelect={() => BulletedListBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={NumberedListBlock.name}
|
|
||||||
iconName={NumberedListBlock.iconName}
|
|
||||||
active={blockType === 'number'}
|
|
||||||
onSelect={() => NumberedListBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={ChecklistBlock.name}
|
|
||||||
iconName={ChecklistBlock.iconName}
|
|
||||||
active={blockType === 'check'}
|
|
||||||
onSelect={() => ChecklistBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name="Table"
|
|
||||||
iconName="table"
|
|
||||||
onSelect={() =>
|
|
||||||
showModal('Insert Table', (onClose) => <InsertTableDialog activeEditor={editor} onClose={onClose} />)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name="Image from URL"
|
|
||||||
iconName="image"
|
|
||||||
onSelect={() =>
|
|
||||||
showModal('Insert image from URL', (onClose) => <InsertRemoteImageDialog onClose={onClose} />)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={CodeBlock.name}
|
|
||||||
iconName={CodeBlock.iconName}
|
|
||||||
active={blockType === 'code'}
|
|
||||||
onSelect={() => CodeBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={QuoteBlock.name}
|
|
||||||
iconName={QuoteBlock.iconName}
|
|
||||||
active={blockType === 'quote'}
|
|
||||||
onSelect={() => QuoteBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={DividerBlock.name}
|
|
||||||
iconName={DividerBlock.iconName}
|
|
||||||
onSelect={() => DividerBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={CollapsibleBlock.name}
|
|
||||||
iconName={CollapsibleBlock.iconName}
|
|
||||||
onSelect={() => CollapsibleBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
<ToolbarButton
|
|
||||||
name={PasswordBlock.name}
|
|
||||||
iconName={PasswordBlock.iconName}
|
|
||||||
onSelect={() => PasswordBlock.onSelect(editor)}
|
|
||||||
/>
|
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
{isMobile && (
|
{isMobile && (
|
||||||
<button
|
<button
|
||||||
@@ -638,7 +597,7 @@ const ToolbarPlugin = () => {
|
|||||||
anchorElement={tocAnchorRef}
|
anchorElement={tocAnchorRef}
|
||||||
open={isTOCOpen}
|
open={isTOCOpen}
|
||||||
togglePopover={() => setIsTOCOpen(!isTOCOpen)}
|
togglePopover={() => setIsTOCOpen(!isTOCOpen)}
|
||||||
side="bottom"
|
side={isMobile ? 'top' : 'bottom'}
|
||||||
align="start"
|
align="start"
|
||||||
className="py-1"
|
className="py-1"
|
||||||
disableMobileFullscreenTakeover
|
disableMobileFullscreenTakeover
|
||||||
@@ -686,6 +645,207 @@ const ToolbarPlugin = () => {
|
|||||||
}}
|
}}
|
||||||
</LexicalTableOfContents>
|
</LexicalTableOfContents>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
<Popover
|
||||||
|
title="Text formatting options"
|
||||||
|
anchorElement={textFormatAnchorRef}
|
||||||
|
open={isTextFormatMenuOpen}
|
||||||
|
togglePopover={() => setIsTextFormatMenuOpen(!isTextFormatMenuOpen)}
|
||||||
|
side={isMobile ? 'top' : 'bottom'}
|
||||||
|
align="start"
|
||||||
|
className="py-1"
|
||||||
|
disableMobileFullscreenTakeover
|
||||||
|
disableFlip
|
||||||
|
containerClassName="md:!min-w-60 md:!w-auto"
|
||||||
|
>
|
||||||
|
<Menu
|
||||||
|
a11yLabel="Text formatting options"
|
||||||
|
isOpen
|
||||||
|
className="!px-0"
|
||||||
|
onClick={() => setIsTextFormatMenuOpen(false)}
|
||||||
|
>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Highlight"
|
||||||
|
iconName="draw"
|
||||||
|
active={isHighlight}
|
||||||
|
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'highlight')}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Strikethrough"
|
||||||
|
iconName="strikethrough"
|
||||||
|
active={isStrikethrough}
|
||||||
|
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'strikethrough')}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Subscript"
|
||||||
|
iconName="subscript"
|
||||||
|
active={isSubscript}
|
||||||
|
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'subscript')}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Superscript"
|
||||||
|
iconName="superscript"
|
||||||
|
active={isSuperscript}
|
||||||
|
onClick={() => editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'superscript')}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</Popover>
|
||||||
|
<Popover
|
||||||
|
title="Text style"
|
||||||
|
anchorElement={textStyleAnchorRef}
|
||||||
|
open={isTextStyleMenuOpen}
|
||||||
|
togglePopover={() => setIsTextStyleMenuOpen(!isTextStyleMenuOpen)}
|
||||||
|
side={isMobile ? 'top' : 'bottom'}
|
||||||
|
align="start"
|
||||||
|
className="py-1"
|
||||||
|
disableMobileFullscreenTakeover
|
||||||
|
disableFlip
|
||||||
|
containerClassName="md:!min-w-60 md:!w-auto"
|
||||||
|
>
|
||||||
|
<Menu a11yLabel="Text style" isOpen className="!px-0" onClick={() => setIsTextStyleMenuOpen(false)}>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Normal"
|
||||||
|
iconName="paragraph"
|
||||||
|
active={blockType === 'paragraph'}
|
||||||
|
onClick={() => ParagraphBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Heading 1"
|
||||||
|
iconName="h1"
|
||||||
|
active={blockType === 'h1'}
|
||||||
|
onClick={() => H1Block.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Heading 2"
|
||||||
|
iconName="h2"
|
||||||
|
active={blockType === 'h2'}
|
||||||
|
onClick={() => H2Block.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Heading 3"
|
||||||
|
iconName="h3"
|
||||||
|
active={blockType === 'h3'}
|
||||||
|
onClick={() => H3Block.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<MenuItemSeparator />
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Bulleted List"
|
||||||
|
iconName="list-bulleted"
|
||||||
|
active={blockType === 'bullet'}
|
||||||
|
onClick={() => BulletedListBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Numbered List"
|
||||||
|
iconName="list-numbered"
|
||||||
|
active={blockType === 'number'}
|
||||||
|
onClick={() => NumberedListBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Check List"
|
||||||
|
iconName="list-check"
|
||||||
|
active={blockType === 'check'}
|
||||||
|
onClick={() => ChecklistBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<MenuItemSeparator />
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Quote"
|
||||||
|
iconName="quote"
|
||||||
|
active={blockType === 'quote'}
|
||||||
|
onClick={() => QuoteBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Code Block"
|
||||||
|
iconName="code"
|
||||||
|
active={blockType === 'code'}
|
||||||
|
onClick={() => CodeBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</Popover>
|
||||||
|
<Popover
|
||||||
|
title="Alignment"
|
||||||
|
anchorElement={alignmentAnchorRef}
|
||||||
|
open={isAlignmentMenuOpen}
|
||||||
|
togglePopover={() => setIsAlignmentMenuOpen(!isAlignmentMenuOpen)}
|
||||||
|
side={isMobile ? 'top' : 'bottom'}
|
||||||
|
align="start"
|
||||||
|
className="py-1"
|
||||||
|
disableMobileFullscreenTakeover
|
||||||
|
disableFlip
|
||||||
|
containerClassName="md:!min-w-60 md:!w-auto"
|
||||||
|
>
|
||||||
|
<Menu a11yLabel="Alignment" isOpen className="!px-0" onClick={() => setIsAlignmentMenuOpen(false)}>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Left align"
|
||||||
|
iconName="align-left"
|
||||||
|
active={elementFormat === 'left'}
|
||||||
|
onClick={() => LeftAlignBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Center align"
|
||||||
|
iconName="align-center"
|
||||||
|
active={elementFormat === 'center'}
|
||||||
|
onClick={() => CenterAlignBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Right align"
|
||||||
|
iconName="align-right"
|
||||||
|
active={elementFormat === 'right'}
|
||||||
|
onClick={() => RightAlignBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Justify"
|
||||||
|
iconName="align-justify"
|
||||||
|
active={elementFormat === 'justify'}
|
||||||
|
onClick={() => JustifyAlignBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<MenuItemSeparator />
|
||||||
|
<ToolbarMenuItem name="Indent" iconName="indent" onClick={() => IndentBlock.onSelect(editor)} />
|
||||||
|
<ToolbarMenuItem name="Outdent" iconName="outdent" onClick={() => OutdentBlock.onSelect(editor)} />
|
||||||
|
</Menu>
|
||||||
|
</Popover>
|
||||||
|
<Popover
|
||||||
|
title="Insert"
|
||||||
|
anchorElement={insertAnchorRef}
|
||||||
|
open={isInsertMenuOpen}
|
||||||
|
togglePopover={() => setIsInsertMenuOpen(!isInsertMenuOpen)}
|
||||||
|
side={isMobile ? 'top' : 'bottom'}
|
||||||
|
align="start"
|
||||||
|
className="py-1"
|
||||||
|
disableMobileFullscreenTakeover
|
||||||
|
disableFlip
|
||||||
|
containerClassName="md:!min-w-60 md:!w-auto"
|
||||||
|
>
|
||||||
|
<Menu a11yLabel="Insert" isOpen className="!px-0" onClick={() => setIsInsertMenuOpen(false)}>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Table"
|
||||||
|
iconName="table"
|
||||||
|
onClick={() =>
|
||||||
|
showModal('Insert Table', (onClose) => <InsertTableDialog activeEditor={editor} onClose={onClose} />)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name="Image from URL"
|
||||||
|
iconName="image"
|
||||||
|
onClick={() =>
|
||||||
|
showModal('Insert image from URL', (onClose) => <InsertRemoteImageDialog onClose={onClose} />)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name={DividerBlock.name}
|
||||||
|
iconName={DividerBlock.iconName}
|
||||||
|
onClick={() => DividerBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name={CollapsibleBlock.name}
|
||||||
|
iconName={CollapsibleBlock.iconName}
|
||||||
|
onClick={() => CollapsibleBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
<ToolbarMenuItem
|
||||||
|
name={PasswordBlock.name}
|
||||||
|
iconName={PasswordBlock.iconName}
|
||||||
|
onClick={() => PasswordBlock.onSelect(editor)}
|
||||||
|
/>
|
||||||
|
</Menu>
|
||||||
|
</Popover>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user