mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-12-22 12:01:32 +00:00
refactor: extract visual part of the toolbar-button component and use it in all buttons components
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
3a5ae8df3a
commit
14ba7ea9ce
26 changed files with 127 additions and 109 deletions
|
@ -29,8 +29,8 @@ describe('File upload', () => {
|
|||
})
|
||||
it('via button', () => {
|
||||
cy.getByCypressId('editor-pane').should('have.attr', 'data-cypress-editor-ready', 'true')
|
||||
cy.getByCypressId('editor-toolbar-upload-image-button').should('be.visible')
|
||||
cy.getByCypressId('editor-toolbar-upload-image-input').selectFile(
|
||||
cy.getByCypressId('toolbar.uploadImage').should('be.visible')
|
||||
cy.getByCypressId('toolbar.uploadImage.input').selectFile(
|
||||
{
|
||||
contents: '@demoImage',
|
||||
fileName: 'demo.png',
|
||||
|
@ -80,8 +80,8 @@ describe('File upload', () => {
|
|||
statusCode: 400
|
||||
}
|
||||
)
|
||||
cy.getByCypressId('editor-toolbar-upload-image-button').should('be.visible')
|
||||
cy.getByCypressId('editor-toolbar-upload-image-input').selectFile(
|
||||
cy.getByCypressId('toolbar.uploadImage').should('be.visible')
|
||||
cy.getByCypressId('toolbar.uploadImage.input').selectFile(
|
||||
{
|
||||
contents: '@demoImage',
|
||||
fileName: 'demo.png',
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { TypeBold as IconTypeBold } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const BoldButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '**', '**')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'bold'} icon={IconTypeBold} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'bold'} icon={IconTypeBold} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { prependLinesOfSelection } from '../formatters/prepend-lines-of-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { CheckSquare as IconCheckSquare } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const CheckListButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return prependLinesOfSelection(markdownContent, currentSelection, () => `- [ ] `)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'checkList'} icon={IconCheckSquare} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'checkList'} icon={IconCheckSquare} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { changeCursorsToWholeLineIfNoToCursor } from '../formatters/utils/change-cursors-to-whole-line-if-no-to-cursor'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Code as IconCode } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -17,5 +17,5 @@ export const CodeFenceButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return wrapSelection(changeCursorsToWholeLineIfNoToCursor(markdownContent, currentSelection), '```\n', '\n```')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'code'} icon={IconCode} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'code'} icon={IconCode} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { changeCursorsToWholeLineIfNoToCursor } from '../formatters/utils/change-cursors-to-whole-line-if-no-to-cursor'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { ArrowsCollapse as IconArrowsCollapse } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -21,5 +21,5 @@ export const CollapsibleBlockButton: React.FC = () => {
|
|||
'\n:::\n'
|
||||
)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'collapsibleBlock'} icon={IconArrowsCollapse} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'collapsibleBlock'} icon={IconArrowsCollapse} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { replaceSelection } from '../formatters/replace-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { ChatDots as IconChatDots } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const CommentButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return replaceSelection({ from: currentSelection.to ?? currentSelection.from }, '> []', true)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'comment'} icon={IconChatDots} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'comment'} icon={IconChatDots} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { prependLinesOfSelection } from '../formatters/prepend-lines-of-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { TypeH1 as IconTypeH1 } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const HeaderLevelButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return prependLinesOfSelection(markdownContent, currentSelection, (line) => (line.startsWith('#') ? `#` : `# `))
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'header'} icon={IconTypeH1} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'header'} icon={IconTypeH1} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Eraser as IconEraser } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const HighlightButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '==', '==')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'highlight'} icon={IconEraser} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'highlight'} icon={IconEraser} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { replaceSelection } from '../formatters/replace-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { DashLg as IconDashLg } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const HorizontalLineButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return replaceSelection({ from: currentSelection.to ?? currentSelection.from }, '----\n', true)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'horizontalLine'} icon={IconDashLg} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'horizontalLine'} icon={IconDashLg} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { addLink } from '../formatters/add-link'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Image as IconImage } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const ImageLinkButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return addLink(markdownContent, currentSelection, '!')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'imageLink'} icon={IconImage} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'imageLink'} icon={IconImage} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { TypeItalic as IconTypeItalic } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const ItalicButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '*', '*')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'italic'} icon={IconTypeItalic} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'italic'} icon={IconTypeItalic} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { addLink } from '../formatters/add-link'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Link as IconLink } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const LinkButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return addLink(markdownContent, currentSelection)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'link'} icon={IconLink} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'link'} icon={IconLink} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { prependLinesOfSelection } from '../formatters/prepend-lines-of-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { ListOl as IconListOl } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -20,5 +20,5 @@ export const OrderedListButton: React.FC = () => {
|
|||
(line, lineIndexInBlock) => `${lineIndexInBlock + 1}. `
|
||||
)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'orderedList'} icon={IconListOl} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'orderedList'} icon={IconListOl} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { prependLinesOfSelection } from '../formatters/prepend-lines-of-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Quote as IconQuote } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const QuotesButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return prependLinesOfSelection(markdownContent, currentSelection, () => `> `)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'blockquote'} icon={IconQuote} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'blockquote'} icon={IconQuote} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { TypeStrikethrough as IconTypeStrikethrough } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const StrikethroughButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '~~', '~~')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'strikethrough'} icon={IconTypeStrikethrough} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'strikethrough'} icon={IconTypeStrikethrough} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Subscript as IconSubscript } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const SubscriptButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '~', '~')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'subscript'} icon={IconSubscript} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'subscript'} icon={IconSubscript} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { Superscript as IconSuperscript } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const SuperscriptButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '^', '^')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'superscript'} icon={IconSuperscript} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'superscript'} icon={IconSuperscript} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { wrapSelection } from '../formatters/wrap-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { TypeUnderline as IconTypeUnderline } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const UnderlineButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
|
||||
return wrapSelection(currentSelection, '++', '++')
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'underline'} icon={IconTypeUnderline} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'underline'} icon={IconTypeUnderline} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { FormatterToolbarButton } from '../formatter-toolbar-button'
|
||||
import { prependLinesOfSelection } from '../formatters/prepend-lines-of-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
import { List as IconList } from 'react-bootstrap-icons'
|
||||
|
||||
|
@ -16,5 +16,5 @@ export const UnorderedListButton: React.FC = () => {
|
|||
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
|
||||
return prependLinesOfSelection(markdownContent, currentSelection, () => `- `)
|
||||
}, [])
|
||||
return <ToolbarButton i18nKey={'unorderedList'} icon={IconList} formatter={formatter}></ToolbarButton>
|
||||
return <FormatterToolbarButton i18nKey={'unorderedList'} icon={IconList} formatter={formatter} />
|
||||
}
|
||||
|
|
|
@ -3,29 +3,26 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { cypressId } from '../../../../../utils/cypress-attribute'
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { replaceSelection } from '../formatters/replace-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import { EmojiPickerPopover } from './emoji-picker-popover'
|
||||
import styles from './emoji-picker.module.scss'
|
||||
import { extractEmojiShortCode } from './extract-emoji-short-code'
|
||||
import type { EmojiClickEventDetail } from 'emoji-picker-element/shared'
|
||||
import React, { Fragment, useCallback, useRef, useState } from 'react'
|
||||
import { Button, Overlay } from 'react-bootstrap'
|
||||
import { Overlay } from 'react-bootstrap'
|
||||
import { EmojiSmile as IconEmojiSmile } from 'react-bootstrap-icons'
|
||||
import type { OverlayInjectedProps } from 'react-bootstrap/Overlay'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders a button to open the emoji picker.
|
||||
* @see EmojiPickerPopover
|
||||
*/
|
||||
export const EmojiPickerButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
const changeEditorContent = useChangeEditorContentCallback()
|
||||
const buttonRef = useRef(null)
|
||||
const buttonRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const onEmojiSelected = useCallback(
|
||||
(emojiClickEvent: EmojiClickEventDetail) => {
|
||||
|
@ -57,15 +54,7 @@ export const EmojiPickerButton: React.FC = () => {
|
|||
offset={[0, 0]}>
|
||||
{createPopoverElement}
|
||||
</Overlay>
|
||||
<Button
|
||||
{...cypressId('show-emoji-picker')}
|
||||
variant='light'
|
||||
onClick={showPicker}
|
||||
title={t('editor.editorToolbar.emoji') ?? undefined}
|
||||
disabled={!changeEditorContent}
|
||||
ref={buttonRef}>
|
||||
<UiIcon icon={IconEmojiSmile} />
|
||||
</Button>
|
||||
<ToolbarButton i18nKey={'emoji'} icon={IconEmojiSmile} onClick={showPicker} buttonRef={buttonRef}></ToolbarButton>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ContentFormatter } from '../../change-content-context/use-change-editor-content-callback'
|
||||
import { useChangeEditorContentCallback } from '../../change-content-context/use-change-editor-content-callback'
|
||||
import type { ToolbarButtonProps } from './toolbar-button'
|
||||
import { ToolbarButton } from './toolbar-button'
|
||||
import React, { useCallback } from 'react'
|
||||
|
||||
export interface FormatterToolbarButtonProps extends Omit<ToolbarButtonProps, 'onClick' | 'disabled'> {
|
||||
formatter: ContentFormatter
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button for the editor toolbar that formats the content using a given formatter function.
|
||||
*
|
||||
* @param i18nKey Used to generate a title for the button by interpreting it as translation key in the i18n-namespace `editor.editorToolbar`-
|
||||
* @param iconName A fork awesome icon name that is shown in the button
|
||||
* @param formatter The formatter function changes the editor content on click
|
||||
*/
|
||||
export const FormatterToolbarButton: React.FC<FormatterToolbarButtonProps> = ({ i18nKey, icon, formatter }) => {
|
||||
const changeEditorContent = useChangeEditorContentCallback()
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
changeEditorContent?.(formatter)
|
||||
}, [formatter, changeEditorContent])
|
||||
|
||||
return <ToolbarButton i18nKey={i18nKey} icon={icon} onClick={onClick} disabled={!changeEditorContent} />
|
||||
}
|
|
@ -3,19 +3,17 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { cypressId } from '../../../../../utils/cypress-attribute'
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
|
||||
import { replaceSelection } from '../formatters/replace-selection'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import { createMarkdownTable } from './create-markdown-table'
|
||||
import { CustomTableSizeModal } from './custom-table-size-modal'
|
||||
import './table-picker.module.scss'
|
||||
import { TableSizePickerPopover } from './table-size-picker-popover'
|
||||
import React, { Fragment, useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { Button, Overlay } from 'react-bootstrap'
|
||||
import React, { Fragment, useCallback, useRef, useState } from 'react'
|
||||
import { Overlay } from 'react-bootstrap'
|
||||
import { Table as IconTable } from 'react-bootstrap-icons'
|
||||
import type { OverlayInjectedProps } from 'react-bootstrap/Overlay'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
enum PickerMode {
|
||||
INVISIBLE,
|
||||
|
@ -27,7 +25,6 @@ enum PickerMode {
|
|||
* Toggles the visibility of a table size picker overlay and inserts the result into the editor.
|
||||
*/
|
||||
export const TablePickerButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [pickerMode, setPickerMode] = useState<PickerMode>(PickerMode.INVISIBLE)
|
||||
const onDismiss = useCallback(() => setPickerMode(PickerMode.INVISIBLE), [])
|
||||
const onShowModal = useCallback(() => setPickerMode(PickerMode.CUSTOM), [])
|
||||
|
@ -42,7 +39,6 @@ export const TablePickerButton: React.FC = () => {
|
|||
[changeEditorContent]
|
||||
)
|
||||
|
||||
const tableTitle = useMemo(() => t('editor.editorToolbar.table.titleWithoutSize'), [t])
|
||||
const button = useRef(null)
|
||||
const toggleOverlayVisibility = useCallback(() => {
|
||||
setPickerMode((oldPickerMode) => (oldPickerMode === PickerMode.INVISIBLE ? PickerMode.GRID : PickerMode.INVISIBLE))
|
||||
|
@ -71,15 +67,12 @@ export const TablePickerButton: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
{...cypressId('table-size-picker-button')}
|
||||
variant='light'
|
||||
<ToolbarButton
|
||||
i18nKey={'table.titleWithoutSize'}
|
||||
icon={IconTable}
|
||||
onClick={toggleOverlayVisibility}
|
||||
title={tableTitle}
|
||||
ref={button}
|
||||
disabled={!changeEditorContent}>
|
||||
<UiIcon icon={IconTable} />
|
||||
</Button>
|
||||
buttonRef={button}
|
||||
/>
|
||||
<Overlay
|
||||
target={button.current}
|
||||
onHide={onOverlayHide}
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.table-cell {
|
||||
.cell {
|
||||
&.selected {
|
||||
background: var(--bs-primary);
|
||||
border-color: transparent;
|
||||
}
|
||||
background: transparent;
|
||||
border-color: var(--bs-secondary);
|
||||
margin: 1px;
|
||||
border-radius: 2px;
|
||||
border: solid 1px var(--bs-dark);
|
||||
border: solid 1px;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { concatCssClasses } from '../../../../../utils/concat-css-classes'
|
||||
import { cypressAttribute, cypressId } from '../../../../../utils/cypress-attribute'
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { createNumberRangeArray } from '../../../../common/number-range/number-range'
|
||||
|
@ -55,7 +56,7 @@ export const TableSizePickerPopover = React.forwardRef<HTMLDivElement, TableSize
|
|||
return (
|
||||
<div
|
||||
key={`${row}_${col}`}
|
||||
className={`${styles['table-cell']} ${selected ? 'bg-primary border-primary' : ''}`}
|
||||
className={concatCssClasses(styles.cell, { [styles.selected]: selected })}
|
||||
{...cypressAttribute('selected', selected ? 'true' : 'false')}
|
||||
{...cypressAttribute('col', `${col + 1}`)}
|
||||
{...cypressAttribute('row', `${row + 1}`)}
|
||||
|
@ -70,7 +71,7 @@ export const TableSizePickerPopover = React.forwardRef<HTMLDivElement, TableSize
|
|||
)
|
||||
|
||||
return (
|
||||
<Popover ref={ref} {...cypressId('table-size-picker-popover')} className={`bg-light`} {...props}>
|
||||
<Popover ref={ref} {...cypressId('table-size-picker-popover')} className={`bg-body`} {...props}>
|
||||
<Popover.Header>
|
||||
<TableSizeText tableSize={tableSize} />
|
||||
</Popover.Header>
|
||||
|
|
|
@ -5,9 +5,8 @@
|
|||
*/
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import type { ContentFormatter } from '../../change-content-context/use-change-editor-content-callback'
|
||||
import { useChangeEditorContentCallback } from '../../change-content-context/use-change-editor-content-callback'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import type { PropsWithChildren, RefObject } from 'react'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import type { Icon } from 'react-bootstrap-icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
@ -15,33 +14,41 @@ import { useTranslation } from 'react-i18next'
|
|||
export interface ToolbarButtonProps {
|
||||
i18nKey: string
|
||||
icon: Icon
|
||||
formatter: ContentFormatter
|
||||
onClick: () => void
|
||||
disabled?: boolean
|
||||
buttonRef?: RefObject<HTMLButtonElement>
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a button for the editor toolbar that formats the content using a given formatter function.
|
||||
* Renders a button for the editor toolbar.
|
||||
*
|
||||
* @param i18nKey Used to generate a title for the button by interpreting it as translation key in the i18n-namespace `editor.editorToolbar`-
|
||||
* @param iconName A fork awesome icon name that is shown in the button
|
||||
* @param formatter The formatter function changes the editor content on click
|
||||
* @param iconName An icon that is shown in the button
|
||||
* @param onClick A callback that is executed on click
|
||||
* @param disabled Defines if the button is disabled
|
||||
* @param buttonRef A reference to the button element
|
||||
*/
|
||||
export const ToolbarButton: React.FC<ToolbarButtonProps> = ({ i18nKey, icon, formatter }) => {
|
||||
export const ToolbarButton: React.FC<PropsWithChildren<ToolbarButtonProps>> = ({
|
||||
i18nKey,
|
||||
icon,
|
||||
onClick,
|
||||
disabled = false,
|
||||
buttonRef,
|
||||
children
|
||||
}) => {
|
||||
const { t } = useTranslation('', { keyPrefix: 'editor.editorToolbar' })
|
||||
const changeEditorContent = useChangeEditorContentCallback()
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
changeEditorContent?.(formatter)
|
||||
}, [formatter, changeEditorContent])
|
||||
const title = useMemo(() => t(i18nKey), [i18nKey, t])
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant='light'
|
||||
variant={'light'}
|
||||
onClick={onClick}
|
||||
title={title}
|
||||
disabled={!changeEditorContent}
|
||||
ref={buttonRef}
|
||||
disabled={disabled}
|
||||
{...cypressId('toolbar.' + i18nKey)}>
|
||||
<UiIcon icon={icon} />
|
||||
{children}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,18 +5,16 @@
|
|||
*/
|
||||
import { cypressId } from '../../../../../utils/cypress-attribute'
|
||||
import { Logger } from '../../../../../utils/logger'
|
||||
import { UiIcon } from '../../../../common/icons/ui-icon'
|
||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
||||
import { acceptedMimeTypes } from '../../../../common/upload-image-mimetypes'
|
||||
import { UploadInput } from '../../../../common/upload-input'
|
||||
import { useCodemirrorReferenceContext } from '../../../change-content-context/codemirror-reference-context'
|
||||
import { useHandleUpload } from '../../hooks/use-handle-upload'
|
||||
import { ToolbarButton } from '../toolbar-button'
|
||||
import { extractSelectedText } from './extract-selected-text'
|
||||
import { Optional } from '@mrdrogdrog/optional'
|
||||
import React, { Fragment, useCallback, useRef } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { Upload as IconUpload } from 'react-bootstrap-icons'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
const logger = new Logger('Upload image button')
|
||||
|
||||
|
@ -24,7 +22,6 @@ const logger = new Logger('Upload image button')
|
|||
* Shows a button that uploads a chosen file to the backend and adds the link to the note.
|
||||
*/
|
||||
export const UploadImageButton: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const clickRef = useRef<() => void>()
|
||||
const buttonClick = useCallback(() => {
|
||||
clickRef.current?.()
|
||||
|
@ -49,22 +46,16 @@ export const UploadImageButton: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button
|
||||
variant='light'
|
||||
onClick={buttonClick}
|
||||
disabled={!codeMirror}
|
||||
title={t('editor.editorToolbar.uploadImage') ?? undefined}
|
||||
{...cypressId('editor-toolbar-upload-image-button')}>
|
||||
<UiIcon icon={IconUpload} />
|
||||
</Button>
|
||||
<ShowIf condition={!!codeMirror}>
|
||||
<UploadInput
|
||||
onLoad={onUploadImage}
|
||||
allowedFileTypes={acceptedMimeTypes}
|
||||
onClickRef={clickRef}
|
||||
{...cypressId('editor-toolbar-upload-image-input')}
|
||||
/>
|
||||
</ShowIf>
|
||||
<ToolbarButton i18nKey={'uploadImage'} icon={IconUpload} onClick={buttonClick}>
|
||||
<ShowIf condition={!!codeMirror}>
|
||||
<UploadInput
|
||||
onLoad={onUploadImage}
|
||||
allowedFileTypes={acceptedMimeTypes}
|
||||
onClickRef={clickRef}
|
||||
{...cypressId('toolbar.uploadImage.input')}
|
||||
/>
|
||||
</ShowIf>
|
||||
</ToolbarButton>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue