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:
Philip Molares 2023-02-05 22:05:02 +01:00 committed by Tilman Vatteroth
parent e7246f1484
commit 1c16e25e14
179 changed files with 4974 additions and 1943 deletions

View File

@ -1,84 +0,0 @@
SIL OPEN FONT LICENSE
Version 1.1 - 26 February 2007
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide development
of collaborative font projects, to support the font creation efforts of academic
and linguistic communities, and to provide a free and open framework in which
fonts may be shared and improved in partnership with others.
The OFL allows the licensed fonts to be used, studied, modified and redistributed
freely as long as they are not sold by themselves. The fonts, including any
derivative works, can be bundled, embedded, redistributed and/or sold with
any software provided that any reserved names are not used by derivative works.
The fonts and derivatives, however, cannot be released under any other type
of license. The requirement for fonts to remain under this license does not
apply to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright Holder(s)
under this license and clearly marked as such. This may include source files,
build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the copyright
statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting, or
substituting — in part or in whole — any of the components of the Original
Version, by changing formats or by porting the Font Software to a new environment.
"Author" refers to any designer, engineer, programmer, technical writer or
other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining a copy
of the Font Software, to use, study, copy, merge, embed, modify, redistribute,
and sell modified and unmodified copies of the Font Software, subject to the
following conditions:
1) Neither the Font Software nor any of its individual components, in Original
or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled, redistributed
and/or sold with any software, provided that each copy contains the above
copyright notice and this license. These can be included either as stand-alone
text files, human-readable headers or in the appropriate machine-readable
metadata fields within text or binary files as long as those fields can be
easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font Name(s)
unless explicit written permission is granted by the corresponding Copyright
Holder. This restriction only applies to the primary font name as presented
to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software
shall not be used to promote, endorse or advertise any Modified Version, except
to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s)
or with their explicit written permission.
5) The Font Software, modified or unmodified, in part or in whole, must be
distributed entirely under this license, and must not be distributed under
any other license. The requirement for fonts to remain under this license
does not apply to any document created using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT,
TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL,
INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT
SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@ -88,7 +88,7 @@ If some text is selected it will either put that as the link, if it thinks that
inserting `name=$YourName`, a time by inserting `time=$time` or a color by inserting `color=#FFFFFF` in the `[]`.
Please note that you can only specify one of those per `[]`, but you can use multiple `[]`.
5. **Emoji Picker**
This button opens the emoji picker, where you can choose an emoji or a fork awesome icon to insert into your note.
This button opens the emoji picker, where you can choose an emoji to insert into your note.
![Emoji Picker](../images/interface/toolbar/emoji.png)
## Settings

View File

@ -1,13 +1,12 @@
# HedgeDoc Flavored Markdown
HedgeDoc has its own markdown dialect which supports many features from [CommonMark][commonmark] and [Github Flavored Markdown][gfm]. It also adds some new extensions and is missing some.
HedgeDoc has its own markdown dialect which supports many features from [CommonMark][commonmark]
and [Github Flavored Markdown][gfm]. It also adds some new extensions and is missing some.
These tables tell you what exactly we support in HedgeDoc 1.x (HFM 1) and HedgeDoc 2 (HFM 2).
## Typography
| Feature | HFM 1 | HFM 2 | CommonMark | GFM |
| ------------- | :---: | :---: | :---------------: | :---------------: |
| bold | ☑️ | ☑️ | ☑️ | ☑️ |
@ -36,7 +35,8 @@ These tables tell you what exactly we support in HedgeDoc 1.x (HFM 1) and HedgeD
| task list | ☑️ | ☑️ | | ☑️ |
| defition list | ☑️ | ☑️ | | |
| emoji | [Unicode 6.1][unicode-6] | [Unicode 13][unicode-13] | | |
| [ForkAwesome][fa] | ☑️ with `<i class='fa'>` | ☑️ with shortcodes | | |
| [ForkAwesome][fa] | ☑️ with `<i class='fa'>` | removed | | |
| [Bootstrap Icons][bootstrap-icons] | | ☑️ with shortcodes | | |
| LaTeX | ☑️[^mj] | ☑️[^kt] | | |
[^highlight]: Code blocks with a given language are rendered using syntax highlighting.
@ -59,7 +59,6 @@ These tables tell you what exactly we support in HedgeDoc 1.x (HFM 1) and HedgeD
| image with given size | ☑️ | ☑️ | (☑️ with `<img>`) | (☑️ with `<img>`) |
| table of contents | ☑️ | ☑️ | | |
## Structural elements
| Feature | HFM 1 | HFM 2 | CommonMark | GFM |
@ -70,7 +69,9 @@ These tables tell you what exactly we support in HedgeDoc 1.x (HFM 1) and HedgeD
| Alerts | ☑️ | ☑️ | | |
## Embeddings
HFM 1 includes support for certain embeddings of external content by using the `{%keyword parameter %}` syntax. To increase the readability of the markdown code we decided that HFM 2 should just use plain links if possible.
HFM 1 includes support for certain embeddings of external content by using the `{%keyword parameter %}` syntax. To
increase the readability of the markdown code we decided that HFM 2 should just use plain links if possible.
| Feature | HFM 1 | HFM 2 | CommonMark | GFM |
| --------------------------------------------------- | :---: | :---------------------: | :--------: | :---: |
@ -81,10 +82,13 @@ HFM 1 includes support for certain embeddings of external content by using the `
| [Speakerdeck][speakerdeck] (`{%speakerdeck ... %}`) | ☑️ | removed | | |
| [GitHub Gist][gist] (`{%gist ... %}`) | ☑️ | with plain link[^embed] | | |
[^embed]: The special syntax from HFM 1 is deprecated, but will still work in HFM 2. However, a plain link to the content should be used.
[^embed]: The special syntax from HFM 1 is deprecated, but will still work in HFM 2. However, a plain link to the
content should be used.
## HTML
Besides the basic HTML typography elements (`<p>`, `<a>`, `<b>`, `<ins>`, `<del>`) the following more special HTML elements are supported by some specification.
Besides the basic HTML typography elements (`<p>`, `<a>`, `<b>`, `<ins>`, `<del>`) the following more special HTML
elements are supported by some specification.
| Feature | HedgeDocMark 1 | HedgeDocMark 2 | CommonMark | GFM |
| :-----------: | :------------: | :------------: | :--------: | :---: |
@ -99,14 +103,27 @@ Besides the basic HTML typography elements (`<p>`, `<a>`, `<b>`, `<ins>`, `<del>
| `<plaintext>` | | ☑️ | ☑️ | |
[fa]: https://forkaweso.me/
[bootstrap-icons]: https://icons.getbootstrap.com/
[youtube]: https://www.youtube.com/
[vimeo]: https://vimeo.com/
[slideshare]: https://www.slideshare.net/
[speakerdeck]: https://speakerdeck.com/
[gist]: https://gist.github.com/
[mathjax]: https://www.mathjax.org/
[katex]: https://katex.org/
[gfm]: https://github.github.com/gfm/
[commonmark]: https://spec.commonmark.org/
[unicode-6]: https://unicode.org/versions/Unicode6.1.0/
[unicode-13]: https://unicode.org/versions/Unicode13.0.0/

View File

