mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 10:15:17 -04:00
feat(frontend): replace forkawesome with bootstrap icons
These icon replace fork awesome. A linter informs the user about the deprecation. See https://github.com/hedgedoc/hedgedoc/issues/2929 Co-authored-by: Philip Molares <philip.molares@udo.edu> Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
e7246f1484
commit
1c16e25e14
179 changed files with 4974 additions and 1943 deletions
|
@ -0,0 +1,35 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`bootstrap icon markdown extension doesn't render invalid icon 1`] = `
|
||||
<div>
|
||||
<p />
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`bootstrap icon markdown extension doesn't render missing icon 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
:bi-:
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`bootstrap icon markdown extension renders correct icon 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
<span
|
||||
data-svg-mock="true"
|
||||
data-testid="lazy-bootstrap-icon-alarm"
|
||||
fill="currentColor"
|
||||
height="1em"
|
||||
width="1em"
|
||||
/>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { isBootstrapIconName } from '../../../common/icons/bootstrap-icons'
|
||||
import { LazyBootstrapIcon } from '../../../common/icons/lazy-bootstrap-icon'
|
||||
import type { NodeReplacement } from '../../replace-components/component-replacer'
|
||||
import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import type { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
|
||||
/**
|
||||
* Replaces a bootstrap icon tag with the bootstrap icon react component.
|
||||
*
|
||||
* @see BootstrapIcon
|
||||
*/
|
||||
export class BootstrapIconComponentReplacer extends ComponentReplacer {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
public replace(node: Element): NodeReplacement {
|
||||
const iconName = this.extractIconName(node)
|
||||
if (!iconName || !isBootstrapIconName(iconName)) {
|
||||
return DO_NOT_REPLACE
|
||||
}
|
||||
return React.createElement(LazyBootstrapIcon, { icon: iconName })
|
||||
}
|
||||
|
||||
private extractIconName(element: Element): string | undefined {
|
||||
return element.name === BootstrapIconMarkdownExtension.tagName && element.attribs && element.attribs.id
|
||||
? element.attribs.id
|
||||
: undefined
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { TestMarkdownRenderer } from '../../test-utils/test-markdown-renderer'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import React from 'react'
|
||||
|
||||
describe('bootstrap icon markdown extension', () => {
|
||||
it('renders correct icon', async () => {
|
||||
const view = render(
|
||||
<TestMarkdownRenderer extensions={[new BootstrapIconMarkdownExtension()]} content={':bi-alarm:'} />
|
||||
)
|
||||
await screen.findByTestId('lazy-bootstrap-icon-alarm')
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("doesn't render missing icon", () => {
|
||||
const view = render(<TestMarkdownRenderer extensions={[new BootstrapIconMarkdownExtension()]} content={':bi-:'} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it("doesn't render invalid icon", () => {
|
||||
const view = render(
|
||||
<TestMarkdownRenderer extensions={[new BootstrapIconMarkdownExtension()]} content={':bi-123:'} />
|
||||
)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ComponentReplacer } from '../../replace-components/component-replacer'
|
||||
import { MarkdownRendererExtension } from '../base/markdown-renderer-extension'
|
||||
import { BootstrapIconComponentReplacer } from './bootstrap-icon-component-replacer'
|
||||
import { replaceBootstrapIconsMarkdownItPlugin } from './replace-bootstrap-icons'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
|
||||
/**
|
||||
* Adds Bootstrap icons via the :bi-$name: syntax.
|
||||
*/
|
||||
export class BootstrapIconMarkdownExtension extends MarkdownRendererExtension {
|
||||
public static readonly tagName = 'app-bootstrap-icon'
|
||||
|
||||
public configureMarkdownIt(markdownIt: MarkdownIt): void {
|
||||
replaceBootstrapIconsMarkdownItPlugin(markdownIt)
|
||||
}
|
||||
|
||||
public buildReplacers(): ComponentReplacer[] {
|
||||
return [new BootstrapIconComponentReplacer()]
|
||||
}
|
||||
|
||||
public buildTagNameAllowList(): string[] {
|
||||
return [BootstrapIconMarkdownExtension.tagName]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { replaceBootstrapIconsMarkdownItPlugin } from './replace-bootstrap-icons'
|
||||
import MarkdownIt from 'markdown-it'
|
||||
|
||||
describe('Replace bootstrap icons', () => {
|
||||
let markdownIt: MarkdownIt
|
||||
|
||||
beforeEach(() => {
|
||||
markdownIt = new MarkdownIt('default', {
|
||||
html: false,
|
||||
breaks: true,
|
||||
langPrefix: '',
|
||||
typographer: true
|
||||
})
|
||||
markdownIt.use(replaceBootstrapIconsMarkdownItPlugin)
|
||||
})
|
||||
it(`can detect a correct icon`, () => {
|
||||
expect(markdownIt.renderInline(':bi-alarm:')).toBe('<app-bootstrap-icon id="alarm"></app-bootstrap-icon>')
|
||||
})
|
||||
|
||||
it("won't detect an invalid id", () => {
|
||||
const invalidIcon = ':bi-invalid:'
|
||||
expect(markdownIt.renderInline(invalidIcon)).toBe(invalidIcon)
|
||||
})
|
||||
|
||||
it("won't detect an empty id", () => {
|
||||
const invalidIcon = ':bi-:'
|
||||
expect(markdownIt.renderInline(invalidIcon)).toBe(invalidIcon)
|
||||
})
|
||||
|
||||
it("won't detect a wrong id", () => {
|
||||
const invalidIcon = ':bi-%?(:'
|
||||
expect(markdownIt.renderInline(invalidIcon)).toBe(invalidIcon)
|
||||
})
|
||||
})
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { RegexOptions } from '../../../../external-types/markdown-it-regex/interface'
|
||||
import { isBootstrapIconName } from '../../../common/icons/bootstrap-icons'
|
||||
import { BootstrapIconMarkdownExtension } from './bootstrap-icon-markdown-extension'
|
||||
import type MarkdownIt from 'markdown-it'
|
||||
import markdownItRegex from 'markdown-it-regex'
|
||||
|
||||
const biRegex = /:bi-([\w-]+):/i
|
||||
|
||||
/**
|
||||
* Replacer for bootstrap icon via the :bi-$name: syntax.
|
||||
*/
|
||||
export const replaceBootstrapIconsMarkdownItPlugin: MarkdownIt.PluginSimple = (markdownIt: MarkdownIt) =>
|
||||
markdownItRegex(markdownIt, {
|
||||
name: 'bootstrap-icons',
|
||||
regex: biRegex,
|
||||
replace: (match) => {
|
||||
if (isBootstrapIconName(match)) {
|
||||
// ESLint wants to collapse this tag, but then the tag won't be valid html anymore.
|
||||
// noinspection CheckTagEmptyBody
|
||||
return `<${BootstrapIconMarkdownExtension.tagName} id="${match}"></${BootstrapIconMarkdownExtension.tagName}>`
|
||||
} else {
|
||||
return `:bi-${match}:`
|
||||
}
|
||||
}
|
||||
} as RegexOptions)
|
|
@ -1,17 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Emoji Markdown Extension renders a fork awesome code 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
<i
|
||||
class="fa fa-circle-thin"
|
||||
/>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Emoji Markdown Extension renders a skin tone code 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
|
|
|
@ -24,13 +24,6 @@ describe('Emoji Markdown Extension', () => {
|
|||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders a fork awesome code', () => {
|
||||
const view = render(
|
||||
<TestMarkdownRenderer extensions={[new EmojiMarkdownExtension()]} content={':fa-circle-thin:'} />
|
||||
)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
it('renders a skin tone code', () => {
|
||||
const view = render(<TestMarkdownRenderer extensions={[new EmojiMarkdownExtension()]} content={':skin-tone-3:'} />)
|
||||
expect(view.container).toMatchSnapshot()
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ForkAwesomeIcons } from '../../../common/fork-awesome/fork-awesome-icons'
|
||||
import emojiData from 'emoji-picker-element-data/en/emojibase/data.json'
|
||||
|
||||
interface EmojiEntry {
|
||||
|
@ -28,15 +27,7 @@ const emojiSkinToneModifierMap = [1, 2, 3, 4, 5].reduce((reduceObject, modifierV
|
|||
return reduceObject
|
||||
}, {} as ShortCodeMap)
|
||||
|
||||
const forkAwesomeIconMap = ForkAwesomeIcons.reduce((reduceObject, icon) => {
|
||||
const shortcode = `fa-${icon}`
|
||||
// noinspection CheckTagEmptyBody
|
||||
reduceObject[shortcode] = `<i class='fa fa-${icon}'></i>`
|
||||
return reduceObject
|
||||
}, {} as ShortCodeMap)
|
||||
|
||||
export const combinedEmojiData = {
|
||||
...shortCodeMap,
|
||||
...emojiSkinToneModifierMap,
|
||||
...forkAwesomeIconMap
|
||||
...emojiSkinToneModifierMap
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ import { ClickShield } from '../../replace-components/click-shield/click-shield'
|
|||
import type { NativeRenderer, NodeReplacement, SubNodeTransform } from '../../replace-components/component-replacer'
|
||||
import { ComponentReplacer, DO_NOT_REPLACE } from '../../replace-components/component-replacer'
|
||||
import type { Element } from 'domhandler'
|
||||
import React from 'react'
|
||||
import { Globe as IconGlobe } from 'react-bootstrap-icons'
|
||||
|
||||
/**
|
||||
* Capsules <iframe> elements with a click shield.
|
||||
|
@ -19,7 +21,7 @@ export class IframeCapsuleReplacer extends ComponentReplacer {
|
|||
DO_NOT_REPLACE
|
||||
) : (
|
||||
<ClickShield
|
||||
hoverIcon={'globe'}
|
||||
hoverIcon={IconGlobe}
|
||||
targetDescription={node.attribs.src}
|
||||
data-cypress-id={'iframe-capsule-click-shield'}>
|
||||
{nativeRenderer()}
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import { acceptedMimeTypes } from '../../../common/upload-image-mimetypes'
|
||||
import { useOnImageUpload } from './hooks/use-on-image-upload'
|
||||
import { usePlaceholderSizeStyle } from './hooks/use-placeholder-size-style'
|
||||
import styles from './image-placeholder.module.scss'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { Upload as IconUpload } from 'react-bootstrap-icons'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface PlaceholderImageFrameProps {
|
||||
|
@ -105,7 +106,7 @@ export const ImagePlaceholder: React.FC<PlaceholderImageFrameProps> = ({
|
|||
</div>
|
||||
</div>
|
||||
<Button size={'sm'} variant={'primary'} onClick={uploadButtonClicked}>
|
||||
<ForkAwesomeIcon icon={'upload'} fixedWidth={true} className='my-2' />
|
||||
<UiIcon icon={IconUpload} className='my-2' />
|
||||
<Trans i18nKey={'editor.embeddings.placeholderImage.upload'} className='my-2' />
|
||||
</Button>
|
||||
</span>
|
||||
|
|
|
@ -3,9 +3,10 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { UiIcon } from '../../../common/icons/ui-icon'
|
||||
import { usePlaceholderSizeStyle } from '../image-placeholder/hooks/use-placeholder-size-style'
|
||||
import React from 'react'
|
||||
import { GearFill as IconGearFill } from 'react-bootstrap-icons'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface UploadIndicatingFrameProps {
|
||||
|
@ -30,7 +31,7 @@ export const UploadIndicatingFrame: React.FC<UploadIndicatingFrameProps> = ({ wi
|
|||
<span className={'h1 border-bottom-0 my-2'}>
|
||||
<Trans i18nKey={'renderer.uploadIndicator.uploadMessage'} />
|
||||
</span>
|
||||
<ForkAwesomeIcon icon={'cog'} size={'5x'} fixedWidth={true} className='my-2 fa-spin' />
|
||||
<UiIcon icon={IconGearFill} size={5} className='my-2' spin={true} />
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
import { optionalAppExtensions } from '../../../extensions/extra-integrations/optional-app-extensions'
|
||||
import type { MarkdownRendererExtension } from '../extensions/base/markdown-renderer-extension'
|
||||
import { BootstrapIconMarkdownExtension } from '../extensions/bootstrap-icons/bootstrap-icon-markdown-extension'
|
||||
import { DebuggerMarkdownExtension } from '../extensions/debugger-markdown-extension'
|
||||
import { EmojiMarkdownExtension } from '../extensions/emoji/emoji-markdown-extension'
|
||||
import { GenericSyntaxMarkdownExtension } from '../extensions/generic-syntax-markdown-extension'
|
||||
|
@ -53,6 +54,7 @@ export const useMarkdownExtensions = (
|
|||
new UploadIndicatingImageFrameMarkdownExtension(),
|
||||
new LinkAdjustmentMarkdownExtension(baseUrl),
|
||||
new EmojiMarkdownExtension(),
|
||||
new BootstrapIconMarkdownExtension(),
|
||||
new GenericSyntaxMarkdownExtension(),
|
||||
new LinkifyFixMarkdownExtension(),
|
||||
new DebuggerMarkdownExtension(),
|
||||
|
|
|
@ -6,14 +6,13 @@
|
|||
import type { PropsWithDataCypressId } from '../../../../utils/cypress-attribute'
|
||||
import { cypressId } from '../../../../utils/cypress-attribute'
|
||||
import { Logger } from '../../../../utils/logger'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import type { IconName } from '../../../common/fork-awesome/types'
|
||||
import { ShowIf } from '../../../common/show-if/show-if'
|
||||
import { ProxyImageFrame } from '../../extensions/image/proxy-image-frame'
|
||||
import styles from './click-shield.module.scss'
|
||||
import type { Property } from 'csstype'
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import type { Icon } from 'react-bootstrap-icons'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
const log = new Logger('OneClickEmbedding')
|
||||
|
@ -21,7 +20,7 @@ const log = new Logger('OneClickEmbedding')
|
|||
export interface ClickShieldProps extends PropsWithChildren<PropsWithDataCypressId> {
|
||||
onImageFetch?: () => Promise<string>
|
||||
fallbackPreviewImageUrl?: string
|
||||
hoverIcon: IconName
|
||||
hoverIcon: Icon
|
||||
targetDescription: string
|
||||
containerClassName?: string
|
||||
fallbackBackgroundColor?: Property.BackgroundColor
|
||||
|
@ -104,6 +103,16 @@ export const ClickShield: React.FC<ClickShieldProps> = ({
|
|||
|
||||
const hoverTextTranslationValues = useMemo(() => ({ target: targetDescription }), [targetDescription])
|
||||
|
||||
const icon = useMemo(
|
||||
() =>
|
||||
React.createElement(hoverIcon, {
|
||||
width: '5em',
|
||||
height: '5em',
|
||||
className: 'mb-2'
|
||||
}),
|
||||
[hoverIcon]
|
||||
)
|
||||
|
||||
return (
|
||||
<span className={containerClassName} {...cypressId(props['data-cypress-id'])}>
|
||||
<ShowIf condition={showChildren}>{children}</ShowIf>
|
||||
|
@ -114,7 +123,7 @@ export const ClickShield: React.FC<ClickShieldProps> = ({
|
|||
<span className={`${styles['preview-hover-text']}`}>
|
||||
<Trans i18nKey={'renderer.clickShield.previewHoverText'} values={hoverTextTranslationValues} />
|
||||
</span>
|
||||
<ForkAwesomeIcon icon={hoverIcon} size={'5x'} className={'mb-2'} />
|
||||
{icon}
|
||||
</span>
|
||||
</span>
|
||||
</ShowIf>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue