Merge pull request #20436 from overleaf/ii-bs5-editor-toolbar

[web] BS5 editor toolbar

GitOrigin-RevId: a517fd52d648d165e89231d6f5551c026a951c43
This commit is contained in:
ilkin-overleaf
2024-10-01 08:04:42 +00:00
committed by Copybot
parent 9aef0cee70
commit 35728d7681
59 changed files with 1764 additions and 764 deletions
@@ -1,37 +1,28 @@
import { FC, memo, useRef } from 'react'
import { Button, ListGroup, Overlay, Popover } from 'react-bootstrap'
import Icon from '../../../../shared/components/icon'
import useDropdown from '../../../../shared/hooks/use-dropdown'
import Tooltip from '../../../../shared/components/tooltip'
import OLListGroup from '@/features/ui/components/ol/ol-list-group'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
import { EditorView } from '@codemirror/view'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import { useCodeMirrorViewContext } from '../codemirror-context'
import MaterialIcon from '../../../../shared/components/material-icon'
export const ToolbarButtonMenu: FC<{
id: string
label: string
icon: string
materialIcon?: boolean
icon: React.ReactNode
altCommand?: (view: EditorView) => void
}> = memo(function ButtonMenu({
icon,
id,
label,
materialIcon,
altCommand,
children,
}) {
}> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) {
const target = useRef<any>(null)
const { open, onToggle, ref } = useDropdown()
const view = useCodeMirrorViewContext()
const button = (
<Button
<button
type="button"
className="ol-cm-toolbar-button"
className="ol-cm-toolbar-button btn"
aria-label={label}
bsStyle={null}
onMouseDown={event => {
event.preventDefault()
event.stopPropagation()
@@ -48,36 +39,36 @@ export const ToolbarButtonMenu: FC<{
}}
ref={target}
>
{materialIcon ? <MaterialIcon type={icon} /> : <Icon type={icon} fw />}
</Button>
{icon}
</button>
)
const overlay = (
<Overlay
<OLOverlay
show={open}
target={target.current}
placement="bottom"
container={view.dom}
containerPadding={0}
animation
transition
rootClose
onHide={() => onToggle(false)}
>
<Popover
<OLPopover
id={`${id}-menu`}
ref={ref}
className="ol-cm-toolbar-button-menu-popover"
>
<ListGroup
<OLListGroup
role="menu"
onClick={() => {
onToggle(false)
}}
>
{children}
</ListGroup>
</Popover>
</Overlay>
</OLListGroup>
</OLPopover>
</OLOverlay>
)
if (!label) {
@@ -91,14 +82,14 @@ export const ToolbarButtonMenu: FC<{
return (
<>
<Tooltip
<OLTooltip
hidden={open}
id={id}
description={<div>{label}</div>}
overlayProps={{ placement: 'bottom' }}
>
{button}
</Tooltip>
</OLTooltip>
{overlay}
</>
)
@@ -1,6 +1,8 @@
import { ListGroupItem } from 'react-bootstrap'
import { ToolbarButtonMenu } from './button-menu'
import Icon from '../../../../shared/components/icon'
import Icon from '@/shared/components/icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import MaterialIcon from '@/shared/components/material-icon'
import OLListGroupItem from '@/features/ui/components/ol/ol-list-group-item'
import { memo, useCallback } from 'react'
import { FigureModalSource } from '../figure-modal/figure-modal-context'
import { useTranslation } from 'react-i18next'
@@ -32,44 +34,61 @@ export const InsertFigureDropdown = memo(function InsertFigureDropdown() {
<ToolbarButtonMenu
id="toolbar-figure"
label={t('toolbar_insert_figure')}
icon="picture-o"
icon={
<BootstrapVersionSwitcher
bs3={<Icon fw type="picture-o" />}
bs5={<MaterialIcon type="imagesmode" />}
/>
}
altCommand={insertFigure}
>
<ListGroupItem
<OLListGroupItem
onClick={() =>
openFigureModal(FigureModalSource.FILE_UPLOAD, 'file-upload')
}
>
<Icon type="upload" fw />
<BootstrapVersionSwitcher
bs3={<Icon type="upload" fw />}
bs5={<MaterialIcon type="upload" />}
/>
{t('upload_from_computer')}
</ListGroupItem>
<ListGroupItem
</OLListGroupItem>
<OLListGroupItem
onClick={() =>
openFigureModal(FigureModalSource.FILE_TREE, 'current-project')
}
>
<Icon type="archive" fw />
<BootstrapVersionSwitcher
bs3={<Icon type="archive" fw />}
bs5={<MaterialIcon type="inbox" />}
/>
{t('from_project_files')}
</ListGroupItem>
</OLListGroupItem>
{(hasLinkedProjectFileFeature || hasLinkedProjectOutputFileFeature) && (
<ListGroupItem
<OLListGroupItem
onClick={() =>
openFigureModal(FigureModalSource.OTHER_PROJECT, 'other-project')
}
>
<Icon type="folder-open" fw />
<BootstrapVersionSwitcher
bs3={<Icon type="folder-open" fw />}
bs5={<MaterialIcon type="folder_open" />}
/>
{t('from_another_project')}
</ListGroupItem>
</OLListGroupItem>
)}
{hasLinkUrlFeature && (
<ListGroupItem
<OLListGroupItem
onClick={() =>
openFigureModal(FigureModalSource.FROM_URL, 'from-url')
}
>
<Icon type="globe" fw />
<BootstrapVersionSwitcher
bs3={<Icon type="globe" fw />}
bs5={<MaterialIcon type="public" />}
/>
{t('from_url')}
</ListGroupItem>
</OLListGroupItem>
)}
</ToolbarButtonMenu>
)
@@ -1,4 +1,3 @@
import { ListGroupItem } from 'react-bootstrap'
import { ToolbarButtonMenu } from './button-menu'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import MaterialIcon from '../../../../shared/components/material-icon'
@@ -9,6 +8,7 @@ import {
wrapInInlineMath,
} from '../../extensions/toolbar/commands'
import { memo } from 'react'
import OLListGroupItem from '@/features/ui/components/ol/ol-list-group-item'
export const MathDropdown = memo(function MathDropdown() {
const { t } = useTranslation()
@@ -18,10 +18,9 @@ export const MathDropdown = memo(function MathDropdown() {
<ToolbarButtonMenu
id="toolbar-math"
label={t('toolbar_insert_math')}
icon="calculate"
materialIcon
icon={<MaterialIcon type="calculate" />}
>
<ListGroupItem
<OLListGroupItem
aria-label={t('toolbar_insert_inline_math')}
onClick={event => {
emitToolbarEvent(view, 'toolbar-inline-math')
@@ -32,8 +31,8 @@ export const MathDropdown = memo(function MathDropdown() {
>
<MaterialIcon type="123" />
<span>{t('toolbar_insert_inline_math')}</span>
</ListGroupItem>
<ListGroupItem
</OLListGroupItem>
<OLListGroupItem
aria-label={t('toolbar_insert_display_math')}
onClick={event => {
emitToolbarEvent(view, 'toolbar-display-math')
@@ -44,7 +43,7 @@ export const MathDropdown = memo(function MathDropdown() {
>
<MaterialIcon type="view_day" />
<span>{t('toolbar_insert_display_math')}</span>
</ListGroupItem>
</OLListGroupItem>
</ToolbarButtonMenu>
)
})
@@ -1,21 +1,26 @@
import { FC, LegacyRef, useRef } from 'react'
import { Button, Overlay, Popover } from 'react-bootstrap'
import { FC, useRef } from 'react'
import classnames from 'classnames'
import Icon from '../../../../shared/components/icon'
import MaterialIcon from '@/shared/components/material-icon'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { useCodeMirrorViewContext } from '../codemirror-context'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
import { bsVersion } from '@/features/utils/bootstrap-5'
export const ToolbarOverflow: FC<{
overflowed: boolean
overflowOpen: boolean
setOverflowOpen: (open: boolean) => void
overflowRef?: LegacyRef<Popover>
overflowRef?: React.Ref<HTMLDivElement>
}> = ({ overflowed, overflowOpen, setOverflowOpen, overflowRef, children }) => {
const buttonRef = useRef<Button>(null)
const buttonRef = useRef<HTMLButtonElement>(null)
const view = useCodeMirrorViewContext()
const className = classnames(
'ol-cm-toolbar-button',
'ol-cm-toolbar-overflow-toggle',
bsVersion({ bs3: 'btn' }),
{
'ol-cm-toolbar-overflow-toggle-visible': overflowed,
}
@@ -23,13 +28,12 @@ export const ToolbarOverflow: FC<{
return (
<>
<Button
<button
ref={buttonRef}
type="button"
id="toolbar-more"
className={className}
aria-label="More"
bsStyle={null}
onMouseDown={event => {
event.preventDefault()
event.stopPropagation()
@@ -38,22 +42,26 @@ export const ToolbarOverflow: FC<{
setOverflowOpen(!overflowOpen)
}}
>
<Icon type="ellipsis-h" fw />
</Button>
<BootstrapVersionSwitcher
bs3={<Icon type="ellipsis-h" fw />}
bs5={<MaterialIcon type="more_horiz" />}
/>
</button>
<Overlay
<OLOverlay
show={overflowOpen}
target={buttonRef.current ?? undefined}
target={buttonRef.current}
placement="bottom"
container={view.dom}
containerPadding={0}
animation
// containerPadding={0}
transition
rootClose
onHide={() => setOverflowOpen(false)}
>
<Popover id="popover-toolbar-overflow" ref={overflowRef}>
<OLPopover id="popover-toolbar-overflow" ref={overflowRef}>
<div className="ol-cm-toolbar-overflow">{children}</div>
</Popover>
</Overlay>
</OLPopover>
</OLOverlay>
</>
)
}
@@ -8,7 +8,8 @@ import {
setSectionHeadingLevel,
} from '../../extensions/toolbar/sections'
import { useCallback, useMemo, useRef } from 'react'
import { Overlay, Popover } from 'react-bootstrap'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
import useEventListener from '../../../../shared/hooks/use-event-listener'
import useDropdown from '../../../../shared/hooks/use-dropdown'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
@@ -69,17 +70,27 @@ export const SectionHeadingDropdown = () => {
</button>
{overflowOpen && (
<Overlay
<OLOverlay
show
onHide={() => setOverflowOpen(false)}
animation={false}
transition={false}
container={view.dom}
containerPadding={0}
placement="bottom"
rootClose
target={toggleButtonRef.current ?? undefined}
target={toggleButtonRef.current}
popperConfig={{
modifiers: [
{
name: 'offset',
options: {
offset: [0, 1],
},
},
],
}}
>
<Popover
<OLPopover
id="popover-toolbar-section-heading"
className="ol-cm-toolbar-menu-popover"
>
@@ -113,8 +124,8 @@ export const SectionHeadingDropdown = () => {
</button>
))}
</div>
</Popover>
</Overlay>
</OLPopover>
</OLOverlay>
)}
</>
)
@@ -2,9 +2,10 @@ import { FC, memo, useCallback, useRef, useState } from 'react'
import * as commands from '../../extensions/toolbar/commands'
import { useTranslation } from 'react-i18next'
import useDropdown from '../../../../shared/hooks/use-dropdown'
import { Button, Overlay, Popover } from 'react-bootstrap'
import { useCodeMirrorViewContext } from '../codemirror-context'
import Tooltip from '../../../../shared/components/tooltip'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import OLOverlay from '@/features/ui/components/ol/ol-overlay'
import OLPopover from '@/features/ui/components/ol/ol-popover'
import MaterialIcon from '../../../../shared/components/material-icon'
import classNames from 'classnames'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
@@ -27,17 +28,16 @@ export const TableInserterDropdown = memo(() => {
return (
<>
<Tooltip
<OLTooltip
hidden={open}
id="toolbar-table"
description={<div>{t('toolbar_insert_table')}</div>}
overlayProps={{ placement: 'bottom' }}
>
<Button
<button
type="button"
className="ol-cm-toolbar-button"
className="ol-cm-toolbar-button btn"
aria-label={t('toolbar_insert_table')}
bsStyle={null}
onMouseDown={event => {
event.preventDefault()
event.stopPropagation()
@@ -48,19 +48,19 @@ export const TableInserterDropdown = memo(() => {
ref={target}
>
<MaterialIcon type="table_chart" />
</Button>
</Tooltip>
<Overlay
</button>
</OLTooltip>
<OLOverlay
show={open}
target={target.current}
placement="bottom"
container={view.dom}
containerPadding={0}
animation
transition
rootClose
onHide={() => onToggle(false)}
>
<Popover
<OLPopover
id="toolbar-table-menu"
ref={ref}
className="ol-cm-toolbar-button-menu-popover ol-cm-toolbar-button-menu-popover-unstyled"
@@ -68,8 +68,8 @@ export const TableInserterDropdown = memo(() => {
<div className="ol-cm-toolbar-table-grid-popover">
<SizeGrid sizeX={10} sizeY={10} onSizeSelected={onSizeSelected} />
</div>
</Popover>
</Overlay>
</OLPopover>
</OLOverlay>
</>
)
})
@@ -1,11 +1,13 @@
import { memo, useCallback } from 'react'
import { EditorView } from '@codemirror/view'
import { useCodeMirrorViewContext } from '../codemirror-context'
import { Button } from 'react-bootstrap'
import classnames from 'classnames'
import Tooltip from '../../../../shared/components/tooltip'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import Icon from '../../../../shared/components/icon'
import MaterialIcon from '@/shared/components/material-icon'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { bsVersion } from '@/features/utils/bootstrap-5'
export const ToolbarButton = memo<{
id: string
@@ -49,18 +51,31 @@ export const ToolbarButton = memo<{
)
const button = (
<Button
className={classnames('ol-cm-toolbar-button', className, { hidden })}
<button
className={classnames(
'ol-cm-toolbar-button',
bsVersion({ bs3: 'btn' }),
className,
{
active,
hidden,
}
)}
aria-label={label}
onMouseDown={handleMouseDown}
onClick={!disabled ? handleClick : undefined}
bsStyle={null}
active={active}
aria-disabled={disabled}
type="button"
>
{textIcon ? icon : <Icon type={icon} fw accessibilityLabel={label} />}
</Button>
{textIcon ? (
icon
) : (
<BootstrapVersionSwitcher
bs3={<Icon type={icon} fw accessibilityLabel={label} />}
bs5={<MaterialIcon type={icon} accessibilityLabel={label} />}
/>
)}
</button>
)
if (!label) {
@@ -75,12 +90,12 @@ export const ToolbarButton = memo<{
)
return (
<Tooltip
<OLTooltip
id={id}
description={description}
overlayProps={{ placement: 'bottom' }}
>
{button}
</Tooltip>
</OLTooltip>
)
})
@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'
import { MathDropdown } from './math-dropdown'
import { TableInserterDropdown } from './table-inserter-dropdown'
import { withinFormattingCommand } from '@/features/source-editor/utils/tree-operations/formatting'
import { bsVersion } from '@/features/utils/bootstrap-5'
const isMac = /Mac/.test(window.navigator?.platform)
@@ -37,19 +38,22 @@ export const ToolbarItems: FC<{
return (
<>
{showGroup('group-history') && (
<div className="ol-cm-toolbar-button-group">
<div
className="ol-cm-toolbar-button-group"
aria-label={t('toolbar_undo_redo_actions')}
>
<ToolbarButton
id="toolbar-undo"
label={t('toolbar_undo')}
command={undo}
icon="undo"
icon={bsVersion({ bs5: 'undo', bs3: 'undo' }) as string}
shortcut={isMac ? '⌘Z' : 'Ctrl+Z'}
/>
<ToolbarButton
id="toolbar-redo"
label={t('toolbar_redo')}
command={redo}
icon="repeat"
icon={bsVersion({ bs5: 'redo', bs3: 'repeat' }) as string}
shortcut={isMac ? '⇧⌘Z' : 'Ctrl+Y'}
/>
</div>
@@ -60,18 +64,22 @@ export const ToolbarItems: FC<{
<div
className="ol-cm-toolbar-button-group"
data-overflow="group-section"
aria-label={t('toolbar_text_formatting')}
>
<SectionHeadingDropdown />
</div>
)}
{showGroup('group-format') && (
<div className="ol-cm-toolbar-button-group">
<div
className="ol-cm-toolbar-button-group"
aria-label={t('toolbar_text_style')}
>
<ToolbarButton
id="toolbar-format-bold"
label={t('toolbar_format_bold')}
command={commands.toggleBold}
active={isActive('\\textbf')}
icon="bold"
icon={bsVersion({ bs5: 'format_bold', bs3: 'bold' }) as string}
shortcut={isMac ? '⌘B' : 'Ctrl+B'}
/>
<ToolbarButton
@@ -79,7 +87,9 @@ export const ToolbarItems: FC<{
label={t('toolbar_format_italic')}
command={commands.toggleItalic}
active={isActive('\\textit')}
icon="italic"
icon={
bsVersion({ bs5: 'format_italic', bs3: 'italic' }) as string
}
shortcut={isMac ? '⌘I' : 'Ctrl+I'}
/>
</div>
@@ -88,6 +98,7 @@ export const ToolbarItems: FC<{
<div
className="ol-cm-toolbar-button-group"
data-overflow="group-math"
aria-label={t('toolbar_insert_math_and_symbols')}
>
<MathDropdown />
{symbolPaletteAvailable && (
@@ -107,24 +118,25 @@ export const ToolbarItems: FC<{
<div
className="ol-cm-toolbar-button-group"
data-overflow="group-misc"
aria-label={t('toolbar_insert_misc')}
>
<ToolbarButton
id="toolbar-href"
label={t('toolbar_insert_link')}
command={commands.wrapInHref}
icon="link"
icon={bsVersion({ bs5: 'link', bs3: 'link' }) as string}
/>
<ToolbarButton
id="toolbar-ref"
label={t('toolbar_insert_cross_reference')}
command={commands.insertRef}
icon="tag"
icon={bsVersion({ bs5: 'sell', bs3: 'tag' }) as string}
/>
<ToolbarButton
id="toolbar-cite"
label={t('toolbar_insert_citation')}
command={commands.insertCite}
icon="book"
icon={bsVersion({ bs5: 'menu_book', bs3: 'book' }) as string}
/>
<InsertFigureDropdown />
<TableInserterDropdown />
@@ -134,24 +146,40 @@ export const ToolbarItems: FC<{
<div
className="ol-cm-toolbar-button-group"
data-overflow="group-list"
aria-label={t('toolbar_list_indentation')}
>
<ToolbarButton
id="toolbar-bullet-list"
label={t('toolbar_bullet_list')}
command={commands.toggleBulletList}
icon="list-ul"
icon={
bsVersion({
bs5: 'format_list_bulleted',
bs3: 'list-ul',
}) as string
}
/>
<ToolbarButton
id="toolbar-numbered-list"
label={t('toolbar_numbered_list')}
command={commands.toggleNumberedList}
icon="list-ol"
icon={
bsVersion({
bs5: 'format_list_numbered',
bs3: 'list-ol',
}) as string
}
/>
<ToolbarButton
id="toolbar-format-indent-decrease"
label={t('toolbar_decrease_indent')}
command={commands.indentDecrease}
icon="outdent"
icon={
bsVersion({
bs5: 'format_indent_decrease',
bs3: 'outdent',
}) as string
}
shortcut={visual ? (isMac ? '⌘[' : 'Ctrl+[') : undefined}
disabled={listDepth < 2}
/>
@@ -159,7 +187,12 @@ export const ToolbarItems: FC<{
id="toolbar-format-indent-increase"
label={t('toolbar_increase_indent')}
command={commands.indentIncrease}
icon="indent"
icon={
bsVersion({
bs5: 'format_indent_increase',
bs3: 'indent',
}) as string
}
shortcut={visual ? (isMac ? '⌘]' : 'Ctrl+]') : undefined}
disabled={listDepth < 1}
/>