Merge pull request #33600 from overleaf/worktree-mg-bullet-list-compact

Combine toolbar list controls and render indentation controls conditionally

GitOrigin-RevId: 48d7c52983449566bfa21b5572915d79e595c704
This commit is contained in:
Malik Glossop
2026-05-28 08:05:48 +00:00
committed by Copybot
parent 4cfaea4621
commit 8af5c2c346
8 changed files with 173 additions and 57 deletions
@@ -2126,6 +2126,7 @@
"toolbar_insert_figure": "",
"toolbar_insert_inline_math": "",
"toolbar_insert_link": "",
"toolbar_insert_list": "",
"toolbar_insert_math": "",
"toolbar_insert_math_and_symbols": "",
"toolbar_insert_math_lowercase": "",
@@ -7,12 +7,14 @@ import OLPopover from '@/shared/components/ol/ol-popover'
import { EditorView } from '@codemirror/view'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import { useCodeMirrorViewContext } from '../codemirror-context'
import classNames from 'classnames'
export const ToolbarButtonMenu: FC<
React.PropsWithChildren<{
id: string
label: string
icon: React.ReactNode
orientation?: 'vertical' | 'horizontal'
disabled?: boolean
disablePopover?: boolean
altCommand?: (view: EditorView) => void
@@ -22,6 +24,7 @@ export const ToolbarButtonMenu: FC<
icon,
id,
label,
orientation = 'vertical',
altCommand,
onToggle,
disabled,
@@ -86,7 +89,10 @@ export const ToolbarButtonMenu: FC<
<OLPopover
id={`${id}-menu`}
ref={ref}
className="ol-cm-toolbar-button-menu-popover"
className={classNames('ol-cm-toolbar-button-menu-popover', {
'ol-cm-toolbar-button-menu-popover-horizontal':
orientation === 'horizontal',
})}
>
<OLListGroup
role="menu"
@@ -0,0 +1,61 @@
import { ToolbarButtonMenu } from './button-menu'
import { emitToolbarEvent } from '../../extensions/toolbar/utils/analytics'
import MaterialIcon from '../../../../shared/components/material-icon'
import { useTranslation } from 'react-i18next'
import { useCodeMirrorViewContext } from '../codemirror-context'
import {
toggleBulletList,
toggleNumberedList,
} from '../../extensions/toolbar/commands'
import { memo } from 'react'
import OLListGroupItem from '@/shared/components/ol/ol-list-group-item'
import OLTooltip from '@/shared/components/ol/ol-tooltip'
export const InsertListDropdown = memo(function InsertListDropdown() {
const { t } = useTranslation()
const view = useCodeMirrorViewContext()
return (
<ToolbarButtonMenu
id="toolbar-insert-list"
label={t('toolbar_insert_list')}
icon={<MaterialIcon type="format_list_bulleted" />}
orientation="horizontal"
>
<OLTooltip
id="toolbar-bullet-list"
description={t('toolbar_bulleted_list')}
overlayProps={{ placement: 'bottom' }}
>
<OLListGroupItem
aria-label={t('toolbar_bulleted_list')}
onClick={event => {
emitToolbarEvent(view, 'toolbar-bullet-list')
event.preventDefault()
toggleBulletList(view)
view.focus()
}}
>
<MaterialIcon type="format_list_bulleted" />
</OLListGroupItem>
</OLTooltip>
<OLTooltip
id="toolbar-numbered-list"
description={t('toolbar_numbered_list')}
overlayProps={{ placement: 'bottom' }}
>
<OLListGroupItem
aria-label={t('toolbar_numbered_list')}
onClick={event => {
emitToolbarEvent(view, 'toolbar-numbered-list')
event.preventDefault()
toggleNumberedList(view)
view.focus()
}}
>
<MaterialIcon type="format_list_numbered" />
</OLListGroupItem>
</OLTooltip>
</ToolbarButtonMenu>
)
})
@@ -9,6 +9,7 @@ import getMeta from '../../../../utils/meta'
import { InsertFigureDropdown } from './insert-figure-dropdown'
import { useTranslation } from 'react-i18next'
import { MathDropdown } from './math-dropdown'
import { InsertListDropdown } from './insert-list-dropdown'
import { TableDropdown } from './table-dropdown'
import { LegacyTableDropdown } from './table-inserter-dropdown-legacy'
import { withinFormattingCommand } from '@/features/source-editor/utils/tree-operations/formatting'
@@ -164,34 +165,26 @@ export const ToolbarItems: FC<{
data-overflow="group-list"
aria-label={t('toolbar_list_indentation')}
>
<ToolbarButton
id="toolbar-bullet-list"
label={t('toolbar_bulleted_list')}
command={commands.toggleBulletList}
icon="format_list_bulleted"
/>
<ToolbarButton
id="toolbar-numbered-list"
label={t('toolbar_numbered_list')}
command={commands.toggleNumberedList}
icon="format_list_numbered"
/>
<ToolbarButton
id="toolbar-format-indent-decrease"
label={t('toolbar_decrease_indent')}
command={commands.indentDecrease}
icon="format_indent_decrease"
shortcut={visual ? (isMac ? '⌘[' : 'Ctrl+[') : undefined}
disabled={listDepth < 2}
/>
<ToolbarButton
id="toolbar-format-indent-increase"
label={t('toolbar_increase_indent')}
command={commands.indentIncrease}
icon="format_indent_increase"
shortcut={visual ? (isMac ? '⌘]' : 'Ctrl+]') : undefined}
disabled={listDepth < 1}
/>
<InsertListDropdown />
{listDepth >= 1 && (
<>
<ToolbarButton
id="toolbar-format-indent-decrease"
label={t('toolbar_decrease_indent')}
command={commands.indentDecrease}
icon="format_indent_decrease"
shortcut={visual ? (isMac ? '⌘[' : 'Ctrl+[') : undefined}
disabled={listDepth < 2}
/>
<ToolbarButton
id="toolbar-format-indent-increase"
label={t('toolbar_increase_indent')}
command={commands.indentIncrease}
icon="format_indent_increase"
shortcut={visual ? (isMac ? '⌘]' : 'Ctrl+]') : undefined}
/>
</>
)}
</div>
)}
</>
@@ -107,6 +107,11 @@ const toolbarTheme = EditorView.theme({
},
},
},
'.ol-cm-toolbar-button-menu-popover-horizontal': {
'& .list-group': {
flexDirection: 'row',
},
},
'.ol-cm-toolbar-button-group': {
display: 'flex',
alignItems: 'center',
@@ -1,15 +1,21 @@
import { forwardRef } from 'react'
import { ListGroupItem, ListGroupItemProps } from 'react-bootstrap'
function OLListGroupItem(props: ListGroupItemProps) {
const as = props.as ?? 'button'
const OLListGroupItem = forwardRef<HTMLElement, ListGroupItemProps>(
function OLListGroupItem(props, ref) {
const as = props.as ?? 'button'
return (
<ListGroupItem
{...props}
as={as}
type={as === 'button' ? 'button' : undefined}
/>
)
}
return (
<ListGroupItem
{...props}
ref={ref}
as={as}
type={as === 'button' ? 'button' : undefined}
/>
)
}
)
OLListGroupItem.displayName = 'OLListGroupItem'
export default OLListGroupItem
+1
View File
@@ -2740,6 +2740,7 @@
"toolbar_insert_figure": "Insert figure",
"toolbar_insert_inline_math": "Insert inline math",
"toolbar_insert_link": "Insert link",
"toolbar_insert_list": "Insert list",
"toolbar_insert_math": "Insert math",
"toolbar_insert_math_and_symbols": "Insert math and symbols",
"toolbar_insert_math_lowercase": "Insert math",
@@ -19,6 +19,11 @@ const clickToolbarButton = (name: string) => {
cy.findByRole('button', { name }).trigger('mouseout')
}
const clickListType = (type: string) => {
cy.findByRole('button', { name: 'Insert list' }).click()
cy.findByRole('button', { name: type }).click()
}
const mountEditor = (content: string) => {
const scope = mockScope(content)
@@ -131,8 +136,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('test')
selectAll()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Bulleted list')
clickListType('Bulleted list')
cy.get('.cm-content').should('have.text', ' test')
@@ -144,8 +148,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('test')
selectAll()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
cy.get('.cm-content').should('have.text', ' test')
@@ -157,8 +160,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('test')
selectAll()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
// expose the markup
cy.get('.cm-line').eq(0).type('{rightArrow}')
@@ -173,7 +175,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
].join('')
)
clickToolbarButton('Bulleted list')
clickListType('Bulleted list')
cy.get('.cm-content').should(
'have.text',
@@ -190,8 +192,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('test')
selectAll()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
// expose the markup
cy.get('.cm-line').eq(0).type('{rightArrow}')
@@ -206,7 +207,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
].join('')
)
clickToolbarButton('Numbered list')
clickListType('Numbered list')
cy.get('.cm-content').should('have.text', 'test')
})
@@ -215,8 +216,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('test\ntest')
selectAll()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
// expose the markup
cy.get('.cm-line').eq(1).type('{rightArrow}')
@@ -252,8 +252,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
cy.get('.cm-line').eq(1).click()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
cy.get('.cm-line').eq(0).type('{upArrow}')
@@ -273,8 +272,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('test\ntest')
selectAll()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
// expose the markup
cy.get('.cm-line').eq(1).type('{rightArrow}')
@@ -310,8 +308,7 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
cy.get('.cm-line').eq(0).click()
clickToolbarButton('More editor toolbar items')
clickToolbarButton('Numbered list')
clickListType('Numbered list')
// expose the markup
cy.get('.cm-line').eq(1).type('{rightArrow}')
@@ -339,4 +336,50 @@ describe('<CodeMirrorEditor/> toolbar in Rich Text mode', function () {
mountEditor('')
cy.findByLabelText('Insert symbol').should('not.exist')
})
it('should show both list type options in the list type dropdown', function () {
mountEditor('test')
cy.findByRole('button', { name: 'Insert list' }).click()
cy.findByRole('button', { name: 'Bulleted list' }).should('exist')
cy.findByRole('button', { name: 'Numbered list' }).should('exist')
})
it('should hide indent buttons when cursor is not in a list', function () {
mountEditor('test')
cy.findByRole('button', { name: 'Increase indent' }).should('not.exist')
cy.findByRole('button', { name: 'Decrease indent' }).should('not.exist')
})
it('should show indent buttons when cursor is inside a list', function () {
mountEditor('test')
selectAll()
clickListType('Bulleted list')
cy.get('.cm-line').eq(0).click()
cy.findByRole('button', { name: 'Increase indent' }).should('exist')
cy.findByRole('button', { name: 'Decrease indent' }).should('exist')
})
it('should disable decrease indent when at the top level of a list', function () {
mountEditor('test')
selectAll()
clickListType('Bulleted list')
cy.get('.cm-line').eq(0).click()
cy.findByRole('button', { name: 'Decrease indent' }).should(
'have.attr',
'aria-disabled',
'true'
)
cy.findByRole('button', { name: 'Increase indent' }).should(
'not.have.attr',
'aria-disabled'
)
})
})