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