mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-29 06:15:29 -04:00
Move and rename files (2/4) (#987)
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
1b7abf9f27
commit
123f959fb3
145 changed files with 586 additions and 301 deletions
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { EditorConfiguration } from 'codemirror'
|
||||
import equal from "fast-deep-equal"
|
||||
import React, { ChangeEvent, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||
import { EditorPreferenceProperty } from './editor-preference-property'
|
||||
|
||||
export interface EditorPreferenceBooleanProps {
|
||||
property: EditorPreferenceProperty
|
||||
}
|
||||
|
||||
export const EditorPreferenceBooleanProperty: React.FC<EditorPreferenceBooleanProps> = ({ property }) => {
|
||||
const preference = useSelector((state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '', equal)
|
||||
|
||||
const { t } = useTranslation()
|
||||
const selectItem = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedItem: boolean = event.target.value === 'true'
|
||||
|
||||
mergeEditorPreferences({
|
||||
[property]: selectedItem
|
||||
} as EditorConfiguration)
|
||||
}, [property])
|
||||
|
||||
const i18nPrefix = `editor.modal.preferences.${property}`
|
||||
|
||||
return (
|
||||
<EditorPreferenceInput onChange={selectItem} property={property} type={EditorPreferenceInputType.SELECT} value={preference}>
|
||||
<option value={'true'}>
|
||||
{t(`${i18nPrefix}.on`)}
|
||||
</option>
|
||||
<option value={'false'}>
|
||||
{t(`${i18nPrefix}.off`)}
|
||||
</option>
|
||||
</EditorPreferenceInput>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React from 'react'
|
||||
import { Form } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export enum EditorPreferenceInputType {
|
||||
SELECT,
|
||||
BOOLEAN,
|
||||
NUMBER
|
||||
}
|
||||
|
||||
export interface EditorPreferenceInputProps {
|
||||
property: string
|
||||
type: EditorPreferenceInputType
|
||||
onChange: React.ChangeEventHandler<HTMLSelectElement>
|
||||
value?: string | number | string[]
|
||||
}
|
||||
|
||||
export const EditorPreferenceInput: React.FC<EditorPreferenceInputProps> = ({ property, type, onChange, value, children }) => {
|
||||
useTranslation()
|
||||
return (
|
||||
<Form.Group controlId={`editor-pref-${property}`}>
|
||||
<Form.Label>
|
||||
<Trans i18nKey={`editor.modal.preferences.${property}${type===EditorPreferenceInputType.NUMBER ? '' : '.label'}`}/>
|
||||
</Form.Label>
|
||||
<Form.Control
|
||||
as={type === EditorPreferenceInputType.NUMBER ? 'input' : 'select'}
|
||||
size='sm'
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
type={type === EditorPreferenceInputType.NUMBER ? 'number' : ''}>
|
||||
{children}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React, { ChangeEvent, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { setEditorLigatures } from '../../../../../redux/editor/methods'
|
||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||
|
||||
export const EditorPreferenceLigaturesSelect: React.FC = () => {
|
||||
const ligaturesEnabled = useSelector((state: ApplicationState) => Boolean(state.editorConfig.ligatures).toString())
|
||||
const saveLigatures = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||
const ligaturesActivated: boolean = event.target.value === 'true'
|
||||
setEditorLigatures(ligaturesActivated)
|
||||
}, [])
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<EditorPreferenceInput onChange={saveLigatures} value={ligaturesEnabled} property={"ligatures"}
|
||||
type={EditorPreferenceInputType.BOOLEAN}>
|
||||
<option value='true'>{t(`common.yes`)}</option>
|
||||
<option value='false'>{t(`common.no`)}</option>
|
||||
</EditorPreferenceInput>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { EditorConfiguration } from 'codemirror'
|
||||
import equal from "fast-deep-equal"
|
||||
import React, { ChangeEvent, useCallback } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||
import { EditorPreferenceProperty } from './editor-preference-property'
|
||||
|
||||
export interface EditorPreferenceNumberProps {
|
||||
property: EditorPreferenceProperty
|
||||
}
|
||||
|
||||
export const EditorPreferenceNumberProperty: React.FC<EditorPreferenceNumberProps> = ({ property }) => {
|
||||
const preference = useSelector((state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '', equal)
|
||||
|
||||
const selectItem = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedItem: number = Number.parseInt(event.target.value)
|
||||
|
||||
mergeEditorPreferences({
|
||||
[property]: selectedItem
|
||||
} as EditorConfiguration)
|
||||
}, [property])
|
||||
|
||||
return (
|
||||
<EditorPreferenceInput onChange={selectItem} property={property} type={EditorPreferenceInputType.NUMBER} value={preference}/>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export enum EditorPreferenceProperty {
|
||||
KEYMAP = 'keyMap',
|
||||
THEME = 'theme',
|
||||
INDENT_WITH_TABS = 'indentWithTabs',
|
||||
INDENT_UNIT = 'indentUnit',
|
||||
SPELL_CHECK = 'spellcheck'
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { EditorConfiguration } from 'codemirror'
|
||||
import equal from "fast-deep-equal"
|
||||
import React, { ChangeEvent, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { mergeEditorPreferences } from '../../../../../redux/editor/methods'
|
||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||
import { EditorPreferenceProperty } from './editor-preference-property'
|
||||
|
||||
export interface EditorPreferenceSelectPropertyProps {
|
||||
property: EditorPreferenceProperty
|
||||
selections: string[]
|
||||
}
|
||||
|
||||
export const EditorPreferenceSelectProperty: React.FC<EditorPreferenceSelectPropertyProps> = ({ property, selections }) => {
|
||||
const preference = useSelector((state: ApplicationState) => state.editorConfig.preferences[property]?.toString() || '', equal)
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const selectItem = useCallback((event: ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedItem: string = event.target.value
|
||||
|
||||
mergeEditorPreferences({
|
||||
[property]: selectedItem
|
||||
} as EditorConfiguration)
|
||||
}, [property])
|
||||
|
||||
const i18nPrefix = `editor.modal.preferences.${property}`
|
||||
|
||||
return (
|
||||
<EditorPreferenceInput onChange={selectItem} property={property} type={EditorPreferenceInputType.SELECT} value={preference}>
|
||||
{selections.map(selection =>
|
||||
<option key={selection} value={selection}>
|
||||
{t(`${i18nPrefix}.${selection}`) }
|
||||
</option>)}
|
||||
</EditorPreferenceInput>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import equal from "fast-deep-equal"
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Button, Form, ListGroup } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { ApplicationState } from '../../../../../redux'
|
||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { CommonModal } from '../../../../common/modals/common-modal'
|
||||
import { ShowIf } from '../../../../common/show-if/show-if'
|
||||
import { EditorPreferenceBooleanProperty } from './editor-preference-boolean-property'
|
||||
import { EditorPreferenceInput, EditorPreferenceInputType } from './editor-preference-input'
|
||||
import { EditorPreferenceLigaturesSelect } from './editor-preference-ligatures-select'
|
||||
import { EditorPreferenceNumberProperty } from './editor-preference-number-property'
|
||||
import { EditorPreferenceProperty } from "./editor-preference-property"
|
||||
import { EditorPreferenceSelectProperty } from "./editor-preference-select-property"
|
||||
|
||||
export const EditorPreferences: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const indentWithTabs = useSelector((state: ApplicationState) => state.editorConfig.preferences.indentWithTabs ?? false, equal)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button variant='light' onClick={() => setShowModal(true)} title={t('editor.editorToolbar.preferences')}>
|
||||
<ForkAwesomeIcon icon="wrench"/>
|
||||
</Button>
|
||||
<CommonModal
|
||||
show={showModal}
|
||||
onHide={() => setShowModal(false)}
|
||||
titleI18nKey={'editor.modal.preferences.title'}
|
||||
closeButton={true}
|
||||
icon={'wrench'}>
|
||||
<Form>
|
||||
<ListGroup>
|
||||
<ListGroup.Item>
|
||||
<EditorPreferenceSelectProperty property={EditorPreferenceProperty.THEME} selections={['one-dark', 'neat']}/>
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item>
|
||||
<EditorPreferenceSelectProperty property={EditorPreferenceProperty.KEYMAP} selections={['sublime', 'emacs', 'vim']}/>
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item>
|
||||
<EditorPreferenceBooleanProperty property={EditorPreferenceProperty.INDENT_WITH_TABS}/>
|
||||
</ListGroup.Item>
|
||||
<ShowIf condition={!indentWithTabs}>
|
||||
<ListGroup.Item>
|
||||
<EditorPreferenceNumberProperty property={EditorPreferenceProperty.INDENT_UNIT}/>
|
||||
</ListGroup.Item>
|
||||
</ShowIf>
|
||||
<ListGroup.Item>
|
||||
<EditorPreferenceLigaturesSelect/>
|
||||
</ListGroup.Item>
|
||||
<ListGroup.Item>
|
||||
<EditorPreferenceInput onChange={() => alert('This feature is not yet implemented.')} property={EditorPreferenceProperty.SPELL_CHECK} type={EditorPreferenceInputType.SELECT}>
|
||||
<option value='off'>Off</option>
|
||||
<option value='en'>English</option>
|
||||
</EditorPreferenceInput>
|
||||
</ListGroup.Item>
|
||||
</ListGroup>
|
||||
</Form>
|
||||
</CommonModal>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import CodeMirror from 'codemirror'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { addEmoji } from '../utils/toolbarButtonUtils'
|
||||
import { EmojiPicker } from './emoji-picker'
|
||||
|
||||
export interface EmojiPickerButtonProps {
|
||||
editor: CodeMirror.Editor
|
||||
}
|
||||
|
||||
export const EmojiPickerButton: React.FC<EmojiPickerButtonProps> = ({ editor }) => {
|
||||
const { t } = useTranslation()
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState(false)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<EmojiPicker
|
||||
show={showEmojiPicker}
|
||||
onEmojiSelected={(emoji) => {
|
||||
setShowEmojiPicker(false)
|
||||
addEmoji(emoji, editor)
|
||||
}}
|
||||
onDismiss={() => setShowEmojiPicker(false)}/>
|
||||
<Button data-cy={'show-emoji-picker'} variant='light' onClick={() => setShowEmojiPicker(old => !old)} title={t('editor.editorToolbar.emoji')}>
|
||||
<ForkAwesomeIcon icon="smile-o"/>
|
||||
</Button>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.emoji-picker-container {
|
||||
z-index: 1111;
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Picker } from 'emoji-picker-element'
|
||||
import { CustomEmoji, EmojiClickEvent, EmojiClickEventDetail } from 'emoji-picker-element/shared'
|
||||
import React, { useEffect, useRef } from 'react'
|
||||
import { useClickAway } from 'react-use'
|
||||
import { useIsDarkModeActivated } from '../../../../../hooks/common/use-is-dark-mode-activated'
|
||||
import './emoji-picker.scss'
|
||||
import forkawesomeIcon from './forkawesome.png'
|
||||
import { ForkAwesomeIcons } from './icon-names'
|
||||
|
||||
export interface EmojiPickerProps {
|
||||
show: boolean
|
||||
onEmojiSelected: (emoji: EmojiClickEventDetail) => void
|
||||
onDismiss: () => void
|
||||
}
|
||||
|
||||
export const customEmojis: CustomEmoji[] = Object.keys(ForkAwesomeIcons).map((name) => ({
|
||||
name: `fa-${name}`,
|
||||
shortcodes: [`fa-${name.toLowerCase()}`],
|
||||
url: forkawesomeIcon,
|
||||
category: 'ForkAwesome'
|
||||
}))
|
||||
|
||||
export const EMOJI_DATA_PATH = '/static/js/emoji-data.json'
|
||||
|
||||
export const emojiPickerConfig = {
|
||||
customEmoji: customEmojis,
|
||||
dataSource: EMOJI_DATA_PATH
|
||||
}
|
||||
|
||||
const twemojiStyle = (): HTMLStyleElement => {
|
||||
const style = document.createElement('style')
|
||||
style.textContent = 'section.picker { --font-family: "Twemoji Mozilla" !important; }'
|
||||
return style
|
||||
}
|
||||
|
||||
export const EmojiPicker: React.FC<EmojiPickerProps> = ({ show, onEmojiSelected, onDismiss }) => {
|
||||
const darkModeEnabled = useIsDarkModeActivated()
|
||||
const pickerContainerRef = useRef<HTMLDivElement>(null)
|
||||
const pickerRef = useRef<Picker>()
|
||||
|
||||
useClickAway(pickerContainerRef, () => {
|
||||
onDismiss()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (!pickerContainerRef.current) {
|
||||
return
|
||||
}
|
||||
const picker = new Picker(emojiPickerConfig)
|
||||
if (picker.shadowRoot) {
|
||||
picker.shadowRoot.appendChild(twemojiStyle())
|
||||
}
|
||||
pickerContainerRef.current.appendChild(picker)
|
||||
|
||||
pickerRef.current = picker
|
||||
return () => {
|
||||
picker.remove()
|
||||
pickerRef.current = undefined
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!pickerRef.current) {
|
||||
return
|
||||
}
|
||||
const emojiClick = (event: EmojiClickEvent): void => {
|
||||
onEmojiSelected(event.detail)
|
||||
}
|
||||
const picker = pickerRef.current
|
||||
picker.addEventListener('emoji-click', emojiClick, true)
|
||||
return () => {
|
||||
picker.removeEventListener('emoji-click', emojiClick, true)
|
||||
}
|
||||
}, [onEmojiSelected])
|
||||
|
||||
useEffect(() => {
|
||||
if (!pickerRef.current) {
|
||||
return
|
||||
}
|
||||
pickerRef.current.setAttribute('class', darkModeEnabled ? 'dark' : 'light')
|
||||
if (darkModeEnabled) {
|
||||
pickerRef.current.removeAttribute('style')
|
||||
} else {
|
||||
pickerRef.current.setAttribute('style', '--background: #f8f9fa')
|
||||
}
|
||||
}, [darkModeEnabled])
|
||||
|
||||
return (
|
||||
<div className={`position-absolute emoji-picker-container ${!show ? 'd-none' : ''}`} ref={pickerContainerRef}/>
|
||||
)
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2018 Dave Gandy & Fork Awesome
|
||||
|
||||
SPDX-License-Identifier: OFL-1.1
|
|
@ -0,0 +1,766 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export enum ForkAwesomeIcons {
|
||||
'500px'='500px',
|
||||
'activitypub'='activitypub',
|
||||
'address-book-o'='address-book-o',
|
||||
'address-book'='address-book',
|
||||
'address-card-o'='address-card-o',
|
||||
'address-card'='address-card',
|
||||
'adjust'='adjust',
|
||||
'adn'='adn',
|
||||
'align-center'='align-center',
|
||||
'align-justify'='align-justify',
|
||||
'align-left'='align-left',
|
||||
'align-right'='align-right',
|
||||
'amazon'='amazon',
|
||||
'ambulance'='ambulance',
|
||||
'american-sign-language-interpreting'='american-sign-language-interpreting',
|
||||
'anchor'='anchor',
|
||||
'android'='android',
|
||||
'angellist'='angellist',
|
||||
'angle-double-down'='angle-double-down',
|
||||
'angle-double-left'='angle-double-left',
|
||||
'angle-double-right'='angle-double-right',
|
||||
'angle-double-up'='angle-double-up',
|
||||
'angle-down'='angle-down',
|
||||
'angle-left'='angle-left',
|
||||
'angle-right'='angle-right',
|
||||
'angle-up'='angle-up',
|
||||
'apple'='apple',
|
||||
'archive-org'='archive-org',
|
||||
'archive'='archive',
|
||||
'archlinux'='archlinux',
|
||||
'area-chart'='area-chart',
|
||||
'arrow-circle-down'='arrow-circle-down',
|
||||
'arrow-circle-left'='arrow-circle-left',
|
||||
'arrow-circle-o-down'='arrow-circle-o-down',
|
||||
'arrow-circle-o-left'='arrow-circle-o-left',
|
||||
'arrow-circle-o-right'='arrow-circle-o-right',
|
||||
'arrow-circle-o-up'='arrow-circle-o-up',
|
||||
'arrow-circle-right'='arrow-circle-right',
|
||||
'arrow-circle-up'='arrow-circle-up',
|
||||
'arrow-down'='arrow-down',
|
||||
'arrow-left'='arrow-left',
|
||||
'arrow-right'='arrow-right',
|
||||
'arrows-alt'='arrows-alt',
|
||||
'arrows-h'='arrows-h',
|
||||
'arrows'='arrows',
|
||||
'arrows-v'='arrows-v',
|
||||
'arrow-up'='arrow-up',
|
||||
'artstation'='artstation',
|
||||
'assistive-listening-systems'='assistive-listening-systems',
|
||||
'asterisk'='asterisk',
|
||||
'at'='at',
|
||||
'att'='att',
|
||||
'audio-description'='audio-description',
|
||||
'backward'='backward',
|
||||
'balance-scale'='balance-scale',
|
||||
'bandcamp'='bandcamp',
|
||||
'ban'='ban',
|
||||
'bar-chart'='bar-chart',
|
||||
'barcode'='barcode',
|
||||
'bars'='bars',
|
||||
'bath'='bath',
|
||||
'battery-empty'='battery-empty',
|
||||
'battery-full'='battery-full',
|
||||
'battery-half'='battery-half',
|
||||
'battery-quarter'='battery-quarter',
|
||||
'battery-three-quarters'='battery-three-quarters',
|
||||
'bed'='bed',
|
||||
'beer'='beer',
|
||||
'behance-square'='behance-square',
|
||||
'behance'='behance',
|
||||
'bell-o'='bell-o',
|
||||
'bell-rigning-o'='bell-rigning-o',
|
||||
'bell-ringing'='bell-ringing',
|
||||
'bell-slash-o'='bell-slash-o',
|
||||
'bell-slash'='bell-slash',
|
||||
'bell'='bell',
|
||||
'bicycle'='bicycle',
|
||||
'binoculars'='binoculars',
|
||||
'biometric'='biometric',
|
||||
'birthday-cake'='birthday-cake',
|
||||
'bitbucket-square'='bitbucket-square',
|
||||
'bitbucket'='bitbucket',
|
||||
'black-tie'='black-tie',
|
||||
'blind'='blind',
|
||||
'bluetooth-b'='bluetooth-b',
|
||||
'bluetooth'='bluetooth',
|
||||
'bold'='bold',
|
||||
'bolt'='bolt',
|
||||
'bomb'='bomb',
|
||||
'bookmark-o'='bookmark-o',
|
||||
'bookmark'='bookmark',
|
||||
'book'='book',
|
||||
'bootstrap'='bootstrap',
|
||||
'braille'='braille',
|
||||
'briefcase'='briefcase',
|
||||
'btc'='btc',
|
||||
'bug'='bug',
|
||||
'building-o'='building-o',
|
||||
'building'='building',
|
||||
'bullhorn'='bullhorn',
|
||||
'bullseye'='bullseye',
|
||||
'bus'='bus',
|
||||
'buysellads'='buysellads',
|
||||
'calculator'='calculator',
|
||||
'calendar-check-o'='calendar-check-o',
|
||||
'calendar-minus-o'='calendar-minus-o',
|
||||
'calendar-o'='calendar-o',
|
||||
'calendar-plus-o'='calendar-plus-o',
|
||||
'calendar'='calendar',
|
||||
'calendar-times-o'='calendar-times-o',
|
||||
'camera-retro'='camera-retro',
|
||||
'camera'='camera',
|
||||
'caret-down'='caret-down',
|
||||
'caret-left'='caret-left',
|
||||
'caret-right'='caret-right',
|
||||
'caret-square-o-down'='caret-square-o-down',
|
||||
'caret-square-o-left'='caret-square-o-left',
|
||||
'caret-square-o-right'='caret-square-o-right',
|
||||
'caret-square-o-up'='caret-square-o-up',
|
||||
'caret-up'='caret-up',
|
||||
'car'='car',
|
||||
'cart-arrow-down'='cart-arrow-down',
|
||||
'cart-plus'='cart-plus',
|
||||
'cc-amex'='cc-amex',
|
||||
'cc-diners-club'='cc-diners-club',
|
||||
'cc-discover'='cc-discover',
|
||||
'cc-jcb'='cc-jcb',
|
||||
'cc-mastercard'='cc-mastercard',
|
||||
'cc-paypal'='cc-paypal',
|
||||
'cc-stripe'='cc-stripe',
|
||||
'cc'='cc',
|
||||
'cc-visa'='cc-visa',
|
||||
'certificate'='certificate',
|
||||
'chain-broken'='chain-broken',
|
||||
'check-circle-o'='check-circle-o',
|
||||
'check-circle'='check-circle',
|
||||
'check-square-o'='check-square-o',
|
||||
'check-square'='check-square',
|
||||
'check'='check',
|
||||
'chevron-circle-down'='chevron-circle-down',
|
||||
'chevron-circle-left'='chevron-circle-left',
|
||||
'chevron-circle-right'='chevron-circle-right',
|
||||
'chevron-circle-up'='chevron-circle-up',
|
||||
'chevron-down'='chevron-down',
|
||||
'chevron-left'='chevron-left',
|
||||
'chevron-right'='chevron-right',
|
||||
'chevron-up'='chevron-up',
|
||||
'child'='child',
|
||||
'chrome'='chrome',
|
||||
'circle-o-notch'='circle-o-notch',
|
||||
'circle-o'='circle-o',
|
||||
'circle'='circle',
|
||||
'circle-thin'='circle-thin',
|
||||
'classicpress-circle'='classicpress-circle',
|
||||
'classicpress'='classicpress',
|
||||
'clipboard'='clipboard',
|
||||
'clock-o'='clock-o',
|
||||
'clone'='clone',
|
||||
'cloud-download'='cloud-download',
|
||||
'cloud'='cloud',
|
||||
'cloud-upload'='cloud-upload',
|
||||
'code-fork'='code-fork',
|
||||
'codepen'='codepen',
|
||||
'code'='code',
|
||||
'codiepie'='codiepie',
|
||||
'coffee'='coffee',
|
||||
'cogs'='cogs',
|
||||
'cog'='cog',
|
||||
'columns'='columns',
|
||||
'commenting-o'='commenting-o',
|
||||
'commenting'='commenting',
|
||||
'comment-o'='comment-o',
|
||||
'comments-o'='comments-o',
|
||||
'comments'='comments',
|
||||
'comment'='comment',
|
||||
'compass'='compass',
|
||||
'compress'='compress',
|
||||
'connectdevelop'='connectdevelop',
|
||||
'contao'='contao',
|
||||
'copyright'='copyright',
|
||||
'creative-commons'='creative-commons',
|
||||
'credit-card-alt'='credit-card-alt',
|
||||
'credit-card'='credit-card',
|
||||
'crop'='crop',
|
||||
'crosshairs'='crosshairs',
|
||||
'css3'='css3',
|
||||
'c'='c',
|
||||
'cubes'='cubes',
|
||||
'cube'='cube',
|
||||
'cutlery'='cutlery',
|
||||
'dashcube'='dashcube',
|
||||
'database'='database',
|
||||
'deaf'='deaf',
|
||||
'debian'='debian',
|
||||
'delicious'='delicious',
|
||||
'desktop'='desktop',
|
||||
'deviantart'='deviantart',
|
||||
'dev-to'='dev-to',
|
||||
'diamond'='diamond',
|
||||
'diaspora'='diaspora',
|
||||
'digg'='digg',
|
||||
'digitalocean'='digitalocean',
|
||||
'discord-alt'='discord-alt',
|
||||
'discord'='discord',
|
||||
'dogmazic'='dogmazic',
|
||||
'dot-circle-o'='dot-circle-o',
|
||||
'download'='download',
|
||||
'dribbble'='dribbble',
|
||||
'dropbox'='dropbox',
|
||||
'drupal'='drupal',
|
||||
'edge'='edge',
|
||||
'eercast'='eercast',
|
||||
'eject'='eject',
|
||||
'ellipsis-h'='ellipsis-h',
|
||||
'ellipsis-v'='ellipsis-v',
|
||||
'emby'='emby',
|
||||
'empire'='empire',
|
||||
'envelope-open-o'='envelope-open-o',
|
||||
'envelope-open'='envelope-open',
|
||||
'envelope-o'='envelope-o',
|
||||
'envelope-square'='envelope-square',
|
||||
'envelope'='envelope',
|
||||
'envira'='envira',
|
||||
'eraser'='eraser',
|
||||
'ethereum'='ethereum',
|
||||
'etsy'='etsy',
|
||||
'eur'='eur',
|
||||
'exchange'='exchange',
|
||||
'exclamation-circle'='exclamation-circle',
|
||||
'exclamation'='exclamation',
|
||||
'exclamation-triangle'='exclamation-triangle',
|
||||
'expand'='expand',
|
||||
'expeditedssl'='expeditedssl',
|
||||
'external-link-square'='external-link-square',
|
||||
'external-link'='external-link',
|
||||
'eyedropper'='eyedropper',
|
||||
'eye-slash'='eye-slash',
|
||||
'eye'='eye',
|
||||
'facebook-messenger'='facebook-messenger',
|
||||
'facebook-official'='facebook-official',
|
||||
'facebook-square'='facebook-square',
|
||||
'facebook'='facebook',
|
||||
'fast-backward'='fast-backward',
|
||||
'fast-forward'='fast-forward',
|
||||
'fax'='fax',
|
||||
'f-droid'='f-droid',
|
||||
'female'='female',
|
||||
'ffmpeg'='ffmpeg',
|
||||
'fighter-jet'='fighter-jet',
|
||||
'file-archive-o'='file-archive-o',
|
||||
'file-audio-o'='file-audio-o',
|
||||
'file-code-o'='file-code-o',
|
||||
'file-epub'='file-epub',
|
||||
'file-excel-o'='file-excel-o',
|
||||
'file-image-o'='file-image-o',
|
||||
'file-o'='file-o',
|
||||
'file-pdf-o'='file-pdf-o',
|
||||
'file-powerpoint-o'='file-powerpoint-o',
|
||||
'files-o'='files-o',
|
||||
'file'='file',
|
||||
'file-text-o'='file-text-o',
|
||||
'file-text'='file-text',
|
||||
'file-video-o'='file-video-o',
|
||||
'file-word-o'='file-word-o',
|
||||
'film'='film',
|
||||
'filter'='filter',
|
||||
'fire-extinguisher'='fire-extinguisher',
|
||||
'firefox'='firefox',
|
||||
'fire'='fire',
|
||||
'first-order'='first-order',
|
||||
'flag-checkered'='flag-checkered',
|
||||
'flag-o'='flag-o',
|
||||
'flag'='flag',
|
||||
'flask'='flask',
|
||||
'flickr'='flickr',
|
||||
'floppy-o'='floppy-o',
|
||||
'folder-open-o'='folder-open-o',
|
||||
'folder-open'='folder-open',
|
||||
'folder-o'='folder-o',
|
||||
'folder'='folder',
|
||||
'font-awesome'='font-awesome',
|
||||
'fonticons'='fonticons',
|
||||
'font'='font',
|
||||
'fork-awesome'='fork-awesome',
|
||||
'fort-awesome'='fort-awesome',
|
||||
'forumbee'='forumbee',
|
||||
'forward'='forward',
|
||||
'foursquare'='foursquare',
|
||||
'free-code-camp'='free-code-camp',
|
||||
'freedombox'='freedombox',
|
||||
'friendica'='friendica',
|
||||
'frown-o'='frown-o',
|
||||
'funkwhale'='funkwhale',
|
||||
'futbol-o'='futbol-o',
|
||||
'gamepad'='gamepad',
|
||||
'gavel'='gavel',
|
||||
'gbp'='gbp',
|
||||
'genderless'='genderless',
|
||||
'get-pocket'='get-pocket',
|
||||
'gg-circle'='gg-circle',
|
||||
'gg'='gg',
|
||||
'gift'='gift',
|
||||
'gimp'='gimp',
|
||||
'gitea'='gitea',
|
||||
'github-alt'='github-alt',
|
||||
'github-square'='github-square',
|
||||
'github'='github',
|
||||
'gitlab'='gitlab',
|
||||
'git-square'='git-square',
|
||||
'git'='git',
|
||||
'glass'='glass',
|
||||
'glide-g'='glide-g',
|
||||
'glide'='glide',
|
||||
'globe-e'='globe-e',
|
||||
'globe'='globe',
|
||||
'globe-w'='globe-w',
|
||||
'gnupg'='gnupg',
|
||||
'gnu-social'='gnu-social',
|
||||
'google-plus-official'='google-plus-official',
|
||||
'google-plus-square'='google-plus-square',
|
||||
'google-plus'='google-plus',
|
||||
'google'='google',
|
||||
'google-wallet'='google-wallet',
|
||||
'graduation-cap'='graduation-cap',
|
||||
'gratipay'='gratipay',
|
||||
'grav'='grav',
|
||||
'hackaday'='hackaday',
|
||||
'hacker-news'='hacker-news',
|
||||
'hackster'='hackster',
|
||||
'hal'='hal',
|
||||
'hand-lizard-o'='hand-lizard-o',
|
||||
'hand-o-down'='hand-o-down',
|
||||
'hand-o-left'='hand-o-left',
|
||||
'hand-o-right'='hand-o-right',
|
||||
'hand-o-up'='hand-o-up',
|
||||
'hand-paper-o'='hand-paper-o',
|
||||
'hand-peace-o'='hand-peace-o',
|
||||
'hand-pointer-o'='hand-pointer-o',
|
||||
'hand-rock-o'='hand-rock-o',
|
||||
'hand-scissors-o'='hand-scissors-o',
|
||||
'handshake-o'='handshake-o',
|
||||
'hand-spock-o'='hand-spock-o',
|
||||
'hashnode'='hashnode',
|
||||
'hashtag'='hashtag',
|
||||
'hdd-o'='hdd-o',
|
||||
'header'='header',
|
||||
'headphones'='headphones',
|
||||
'heartbeat'='heartbeat',
|
||||
'heart-o'='heart-o',
|
||||
'heart'='heart',
|
||||
'history'='history',
|
||||
'home'='home',
|
||||
'hospital-o'='hospital-o',
|
||||
'hourglass-end'='hourglass-end',
|
||||
'hourglass-half'='hourglass-half',
|
||||
'hourglass-o'='hourglass-o',
|
||||
'hourglass-start'='hourglass-start',
|
||||
'hourglass'='hourglass',
|
||||
'houzz'='houzz',
|
||||
'h-square'='h-square',
|
||||
'html5'='html5',
|
||||
'hubzilla'='hubzilla',
|
||||
'i-cursor'='i-cursor',
|
||||
'id-badge'='id-badge',
|
||||
'id-card-o'='id-card-o',
|
||||
'id-card'='id-card',
|
||||
'ils'='ils',
|
||||
'imdb'='imdb',
|
||||
'inbox'='inbox',
|
||||
'indent'='indent',
|
||||
'industry'='industry',
|
||||
'info-circle'='info-circle',
|
||||
'info'='info',
|
||||
'inkscape'='inkscape',
|
||||
'inr'='inr',
|
||||
'instagram'='instagram',
|
||||
'internet-explorer'='internet-explorer',
|
||||
'ioxhost'='ioxhost',
|
||||
'italic'='italic',
|
||||
'jirafeau'='jirafeau',
|
||||
'joomla'='joomla',
|
||||
'joplin'='joplin',
|
||||
'jpy'='jpy',
|
||||
'jsfiddle'='jsfiddle',
|
||||
'julia'='julia',
|
||||
'jupyter'='jupyter',
|
||||
'keybase'='keybase',
|
||||
'keyboard-o'='keyboard-o',
|
||||
'key-modern'='key-modern',
|
||||
'key'='key',
|
||||
'krw'='krw',
|
||||
'language'='language',
|
||||
'laptop'='laptop',
|
||||
'laravel'='laravel',
|
||||
'lastfm-square'='lastfm-square',
|
||||
'lastfm'='lastfm',
|
||||
'leaf'='leaf',
|
||||
'leanpub'='leanpub',
|
||||
'lemon-o'='lemon-o',
|
||||
'level-down'='level-down',
|
||||
'level-up'='level-up',
|
||||
'liberapay-square'='liberapay-square',
|
||||
'liberapay'='liberapay',
|
||||
'life-ring'='life-ring',
|
||||
'lightbulb-o'='lightbulb-o',
|
||||
'line-chart'='line-chart',
|
||||
'linkedin-square'='linkedin-square',
|
||||
'linkedin'='linkedin',
|
||||
'link'='link',
|
||||
'linode'='linode',
|
||||
'linux'='linux',
|
||||
'list-alt'='list-alt',
|
||||
'list-ol'='list-ol',
|
||||
'list'='list',
|
||||
'list-ul'='list-ul',
|
||||
'location-arrow'='location-arrow',
|
||||
'lock'='lock',
|
||||
'long-arrow-down'='long-arrow-down',
|
||||
'long-arrow-left'='long-arrow-left',
|
||||
'long-arrow-right'='long-arrow-right',
|
||||
'long-arrow-up'='long-arrow-up',
|
||||
'low-vision'='low-vision',
|
||||
'magic'='magic',
|
||||
'magnet'='magnet',
|
||||
'male'='male',
|
||||
'map-marker'='map-marker',
|
||||
'map-o'='map-o',
|
||||
'map-pin'='map-pin',
|
||||
'map-signs'='map-signs',
|
||||
'map'='map',
|
||||
'mars-double'='mars-double',
|
||||
'mars-stroke-h'='mars-stroke-h',
|
||||
'mars-stroke'='mars-stroke',
|
||||
'mars-stroke-v'='mars-stroke-v',
|
||||
'mars'='mars',
|
||||
'mastodon-alt'='mastodon-alt',
|
||||
'mastodon-square'='mastodon-square',
|
||||
'mastodon'='mastodon',
|
||||
'matrix-org'='matrix-org',
|
||||
'maxcdn'='maxcdn',
|
||||
'meanpath'='meanpath',
|
||||
'medium-square'='medium-square',
|
||||
'medium'='medium',
|
||||
'medkit'='medkit',
|
||||
'meetup'='meetup',
|
||||
'meh-o'='meh-o',
|
||||
'mercury'='mercury',
|
||||
'microchip'='microchip',
|
||||
'microphone-slash'='microphone-slash',
|
||||
'microphone'='microphone',
|
||||
'minus-circle'='minus-circle',
|
||||
'minus-square-o'='minus-square-o',
|
||||
'minus-square'='minus-square',
|
||||
'minus'='minus',
|
||||
'mixcloud'='mixcloud',
|
||||
'mobile'='mobile',
|
||||
'modx'='modx',
|
||||
'money'='money',
|
||||
'moon-o'='moon-o',
|
||||
'moon'='moon',
|
||||
'motorcycle'='motorcycle',
|
||||
'mouse-pointer'='mouse-pointer',
|
||||
'music'='music',
|
||||
'neuter'='neuter',
|
||||
'newspaper-o'='newspaper-o',
|
||||
'nextcloud-square'='nextcloud-square',
|
||||
'nextcloud'='nextcloud',
|
||||
'nodejs'='nodejs',
|
||||
'object-group'='object-group',
|
||||
'object-ungroup'='object-ungroup',
|
||||
'odnoklassniki-square'='odnoklassniki-square',
|
||||
'odnoklassniki'='odnoklassniki',
|
||||
'opencart'='opencart',
|
||||
'open-collective'='open-collective',
|
||||
'openid'='openid',
|
||||
'opera'='opera',
|
||||
'optin-monster'='optin-monster',
|
||||
'orcid'='orcid',
|
||||
'outdent'='outdent',
|
||||
'pagelines'='pagelines',
|
||||
'paint-brush'='paint-brush',
|
||||
'paperclip'='paperclip',
|
||||
'paper-plane-o'='paper-plane-o',
|
||||
'paper-plane'='paper-plane',
|
||||
'paragraph'='paragraph',
|
||||
'patreon'='patreon',
|
||||
'pause-circle-o'='pause-circle-o',
|
||||
'pause-circle'='pause-circle',
|
||||
'pause'='pause',
|
||||
'paw'='paw',
|
||||
'paypal'='paypal',
|
||||
'peertube'='peertube',
|
||||
'pencil-square-o'='pencil-square-o',
|
||||
'pencil-square'='pencil-square',
|
||||
'pencil'='pencil',
|
||||
'percent'='percent',
|
||||
'phone-square'='phone-square',
|
||||
'phone'='phone',
|
||||
'php'='php',
|
||||
'picture-o'='picture-o',
|
||||
'pie-chart'='pie-chart',
|
||||
'pinterest-p'='pinterest-p',
|
||||
'pinterest-square'='pinterest-square',
|
||||
'pinterest'='pinterest',
|
||||
'pixelfed'='pixelfed',
|
||||
'plane'='plane',
|
||||
'play-circle-o'='play-circle-o',
|
||||
'play-circle'='play-circle',
|
||||
'play'='play',
|
||||
'pleroma'='pleroma',
|
||||
'plug'='plug',
|
||||
'plus-circle'='plus-circle',
|
||||
'plus-square-o'='plus-square-o',
|
||||
'plus-square'='plus-square',
|
||||
'plus'='plus',
|
||||
'podcast'='podcast',
|
||||
'power-off'='power-off',
|
||||
'print'='print',
|
||||
'product-hunt'='product-hunt',
|
||||
'puzzle-piece'='puzzle-piece',
|
||||
'python'='python',
|
||||
'qq'='qq',
|
||||
'qrcode'='qrcode',
|
||||
'question-circle-o'='question-circle-o',
|
||||
'question-circle'='question-circle',
|
||||
'question'='question',
|
||||
'quora'='quora',
|
||||
'quote-left'='quote-left',
|
||||
'quote-right'='quote-right',
|
||||
'random'='random',
|
||||
'ravelry'='ravelry',
|
||||
'react'='react',
|
||||
'rebel'='rebel',
|
||||
'recycle'='recycle',
|
||||
'reddit-alien'='reddit-alien',
|
||||
'reddit-square'='reddit-square',
|
||||
'reddit'='reddit',
|
||||
'refresh'='refresh',
|
||||
'registered'='registered',
|
||||
'renren'='renren',
|
||||
'repeat'='repeat',
|
||||
'reply-all'='reply-all',
|
||||
'reply'='reply',
|
||||
'researchgate'='researchgate',
|
||||
'retweet'='retweet',
|
||||
'road'='road',
|
||||
'rocket'='rocket',
|
||||
'rss-square'='rss-square',
|
||||
'rss'='rss',
|
||||
'rub'='rub',
|
||||
'safari'='safari',
|
||||
'scissors'='scissors',
|
||||
'scribd'='scribd',
|
||||
'scuttlebutt'='scuttlebutt',
|
||||
'search-minus'='search-minus',
|
||||
'search-plus'='search-plus',
|
||||
'search'='search',
|
||||
'sellsy'='sellsy',
|
||||
'server'='server',
|
||||
'shaarli-o'='shaarli-o',
|
||||
'shaarli'='shaarli',
|
||||
'share-alt-square'='share-alt-square',
|
||||
'share-alt'='share-alt',
|
||||
'share-square-o'='share-square-o',
|
||||
'share-square'='share-square',
|
||||
'share'='share',
|
||||
'shield'='shield',
|
||||
'ship'='ship',
|
||||
'shirtsinbulk'='shirtsinbulk',
|
||||
'shopping-bag'='shopping-bag',
|
||||
'shopping-basket'='shopping-basket',
|
||||
'shopping-cart'='shopping-cart',
|
||||
'shower'='shower',
|
||||
'signalapp'='signalapp',
|
||||
'signal'='signal',
|
||||
'sign-in'='sign-in',
|
||||
'sign-language'='sign-language',
|
||||
'sign-out'='sign-out',
|
||||
'simplybuilt'='simplybuilt',
|
||||
'sitemap'='sitemap',
|
||||
'skyatlas'='skyatlas',
|
||||
'skype'='skype',
|
||||
'slack'='slack',
|
||||
'sliders'='sliders',
|
||||
'slideshare'='slideshare',
|
||||
'smile-o'='smile-o',
|
||||
'snapchat-ghost'='snapchat-ghost',
|
||||
'snapchat-square'='snapchat-square',
|
||||
'snapchat'='snapchat',
|
||||
'snowdrift'='snowdrift',
|
||||
'snowflake-o'='snowflake-o',
|
||||
'social-home'='social-home',
|
||||
'sort-alpha-asc'='sort-alpha-asc',
|
||||
'sort-alpha-desc'='sort-alpha-desc',
|
||||
'sort-amount-asc'='sort-amount-asc',
|
||||
'sort-amount-desc'='sort-amount-desc',
|
||||
'sort-asc'='sort-asc',
|
||||
'sort-desc'='sort-desc',
|
||||
'sort-numeric-asc'='sort-numeric-asc',
|
||||
'sort-numeric-desc'='sort-numeric-desc',
|
||||
'sort'='sort',
|
||||
'soundcloud'='soundcloud',
|
||||
'space-shuttle'='space-shuttle',
|
||||
'spell-check'='spell-check',
|
||||
'spinner'='spinner',
|
||||
'spoon'='spoon',
|
||||
'spotify'='spotify',
|
||||
'square-o'='square-o',
|
||||
'square'='square',
|
||||
'stack-exchange'='stack-exchange',
|
||||
'stack-overflow'='stack-overflow',
|
||||
'star-half-o'='star-half-o',
|
||||
'star-half'='star-half',
|
||||
'star-o'='star-o',
|
||||
'star'='star',
|
||||
'steam-square'='steam-square',
|
||||
'steam'='steam',
|
||||
'step-backward'='step-backward',
|
||||
'step-forward'='step-forward',
|
||||
'stethoscope'='stethoscope',
|
||||
'sticky-note-o'='sticky-note-o',
|
||||
'sticky-note'='sticky-note',
|
||||
'stop-circle-o'='stop-circle-o',
|
||||
'stop-circle'='stop-circle',
|
||||
'stop'='stop',
|
||||
'street-view'='street-view',
|
||||
'strikethrough'='strikethrough',
|
||||
'stumbleupon-circle'='stumbleupon-circle',
|
||||
'stumbleupon'='stumbleupon',
|
||||
'subscript'='subscript',
|
||||
'subway'='subway',
|
||||
'suitcase'='suitcase',
|
||||
'sun-o'='sun-o',
|
||||
'sun'='sun',
|
||||
'superpowers'='superpowers',
|
||||
'superscript'='superscript',
|
||||
'syncthing'='syncthing',
|
||||
'table'='table',
|
||||
'tablet'='tablet',
|
||||
'tachometer'='tachometer',
|
||||
'tags'='tags',
|
||||
'tag'='tag',
|
||||
'tasks'='tasks',
|
||||
'taxi'='taxi',
|
||||
'telegram'='telegram',
|
||||
'television'='television',
|
||||
'tencent-weibo'='tencent-weibo',
|
||||
'terminal'='terminal',
|
||||
'text-height'='text-height',
|
||||
'text-width'='text-width',
|
||||
'themeisle'='themeisle',
|
||||
'thermometer-empty'='thermometer-empty',
|
||||
'thermometer-full'='thermometer-full',
|
||||
'thermometer-half'='thermometer-half',
|
||||
'thermometer-quarter'='thermometer-quarter',
|
||||
'thermometer-three-quarters'='thermometer-three-quarters',
|
||||
'th-large'='th-large',
|
||||
'th-list'='th-list',
|
||||
'th'='th',
|
||||
'thumbs-down'='thumbs-down',
|
||||
'thumbs-o-down'='thumbs-o-down',
|
||||
'thumbs-o-up'='thumbs-o-up',
|
||||
'thumbs-up'='thumbs-up',
|
||||
'thumb-tack'='thumb-tack',
|
||||
'ticket'='ticket',
|
||||
'times-circle-o'='times-circle-o',
|
||||
'times-circle'='times-circle',
|
||||
'times'='times',
|
||||
'tint'='tint',
|
||||
'tipeee'='tipeee',
|
||||
'toggle-off'='toggle-off',
|
||||
'toggle-on'='toggle-on',
|
||||
'tor-onion'='tor-onion',
|
||||
'trademark'='trademark',
|
||||
'train'='train',
|
||||
'transgender-alt'='transgender-alt',
|
||||
'transgender'='transgender',
|
||||
'trash-o'='trash-o',
|
||||
'trash'='trash',
|
||||
'tree'='tree',
|
||||
'trello'='trello',
|
||||
'tripadvisor'='tripadvisor',
|
||||
'trophy'='trophy',
|
||||
'truck'='truck',
|
||||
'try'='try',
|
||||
'tty'='tty',
|
||||
'tumblr-square'='tumblr-square',
|
||||
'tumblr'='tumblr',
|
||||
'twitch'='twitch',
|
||||
'twitter-square'='twitter-square',
|
||||
'twitter'='twitter',
|
||||
'umbrella'='umbrella',
|
||||
'underline'='underline',
|
||||
'undo'='undo',
|
||||
'universal-access'='universal-access',
|
||||
'university'='university',
|
||||
'unlock-alt'='unlock-alt',
|
||||
'unlock'='unlock',
|
||||
'unslpash'='unslpash',
|
||||
'upload'='upload',
|
||||
'usb'='usb',
|
||||
'usd'='usd',
|
||||
'user-circle-o'='user-circle-o',
|
||||
'user-circle'='user-circle',
|
||||
'user-md'='user-md',
|
||||
'user-o'='user-o',
|
||||
'user-plus'='user-plus',
|
||||
'user-secret'='user-secret',
|
||||
'users'='users',
|
||||
'user'='user',
|
||||
'user-times'='user-times',
|
||||
'venus-double'='venus-double',
|
||||
'venus-mars'='venus-mars',
|
||||
'venus'='venus',
|
||||
'viacoin'='viacoin',
|
||||
'viadeo-square'='viadeo-square',
|
||||
'viadeo'='viadeo',
|
||||
'video-camera'='video-camera',
|
||||
'vimeo-square'='vimeo-square',
|
||||
'vimeo'='vimeo',
|
||||
'vine'='vine',
|
||||
'vk'='vk',
|
||||
'volume-control-phone'='volume-control-phone',
|
||||
'volume-down'='volume-down',
|
||||
'volume-mute'='volume-mute',
|
||||
'volume-off'='volume-off',
|
||||
'volume-up'='volume-up',
|
||||
'weibo'='weibo',
|
||||
'weixin'='weixin',
|
||||
'whatsapp'='whatsapp',
|
||||
'wheelchair-alt'='wheelchair-alt',
|
||||
'wheelchair'='wheelchair',
|
||||
'wifi'='wifi',
|
||||
'wikidata'='wikidata',
|
||||
'wikipedia-w'='wikipedia-w',
|
||||
'window-close-o'='window-close-o',
|
||||
'window-close'='window-close',
|
||||
'window-maximize'='window-maximize',
|
||||
'window-minimize'='window-minimize',
|
||||
'window-restore'='window-restore',
|
||||
'windows'='windows',
|
||||
'wire'='wire',
|
||||
'wordpress'='wordpress',
|
||||
'wpbeginner'='wpbeginner',
|
||||
'wpexplorer'='wpexplorer',
|
||||
'wpforms'='wpforms',
|
||||
'wrench'='wrench',
|
||||
'xing-square'='xing-square',
|
||||
'xing'='xing',
|
||||
'xmpp'='xmpp',
|
||||
'yahoo'='yahoo',
|
||||
'y-combinator'='y-combinator',
|
||||
'yelp'='yelp',
|
||||
'yoast'='yoast',
|
||||
'youtube-play'='youtube-play',
|
||||
'youtube-square'='youtube-square',
|
||||
'youtube'='youtube',
|
||||
'zotero'='zotero'
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useState } from 'react'
|
||||
import { Button, Form, ModalFooter } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { CommonModal } from '../../../../common/modals/common-modal'
|
||||
import { TableSize } from './table-picker'
|
||||
|
||||
export interface CustomTableSizeModalProps {
|
||||
showModal: boolean
|
||||
onDismiss: () => void
|
||||
onTablePicked: (row: number, cols: number) => void
|
||||
}
|
||||
|
||||
export const CustomTableSizeModal: React.FC<CustomTableSizeModalProps> = ({ showModal, onDismiss, onTablePicked }) => {
|
||||
const { t } = useTranslation()
|
||||
const [tableSize, setTableSize] = useState<TableSize>({
|
||||
rows: 0,
|
||||
columns: 0
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setTableSize({
|
||||
rows: 0,
|
||||
columns: 0
|
||||
})
|
||||
}, [showModal])
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
onTablePicked(tableSize.rows, tableSize.columns)
|
||||
onDismiss()
|
||||
}, [onDismiss, tableSize, onTablePicked])
|
||||
|
||||
return (
|
||||
<CommonModal
|
||||
show={showModal}
|
||||
onHide={() => onDismiss()}
|
||||
titleI18nKey={'editor.editorToolbar.table.customSize'}
|
||||
closeButton={true}
|
||||
icon={'table'}>
|
||||
<div className={'col-lg-10 d-flex flex-row p-3 align-items-center'}>
|
||||
<Form.Control
|
||||
type={'number'}
|
||||
min={1}
|
||||
placeholder={t('editor.editorToolbar.table.cols')}
|
||||
isInvalid={tableSize.columns <= 0}
|
||||
onChange={(event) => {
|
||||
const value = Number.parseInt(event.currentTarget.value)
|
||||
setTableSize(old => ({
|
||||
rows: old.rows,
|
||||
columns: isNaN(value) ? 0 : value
|
||||
}))
|
||||
}}
|
||||
/>
|
||||
<ForkAwesomeIcon icon='times' className='mx-2' fixedWidth={true}/>
|
||||
<Form.Control
|
||||
type={'number'}
|
||||
min={1}
|
||||
placeholder={t('editor.editorToolbar.table.rows')}
|
||||
isInvalid={tableSize.rows <= 0}
|
||||
onChange={(event) => {
|
||||
const value = Number.parseInt(event.currentTarget.value)
|
||||
setTableSize(old => ({
|
||||
rows: isNaN(value) ? 0 : value,
|
||||
columns: old.columns
|
||||
}))
|
||||
}}/>
|
||||
</div>
|
||||
<ModalFooter>
|
||||
<Button onClick={onClick} disabled={tableSize.rows <= 0 || tableSize.columns <= 0}>
|
||||
{t('editor.editorToolbar.table.create')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</CommonModal>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import CodeMirror from 'codemirror'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { addTable } from '../utils/toolbarButtonUtils'
|
||||
import { TablePicker } from './table-picker'
|
||||
|
||||
export interface TablePickerButtonProps {
|
||||
editor: CodeMirror.Editor
|
||||
}
|
||||
|
||||
export const TablePickerButton: React.FC<TablePickerButtonProps> = ({ editor }) => {
|
||||
const { t } = useTranslation()
|
||||
const [showTablePicker, setShowTablePicker] = useState(false)
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<TablePicker
|
||||
show={showTablePicker}
|
||||
onDismiss={() => setShowTablePicker(false)}
|
||||
onTablePicked={(rows, cols) => {
|
||||
setShowTablePicker(false)
|
||||
addTable(editor, rows, cols)
|
||||
}}
|
||||
/>
|
||||
<Button data-cy={'show-table-overlay'} variant='light' onClick={() => setShowTablePicker(old => !old)} title={t('editor.editorToolbar.table.title')}>
|
||||
<ForkAwesomeIcon icon="table"/>
|
||||
</Button>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
|
||||
|
||||
.table-picker-container {
|
||||
z-index: 1111;
|
||||
|
||||
@import "../../../../../style/variables.light";
|
||||
|
||||
.table-cell {
|
||||
border-top: 1px solid $dark;
|
||||
border-left: 1px solid $dark;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-bottom: 1px solid $dark;
|
||||
border-right: 1px solid $dark;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 15px [col-start]);
|
||||
grid-template-rows: repeat(8, 15px [row-start]);
|
||||
}
|
||||
|
||||
body.dark {
|
||||
@import "../../../../../style/variables.dark";
|
||||
|
||||
.table-cell {
|
||||
border-top: 1px solid $dark;
|
||||
border-left: 1px solid $dark;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
border-bottom: 1px solid $dark;
|
||||
border-right: 1px solid $dark;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(10, 15px [col-start]);
|
||||
grid-template-rows: repeat(8, 15px [row-start]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useClickAway } from 'react-use'
|
||||
import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { createNumberRangeArray } from '../../../../common/number-range/number-range'
|
||||
import { CustomTableSizeModal } from './custom-table-size-modal'
|
||||
import './table-picker.scss'
|
||||
|
||||
export interface TablePickerProps {
|
||||
show: boolean
|
||||
onDismiss: () => void
|
||||
onTablePicked: (row: number, cols: number) => void
|
||||
}
|
||||
|
||||
export type TableSize = {
|
||||
rows: number,
|
||||
columns: number
|
||||
}
|
||||
|
||||
export const TablePicker: React.FC<TablePickerProps> = ({ show, onDismiss, onTablePicked }) => {
|
||||
const { t } = useTranslation()
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const [tableSize, setTableSize] = useState<TableSize>()
|
||||
const [showDialog, setShowDialog] = useState(false)
|
||||
|
||||
useClickAway(containerRef, () => {
|
||||
onDismiss()
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setTableSize(undefined)
|
||||
}, [show])
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (tableSize) {
|
||||
onTablePicked(tableSize.rows, tableSize.columns)
|
||||
}
|
||||
}, [onTablePicked, tableSize])
|
||||
|
||||
return (
|
||||
<div className={`position-absolute table-picker-container p-2 ${!show || showDialog ? 'd-none' : ''} bg-light`} ref={containerRef} role="grid">
|
||||
<p className={'lead'}>
|
||||
{ tableSize
|
||||
? t('editor.editorToolbar.table.size', { cols: tableSize?.columns, rows: tableSize.rows })
|
||||
: t('editor.editorToolbar.table.title')
|
||||
}
|
||||
</p>
|
||||
<div className={'table-container'}>
|
||||
{createNumberRangeArray(8).map((row: number) => (
|
||||
createNumberRangeArray(10).map((col: number) => (
|
||||
<div
|
||||
key={`${row}_${col}`}
|
||||
className={`table-cell ${tableSize && row < tableSize.rows && col < tableSize.columns ? 'bg-primary' : ''}`}
|
||||
onMouseEnter={() => {
|
||||
setTableSize({
|
||||
rows: row + 1,
|
||||
columns: col + 1
|
||||
})
|
||||
}}
|
||||
title={t('editor.editorToolbar.table.size', { cols: col + 1, rows: row + 1 })}
|
||||
onClick={onClick}
|
||||
/>
|
||||
)
|
||||
)
|
||||
))}
|
||||
</div>
|
||||
<div className="d-flex justify-content-center mt-2">
|
||||
<Button data-cy={'show-custom-table-modal'} className={'text-center'} onClick={() => setShowDialog(true)}>
|
||||
<ForkAwesomeIcon icon="table"/> {t('editor.editorToolbar.table.customSize')}
|
||||
</Button>
|
||||
<CustomTableSizeModal
|
||||
showModal={showDialog}
|
||||
onDismiss={() => setShowDialog(false)}
|
||||
onTablePicked={onTablePicked}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*!
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.btn-toolbar {
|
||||
border-bottom: 1px solid #ededed;
|
||||
border-top: 1px solid #ededed;
|
||||
|
||||
.btn {
|
||||
padding: 0.1875rem 0.5rem;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.btn-group:not(:last-of-type)::after {
|
||||
background-color: #e2e6ea;
|
||||
width: 2px;
|
||||
padding: 0.25rem 0;
|
||||
content: ' ';
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
117
src/components/editor-page/editor-pane/tool-bar/tool-bar.tsx
Normal file
117
src/components/editor-page/editor-pane/tool-bar/tool-bar.tsx
Normal file
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Editor } from 'codemirror'
|
||||
import React from 'react'
|
||||
import { Button, ButtonGroup, ButtonToolbar } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { EditorPreferences } from './editor-preferences/editor-preferences'
|
||||
import { EmojiPickerButton } from './emoji-picker/emoji-picker-button'
|
||||
import { TablePickerButton } from './table-picker/table-picker-button'
|
||||
import './tool-bar.scss'
|
||||
import { UploadImageButton } from './upload-image-button'
|
||||
import {
|
||||
addCodeFences,
|
||||
addCollapsableBlock,
|
||||
addComment,
|
||||
addHeaderLevel,
|
||||
addImage,
|
||||
addLine,
|
||||
addLink,
|
||||
addList,
|
||||
addOrderedList,
|
||||
addQuotes,
|
||||
addTaskList,
|
||||
makeSelectionBold,
|
||||
makeSelectionItalic,
|
||||
strikeThroughSelection,
|
||||
subscriptSelection,
|
||||
superscriptSelection,
|
||||
underlineSelection
|
||||
} from './utils/toolbarButtonUtils'
|
||||
|
||||
export interface ToolBarProps {
|
||||
editor?: Editor
|
||||
}
|
||||
|
||||
export const ToolBar: React.FC<ToolBarProps> = ({ editor }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<ButtonToolbar className='bg-light'>
|
||||
<ButtonGroup className={'mx-1 flex-wrap'}>
|
||||
<Button data-cy={'format-bold'} variant='light' onClick={() => makeSelectionBold(editor)} title={t('editor.editorToolbar.bold')}>
|
||||
<ForkAwesomeIcon icon="bold"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-italic'} variant='light' onClick={() => makeSelectionItalic(editor)} title={t('editor.editorToolbar.italic')}>
|
||||
<ForkAwesomeIcon icon="italic"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-underline'} variant='light' onClick={() => underlineSelection(editor)} title={t('editor.editorToolbar.underline')}>
|
||||
<ForkAwesomeIcon icon="underline"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-strikethrough'} variant='light' onClick={() => strikeThroughSelection(editor)} title={t('editor.editorToolbar.strikethrough')}>
|
||||
<ForkAwesomeIcon icon="strikethrough"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-subscript'} variant='light' onClick={() => subscriptSelection(editor)} title={t('editor.editorToolbar.subscript')}>
|
||||
<ForkAwesomeIcon icon="subscript"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-superscript'} variant='light' onClick={() => superscriptSelection(editor)} title={t('editor.editorToolbar.superscript')}>
|
||||
<ForkAwesomeIcon icon="superscript"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className={'mx-1 flex-wrap'}>
|
||||
<Button data-cy={'format-heading'} variant='light' onClick={() => addHeaderLevel(editor)} title={t('editor.editorToolbar.header')}>
|
||||
<ForkAwesomeIcon icon="header"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-code-block'} variant='light' onClick={() => addCodeFences(editor)} title={t('editor.editorToolbar.code')}>
|
||||
<ForkAwesomeIcon icon="code"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-block-quote'} variant='light' onClick={() => addQuotes(editor)} title={t('editor.editorToolbar.blockquote')}>
|
||||
<ForkAwesomeIcon icon="quote-right"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-unordered-list'} variant='light' onClick={() => addList(editor)} title={t('editor.editorToolbar.unorderedList')}>
|
||||
<ForkAwesomeIcon icon="list"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-ordered-list'} variant='light' onClick={() => addOrderedList(editor)} title={t('editor.editorToolbar.orderedList')}>
|
||||
<ForkAwesomeIcon icon="list-ol"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-check-list'} variant='light' onClick={() => addTaskList(editor)} title={t('editor.editorToolbar.checkList')}>
|
||||
<ForkAwesomeIcon icon="check-square"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className={'mx-1 flex-wrap'}>
|
||||
<Button data-cy={'format-link'} variant='light' onClick={() => addLink(editor)} title={t('editor.editorToolbar.link')}>
|
||||
<ForkAwesomeIcon icon="link"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-image'} variant='light' onClick={() => addImage(editor)} title={t('editor.editorToolbar.image')}>
|
||||
<ForkAwesomeIcon icon="picture-o"/>
|
||||
</Button>
|
||||
<UploadImageButton editor={editor}/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className={'mx-1 flex-wrap'}>
|
||||
<TablePickerButton editor={editor}/>
|
||||
<Button data-cy={'format-add-line'} variant='light' onClick={() => addLine(editor)} title={t('editor.editorToolbar.line')}>
|
||||
<ForkAwesomeIcon icon="minus"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-collapsable-block'} variant='light' onClick={() => addCollapsableBlock(editor)} title={t('editor.editorToolbar.collapsableBlock')}>
|
||||
<ForkAwesomeIcon icon="caret-square-o-down"/>
|
||||
</Button>
|
||||
<Button data-cy={'format-add-comment'} variant='light' onClick={() => addComment(editor)} title={t('editor.editorToolbar.comment')}>
|
||||
<ForkAwesomeIcon icon="comment"/>
|
||||
</Button>
|
||||
<EmojiPickerButton editor={editor}/>
|
||||
</ButtonGroup>
|
||||
<ButtonGroup className={'mx-1 flex-wrap'}>
|
||||
<EditorPreferences/>
|
||||
</ButtonGroup>
|
||||
</ButtonToolbar>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Editor } from 'codemirror'
|
||||
import React, { Fragment, useCallback, useRef } from 'react'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
|
||||
import { UploadInput } from '../../sidebar/upload-input'
|
||||
import { handleUpload } from '../upload-handler'
|
||||
import { supportedMimeTypesJoined } from './utils/upload-image-mimetypes'
|
||||
|
||||
export interface UploadImageButtonProps {
|
||||
editor?: Editor
|
||||
}
|
||||
|
||||
export const UploadImageButton: React.FC<UploadImageButtonProps> = ({ editor }) => {
|
||||
const { t } = useTranslation()
|
||||
const clickRef = useRef<(() => void)>()
|
||||
const buttonClick = useCallback(() => {
|
||||
clickRef.current?.()
|
||||
}, [])
|
||||
|
||||
const onUploadImage = useCallback((file: File) => {
|
||||
if (editor) {
|
||||
handleUpload(file, editor)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}, [editor])
|
||||
|
||||
if (!editor) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Button variant='light' onClick={buttonClick} title={t('editor.editorToolbar.uploadImage')}>
|
||||
<ForkAwesomeIcon icon={'upload'}/>
|
||||
</Button>
|
||||
<UploadInput onLoad={onUploadImage} acceptedFiles={supportedMimeTypesJoined} onClickRef={clickRef}/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { EmojiClickEventDetail, NativeEmoji } from 'emoji-picker-element/shared'
|
||||
|
||||
export const getEmojiIcon = (emoji: EmojiClickEventDetail): string => {
|
||||
if (emoji.unicode) {
|
||||
return emoji.unicode
|
||||
}
|
||||
if (emoji.name) {
|
||||
// noinspection CheckTagEmptyBody
|
||||
return `<i class="fa ${emoji.name}"></i>`
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
export const getEmojiShortCode = (emoji: EmojiClickEventDetail): string|undefined => {
|
||||
if (!emoji.emoji.shortcodes) {
|
||||
return undefined
|
||||
}
|
||||
let skinToneModifier = ''
|
||||
if ((emoji.emoji as NativeEmoji).skins && emoji.skinTone !== 0) {
|
||||
skinToneModifier = `:skin-tone-${emoji.skinTone as number}:`
|
||||
}
|
||||
return `:${emoji.emoji.shortcodes[0]}:${skinToneModifier}`
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Editor } from 'codemirror'
|
||||
import { EmojiClickEventDetail } from 'emoji-picker-element/shared'
|
||||
import { createNumberRangeArray } from '../../../../common/number-range/number-range'
|
||||
import { getEmojiShortCode } from './emojiUtils'
|
||||
|
||||
export const makeSelectionBold = (editor: Editor): void => wrapTextWith(editor, '**')
|
||||
export const makeSelectionItalic = (editor: Editor): void => wrapTextWith(editor, '*')
|
||||
export const strikeThroughSelection = (editor: Editor): void => wrapTextWith(editor, '~~')
|
||||
export const underlineSelection = (editor: Editor): void => wrapTextWith(editor, '++')
|
||||
export const subscriptSelection = (editor: Editor): void => wrapTextWith(editor, '~')
|
||||
export const superscriptSelection = (editor: Editor): void => wrapTextWith(editor, '^')
|
||||
export const markSelection = (editor: Editor): void => wrapTextWith(editor, '==')
|
||||
|
||||
export const addHeaderLevel = (editor: Editor): void => changeLines(editor, line => line.startsWith('#') ? `#${line}` : `# ${line}`)
|
||||
export const addCodeFences = (editor: Editor): void => wrapTextWithOrJustPut(editor, '```\n', '\n```')
|
||||
export const addQuotes = (editor: Editor): void => insertOnStartOfLines(editor, '> ')
|
||||
|
||||
export const addList = (editor: Editor): void => createList(editor, () => '- ')
|
||||
export const addOrderedList = (editor: Editor): void => createList(editor, j => `${j}. `)
|
||||
export const addTaskList = (editor: Editor): void => createList(editor, () => '- [ ] ')
|
||||
|
||||
export const addImage = (editor: Editor): void => addLink(editor, '!')
|
||||
|
||||
export const addLine = (editor: Editor): void => changeLines(editor, line => `${line}\n----`)
|
||||
export const addCollapsableBlock = (editor: Editor): void => changeLines(editor, line => `${line}\n:::spoiler Toggle label\n Toggled content\n:::`)
|
||||
export const addComment = (editor: Editor): void => changeLines(editor, line => `${line}\n> []`)
|
||||
export const addTable = (editor: Editor, rows: number, columns: number): void => {
|
||||
const rowArray = createNumberRangeArray(rows)
|
||||
const colArray = createNumberRangeArray(columns).map(col => col + 1)
|
||||
const head = '| # ' + colArray.join(' | # ') + ' |'
|
||||
const divider = '| ' + colArray.map(() => '----').join(' | ') + ' |'
|
||||
const body = rowArray.map(() => '| ' + colArray.map(() => 'Text').join(' | ') + ' |').join('\n')
|
||||
const table = `${head}\n${divider}\n${body}`
|
||||
changeLines(editor, line => `${line}\n${table}`)
|
||||
}
|
||||
|
||||
export const addEmoji = (emoji: EmojiClickEventDetail, editor: Editor): void => {
|
||||
const shortCode = getEmojiShortCode(emoji)
|
||||
if (shortCode) {
|
||||
insertAtCursor(editor, shortCode)
|
||||
}
|
||||
}
|
||||
|
||||
export const wrapTextWith = (editor: Editor, symbol: string, endSymbol?: string): void => {
|
||||
if (!editor.getSelection()) {
|
||||
return
|
||||
}
|
||||
const ranges = editor.listSelections()
|
||||
for (const range of ranges) {
|
||||
if (range.empty()) {
|
||||
continue
|
||||
}
|
||||
const from = range.from()
|
||||
const to = range.to()
|
||||
|
||||
const selection = editor.getRange(from, to)
|
||||
editor.replaceRange(symbol + selection + (endSymbol || symbol), from, to, '+input')
|
||||
range.head.ch += symbol.length
|
||||
range.anchor.ch += endSymbol ? endSymbol.length : symbol.length
|
||||
}
|
||||
editor.setSelections(ranges)
|
||||
}
|
||||
|
||||
const wrapTextWithOrJustPut = (editor: Editor, symbol: string, endSymbol?: string): void => {
|
||||
if (!editor.getSelection()) {
|
||||
const cursor = editor.getCursor()
|
||||
const lineNumber = cursor.line
|
||||
const line = editor.getLine(lineNumber)
|
||||
const replacement = /\s*\\n/.exec(line) ? `${symbol}${endSymbol ?? ''}` : `${symbol}${line}${endSymbol ?? ''}`
|
||||
editor.replaceRange(replacement,
|
||||
{ line: cursor.line, ch: 0 },
|
||||
{ line: cursor.line, ch: line.length },
|
||||
'+input')
|
||||
}
|
||||
wrapTextWith(editor, symbol, endSymbol ?? symbol)
|
||||
}
|
||||
|
||||
export const insertOnStartOfLines = (editor: Editor, symbol: string): void => {
|
||||
const cursor = editor.getCursor()
|
||||
const ranges = editor.listSelections()
|
||||
for (const range of ranges) {
|
||||
const from = range.empty() ? { line: cursor.line, ch: 0 } : range.from()
|
||||
const to = range.empty() ? { line: cursor.line, ch: editor.getLine(cursor.line).length } : range.to()
|
||||
const selection = editor.getRange(from, to)
|
||||
const lines = selection.split('\n')
|
||||
editor.replaceRange(lines.map(line => `${symbol}${line}`).join('\n'), from, to, '+input')
|
||||
}
|
||||
editor.setSelections(ranges)
|
||||
}
|
||||
|
||||
export const changeLines = (editor: Editor, replaceFunction: (line: string) => string): void => {
|
||||
const cursor = editor.getCursor()
|
||||
const ranges = editor.listSelections()
|
||||
for (const range of ranges) {
|
||||
const lineNumber = range.empty() ? cursor.line : range.from().line
|
||||
const line = editor.getLine(lineNumber)
|
||||
editor.replaceRange(replaceFunction(line), { line: lineNumber, ch: 0 }, {
|
||||
line: lineNumber,
|
||||
ch: line.length
|
||||
}, '+input')
|
||||
}
|
||||
editor.setSelections(ranges)
|
||||
}
|
||||
|
||||
export const createList = (editor: Editor, listMark: (i: number) => string): void => {
|
||||
const cursor = editor.getCursor()
|
||||
const ranges = editor.listSelections()
|
||||
for (const range of ranges) {
|
||||
const from = range.empty() ? { line: cursor.line, ch: 0 } : range.from()
|
||||
const to = range.empty() ? { line: cursor.line, ch: editor.getLine(cursor.line).length } : range.to()
|
||||
|
||||
const selection = editor.getRange(from, to)
|
||||
const lines = selection.split('\n')
|
||||
editor.replaceRange(lines.map((line, i) => `${listMark(i + 1)}${line}`).join('\n'), from, to, '+input')
|
||||
}
|
||||
editor.setSelections(ranges)
|
||||
}
|
||||
|
||||
export const addLink = (editor: Editor, prefix?: string): void => {
|
||||
const cursor = editor.getCursor()
|
||||
const ranges = editor.listSelections()
|
||||
for (const range of ranges) {
|
||||
const from = range.empty() ? { line: cursor.line, ch: cursor.ch } : range.from()
|
||||
const to = range.empty() ? { line: cursor.line, ch: cursor.ch } : range.to()
|
||||
const selection = editor.getRange(from, to)
|
||||
const linkRegex = /^(?:https?|ftp|mailto):/
|
||||
if (linkRegex.exec(selection)) {
|
||||
editor.replaceRange(`${prefix || ''}[](${selection})`, from, to, '+input')
|
||||
} else {
|
||||
editor.replaceRange(`${prefix || ''}[${selection}](https://)`, from, to, '+input')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const insertAtCursor = (editor: Editor, text: string): void => {
|
||||
const cursor = editor.getCursor()
|
||||
const ranges = editor.listSelections()
|
||||
for (const range of ranges) {
|
||||
const from = range.empty() ? { line: cursor.line, ch: cursor.ch } : range.from()
|
||||
const to = range.empty() ? { line: cursor.line, ch: cursor.ch } : range.to()
|
||||
editor.replaceRange(`${text}`, from, to, '+input')
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export const supportedMimeTypes: string[] = [
|
||||
'application/pdf',
|
||||
'image/apng',
|
||||
'image/bmp',
|
||||
'image/gif',
|
||||
'image/heif',
|
||||
'image/heic',
|
||||
'image/heif-sequence',
|
||||
'image/heic-sequence',
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/svg+xml',
|
||||
'image/tiff',
|
||||
'image/webp'
|
||||
]
|
||||
|
||||
export const supportedMimeTypesJoined = supportedMimeTypes.join(', ')
|
Loading…
Add table
Add a link
Reference in a new issue