@ -19,7 +19,6 @@ SPDX-License-Identifier: CC-BY-SA-4.0
- `{%pdf https://example.org/example-pdf.pdf %}` -> Embedding removed
- The use of `sequence` as code block language ([Why?](https://github.com/hedgedoc/react-client/issues/488#issuecomment-683262875))
- Comma-separated definition of tags in the yaml-frontmatter
- Fork Awesome Icons will be removed in a future release
### Removed
@ -43,6 +42,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
- F9 shortcut to sort lines
- Highlight.JS language support for `1c` was removed.
- Support for tag definitions in headings
- Fork Awesome has been replaced with [Bootstrap Icons](https://icons.getbootstrap.com/)
### Added
@ -54,9 +54,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
- HedgeDoc instances can be branded either with a '@ \<custom string\>' or '@ \<custom logo\>' after the HedgeDoc logo and text
- Images will be loaded via proxy if an image proxy is configured in the backend
- Asciinema videos may be embedded by pasting the URL of one video into a single line
- The toolbar includes an emoji and fork-awesome icon picker.
- The toolbar includes an emoji picker.
- Collapsible blocks can be added via a toolbar button or via autocompletion of "<details"
- Added shortcodes for [fork-awesome icons](https://forkaweso.me/Fork-Awesome/icons/) (e.g. `:fa-picture-o:`)
- Added shortcodes for icons (e.g. `:bi-picture:`)
- The code button now adds code fences even if the user selected nothing beforehand
- Code blocks with 'csv' as language render as tables.
- All images can be clicked to show them in full screen.

View File

@ -10,7 +10,6 @@
@import '~react-bootstrap-typeahead/css/Typeahead';
@import "~@fontsource/source-sans-pro/index.css";
@import "~twemoji-colr-font/twemoji";
@import '~fork-awesome/css/fork-awesome.min.css';
@import '~firacode/distr/fira_code';
@import "typeahead";
@import "./button-inside";
@ -79,16 +78,6 @@ body {
cursor: zoom-out;
}
.faded-fa {
.fa, &::after {
opacity: 0.5;
}
&:hover .fa, &:hover::after {
opacity: 1;
}
}
.dropup .dropdown-toggle, .dropdown-toggle {
&.no-arrow::after {
content: initial;

View File

@ -23,6 +23,16 @@ const customJestConfig = {
testEnvironment: 'jsdom',
testPathIgnorePatterns: ['/node_modules/', '/cypress/']
}
// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
module.exports = createJestConfig(customJestConfig)
module.exports = async () => {
const nextJestConfig = await createJestConfig(customJestConfig)()
return {
...nextJestConfig,
moduleNameMapper: {
...nextJestConfig.moduleNameMapper,
'^.+\\.(svg)$': '<rootDir>/src/test-utils/svg-mock.tsx',
'^react-bootstrap-icons$': '<rootDir>/src/test-utils/bootstrap-icon-mocks.tsx',
'^react-bootstrap-icons/dist/icons/.*$': '<rootDir>/src/test-utils/svg-mock.tsx'
}
}
}

View File

@ -223,7 +223,7 @@
"shortcode": "The {{shortcode}} short-code is deprecated and will be removed in a future release. Use a single line URL instead.",
"frontmatter": "The yaml-metadata is invalid.",
"frontmatter-tags": "The comma-separated definition of tags in the yaml-metadata is deprecated. Use a yaml-array instead.",
"fork-awesome": "Fork Awesome is deprecated and will be removed in a future release. Please use bootstrap icons instead. See {{link}} for more information."
"fork-awesome": "The usage of Fork Awesome is deprecated and has been removed. Please use bootstrap icons instead. See {{link}} for more information."
},
"upload": {
"uploadFile": {

View File

@ -31,29 +31,33 @@ if (isMockMode) {
`)
}
/** @type {import('@svgr/webpack').LoaderOptions} */
const svgrConfig = {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
}
}
}
]
}
}
/** @type {import('next').NextConfig} */
const rawNextConfig = {
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/i,
issuer: /\.[jt]sx?$/,
use: [
{
loader: '@svgr/webpack',
options: {
svgoConfig: {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false
}
}
}
]
}
}
options: svgrConfig
}
]
})

View File

@ -74,7 +74,6 @@
"fast-deep-equal": "3.1.3",
"firacode": "6.2.0",
"flowchart.js": "1.17.1",
"fork-awesome": "1.2.0",
"highlight.js": "11.7.0",
"htmlparser2": "8.0.1",
"i18next": "22.4.10",
@ -102,6 +101,7 @@
"next": "13.1.6",
"react": "18.2.0",
"react-bootstrap": "2.7.2",
"react-bootstrap-icons": "1.10.2",
"react-bootstrap-typeahead": "6.0.0",
"react-diff-viewer": "3.1.1",
"react-dom": "18.2.0",

View File

@ -43,8 +43,8 @@
z-index: 1000;
position: relative;
font-size: 3em;
height: 240px;
width: 203px;
height: 5em;
width: 5em;
color: #ffffff;
text-shadow: 4px 4px 0 #3b4045;
@ -55,10 +55,11 @@
animation: fill 6s infinite;
width: 100%;
&, :global(.fa) {
& > * {
position: absolute;
bottom: 0;
left: 0;
z-index: 1001;
}
}
}

View File

@ -3,11 +3,13 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../common/icons/ui-icon'
import { createNumberRangeArray } from '../../common/number-range/number-range'
import styles from './animations.module.scss'
import { IconRow } from './icon-row'
import React, { useMemo } from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { PencilFill as IconPencilFill } from 'react-bootstrap-icons'
export interface HedgeDocLogoProps {
error: boolean
@ -25,10 +27,10 @@ export const LoadingAnimation: React.FC<HedgeDocLogoProps> = ({ error }) => {
<div className={`position-relative ${error ? styles.error : ''}`}>
<div className={styles.logo}>
<div>
<ForkAwesomeIcon icon={'pencil'} className={styles.background} size={'5x'}></ForkAwesomeIcon>
<UiIcon icon={IconPencilFill} className={styles.background} size={5} />
</div>
<div className={`${styles.overlay}`}>
<ForkAwesomeIcon icon={'pencil'} size={'5x'}></ForkAwesomeIcon>
<UiIcon icon={IconPencil} size={5} />
</div>
</div>
<div className={styles.pulse}></div>

View File

@ -3,28 +3,39 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import type { IconName } from '../../common/fork-awesome/types'
import styles from './animations.module.scss'
import React, { useEffect, useState } from 'react'
import React, { Fragment, useEffect, useState } from 'react'
import type { Icon } from 'react-bootstrap-icons'
import { FileText as IconFileText } from 'react-bootstrap-icons'
import { File as IconFile } from 'react-bootstrap-icons'
import { Fonts as IconFonts } from 'react-bootstrap-icons'
import { Gear as IconGear } from 'react-bootstrap-icons'
import { KeyboardFill as IconKeyboardFill } from 'react-bootstrap-icons'
import { ListCheck as IconListCheck } from 'react-bootstrap-icons'
import { Markdown as IconMarkdown } from 'react-bootstrap-icons'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { Person as IconPerson } from 'react-bootstrap-icons'
import { Tag as IconTag } from 'react-bootstrap-icons'
import { TypeBold as IconTypeBold } from 'react-bootstrap-icons'
import { TypeItalic as IconTypeItalic } from 'react-bootstrap-icons'
const elements: IconName[] = [
'file-text',
'markdown',
'pencil',
'bold',
'italic',
'align-justify',
'tag',
'user',
'file',
'keyboard-o',
'cog',
'font'
const elements: Icon[] = [
IconFileText,
IconMarkdown,
IconPencil,
IconTypeBold,
IconTypeItalic,
IconListCheck,
IconTag,
IconPerson,
IconFile,
IconKeyboardFill,
IconGear,
IconFonts
]
/**
* Chooses a random fork awesome icon from a predefined set and renders it.
* Chooses a random icon from a predefined set and renders it.
*
* The component uses a static icon in the first rendering and will choose the random icon after that.
* This is done because if the loading screen is prepared using SSR and then hydrated in the client, the rendered css class isn't the expected one from the SSR. (It's random. d'uh).
@ -33,13 +44,12 @@ const elements: IconName[] = [
* See https://nextjs.org/docs/messages/react-hydration-error
*/
export const RandomIcon: React.FC = () => {
const [icon, setIcon] = useState<number | undefined>()
const [icon, setIcon] = useState<JSX.Element | undefined>()
useEffect(() => {
setIcon(Math.floor(Math.random() * elements.length))
const index = Math.floor(Math.random() * elements.length)
setIcon(React.createElement(elements[index], { className: styles.particle }))
}, [])
return icon === undefined ? null : (
<ForkAwesomeIcon icon={elements[icon]} className={styles.particle}></ForkAwesomeIcon>
)
return <Fragment>{icon}</Fragment>
}

View File

@ -5,9 +5,7 @@ exports[`Async loading boundary shows a waiting spinner if loading 1`] = `
<div
class="m-3 d-flex align-items-center justify-content-center"
>
<i
class="fa fa-spinner fa-spin "
/>
BootstrapIconMock_ArrowRepeat
</div>
</div>
`;

View File

@ -7,9 +7,7 @@ exports[`Copy to clipboard button show an error text if clipboard api isn't avai
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -22,9 +20,7 @@ exports[`Copy to clipboard button show an error text if clipboard api isn't avai
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -36,9 +32,7 @@ exports[`Copy to clipboard button shows an error text if writing failed 1`] = `
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -51,9 +45,7 @@ exports[`Copy to clipboard button shows an error text if writing failed 2`] = `
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -65,9 +57,7 @@ exports[`Copy to clipboard button shows an success text if writing succeeded 1`]
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;
@ -80,9 +70,7 @@ exports[`Copy to clipboard button shows an success text if writing succeeded 2`]
title="renderer.highlightCode.copyCode"
type="button"
>
<i
class="fa fa-files-o "
/>
BootstrapIconMock_Files
</button>
</div>
`;

View File

@ -5,10 +5,11 @@
*/
import type { PropsWithDataCypressId } from '../../../../utils/cypress-attribute'
import { cypressId } from '../../../../utils/cypress-attribute'
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../icons/ui-icon'
import { useCopyOverlay } from '../hooks/use-copy-overlay'
import React, { Fragment, useRef } from 'react'
import { Button } from 'react-bootstrap'
import { Files as IconFiles } from 'react-bootstrap-icons'
import type { Variant } from 'react-bootstrap/types'
import { useTranslation } from 'react-i18next'
@ -46,7 +47,7 @@ export const CopyToClipboardButton: React.FC<CopyToClipboardButtonProps> = ({
title={t('renderer.highlightCode.copyCode') ?? undefined}
onClick={copyToClipboard}
{...cypressId(props)}>
<ForkAwesomeIcon icon='files-o' />
<UiIcon icon={IconFiles} />
</Button>
{overlayElement}
</Fragment>

View File

@ -5,11 +5,12 @@
*/
import { isClientSideRendering } from '../../../../utils/is-client-side-rendering'
import { Logger } from '../../../../utils/logger'
import { ForkAwesomeIcon } from '../../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../icons/ui-icon'
import { ShowIf } from '../../show-if/show-if'
import { CopyToClipboardButton } from '../copy-to-clipboard-button/copy-to-clipboard-button'
import React, { useCallback, useMemo } from 'react'
import { Button, FormControl, InputGroup } from 'react-bootstrap'
import { Share as IconShare } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface CopyableFieldProps {
@ -57,7 +58,7 @@ export const CopyableField: React.FC<CopyableFieldProps> = ({ content, shareOrig
<ShowIf condition={sharingSupported}>
<InputGroup.Text>
<Button variant='secondary' title={'Share'} onClick={doShareAction}>
<ForkAwesomeIcon icon='share-alt' />
<UiIcon icon={IconShare} />
</Button>
</InputGroup.Text>
</ShowIf>

View File

@ -1,65 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[` 1`] = `
<div>
<i
class="fa fa-heart fa-stack-1x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders a heart correctly 1`] = `
<div>
<i
class="fa fa-heart "
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 2x 1`] = `
<div>
<i
class="fa fa-heart fa-2x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 3x 1`] = `
<div>
<i
class="fa fa-heart fa-3x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 4x 1`] = `
<div>
<i
class="fa fa-heart fa-4x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders in size 5x 1`] = `
<div>
<i
class="fa fa-heart fa-5x"
/>
</div>
`;
exports[`ForkAwesomeIcon renders with additional className 1`] = `
<div>
<i
class="fa fa-heart testClass "
/>
</div>
`;
exports[`ForkAwesomeIcon renders with fixed width icon 1`] = `
<div>
<i
class="fa fa-fw fa-heart "
/>
</div>
`;

View File

@ -1,76 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ForkAwesomeStack renders a heart and a download icon stack 1`] = `
<div>
<span
class="fa-stack "
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 2x 1`] = `
<div>
<span
class="fa-stack fa-2x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 3x 1`] = `
<div>
<span
class="fa-stack fa-3x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 4x 1`] = `
<div>
<span
class="fa-stack fa-4x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;
exports[`ForkAwesomeStack renders in size 5x 1`] = `
<div>
<span
class="fa-stack fa-5x"
>
<i
class="fa fa-heart fa-stack-1x"
/>
<i
class="fa fa-download fa-stack-1x"
/>
</span>
</div>
`;

View File

@ -1,46 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from './fork-awesome-icon'
import type { IconName } from './types'
import { render } from '@testing-library/react'
describe('ForkAwesomeIcon', () => {
const icon: IconName = 'heart'
it('renders a heart correctly', () => {
const view = render(<ForkAwesomeIcon icon={icon} />)
expect(view.container).toMatchSnapshot()
})
it('renders with fixed width icon', () => {
const view = render(<ForkAwesomeIcon icon={icon} fixedWidth={true} />)
expect(view.container).toMatchSnapshot()
})
it('renders with additional className', () => {
const view = render(<ForkAwesomeIcon icon={icon} className={'testClass'} />)
expect(view.container).toMatchSnapshot()
})
describe('renders in size', () => {
it('2x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'2x'} />)
expect(view.container).toMatchSnapshot()
})
it('3x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'3x'} />)
expect(view.container).toMatchSnapshot()
})
it('4x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'4x'} />)
expect(view.container).toMatchSnapshot()
})
it('5x', () => {
const view = render(<ForkAwesomeIcon icon={icon} size={'5x'} />)
expect(view.container).toMatchSnapshot()
})
})
describe('renders in stack', () => {
const view = render(<ForkAwesomeIcon icon={icon} stacked={true} />)
expect(view.container).toMatchSnapshot()
})
})

View File

@ -1,39 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { IconName, IconSize } from './types'
import React from 'react'
export interface ForkAwesomeIconProps {
icon: IconName
className?: string
fixedWidth?: boolean
size?: IconSize
stacked?: boolean
}
/**
* Renders a fork awesome icon.
*
* @param icon The icon that should be rendered.
* @param fixedWidth If the icon should be rendered with a fixed width.
* @param size The size class the icon should be rendered in.
* @param className Additional classes the icon should get.
* @param stacked If the icon is part of a {@link ForkAwesomeStack stack}.
* @see https://forkaweso.me
*/
export const ForkAwesomeIcon: React.FC<ForkAwesomeIconProps> = ({
icon,
fixedWidth = false,
size,
className,
stacked = false
}) => {
const fixedWithClass = fixedWidth ? 'fa-fw' : ''
const sizeClass = size ? `-${size}` : stacked ? '-1x' : ''
const stackClass = stacked ? '-stack' : ''
const extraClasses = `${className ?? ''} ${sizeClass || stackClass ? `fa${stackClass}${sizeClass}` : ''}`
return <i className={`fa ${fixedWithClass} fa-${icon} ${extraClasses}`} />
}

View File

@ -1,804 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export const ForkAwesomeIcons = [
'500px',
'activitypub',
'address-book-o',
'address-book',
'address-card-o',
'address-card',
'adjust',
'adn',
'align-center',
'align-justify',
'align-left',
'align-right',
'amazon',
'ambulance',
'american-sign-language-interpreting',
'anchor',
'android',
'angellist',
'angle-double-down',
'angle-double-left',
'angle-double-right',
'angle-double-up',
'angle-down',
'angle-left',
'angle-right',
'angle-up',
'apple',
'archive-org',
'archive',
'archlinux',
'area-chart',
'arrow-circle-down',
'arrow-circle-left',
'arrow-circle-o-down',
'arrow-circle-o-left',
'arrow-circle-o-right',
'arrow-circle-o-up',
'arrow-circle-right',
'arrow-circle-up',
'arrow-down',
'arrow-left',
'arrow-right',
'arrows-alt',
'arrows-h',
'arrows',
'arrows-v',
'arrow-up',
'artstation',
'askfm',
'assistive-listening-systems',
'asterisk',
'at',
'att',
'audio-description',
'backward',
'balance-scale',
'bandcamp',
'ban',
'bar-chart',
'barcode',
'bars',
'bath',
'battery-empty',
'battery-full',
'battery-half',
'battery-quarter',
'battery-three-quarters',
'bed',
'beer',
'behance-square',
'behance',
'bell-o',
'bell-rigning-o',
'bell-ringing',
'bell-slash-o',
'bell-slash',
'bell',
'bicycle',
'binoculars',
'biometric',
'birthday-cake',
'bitbucket-square',
'bitbucket',
'black-tie',
'blind',
'blockstack',
'bluetooth-b',
'bluetooth',
'boardgamegeek',
'bold',
'bolt',
'bomb',
'bookmark-o',
'bookmark',
'book',
'bootstrap',
'braille',
'briefcase',
'btc',
'bug',
'building-o',
'building',
'bullhorn',
'bullseye',
'bunny',
'bus',
'buymeacoffee',
'buysellads',
'calculator',
'calendar-check-o',
'calendar-minus-o',
'calendar-o',
'calendar-plus-o',
'calendar',
'calendar-times-o',
'camera-retro',
'camera',
'caret-down',
'caret-left',
'caret-right',
'caret-square-o-down',
'caret-square-o-left',
'caret-square-o-right',
'caret-square-o-up',
'caret-up',
'car',
'cart-arrow-down',
'cart-plus',
'cc-amex',
'cc-by',
'cc-cc',
'cc-diners-club',
'cc-discover',
'cc-jcb',
'cc-mastercard',
'cc-nc-eu',
'cc-nc-jp',
'cc-nc',
'cc-nd',
'cc-paypal',
'cc-pd',
'cc-remix',
'cc-sa',
'cc-share',
'cc-stripe',
'cc',
'cc-visa',
'cc-zero',
'certificate',
'chain-broken',
'check-circle-o',
'check-circle',
'check-square-o',
'check-square',
'check',
'chevron-circle-down',
'chevron-circle-left',
'chevron-circle-right',
'chevron-circle-up',
'chevron-down',
'chevron-left',
'chevron-right',
'chevron-up',
'child',
'chrome',
'circle-o-notch',
'circle-o',
'circle',
'circle-thin',
'classicpress-circle',
'classicpress',
'clipboard',
'clock-o',
'clone',
'cloud-download',
'cloud',
'cloud-upload',
'code-fork',
'codepen',
'code',
'codiepie',
'coffee',
'cogs',
'cog',
'columns',
'commenting-o',
'commenting',
'comment-o',
'comments-o',
'comments',
'comment',
'compass',
'compress',
'connectdevelop',
'contao',
'conway-glider',
'copyright',
'creative-commons',
'credit-card-alt',
'credit-card',
'crop',
'crosshairs',
'csharp',
'css3',
'c',
'cubes',
'cube',
'cutlery',
'dashcube',
'database',
'deaf',
'debian',
'delicious',
'desktop',
'deviantart',
'dev-to',
'diamond',
'diaspora',
'digg',
'digitalocean',
'discord-alt',
'discord',
'dogmazic',
'dot-circle-o',
'download',
'dribbble',
'dropbox',
'drupal',
'edge',
'eercast',
'eject',
'ellipsis-h',
'ellipsis-v',
'email-bulk-o',
'email-bulk',
'emby',
'empire',
'envelope-open-o',
'envelope-open',
'envelope-o',
'envelope-square',
'envelope',
'envira',
'eraser',
'ethereum',
'etsy',
'eur',
'exchange',
'exclamation-circle',
'exclamation',
'exclamation-triangle',
'expand',
'expeditedssl',
'external-link-square',
'external-link',
'eyedropper',
'eye-slash',
'eye',
'facebook-messenger',
'facebook-official',
'facebook-square',
'facebook',
'fast-backward',
'fast-forward',
'fax',
'f-droid',
'female',
'ffmpeg',
'fighter-jet',
'file-archive-o',
'file-audio-o',
'file-code-o',
'file-epub',
'file-excel-o',
'file-image-o',
'file-o',
'file-pdf-o',
'file-powerpoint-o',
'files-o',
'file',
'file-text-o',
'file-text',
'file-video-o',
'file-word-o',
'film',
'filter',
'fire-extinguisher',
'firefox',
'fire',
'first-order',
'flag-checkered',
'flag-o',
'flag',
'flask',
'flickr',
'floppy-o',
'folder-open-o',
'folder-open',
'folder-o',
'folder',
'font-awesome',
'fonticons',
'font',
'fork-awesome',
'fort-awesome',
'forumbee',
'forward',
'foursquare',
'free-code-camp',
'freedombox',
'friendica',
'frown-o',
'funkwhale',
'futbol-o',
'gamepad',
'gavel',
'gbp',
'genderless',
'get-pocket',
'gg-circle',
'gg',
'gift',
'gimp',
'gitea',
'github-alt',
'github-square',
'github',
'gitlab',
'git-square',
'git',
'glass',
'glide-g',
'glide',
'globe-e',
'globe',
'globe-w',
'gnupg',
'gnu-social',
'gnu',
'google-play',
'google-plus-official',
'google-plus-square',
'google-plus',
'google',
'google-wallet',
'graduation-cap',
'gratipay',
'grav',
'hackaday',
'hacker-news',
'hackster',
'hal',
'hand-lizard-o',
'hand-o-down',
'hand-o-left',
'hand-o-right',
'hand-o-up',
'hand-paper-o',
'hand-peace-o',
'hand-pointer-o',
'hand-rock-o',
'hand-scissors-o',
'handshake-o',
'hand-spock-o',
'hashnode',
'hashtag',
'hdd-o',
'header',
'headphones',
'heartbeat',
'heart-o',
'heart',
'heroku',
'history',
'home-assistant',
'home',
'hospital-o',
'hourglass-end',
'hourglass-half',
'hourglass-o',
'hourglass-start',
'hourglass',
'houzz',
'h-square',
'html5',
'hubzilla',
'i-cursor',
'id-badge',
'id-card-o',
'id-card',
'ils',
'imdb',
'inbox',
'indent',
'industry',
'info-circle',
'info',
'inkscape',
'inr',
'instagram',
'internet-explorer',
'ioxhost',
'italic',
'java',
'jirafeau',
'joomla',
'joplin',
'jpy',
'jsfiddle',
'julia',
'jupyter',
'keybase',
'keyboard-o',
'key-modern',
'key',
'krw',
'language',
'laptop',
'laravel',
'lastfm-square',
'lastfm',
'leaf',
'leanpub',
'lemon-o',
'level-down',
'level-up',
'liberapay-square',
'liberapay',
'life-ring',
'lightbulb-o',
'line-chart',
'linkedin-square',
'linkedin',
'link',
'linode',
'linux',
'list-alt',
'list-ol',
'list',
'list-ul',
'location-arrow',
'lock',
'long-arrow-down',
'long-arrow-left',
'long-arrow-right',
'long-arrow-up',
'low-vision',
'magic',
'magnet',
'male',
'map-marker',
'map-o',
'map-pin',
'map-signs',
'map',
'mariadb',
'markdown',
'mars-double',
'mars-stroke-h',
'mars-stroke',
'mars-stroke-v',
'mars',
'mastodon-alt',
'mastodon-square',
'mastodon',
'matrix-org',
'maxcdn',
'meanpath',
'medium-square',
'medium',
'medkit',
'meetup',
'meh-o',
'mercury',
'microchip',
'microphone-slash',
'microphone',
'minus-circle',
'minus-square-o',
'minus-square',
'minus',
'mixcloud',
'mobile',
'modx',
'money',
'moon-o',
'moon',
'motorcycle',
'mouse-pointer',
'music',
'mysql',
'neuter',
'newspaper-o',
'nextcloud-square',
'nextcloud',
'nodejs',
'nordcast',
'object-group',
'object-ungroup',
'odnoklassniki-square',
'odnoklassniki',
'opencart',
'open-collective',
'openid',
'opera',
'optin-monster',
'orcid',
'outdent',
'pagelines',
'paint-brush',
'paperclip',
'paper-plane-o',
'paper-plane',
'paragraph',
'patreon',
'pause-circle-o',
'pause-circle',
'pause',
'paw',
'paypal',
'peertube',
'pencil-square-o',
'pencil-square',
'pencil',
'percent',
'phone-square',
'phone',
'php',
'picture-o',
'pie-chart',
'pinterest-p',
'pinterest-square',
'pinterest',
'pixelfed',
'plane',
'play-circle-o',
'play-circle',
'play',
'pleroma',
'plug',
'plume',
'plus-circle',
'plus-square-o',
'plus-square',
'plus',
'podcast',
'postgresql',
'power-off',
'print',
'product-hunt',
'puzzle-piece',
'python',
'qq',
'qrcode',
'question-circle-o',
'question-circle',
'question',
'quora',
'quote-left',
'quote-right',
'random',
'ravelry',
'react',
'rebel',
'recycle',
'reddit-alien',
'reddit-square',
'reddit',
'refresh',
'registered',
'renren',
'repeat',
'reply-all',
'reply',
'researchgate',
'retweet',
'road',
'rocket',
'rss-square',
'rss',
'rub',
'safari',
'sass-alt',
'sass',
'scissors',
'scribd',
'scuttlebutt',
'search-minus',
'search-plus',
'search',
'sellsy',
'server',
'shaarli-o',
'shaarli',
'share-alt-square',
'share-alt',
'share-square-o',
'share-square',
'share',
'shield',
'ship',
'shirtsinbulk',
'shopping-bag',
'shopping-basket',
'shopping-cart',
'shower',
'signalapp',
'signal',
'sign-in',
'sign-language',
'sign-out',
'simplybuilt',
'sitemap',
'skate',
'sketchfab',
'skyatlas',
'skype',
'slack',
'sliders',
'slideshare',
'smile-o',
'snapchat-ghost',
'snapchat-square',
'snapchat',
'snowdrift',
'snowflake-o',
'social-home',
'sort-alpha-asc',
'sort-alpha-desc',
'sort-amount-asc',
'sort-amount-desc',
'sort-asc',
'sort-desc',
'sort-numeric-asc',
'sort-numeric-desc',
'sort',
'soundcloud',
'space-shuttle',
'spell-check',
'spinner',
'spoon',
'spotify',
'square-o',
'square',
'stack-exchange',
'stack-overflow',
'star-half-o',
'star-half',
'star-o',
'star',
'steam-square',
'steam',
'step-backward',
'step-forward',
'stethoscope',
'sticky-note-o',
'sticky-note',
'stop-circle-o',
'stop-circle',
'stop',
'street-view',
'strikethrough',
'stumbleupon-circle',
'stumbleupon',
'subscript',
'subway',
'suitcase',
'sun-o',
'sun',
'superpowers',
'superscript',
'syncthing',
'table',
'tablet',
'tachometer',
'tags',
'tag',
'tasks',
'taxi',
'telegram',
'television',
'tencent-weibo',
'terminal',
'tex',
'text-height',
'textpattern',
'text-width',
'themeisle',
'thermometer-empty',
'thermometer-full',
'thermometer-half',
'thermometer-quarter',
'thermometer-three-quarters',
'th-large',
'th-list',
'th',
'thumbs-down',
'thumbs-o-down',
'thumbs-o-up',
'thumbs-up',
'thumb-tack',
'ticket',
'times-circle-o',
'times-circle',
'times',
'tint',
'tipeee',
'toggle-off',
'toggle-on',
'tor-onion',
'trademark',
'train',
'transgender-alt',
'transgender',
'trash-o',
'trash',
'tree',
'trello',
'tripadvisor',
'trophy',
'truck',
'try',
'tty',
'tumblr-square',
'tumblr',
'twitch',
'twitter-square',
'twitter',
'umbrella',
'underline',
'undo',
'unity',
'universal-access',
'university',
'unlock-alt',
'unlock',
'unslpash',
'upload',
'usb',
'usd',
'user-circle-o',
'user-circle',
'user-md',
'user-o',
'user-plus',
'user-secret',
'users',
'user',
'user-times',
'venus-double',
'venus-mars',
'venus',
'viacoin',
'viadeo-square',
'viadeo',
'video-camera',
'vimeo-square',
'vimeo',
'vine',
'vk',
'volume-control-phone',
'volume-down',
'volume-mute',
'volume-off',
'volume-up',
'weibo',
'weixin',
'whatsapp',
'wheelchair-alt',
'wheelchair',
'wifi',
'wikidata',
'wikipedia-w',
'window-close-o',
'window-close',
'window-maximize',
'window-minimize',
'window-restore',
'windows',
'wire',
'wordpress',
'wpbeginner',
'wpexplorer',
'wpforms',
'wrench',
'xing-square',
'xing',
'xmpp',
'yahoo',
'y-combinator',
'yelp',
'yoast',
'youtube-play',
'youtube-square',
'youtube',
'zotero'
] as const

View File

@ -1,39 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ForkAwesomeIconProps } from './fork-awesome-icon'
import { ForkAwesomeIcon } from './fork-awesome-icon'
import { ForkAwesomeStack } from './fork-awesome-stack'
import { render } from '@testing-library/react'
import type { ReactElement } from 'react'
describe('ForkAwesomeStack', () => {
const stack: Array<ReactElement<ForkAwesomeIconProps>> = [
<ForkAwesomeIcon icon={'heart'} key={'heart'} />,
<ForkAwesomeIcon icon={'download'} key={'download'} />
]
it('renders a heart and a download icon stack', () => {
const view = render(<ForkAwesomeStack>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
describe('renders in size', () => {
it('2x', () => {
const view = render(<ForkAwesomeStack size={'2x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
it('3x', () => {
const view = render(<ForkAwesomeStack size={'3x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
it('4x', () => {
const view = render(<ForkAwesomeStack size={'4x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
it('5x', () => {
const view = render(<ForkAwesomeStack size={'5x'}>{stack}</ForkAwesomeStack>)
expect(view.container).toMatchSnapshot()
})
})
})

View File

@ -1,34 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ForkAwesomeIconProps } from './fork-awesome-icon'
import { ForkAwesomeIcon } from './fork-awesome-icon'
import type { IconSize } from './types'
import type { ReactElement } from 'react'
import React from 'react'
export interface ForkAwesomeStackProps {
size?: IconSize
children: ReactElement<ForkAwesomeIconProps> | Array<ReactElement<ForkAwesomeIconProps>>
}
/**
* A stack of {@link ForkAwesomeIcon ForkAwesomeIcons}.
*
* @param size Which size the stack should have.
* @param children One or more {@link ForkAwesomeIcon ForkAwesomeIcons} to be stacked.
*/
export const ForkAwesomeStack: React.FC<ForkAwesomeStackProps> = ({ size, children }) => {
return (
<span className={`fa-stack ${size ? 'fa-' : ''}${size ?? ''}`}>
{React.Children.map(children, (child) => {
if (!React.isValidElement<ForkAwesomeIconProps>(child)) {
return null
}
return <ForkAwesomeIcon {...child.props} stacked={true} />
})}
</span>
)
}

View File

@ -1,9 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ForkAwesomeIcons } from './fork-awesome-icons'
export type IconName = (typeof ForkAwesomeIcons)[number]
export type IconSize = '2x' | '3x' | '4x' | '5x'

View File

@ -6,8 +6,7 @@
import LogoBwHorizontal from './logo_text_bw_horizontal.svg'
import LogoColorVertical from './logo_text_color_vertical.svg'
import LogoWbHorizontal from './logo_text_wb_horizontal.svg'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import React from 'react'
export enum HedgeDocLogoSize {
SMALL = 32,
@ -33,17 +32,13 @@ export enum HedgeDocLogoType {
* @param logoType The logo type to be used.
*/
export const HedgeDocLogoWithText: React.FC<HedgeDocLogoProps> = ({ size = HedgeDocLogoSize.MEDIUM, logoType }) => {
const { t } = useTranslation()
const altText = useMemo(() => t('app.icon'), [t])
const style = useMemo(() => ({ height: size }), [size])
switch (logoType) {
case HedgeDocLogoType.COLOR_VERTICAL:
return <LogoColorVertical className={'w-auto'} title={altText} style={style} />
return <LogoColorVertical className={'w-auto'} height={`${size}px`} width={'auto'} />
case HedgeDocLogoType.BW_HORIZONTAL:
return <LogoBwHorizontal className={'w-auto'} title={altText} style={style} />
return <LogoBwHorizontal className={'w-auto'} height={`${size}px`} width={'auto'} />
case HedgeDocLogoType.WB_HORIZONTAL:
return <LogoWbHorizontal className={'w-auto'} title={altText} style={style} />
return <LogoWbHorizontal className={'w-auto'} height={`${size}px`} width={'auto'} />
default:
return null
}

View File

@ -10,9 +10,7 @@ exports[`IconButton correctly uses the onClick callback 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -33,9 +31,7 @@ exports[`IconButton renders heart icon 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -56,9 +52,7 @@ exports[`IconButton renders with additional className 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -79,9 +73,7 @@ exports[`IconButton renders with border 1`] = `
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-heart icon "
/>
BootstrapIconMock_Heart
</span>
<span
class="text-part d-flex align-items-center"
@ -91,26 +83,3 @@ exports[`IconButton renders with border 1`] = `
</button>
</div>
`;
exports[`IconButton renders with fixed width icon 1`] = `
<div>
<button
class="btn-icon p-0 d-inline-flex align-items-stretch btn btn-primary"
data-testid="icon-button"
type="button"
>
<span
class="icon-part d-flex align-items-center"
>
<i
class="fa fa-fw fa-heart icon "
/>
</span>
<span
class="text-part d-flex align-items-center"
>
test with fixed with icon
</span>
</button>
</div>
`;

View File

@ -3,35 +3,26 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { IconName } from '../fork-awesome/types'
import { IconButton } from './icon-button'
import { fireEvent, render, screen } from '@testing-library/react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('IconButton', () => {
const icon: IconName = 'heart'
it('renders heart icon', () => {
const view = render(<IconButton icon={icon}>test</IconButton>)
const view = render(<IconButton icon={IconHeart}>test</IconButton>)
expect(view.container).toMatchSnapshot()
})
it('renders with border', () => {
const view = render(
<IconButton icon={icon} border={true}>
<IconButton icon={IconHeart} border={true}>
test with border
</IconButton>
)
expect(view.container).toMatchSnapshot()
})
it('renders with fixed width icon', () => {
const view = render(
<IconButton icon={icon} iconFixedWidth={true}>
test with fixed with icon
</IconButton>
)
expect(view.container).toMatchSnapshot()
})
it('renders with additional className', () => {
const view = render(
<IconButton icon={icon} className={'testClass'}>
<IconButton icon={IconHeart} className={'testClass'}>
test with additional className
</IconButton>
)
@ -40,7 +31,7 @@ describe('IconButton', () => {
it('correctly uses the onClick callback', async () => {
const onClick = jest.fn()
const view = render(
<IconButton icon={icon} onClick={onClick}>
<IconButton icon={IconHeart} onClick={onClick}>
test with onClick
</IconButton>
)

View File

@ -5,37 +5,38 @@
*/
import type { PropsWithDataTestId } from '../../../utils/test-id'
import { testId } from '../../../utils/test-id'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import styles from './icon-button.module.scss'
import React from 'react'
import type { ButtonProps } from 'react-bootstrap'
import { Button } from 'react-bootstrap'
import type { Icon } from 'react-bootstrap-icons'
export interface IconButtonProps extends ButtonProps, PropsWithDataTestId {
icon: IconName
icon: Icon
onClick?: () => void
border?: boolean
iconFixedWidth?: boolean
iconSize?: number | string
}
/**
* A generic {@link Button button} with an {@link ForkAwesomeIcon icon} in it.
* A generic {@link Button button} with an icon in it.
*
* @param icon Which icon should be used
* @param children The children that will be added as the content of the button.
* @param iconFixedWidth If the icon should be of fixed width.
* @param border Should the button have a border.
* @param className Additional class names added to the button.
* @param iconSize Size of the icon
* @param props Additional props for the button.
*/
export const IconButton: React.FC<IconButtonProps> = ({
icon,
children,
iconFixedWidth = false,
border = false,
className,
iconSize,
...props
}) => {
return (
@ -46,7 +47,7 @@ export const IconButton: React.FC<IconButtonProps> = ({
}`}
{...testId('icon-button')}>
<span className={`${styles['icon-part']} d-flex align-items-center`}>
<ForkAwesomeIcon icon={icon} fixedWidth={iconFixedWidth} className={'icon'} />
<UiIcon size={iconSize} icon={icon} className={'icon'} />
</span>
<ShowIf condition={!!children}>
<span className={`${styles['text-part']} d-flex align-items-center`}>{children}</span>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { testId } from '../../../utils/test-id'
import { BootstrapLazyIcons } from './bootstrap-icons'
import type { BootstrapIconName } from './bootstrap-icons'
import React, { Suspense, useMemo } from 'react'
export interface LazyBootstrapIconProps {
icon: BootstrapIconName
size?: number
className?: string
}
/**
* Renders a bootstrap icon.
*
* @param iconName the internal name of the icon
* @param size the size of the icon - the default is 1
* @see https://icons.getbootstrap.com/
*/
export const LazyBootstrapIcon: React.FC<LazyBootstrapIconProps> = ({ icon, size, className }) => {
const fullSize = useMemo(() => `${size ?? 1}em`, [size])
const Icon = BootstrapLazyIcons[icon]
return (
<Suspense fallback={<></>}>
<Icon
width={fullSize}
height={fullSize}
fill={'currentColor'}
className={className}
{...testId(`lazy-bootstrap-icon-${icon}`)}></Icon>
</Suspense>
)
}

View File

@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import styles from './ui-icons.module.scss'
import React, { Fragment, useMemo } from 'react'
import type { Icon } from 'react-bootstrap-icons'
export interface UiIconProps {
icon: Icon | undefined
nbsp?: boolean
size?: number | string
className?: string
spin?: boolean
}
export const UiIcon: React.FC<UiIconProps> = ({ icon, nbsp, className, size, spin }) => {
const finalSize = useMemo(() => {
if (size === undefined) {
return '1em'
} else if (typeof size === 'number') {
return `${size}em`
} else {
return size
}
}, [size])
const finalClassName = useMemo(() => {
return `${spin ? styles.spin : ''} ${className ?? ''}`
}, [className, spin])
if (icon) {
return (
<Fragment>
{React.createElement(icon, {
className: finalClassName,
width: finalSize,
height: finalSize
})}
{nbsp ? <Fragment>&nbsp;</Fragment> : null}
</Fragment>
)
} else {
return null
}
}

View File

@ -0,0 +1,20 @@
/*!
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
.spin {
transform-origin: center center;
@keyframes rotation {
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
}
animation: rotation linear 2s infinite;
}

View File

@ -52,9 +52,7 @@ exports[`ExternalLink renders an external link with an icon 1`] = `
rel="noopener noreferrer"
target="_blank"
>
<i
class="fa fa-fw fa-heart "
/>
BootstrapIconMock_Heart
 
testText
</a>

View File

@ -40,9 +40,7 @@ exports[`InternalLink renders an internal link with an icon 1`] = `
class="text-light"
href="/test"
>
<i
class="fa fa-fw fa-heart "
/>
BootstrapIconMock_Heart
 
testText
</a>

View File

@ -5,6 +5,7 @@
*/
import { ExternalLink } from './external-link'
import { render } from '@testing-library/react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('ExternalLink', () => {
const href = 'https://example.com'
@ -14,7 +15,7 @@ describe('ExternalLink', () => {
expect(view.container).toMatchSnapshot()
})
it('renders an external link with an icon', () => {
const view = render(<ExternalLink text={text} href={href} icon={'heart'} />)
const view = render(<ExternalLink text={text} href={href} icon={IconHeart} />)
expect(view.container).toMatchSnapshot()
})
it('renders an external link with an id', () => {

View File

@ -3,9 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { ShowIf } from '../show-if/show-if'
import { UiIcon } from '../icons/ui-icon'
import type { LinkWithTextProps } from './types'
import React from 'react'
@ -31,10 +29,7 @@ export const ExternalLink: React.FC<LinkWithTextProps> = ({
}) => {
return (
<a href={href} target='_blank' rel='noopener noreferrer' id={id} className={className} title={title} dir='auto'>
<ShowIf condition={!!icon}>
<ForkAwesomeIcon icon={icon as IconName} fixedWidth={true} />
&nbsp;
</ShowIf>
<UiIcon icon={icon} nbsp={true} />
{text}
</a>
)

View File

@ -5,6 +5,7 @@
*/
import { InternalLink } from './internal-link'
import { render } from '@testing-library/react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('InternalLink', () => {
const href = '/test'
@ -14,7 +15,7 @@ describe('InternalLink', () => {
expect(view.container).toMatchSnapshot()
})
it('renders an internal link with an icon', () => {
const view = render(<InternalLink text={text} href={href} icon={'heart'} />)
const view = render(<InternalLink text={text} href={href} icon={IconHeart} />)
expect(view.container).toMatchSnapshot()
})
it('renders an internal link with an id', () => {

View File

@ -3,9 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { ShowIf } from '../show-if/show-if'
import { UiIcon } from '../icons/ui-icon'
import type { LinkWithTextProps } from './types'
import Link from 'next/link'
import React from 'react'
@ -31,10 +29,7 @@ export const InternalLink: React.FC<LinkWithTextProps> = ({
}) => {
return (
<Link href={href} className={className} id={id} title={title}>
<ShowIf condition={!!icon}>
<ForkAwesomeIcon icon={icon as IconName} fixedWidth={true} />
&nbsp;
</ShowIf>
<UiIcon icon={icon} nbsp={true} />
{text}
</Link>
)

View File

@ -3,12 +3,12 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { IconName } from '../fork-awesome/fork-awesome-icon'
import type { TOptionsBase } from 'i18next'
import type { Icon } from 'react-bootstrap-icons'
interface GeneralLinkProp {
href: string
icon?: IconName
icon?: Icon
id?: string
className?: string
title?: string

View File

@ -1,29 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import React from 'react'
import { Button } from 'react-bootstrap'
export interface LockButtonProps {
locked: boolean
onLockedChanged: (newState: boolean) => void
title: string
}
/**
* A button with a lock icon.
*
* @param locked If the button should be shown as locked or not
* @param onLockedChanged The callback to change the state from locked to unlocked and vise-versa.
* @param title The title for the button.
*/
export const LockButton: React.FC<LockButtonProps> = ({ locked, onLockedChanged, title }) => {
return (
<Button variant='dark' size='sm' onClick={() => onLockedChanged(!locked)} title={title}>
{locked ? <ForkAwesomeIcon icon='lock' /> : <ForkAwesomeIcon icon='unlock' />}
</Button>
)
}

View File

@ -148,9 +148,7 @@ exports[`CommonModal render correctly with title icon 1`] = `
<div
class="modal-title h4"
>
<i
class="fa fa-heart "
/>
BootstrapIconMock_Heart
 
<span />
</div>

View File

@ -7,6 +7,7 @@ import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
import { CommonModal } from './common-modal'
import { fireEvent, render, screen } from '@testing-library/react'
import React from 'react'
import { Heart as IconHeart } from 'react-bootstrap-icons'
describe('CommonModal', () => {
afterAll(() => {
@ -65,7 +66,7 @@ describe('CommonModal', () => {
it('render correctly with title icon', async () => {
render(
<CommonModal show={true} titleIcon={'heart'}>
<CommonModal show={true} titleIcon={IconHeart}>
testText
</CommonModal>
)

View File

@ -6,12 +6,12 @@
import type { PropsWithDataCypressId } from '../../../utils/cypress-attribute'
import { cypressId } from '../../../utils/cypress-attribute'
import { testId } from '../../../utils/test-id'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import type { IconName } from '../fork-awesome/types'
import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import type { PropsWithChildren } from 'react'
import React, { useMemo } from 'react'
import { Modal } from 'react-bootstrap'
import type { Icon } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
export interface ModalVisibilityProps {
@ -23,7 +23,7 @@ export interface ModalContentProps {
titleI18nKey?: string
title?: string
showCloseButton?: boolean
titleIcon?: IconName
titleIcon?: Icon
modalSize?: 'lg' | 'sm' | 'xl'
additionalClasses?: string
}
@ -74,10 +74,7 @@ export const CommonModal: React.FC<PropsWithChildren<CommonModalProps>> = ({
size={modalSize}>
<Modal.Header closeButton={!!showCloseButton}>
<Modal.Title>
<ShowIf condition={!!titleIcon}>
<ForkAwesomeIcon icon={titleIcon as IconName} />
&nbsp;
</ShowIf>
<UiIcon icon={titleIcon} nbsp={true} />
{titleElement}
</Modal.Title>
</Modal.Header>

View File

@ -1,20 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`create non existing note hint shows success message when the note has been created 1`] = `
<div>
<div
class="fade mt-5 alert alert-info show"
data-testid="noteCreated"
role="alert"
>
<i
class="fa fa-check-circle me-2 "
/>
noteLoadingBoundary.createNote.success
</div>
</div>
`;
exports[`create non existing note hint renders a waiting message when button is clicked 1`] = `
<div>
<div
@ -22,9 +7,7 @@ exports[`create non existing note hint renders a waiting message when button is
data-testid="loadingMessage"
role="alert"
>
<i
class="fa fa-spinner fa-spin me-2 "
/>
BootstrapIconMock_ArrowRepeat
noteLoadingBoundary.createNote.creating
</div>
</div>
@ -62,10 +45,21 @@ exports[`create non existing note hint shows an error message if note couldn't b
data-testid="failedMessage"
role="alert"
>
<i
class="fa fa-exclamation-triangle me-1 "
/>
BootstrapIconMock_ExclamationTriangle
noteLoadingBoundary.createNote.error
</div>
</div>
`;
exports[`create non existing note hint shows success message when the note has been created 1`] = `
<div>
<div
class="fade mt-5 alert alert-info show"
data-testid="noteCreated"
role="alert"
>
BootstrapIconMock_CheckCircle
noteLoadingBoundary.createNote.success
</div>
</div>
`;

View File

@ -6,10 +6,13 @@
import { createNoteWithPrimaryAlias } from '../../../api/notes'
import { useSingleStringUrlParameter } from '../../../hooks/common/use-single-string-url-parameter'
import { testId } from '../../../utils/test-id'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import React, { useCallback, useEffect } from 'react'
import { Alert, Button } from 'react-bootstrap'
import { ArrowRepeat as IconArrowRepeat } from 'react-bootstrap-icons'
import { CheckCircle as IconCheckCircle } from 'react-bootstrap-icons'
import { ExclamationTriangle as IconExclamationTriangle } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
import { useAsyncFn } from 'react-use'
@ -49,21 +52,21 @@ export const CreateNonExistingNoteHint: React.FC<CreateNonExistingNoteHintProps>
} else if (returnState.value) {
return (
<Alert variant={'info'} {...testId('noteCreated')} className={'mt-5'}>
<ForkAwesomeIcon icon={'check-circle'} className={'me-2'} />
<UiIcon icon={IconCheckCircle} className={'me-2'} />
<Trans i18nKey={'noteLoadingBoundary.createNote.success'} />
</Alert>
)
} else if (returnState.loading) {
return (
<Alert variant={'info'} {...testId('loadingMessage')} className={'mt-5'}>
<ForkAwesomeIcon icon={'spinner'} className={'fa-spin me-2'} />
<UiIcon icon={IconArrowRepeat} className={'me-2'} spin={true} />
<Trans i18nKey={'noteLoadingBoundary.createNote.creating'} />
</Alert>
)
} else if (returnState.error !== undefined) {
return (
<Alert variant={'danger'} {...testId('failedMessage')} className={'mt-5'}>
<ForkAwesomeIcon icon={'exclamation-triangle'} className={'me-1'} />
<UiIcon icon={IconExclamationTriangle} className={'me-1'} />
<Trans i18nKey={'noteLoadingBoundary.createNote.error'} />
</Alert>
)
@ -82,7 +85,7 @@ export const CreateNonExistingNoteHint: React.FC<CreateNonExistingNoteHintProps>
onClick={onClickHandler}
{...testId('createNoteButton')}>
<ShowIf condition={returnState.loading}>
<ForkAwesomeIcon icon={'spinner'} className={'fa-spin me-2'} />
<UiIcon icon={IconArrowRepeat} className={'me-2'} spin={true} />
</ShowIf>
<Trans i18nKey={'noteLoadingBoundary.createNote.create'} />
</Button>

View File

@ -3,8 +3,9 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
import { UiIcon } from '../icons/ui-icon'
import React from 'react'
import { ArrowRepeat as IconArrowRepeat } from 'react-bootstrap-icons'
/**
* Renders a indefinitely spinning spinner.
@ -12,7 +13,7 @@ import React from 'react'
export const WaitSpinner: React.FC = () => {
return (
<div className={'m-3 d-flex align-items-center justify-content-center'}>
<ForkAwesomeIcon icon={'spinner'} className={'fa-spin'} />
<UiIcon icon={IconArrowRepeat} spin={true} />
</div>
)
}

View File

@ -10,6 +10,7 @@ import { NoteInfoLineCreated } from '../editor-page/document-bar/note-info/note-
import { NoteInfoLineUpdated } from '../editor-page/document-bar/note-info/note-info-line-updated'
import styles from './document-infobar.module.scss'
import React from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
@ -35,7 +36,7 @@ export const DocumentInfobar: React.FC = () => {
<InternalLink
text={''}
href={`/n/${noteDetails.primaryAddress}`}
icon={'pencil'}
icon={IconPencil}
className={'text-primary text-decoration-none mx-1'}
title={t('views.readOnly.editNote') ?? undefined}
/>

View File

@ -34,6 +34,7 @@ export const CheatsheetTabContent: React.FC = () => {
`[${t('editor.editorToolbar.link')}](https://example.com)`,
`![${t('editor.editorToolbar.image')}](/icons/apple-touch-icon.png)`,
':smile:',
':bi-bootstrap:',
`:::info\n${t('editor.help.cheatsheet.exampleAlert')}\n:::`
],
[checked, t]

View File

@ -5,10 +5,11 @@
*/
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
import { cypressId } from '../../../../utils/cypress-attribute'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import { HelpModal } from './help-modal'
import React, { Fragment } from 'react'
import { Button } from 'react-bootstrap'
import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
/**
@ -27,7 +28,7 @@ export const HelpButton: React.FC = () => {
size='sm'
variant='outline-light'
onClick={showModal}>
<ForkAwesomeIcon icon='question-circle' />
<UiIcon icon={IconQuestionCircle} />
</Button>
<HelpModal show={modalVisibility} onHide={closeModal} />
</Fragment>

View File

@ -10,6 +10,7 @@ import { LinksTabContent } from './links-tab-content'
import { ShortcutTabContent } from './shortcuts-tab-content'
import React, { useMemo, useState } from 'react'
import { Button, Modal } from 'react-bootstrap'
import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
export enum HelpTabStatus {
@ -47,7 +48,7 @@ export const HelpModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
const modalTitle = useMemo(() => t('editor.documentBar.help') + ' - ' + t(`editor.help.${tab}`), [t, tab])
return (
<CommonModal modalSize={'lg'} titleIcon={'question-circle'} show={show} onHide={onHide} title={modalTitle}>
<CommonModal modalSize={'lg'} titleIcon={IconQuestionCircle} show={show} onHide={onHide} title={modalTitle}>
<Modal.Body>
<nav className='nav nav-tabs'>
<Button

View File

@ -8,6 +8,11 @@ import { TranslatedExternalLink } from '../../../common/links/translated-externa
import { TranslatedInternalLink } from '../../../common/links/translated-internal-link'
import React from 'react'
import { Col, Row } from 'react-bootstrap'
import { Dot as IconDot } from 'react-bootstrap-icons'
import { Flag as IconFlag } from 'react-bootstrap-icons'
import { Hash as IconHash } from 'react-bootstrap-icons'
import { PeopleFill as IconPeopleFill } from 'react-bootstrap-icons'
import { Tag as IconTag } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
@ -28,7 +33,7 @@ export const LinksTabContent: React.FC = () => {
<TranslatedExternalLink
i18nKey='editor.help.contacts.community'
href={links.community}
icon='users'
icon={IconPeopleFill}
className='text-primary'
/>
</li>
@ -37,7 +42,7 @@ export const LinksTabContent: React.FC = () => {
i18nKey='editor.help.contacts.meetUsOn'
i18nOption={{ service: 'Matrix' }}
href={links.chat}
icon='hashtag'
icon={IconHash}
className='text-primary'
/>
</li>
@ -45,7 +50,7 @@ export const LinksTabContent: React.FC = () => {
<TranslatedExternalLink
i18nKey='editor.help.contacts.reportIssue'
href={links.backendIssues}
icon='tag'
icon={IconTag}
className='text-primary'
/>
</li>
@ -53,7 +58,7 @@ export const LinksTabContent: React.FC = () => {
<TranslatedExternalLink
i18nKey='editor.help.contacts.helpTranslating'
href={links.translate}
icon='language'
icon={IconFlag}
className='text-primary'
/>
</li>
@ -70,7 +75,7 @@ export const LinksTabContent: React.FC = () => {
<TranslatedInternalLink
i18nKey='editor.help.documents.features'
href='/n/features'
icon='dot-circle-o'
icon={IconDot}
className='text-primary'
/>
</li>
@ -78,7 +83,7 @@ export const LinksTabContent: React.FC = () => {
<TranslatedInternalLink
i18nKey='editor.help.documents.yamlMetadata'
href='/n/yaml-metadata'
icon='dot-circle-o'
icon={IconDot}
className='text-primary'
/>
</li>
@ -86,7 +91,7 @@ export const LinksTabContent: React.FC = () => {
<TranslatedInternalLink
i18nKey='editor.help.documents.slideExample'
href='/n/slide-example'
icon='dot-circle-o'
icon={IconDot}
className='text-primary'
/>
</li>

View File

@ -3,10 +3,10 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { IconButton } from '../../common/icon-button/icon-button'
import Link from 'next/link'
import React from 'react'
import { Button } from 'react-bootstrap'
import { Plus as IconPlus } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
@ -17,9 +17,9 @@ export const NewNoteButton: React.FC = () => {
return (
<Link href={'/new'} passHref={true}>
<Button className='mx-2' size='sm' variant='primary'>
<ForkAwesomeIcon icon='plus' /> <Trans i18nKey='editor.appBar.new' />
</Button>
<IconButton className='mx-2' iconSize={1.5} icon={IconPlus}>
<Trans i18nKey='editor.appBar.new' />
</IconButton>
</Link>
)
}

View File

@ -4,10 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../hooks/common/use-application-state'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../common/icons/ui-icon'
import Link from 'next/link'
import React from 'react'
import { Button } from 'react-bootstrap'
import { FileEarmarkTextFill as IconFileEarmarkTextFill } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
/**
@ -24,7 +25,7 @@ export const ReadOnlyModeButton: React.FC = () => {
className='ms-2 text-secondary'
size='sm'
variant='outline-light'>
<ForkAwesomeIcon icon='file-text-o' />
<UiIcon icon={IconFileEarmarkTextFill} />
</Button>
</Link>
)

View File

@ -4,10 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../hooks/common/use-application-state'
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../common/icons/ui-icon'
import Link from 'next/link'
import React from 'react'
import { Button } from 'react-bootstrap'
import { Tv as IconTv } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
/**
@ -24,7 +25,7 @@ export const SlideModeButton: React.FC = () => {
className='ms-2 text-secondary'
size='sm'
variant='outline-light'>
<ForkAwesomeIcon icon='television' />
<UiIcon icon={IconTv} />
</Button>
</Link>
)

View File

@ -20,9 +20,7 @@ exports[`AliasesAddForm renders the input form 1`] = `
title="editor.modal.aliases.addAlias"
type="submit"
>
<i
class="fa fa-plus "
/>
BootstrapIconMock_Plus
</button>
</div>
</form>

View File

@ -13,9 +13,7 @@ exports[`AliasesListEntry renders an AliasesListEntry that is not primary 1`] =
title="editor.modal.aliases.makePrimary"
type="button"
>
<i
class="fa fa-star-o "
/>
BootstrapIconMock_StarFill
</button>
<button
class="text-danger btn btn-light"
@ -23,9 +21,7 @@ exports[`AliasesListEntry renders an AliasesListEntry that is not primary 1`] =
title="editor.modal.aliases.removeAlias"
type="button"
>
<i
class="fa fa-times "
/>
BootstrapIconMock_X
</button>
</div>
</li>
@ -46,9 +42,7 @@ exports[`AliasesListEntry renders an AliasesListEntry that is primary 1`] = `
title="editor.modal.aliases.isPrimary"
type="button"
>
<i
class="fa fa-star "
/>
BootstrapIconMock_Star
</button>
<button
class="text-danger btn btn-light"
@ -56,9 +50,7 @@ exports[`AliasesListEntry renders an AliasesListEntry that is primary 1`] = `
title="editor.modal.aliases.removeAlias"
type="button"
>
<i
class="fa fa-times "
/>
BootstrapIconMock_X
</button>
</div>
</li>

View File

@ -8,11 +8,12 @@ import { useApplicationState } from '../../../../hooks/common/use-application-st
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { updateMetadata } from '../../../../redux/note-details/methods'
import { testId } from '../../../../utils/test-id'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import type { FormEvent } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { Button, Form, InputGroup } from 'react-bootstrap'
import { Plus as IconPlus } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
const validAliasRegex = /^[a-z0-9_-]*$/
@ -63,7 +64,7 @@ export const AliasesAddForm: React.FC = () => {
disabled={!newAliasValid || newAlias === ''}
title={t('editor.modal.aliases.addAlias') ?? undefined}
{...testId('addAliasButton')}>
<ForkAwesomeIcon icon={'plus'} />
<UiIcon icon={IconPlus} />
</Button>
</InputGroup>
</form>

View File

@ -7,11 +7,14 @@ import { deleteAlias, markAliasAsPrimary } from '../../../../api/alias'
import type { Alias } from '../../../../api/alias/types'
import { updateMetadata } from '../../../../redux/note-details/methods'
import { testId } from '../../../../utils/test-id'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import { ShowIf } from '../../../common/show-if/show-if'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import React, { useCallback } from 'react'
import { Button } from 'react-bootstrap'
import { StarFill as IconStarFill } from 'react-bootstrap-icons'
import { Star as IconStar } from 'react-bootstrap-icons'
import { X as IconX } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface AliasesListEntryProps {
@ -50,7 +53,7 @@ export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) =>
disabled={true}
title={t('editor.modal.aliases.isPrimary') ?? undefined}
{...testId('aliasIsPrimary')}>
<ForkAwesomeIcon icon={'star'} />
<UiIcon icon={IconStar} />
</Button>
</ShowIf>
<ShowIf condition={!alias.primaryAlias}>
@ -60,7 +63,7 @@ export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) =>
title={t('editor.modal.aliases.makePrimary') ?? undefined}
onClick={onMakePrimaryClick}
{...testId('aliasButtonMakePrimary')}>
<ForkAwesomeIcon icon={'star-o'} />
<UiIcon icon={IconStarFill} />
</Button>
</ShowIf>
<Button
@ -69,7 +72,7 @@ export const AliasesListEntry: React.FC<AliasesListEntryProps> = ({ alias }) =>
title={t('editor.modal.aliases.removeAlias') ?? undefined}
onClick={onRemoveClick}
{...testId('aliasButtonRemove')}>
<ForkAwesomeIcon icon={'times'} />
<UiIcon icon={IconX} />
</Button>
</div>
</li>

View File

@ -7,6 +7,7 @@ import { useApplicationState } from '../../../../hooks/common/use-application-st
import { NoteInfoLine } from './note-info-line'
import { UnitalicBoldContent } from './unitalic-bold-content'
import React from 'react'
import { People as IconPeople } from 'react-bootstrap-icons'
import { Trans } from 'react-i18next'
/**
@ -16,7 +17,7 @@ export const NoteInfoLineContributors: React.FC = () => {
const contributors = useApplicationState((state) => state.noteDetails.editedBy.length)
return (
<NoteInfoLine icon={'users'} size={'2x'}>
<NoteInfoLine icon={IconPeople} size={2}>
<Trans i18nKey={'editor.modal.documentInfo.usersContributed'}>
<UnitalicBoldContent text={contributors} />
</Trans>

View File

@ -9,6 +9,7 @@ import type { NoteInfoTimeLineProps } from './note-info-time-line'
import { UnitalicBoldTimeFromNow } from './utils/unitalic-bold-time-from-now'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { Plus as IconPlus } from 'react-bootstrap-icons'
import { Trans } from 'react-i18next'
/**
@ -21,7 +22,7 @@ export const NoteInfoLineCreated: React.FC<NoteInfoTimeLineProps> = ({ size }) =
const noteCreateDateTime = useMemo(() => DateTime.fromSeconds(noteCreateTime), [noteCreateTime])
return (
<NoteInfoLine icon={'plus'} size={size}>
<NoteInfoLine icon={IconPlus} size={size}>
<Trans i18nKey={'editor.modal.documentInfo.created'}>
<UnitalicBoldTimeFromNow time={noteCreateDateTime} />
</Trans>

View File

@ -11,6 +11,7 @@ import { UnitalicBoldTimeFromNow } from './utils/unitalic-bold-time-from-now'
import { UnitalicBoldTrans } from './utils/unitalic-bold-trans'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
@ -38,7 +39,7 @@ export const NoteInfoLineUpdated: React.FC<NoteInfoTimeLineProps> = ({ size }) =
}, [noteUpdateUser, size])
return (
<NoteInfoLine icon={'pencil'} size={size}>
<NoteInfoLine icon={IconPencil} size={size}>
<Trans i18nKey={'editor.modal.documentInfo.edited'}>
{userBlock}
<UnitalicBoldTimeFromNow time={noteUpdateDateTime} />

View File

@ -14,6 +14,7 @@ import { NoteInfoLine } from './note-info-line'
import { UnitalicBoldContent } from './unitalic-bold-content'
import type { PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { AlignStart as IconAlignStart } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
@ -38,7 +39,7 @@ export const NoteInfoLineWordCount: React.FC<PropsWithChildren<unknown>> = () =>
}, [editorToRendererCommunicator, rendererReady])
return (
<NoteInfoLine icon={'align-left'} size={'2x'}>
<NoteInfoLine icon={IconAlignStart} size={2}>
<ShowIf condition={wordCount === null}>
<Trans i18nKey={'common.loading'} />
</ShowIf>

View File

@ -3,14 +3,14 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import type { IconName } from '../../../common/fork-awesome/types'
import { UiIcon } from '../../../common/icons/ui-icon'
import type { PropsWithChildren } from 'react'
import React from 'react'
import type { Icon } from 'react-bootstrap-icons'
export interface NoteInfoLineProps {
icon: IconName
size?: '2x' | '3x' | '4x' | '5x' | undefined
icon: Icon
size?: 2 | 3 | 4 | 5 | undefined
}
/**
@ -24,7 +24,7 @@ export interface NoteInfoLineProps {
export const NoteInfoLine: React.FC<PropsWithChildren<NoteInfoLineProps>> = ({ icon, size, children }) => {
return (
<span className={'d-flex align-items-center'}>
<ForkAwesomeIcon icon={icon} size={size} fixedWidth={true} className={'mx-2'} />
<UiIcon icon={icon} size={size} className={'mx-2'} />
<i className={'d-flex align-items-center'}>{children}</i>
</span>
)

View File

@ -33,10 +33,10 @@ export const NoteInfoModal: React.FC<ModalVisibilityProps> = ({ show, onHide })
<Modal.Body>
<ListGroup>
<ListGroup.Item>
<NoteInfoLineCreated size={'2x'} />
<NoteInfoLineCreated size={2} />
</ListGroup.Item>
<ListGroup.Item>
<NoteInfoLineUpdated size={'2x'} />
<NoteInfoLineUpdated size={2} />
</ListGroup.Item>
<ListGroup.Item>
<NoteInfoLineContributors />

View File

@ -4,5 +4,5 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
export interface NoteInfoTimeLineProps {
size?: '2x' | '3x' | '4x' | '5x'
size?: 2 | 3 | 4 | 5
}

View File

@ -4,9 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import React, { useCallback, useState } from 'react'
import { Button, FormControl, InputGroup } from 'react-bootstrap'
import { Plus as IconPlus } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface PermissionAddEntryFieldProps {
@ -35,7 +36,7 @@ export const PermissionAddEntryField: React.FC<PermissionAddEntryFieldProps> = (
<InputGroup className={'me-1 mb-1'}>
<FormControl value={newEntryIdentifier} placeholder={t(i18nKey) ?? undefined} onChange={onChange} />
<Button variant='light' className={'text-secondary ms-2'} title={t(i18nKey) ?? undefined} onClick={onSubmit}>
<ForkAwesomeIcon icon={'plus'} />
<UiIcon icon={IconPlus} />
</Button>
</InputGroup>
</li>

View File

@ -3,10 +3,13 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import { AccessLevel } from './types'
import React, { useMemo } from 'react'
import { Button, ToggleButtonGroup } from 'react-bootstrap'
import { Eye as IconEye } from 'react-bootstrap-icons'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { X as IconX } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
interface PermissionEntryButtonI18nKeys {
@ -73,20 +76,20 @@ export const PermissionEntryButtons: React.FC<PermissionEntryButtonsProps> = ({
className={'text-danger me-2'}
title={t(i18nKeys.remove, { name }) ?? undefined}
onClick={onRemove}>
<ForkAwesomeIcon icon={'times'} />
<UiIcon icon={IconX} />
</Button>
<ToggleButtonGroup type='radio' name='edit-mode' value={currentSetting}>
<Button
title={t(i18nKeys.setReadOnly, { name }) ?? undefined}
variant={currentSetting === AccessLevel.READ_ONLY ? 'secondary' : 'outline-secondary'}
onClick={onSetReadOnly}>
<ForkAwesomeIcon icon='eye' />
<UiIcon icon={IconEye} />
</Button>
<Button
title={t(i18nKeys.setWriteable, { name }) ?? undefined}
variant={currentSetting === AccessLevel.WRITEABLE ? 'secondary' : 'outline-secondary'}
onClick={onSetWriteable}>
<ForkAwesomeIcon icon='pencil' />
<UiIcon icon={IconPencil} />
</Button>
</ToggleButtonGroup>
</div>

View File

@ -6,11 +6,14 @@
import { removeGroupPermission, setGroupPermission } from '../../../../api/permissions'
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { setNotePermissionsFromServer } from '../../../../redux/note-details/methods'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { IconButton } from '../../../common/icon-button/icon-button'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import { AccessLevel, SpecialGroup } from './types'
import React, { useCallback, useMemo } from 'react'
import { Button, ToggleButtonGroup } from 'react-bootstrap'
import { ToggleButtonGroup } from 'react-bootstrap'
import { Eye as IconEye } from 'react-bootstrap-icons'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface PermissionEntrySpecialGroupProps {
@ -67,24 +70,27 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
<span>{name}</span>
<div>
<ToggleButtonGroup type='radio' name='edit-mode'>
<Button
<IconButton
icon={IconSlashCircle}
title={t('editor.modal.permissions.denyGroup', { name }) ?? undefined}
variant={level === AccessLevel.NONE ? 'secondary' : 'outline-secondary'}
onClick={onSetEntryDenied}>
<ForkAwesomeIcon icon={'ban'} />
</Button>
<Button
onClick={onSetEntryDenied}
className={'p-1'}
/>
<IconButton
icon={IconEye}
title={t('editor.modal.permissions.viewOnlyGroup', { name }) ?? undefined}
variant={level === AccessLevel.READ_ONLY ? 'secondary' : 'outline-secondary'}
onClick={onSetEntryReadOnly}>
<ForkAwesomeIcon icon={'eye'} />
</Button>
<Button
onClick={onSetEntryReadOnly}
className={'p-1'}
/>
<IconButton
icon={IconPencil}
title={t('editor.modal.permissions.editGroup', { name }) ?? undefined}
variant={level === AccessLevel.WRITEABLE ? 'secondary' : 'outline-secondary'}
onClick={() => onSetEntryWriteable}>
<ForkAwesomeIcon icon={'pencil'} />
</Button>
onClick={onSetEntryWriteable}
className={'p-1'}
/>
</ToggleButtonGroup>
</div>
</li>

View File

@ -4,9 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import React, { useCallback, useMemo, useState } from 'react'
import { Button, FormControl, InputGroup } from 'react-bootstrap'
import { Check as IconCheck } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface PermissionOwnerChangeProps {
@ -44,7 +45,7 @@ export const PermissionOwnerChange: React.FC<PermissionOwnerChangeProps> = ({ on
onClick={onClickConfirm}
className={'ms-2'}
disabled={confirmButtonDisabled}>
<ForkAwesomeIcon icon={'check'} />
<UiIcon icon={IconCheck} />
</Button>
</InputGroup>
)

View File

@ -4,10 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useApplicationState } from '../../../../hooks/common/use-application-state'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username'
import React, { Fragment } from 'react'
import { Button } from 'react-bootstrap'
import { Pencil as IconPencil } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface PermissionOwnerInfoProps {
@ -30,7 +31,7 @@ export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps> = ({ onEdit
variant='light'
title={t('editor.modal.permissions.ownerChange.button') ?? undefined}
onClick={onEditOwner}>
<ForkAwesomeIcon icon={'pencil'} />
<UiIcon icon={IconPencil} />
</Button>
</Fragment>
)

View File

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { RevisionMetadata } from '../../../../api/revisions/types'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../common/icons/ui-icon'
import { ShowIf } from '../../../common/show-if/show-if'
import { UserAvatar } from '../../../common/user-avatar/user-avatar'
import { WaitSpinner } from '../../../common/wait-spinner/wait-spinner'
@ -14,6 +14,9 @@ import { getUserDataForRevision } from './utils'
import { DateTime } from 'luxon'
import React, { useMemo } from 'react'
import { ListGroup } from 'react-bootstrap'
import { Clock as IconClock } from 'react-bootstrap-icons'
import { FileText as IconFileText } from 'react-bootstrap-icons'
import { Person as IconPerson } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
import { useAsync } from 'react-use'
@ -57,15 +60,15 @@ export const RevisionListEntry: React.FC<RevisionListEntryProps> = ({ active, on
action
className={`${styles['revision-item']} d-flex flex-column`}>
<span>
<ForkAwesomeIcon icon={'clock-o'} className='mx-2' />
<UiIcon icon={IconClock} className='mx-2' />
{revisionCreationTime}
</span>
<span>
<ForkAwesomeIcon icon={'file-text-o'} className='mx-2' />
<UiIcon icon={IconFileText} className='mx-2' />
<Trans i18nKey={'editor.modal.revision.length'} />: {revision.length}
</span>
<span className={'d-flex flex-row my-1 align-items-center'}>
<ForkAwesomeIcon icon={'user-o'} className={'mx-2'} />
<UiIcon icon={IconPerson} className={'mx-2'} />
<ShowIf condition={revisionAuthors.loading}>
<WaitSpinner />
</ShowIf>

View File

@ -11,6 +11,7 @@ import styles from './revision-modal.module.scss'
import { RevisionViewer } from './revision-viewer'
import React, { useState } from 'react'
import { Col, Modal, Row } from 'react-bootstrap'
import { ClockHistory as IconClockHistory } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
/**
@ -28,7 +29,7 @@ export const RevisionModal: React.FC<ModalVisibilityProps> = ({ show, onHide })
show={show}
onHide={onHide}
titleI18nKey={'editor.modal.revision.title'}
titleIcon={'history'}
titleIcon={IconClockHistory}
showCloseButton={true}
modalSize={'xl'}
additionalClasses={styles['revision-modal']}>

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { TypeBold as IconTypeBold } from 'react-bootstrap-icons'
/**
* Renders a button to make the selection in the {@link Editor editor} bold.
@ -15,5 +16,5 @@ export const BoldButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '**', '**')
}, [])
return <ToolbarButton i18nKey={'bold'} iconName={'bold'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'bold'} icon={IconTypeBold} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
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'
/**
* Renders a button to create a checklist in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const CheckListButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
return prependLinesOfSelection(markdownContent, currentSelection, () => `- [ ] `)
}, [])
return <ToolbarButton i18nKey={'checkList'} iconName={'check-square'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'checkList'} icon={IconCheckSquare} formatter={formatter}></ToolbarButton>
}

View File

@ -8,6 +8,7 @@ import { changeCursorsToWholeLineIfNoToCursor } from '../formatters/utils/change
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { Code as IconCode } from 'react-bootstrap-icons'
/**
* Renders a button to create a code fence in the {@link Editor editor}.
@ -16,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'} iconName={'code'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'code'} icon={IconCode} formatter={formatter}></ToolbarButton>
}

View File

@ -8,6 +8,7 @@ import { changeCursorsToWholeLineIfNoToCursor } from '../formatters/utils/change
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { ArrowsCollapse as IconArrowsCollapse } from 'react-bootstrap-icons'
/**
* Renders a button to create a spoiler section in the {@link Editor editor}.
@ -20,7 +21,5 @@ export const CollapsibleBlockButton: React.FC = () => {
'\n:::\n'
)
}, [])
return (
<ToolbarButton i18nKey={'collapsibleBlock'} iconName={'caret-square-o-down'} formatter={formatter}></ToolbarButton>
)
return <ToolbarButton i18nKey={'collapsibleBlock'} icon={IconArrowsCollapse} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { replaceSelection } from '../formatters/replace-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { ChatDots as IconChatDots } from 'react-bootstrap-icons'
/**
* Renders a button to create a comment in the {@link Editor editor}.
@ -15,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'} iconName={'comment'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'comment'} icon={IconChatDots} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
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'
/**
* Renders a button to add a header in the {@link Editor editor}.
@ -15,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'} iconName={'header'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'header'} icon={IconTypeH1} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { Eraser as IconEraser } from 'react-bootstrap-icons'
/**
* Renders a button that highlights the selection in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const HighlightButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '==', '==')
}, [])
return <ToolbarButton i18nKey={'highlight'} iconName={'eraser'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'highlight'} icon={IconEraser} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { replaceSelection } from '../formatters/replace-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { DashLg as IconDashLg } from 'react-bootstrap-icons'
/**
* Renders a button to insert a horizontal line in the {@link Editor editor}.
@ -15,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'} iconName={'minus'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'horizontalLine'} icon={IconDashLg} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { addLink } from '../formatters/add-link'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { Image as IconImage } from 'react-bootstrap-icons'
/**
* Renders a button to insert an image in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const ImageLinkButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
return addLink(markdownContent, currentSelection, '!')
}, [])
return <ToolbarButton i18nKey={'imageLink'} iconName={'picture-o'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'imageLink'} icon={IconImage} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { TypeItalic as IconTypeItalic } from 'react-bootstrap-icons'
/**
* Renders a button to make the selection in the {@link Editor editor} italic.
@ -15,5 +16,5 @@ export const ItalicButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '*', '*')
}, [])
return <ToolbarButton i18nKey={'italic'} iconName={'italic'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'italic'} icon={IconTypeItalic} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { addLink } from '../formatters/add-link'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { Link as IconLink } from 'react-bootstrap-icons'
/**
* Renders a button to insert a link in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const LinkButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
return addLink(markdownContent, currentSelection)
}, [])
return <ToolbarButton i18nKey={'link'} iconName={'link'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'link'} icon={IconLink} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
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'
/**
* Renders a button to insert an ordered list in the {@link Editor editor}.
@ -19,5 +20,5 @@ export const OrderedListButton: React.FC = () => {
(line, lineIndexInBlock) => `${lineIndexInBlock + 1}. `
)
}, [])
return <ToolbarButton i18nKey={'orderedList'} iconName={'list-ol'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'orderedList'} icon={IconListOl} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
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'
/**
* Renders a button to insert a quotation in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const QuotesButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
return prependLinesOfSelection(markdownContent, currentSelection, () => `> `)
}, [])
return <ToolbarButton i18nKey={'blockquote'} iconName={'quote-right'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'blockquote'} icon={IconQuote} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { TypeStrikethrough as IconTypeStrikethrough } from 'react-bootstrap-icons'
/**
* Renders a button to strike through the selection in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const StrikethroughButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '~~', '~~')
}, [])
return <ToolbarButton i18nKey={'strikethrough'} iconName={'strikethrough'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'strikethrough'} icon={IconTypeStrikethrough} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { Subscript as IconSubscript } from 'react-bootstrap-icons'
/**
* Renders a button to format the selection in the {@link Editor editor} as subscript.
@ -15,5 +16,5 @@ export const SubscriptButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '~', '~')
}, [])
return <ToolbarButton i18nKey={'subscript'} iconName={'subscript'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'subscript'} icon={IconSubscript} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { Superscript as IconSuperscript } from 'react-bootstrap-icons'
/**
* Renders a button to format the selection in the {@link Editor editor} as superscript.
@ -15,5 +16,5 @@ export const SuperscriptButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '^', '^')
}, [])
return <ToolbarButton i18nKey={'superscript'} iconName={'superscript'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'superscript'} icon={IconSuperscript} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
import { wrapSelection } from '../formatters/wrap-selection'
import { ToolbarButton } from '../toolbar-button'
import React, { useCallback } from 'react'
import { TypeUnderline as IconTypeUnderline } from 'react-bootstrap-icons'
/**
* Renders a button to underline the selection in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const UnderlineButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection }) => {
return wrapSelection(currentSelection, '++', '++')
}, [])
return <ToolbarButton i18nKey={'underline'} iconName={'underline'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'underline'} icon={IconTypeUnderline} formatter={formatter}></ToolbarButton>
}

View File

@ -7,6 +7,7 @@ import type { ContentFormatter } from '../../../change-content-context/change-co
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'
/**
* Renders a button to insert an unordered list in the {@link Editor editor}.
@ -15,5 +16,5 @@ export const UnorderedListButton: React.FC = () => {
const formatter: ContentFormatter = useCallback(({ currentSelection, markdownContent }) => {
return prependLinesOfSelection(markdownContent, currentSelection, () => `- `)
}, [])
return <ToolbarButton i18nKey={'unorderedList'} iconName={'list'} formatter={formatter}></ToolbarButton>
return <ToolbarButton i18nKey={'unorderedList'} icon={IconList} formatter={formatter}></ToolbarButton>
}

View File

@ -4,7 +4,7 @@
* 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 { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
import { replaceSelection } from '../formatters/replace-selection'
import { EmojiPickerPopover } from './emoji-picker-popover'
@ -13,6 +13,7 @@ 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 { EmojiSmile as IconEmojiSmile } from 'react-bootstrap-icons'
import type { OverlayInjectedProps } from 'react-bootstrap/Overlay'
import { useTranslation } from 'react-i18next'
@ -63,7 +64,7 @@ export const EmojiPickerButton: React.FC = () => {
title={t('editor.editorToolbar.emoji') ?? undefined}
disabled={!changeEditorContent}
ref={buttonRef}>
<ForkAwesomeIcon icon='smile-o' />
<UiIcon icon={IconEmojiSmile} />
</Button>
</Fragment>
)

View File

@ -1,30 +1,21 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import fontStyles from '../../../../../../global-styles/variables.module.scss'
import { useDarkModeState } from '../../../../../hooks/common/use-dark-mode-state'
import { ForkAwesomeIcons } from '../../../../common/fork-awesome/fork-awesome-icons'
import styles from './emoji-picker.module.scss'
import forkawesomeIcon from './forkawesome.png'
import { Picker } from 'emoji-picker-element'
import type { CustomEmoji, EmojiClickEvent, EmojiClickEventDetail } from 'emoji-picker-element/shared'
import type { EmojiClickEvent, EmojiClickEventDetail } from 'emoji-picker-element/shared'
import type { PickerConstructorOptions } from 'emoji-picker-element/shared'
import React, { useEffect, useRef } from 'react'
import { Popover } from 'react-bootstrap'
import type { PopoverProps } from 'react-bootstrap/Popover'
const customEmojis: CustomEmoji[] = ForkAwesomeIcons.map((name) => ({
name: `fa-${name}`,
shortcodes: [`fa-${name.toLowerCase()}`],
url: forkawesomeIcon.src,
category: 'ForkAwesome'
}))
const EMOJI_DATA_PATH = '_next/static/js/emoji-data.json'
const emojiPickerConfig = {
customEmoji: customEmojis,
const emojiPickerConfig: PickerConstructorOptions = {
dataSource: EMOJI_DATA_PATH
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,3 +0,0 @@
SPDX-FileCopyrightText: 2018 Dave Gandy & Fork Awesome
SPDX-License-Identifier: OFL-1.1

View File

@ -4,12 +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 { CommonModal } from '../../../../common/modals/common-modal'
import type { TableSize } from './table-size-picker-popover'
import type { ChangeEvent } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { Button, Form, ModalFooter } from 'react-bootstrap'
import { Table as IconTable } from 'react-bootstrap-icons'
import { X as IconX } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
export interface CustomTableSizeModalProps {
@ -67,7 +69,7 @@ export const CustomTableSizeModal: React.FC<CustomTableSizeModalProps> = ({ show
onHide={onDismiss}
titleI18nKey={'editor.editorToolbar.table.customSize'}
showCloseButton={true}
titleIcon={'table'}
titleIcon={IconTable}
{...cypressId('custom-table-size-modal')}>
<div className={'col-lg-10 d-flex flex-row p-3 align-items-center'}>
<Form.Control
@ -77,7 +79,7 @@ export const CustomTableSizeModal: React.FC<CustomTableSizeModalProps> = ({ show
isInvalid={tableSize.columns <= 0}
onChange={onColChange}
/>
<ForkAwesomeIcon icon='times' className='mx-2' fixedWidth={true} />
<UiIcon icon={IconX} className='mx-2' />
<Form.Control
type={'number'}
min={1}

View File

@ -4,7 +4,7 @@
* 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 { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
import { replaceSelection } from '../formatters/replace-selection'
import { createMarkdownTable } from './create-markdown-table'
@ -13,6 +13,7 @@ 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 { Table as IconTable } from 'react-bootstrap-icons'
import type { OverlayInjectedProps } from 'react-bootstrap/Overlay'
import { useTranslation } from 'react-i18next'
@ -77,7 +78,7 @@ export const TablePickerButton: React.FC = () => {
title={tableTitle}
ref={button}
disabled={!changeEditorContent}>
<ForkAwesomeIcon icon='table' />
<UiIcon icon={IconTable} />
</Button>
<Overlay
target={button.current}

View File

@ -4,12 +4,13 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { cypressAttribute, cypressId } from '../../../../../utils/cypress-attribute'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../../common/icons/ui-icon'
import { createNumberRangeArray } from '../../../../common/number-range/number-range'
import styles from './table-picker.module.scss'
import { TableSizeText } from './table-size-text'
import React, { useCallback, useMemo, useState } from 'react'
import { Button, Popover } from 'react-bootstrap'
import { Table as IconTable } from 'react-bootstrap-icons'
import type { PopoverProps } from 'react-bootstrap/Popover'
import { Trans, useTranslation } from 'react-i18next'
@ -79,7 +80,7 @@ export const TableSizePickerPopover = React.forwardRef<HTMLDivElement, TableSize
</div>
<div className='d-flex justify-content-center mt-2'>
<Button {...cypressId('show-custom-table-modal')} className={'text-center'} onClick={onShowCustomSizeModal}>
<ForkAwesomeIcon icon='table' />
<UiIcon icon={IconTable} />
&nbsp;
<Trans i18nKey={'editor.editorToolbar.table.customSize'} />
</Button>

View File

@ -4,17 +4,17 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { cypressId } from '../../../../utils/cypress-attribute'
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
import type { IconName } from '../../../common/fork-awesome/types'
import { UiIcon } from '../../../common/icons/ui-icon'
import type { ContentFormatter } from '../../change-content-context/change-content-context'
import { useChangeEditorContentCallback } from '../../change-content-context/use-change-editor-content-callback'
import React, { useCallback, useMemo } from 'react'
import { Button } from 'react-bootstrap'
import type { Icon } from 'react-bootstrap-icons'
import { useTranslation } from 'react-i18next'
export interface ToolbarButtonProps {
i18nKey: string
iconName: IconName
icon: Icon
formatter: ContentFormatter
}
@ -25,7 +25,7 @@ export interface ToolbarButtonProps {
* @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 ToolbarButton: React.FC<ToolbarButtonProps> = ({ i18nKey, iconName, formatter }) => {
export const ToolbarButton: React.FC<ToolbarButtonProps> = ({ i18nKey, icon, formatter }) => {
const { t } = useTranslation('', { keyPrefix: 'editor.editorToolbar' })
const changeEditorContent = useChangeEditorContentCallback()
@ -41,7 +41,7 @@ export const ToolbarButton: React.FC<ToolbarButtonProps> = ({ i18nKey, iconName,
title={title}
disabled={!changeEditorContent}
{...cypressId('toolbar.' + i18nKey)}>
<ForkAwesomeIcon icon={iconName} />
<UiIcon icon={icon} />
</Button>
)
}

View File

@ -5,7 +5,7 @@
*/
import { cypressId } from '../../../../../utils/cypress-attribute'
import { Logger } from '../../../../../utils/logger'
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
import { UiIcon } from '../../../../common/icons/ui-icon'
import { ShowIf } from '../../../../common/show-if/show-if'
import { acceptedMimeTypes } from '../../../../common/upload-image-mimetypes'
import { useCodeMirrorReference } from '../../../change-content-context/change-content-context'
@ -15,6 +15,7 @@ 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')
@ -54,7 +55,7 @@ export const UploadImageButton: React.FC = () => {
disabled={!codeMirror}
title={t('editor.editorToolbar.uploadImage') ?? undefined}
{...cypressId('editor-toolbar-upload-image-button')}>
<ForkAwesomeIcon icon={'upload'} />
<UiIcon icon={IconUpload} />
</Button>
<ShowIf condition={!!codeMirror}>
<UploadInput

View File

@ -15,6 +15,7 @@ import { DeleteNoteModal } from './delete-note-modal'
import { useRouter } from 'next/router'
import type { PropsWithChildren } from 'react'
import React, { Fragment, useCallback } from 'react'
import { Trash as IconTrash } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
const logger = new Logger('note-deletion')
@ -45,7 +46,7 @@ export const DeleteNoteSidebarEntry: React.FC<PropsWithChildren<SpecificSidebarE
<Fragment>
<SidebarButton
{...cypressId('sidebar.deleteNote.button')}
icon={'trash'}
icon={IconTrash}
className={className}
hide={hide}
onClick={showModal}>

Some files were not shown because too many files have changed in this diff Show More