refactor: extract shortcuts from help modal

Co-authored-by: Erik Michelson <github@erik.michelson.eu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-06-29 22:01:01 +02:00 committed by Erik Michelson
parent ae37bd36f9
commit ff004a5a63
11 changed files with 254 additions and 252 deletions

View file

@ -234,35 +234,6 @@
"untitledNote": "Untitled", "untitledNote": "Untitled",
"placeholder": "← Start by entering a title here\n===\nVisit {{host}}features if you don't know what to do.\nHappy hacking :)", "placeholder": "← Start by entering a title here\n===\nVisit {{host}}features if you don't know what to do.\nHappy hacking :)",
"infoToc": "Structure your note with headings to see a table-of-contents here.", "infoToc": "Structure your note with headings to see a table-of-contents here.",
"help": {
"shortcuts": {
"title": "Shortcuts",
"bold": "Make selection bold",
"italic": "Make selection italic",
"underline": "Underline selection",
"strikethrough": "Strike selection through",
"mark": "Mark selection",
"link": "Add link around selection",
"view": "Show only View",
"both": "Show View and Edit",
"edit": "Show only Edit"
},
"links": {
"title": "Links"
},
"contacts": {
"title": "Contacts",
"community": "Join the community",
"meetUsOn": "Meet us on {{service}}",
"helpTranslating": "Help us translating",
"reportIssue": "Report an issue"
},
"documents": {
"title": "Documents",
"yamlMetadata": "YAML Metadata",
"slideExample": "Slide Example"
}
},
"onlineStatus": { "onlineStatus": {
"online": "Online", "online": "Online",
"you": "(You)" "you": "(You)"
@ -513,6 +484,15 @@
"editNote": "Edit this note" "editNote": "Edit this note"
} }
}, },
"appbar": {
"help": {
"help": {
"header": "Help",
"shortcuts": "Shortcuts",
"cheatsheet": "Cheatsheet"
}
}
},
"common": { "common": {
"yes": "Yes", "yes": "Yes",
"no": "No", "no": "No",
@ -643,6 +623,24 @@
} }
} }
}, },
"shortcuts": {
"title": "Shortcuts",
"editor": {
"header": "Editor",
"bold": "Make selection bold",
"italic": "Make selection italic",
"underline": "Underline selection",
"strikethrough": "Strike selection through",
"mark": "Mark selection",
"link": "Add link around selection"
},
"viewMode": {
"header": "View Mode",
"view": "Show only View",
"both": "Show View and Edit",
"edit": "Show only Edit"
}
},
"cheatsheet": { "cheatsheet": {
"button": "Open Cheatsheet", "button": "Open Cheatsheet",
"search": "Search for Cheatsheets", "search": "Search for Cheatsheets",

View file

@ -1,5 +1,5 @@
/* /*
* 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 * SPDX-License-Identifier: AGPL-3.0-only
*/ */
@ -8,13 +8,13 @@ import { useTranslatedText } from '../../../../hooks/common/use-translated-text'
import { useOutlineButtonVariant } from '../../../../hooks/dark-mode/use-outline-button-variant' import { useOutlineButtonVariant } from '../../../../hooks/dark-mode/use-outline-button-variant'
import { cypressId } from '../../../../utils/cypress-attribute' import { cypressId } from '../../../../utils/cypress-attribute'
import { IconButton } from '../../../common/icon-button/icon-button' import { IconButton } from '../../../common/icon-button/icon-button'
import { HelpModal } from './help-modal' import { ShortcutsModal } from '../../../global-dialogs/shortcuts-modal/shortcuts-modal'
import React, { Fragment } from 'react' import React, { Fragment } from 'react'
import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons' import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
import { Trans } from 'react-i18next' import { Trans } from 'react-i18next'
/** /**
* Renders the button to open the {@link HelpModal}. * Renders the button to open the shortcuts modal.
*/ */
export const HelpButton: React.FC = () => { export const HelpButton: React.FC = () => {
const [modalVisibility, showModal, closeModal] = useBooleanState() const [modalVisibility, showModal, closeModal] = useBooleanState()
@ -33,7 +33,7 @@ export const HelpButton: React.FC = () => {
onClick={showModal}> onClick={showModal}>
<Trans i18nKey={'editor.documentBar.help'} /> <Trans i18nKey={'editor.documentBar.help'} />
</IconButton> </IconButton>
<HelpModal show={modalVisibility} onHide={closeModal} /> <ShortcutsModal show={modalVisibility} onHide={closeModal} />
</Fragment> </Fragment>
) )
} }

View file

@ -1,67 +0,0 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ModalVisibilityProps } from '../../../common/modals/common-modal'
import { CommonModal } from '../../../common/modals/common-modal'
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 {
Shortcuts = 'shortcuts.title',
Links = 'links.title'
}
/**
* Renders the help modal.
* This modal shows the user the markdown cheatsheet, shortcuts and different links with further help.
*
* @see CheatsheetTabContent
* @see ShortcutTabContent
* @see LinksTabContent
*
* @param show If the modal should be shown
* @param onHide A callback when the modal should be closed again
*/
export const HelpModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
const [tab, setTab] = useState<HelpTabStatus>(HelpTabStatus.Shortcuts)
const { t } = useTranslation()
const tabContent = useMemo(() => {
switch (tab) {
case HelpTabStatus.Shortcuts:
return <ShortcutTabContent />
case HelpTabStatus.Links:
return <LinksTabContent />
}
}, [tab])
const modalTitle = useMemo(() => t('editor.documentBar.help') + ' - ' + t(`editor.help.${tab}`), [t, tab])
return (
<CommonModal modalSize={'xl'} titleIcon={IconQuestionCircle} show={show} onHide={onHide} title={modalTitle}>
<Modal.Body>
<nav className='nav nav-tabs'>
<Button
variant={'light'}
className={`nav-link nav-item ${tab === HelpTabStatus.Shortcuts ? 'active' : ''}`}
onClick={() => setTab(HelpTabStatus.Shortcuts)}>
<Trans i18nKey={'editor.help.shortcuts.title'} />
</Button>
<Button
variant={'light'}
className={`nav-link nav-item ${tab === HelpTabStatus.Links ? 'active' : ''}`}
onClick={() => setTab(HelpTabStatus.Links)}>
<Trans i18nKey={'editor.help.links.title'} />
</Button>
</nav>
{tabContent}
</Modal.Body>
</CommonModal>
)
}

View file

@ -1,92 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import links from '../../../../links.json'
import { IconMatrixOrg } from '../../../common/icons/additional/icon-matrix-org'
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
import { TranslatedInternalLink } from '../../../common/links/translated-internal-link'
import React from 'react'
import { Col, Row } from 'react-bootstrap'
import { Dot as IconDot, Flag as IconFlag, PeopleFill as IconPeopleFill, Tag as IconTag } from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
/**
* Renders a bunch of links, where further help can be requested.
*/
export const LinksTabContent: React.FC = () => {
useTranslation()
return (
<Row className={'justify-content-center pt-4'}>
<Col lg={4}>
<h3>
<Trans i18nKey='editor.help.contacts.title' />
</h3>
<div>
<ul className='list-unstyled'>
<li>
<TranslatedExternalLink
i18nKey='editor.help.contacts.community'
href={links.community}
icon={IconPeopleFill}
className='text-primary'
/>
</li>
<li>
<TranslatedExternalLink
i18nKey='editor.help.contacts.meetUsOn'
i18nOption={{ service: 'Matrix' }}
href={links.chat}
icon={IconMatrixOrg}
className='text-primary'
/>
</li>
<li>
<TranslatedExternalLink
i18nKey='editor.help.contacts.reportIssue'
href={links.issues}
icon={IconTag}
className='text-primary'
/>
</li>
<li>
<TranslatedExternalLink
i18nKey='editor.help.contacts.helpTranslating'
href={links.translate}
icon={IconFlag}
className='text-primary'
/>
</li>
</ul>
</div>
</Col>
<Col lg={4}>
<h3>
<Trans i18nKey='editor.help.documents.title' />
</h3>
<div>
<ul className='list-unstyled'>
<li>
<TranslatedInternalLink
i18nKey='editor.help.documents.yamlMetadata'
href='/n/yaml-metadata'
icon={IconDot}
className='text-primary'
/>
</li>
<li>
<TranslatedInternalLink
i18nKey='editor.help.documents.slideExample'
href='/n/slide-example'
icon={IconDot}
className='text-primary'
/>
</li>
</ul>
</div>
</Col>
</Row>
)
}

View file

@ -1,60 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { isMac } from '../../utils'
import React, { Fragment, useMemo } from 'react'
import { Card, ListGroup, Row } from 'react-bootstrap'
import { Trans } from 'react-i18next'
/**
* Renders a list of shortcuts usable in HedgeDoc.
*/
export const ShortcutTabContent: React.FC = () => {
const modifierKey = useMemo(() => (isMac() ? <kbd></kbd> : <kbd>Ctrl</kbd>), [])
const altKey = useMemo(() => (isMac() ? <kbd></kbd> : <kbd>Alt</kbd>), [])
const shortcutMap: { [category: string]: { [functionName: string]: JSX.Element[] } } = {
'View Mode': {
'editor.help.shortcuts.view': [<kbd key={'ctrl'}>Ctrl</kbd>, <> + </>, altKey, <> + </>, <kbd key={'v'}>V</kbd>],
'editor.help.shortcuts.both': [<kbd key={'ctrl'}>Ctrl</kbd>, <> + </>, altKey, <> + </>, <kbd key={'b'}>B</kbd>],
'editor.help.shortcuts.edit': [<kbd key={'ctrl'}>Ctrl</kbd>, <> + </>, altKey, <> + </>, <kbd key={'e'}>E</kbd>]
},
Editor: {
'editor.help.shortcuts.bold': [modifierKey, <> + </>, <kbd key={'b'}>B</kbd>],
'editor.help.shortcuts.italic': [modifierKey, <> + </>, <kbd key={'i'}>I</kbd>],
'editor.help.shortcuts.underline': [modifierKey, <> + </>, <kbd key={'u'}>U</kbd>],
'editor.help.shortcuts.strikethrough': [modifierKey, <> + </>, <kbd key={'d'}>D</kbd>],
'editor.help.shortcuts.mark': [modifierKey, <> + </>, <kbd key={'m'}>M</kbd>],
'editor.help.shortcuts.link': [modifierKey, <> + </>, <kbd key={'k'}>K</kbd>]
}
}
return (
<Row className={'justify-content-center pt-4'}>
{Object.keys(shortcutMap).map((category) => {
return (
<Card key={category} className={'m-2 w-50'}>
<Card.Header>{category}</Card.Header>
<ListGroup variant='flush'>
{Object.entries(shortcutMap[category]).map(([functionName, shortcuts]) => {
return (
<ListGroup.Item key={functionName} className={'d-flex justify-content-between'}>
<span>
<Trans i18nKey={functionName} />
</span>
<span>
{shortcuts.map((shortcut, shortcutIndex) => (
<Fragment key={shortcutIndex}>{shortcut}</Fragment>
))}
</span>
</ListGroup.Item>
)
})}
</ListGroup>
</Card>
)
})}
</Row>
)
}

View file

@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { isMac } from '../../editor-page/utils'
import React from 'react'
/**
* Renders a keyboard alt/option key hint depending on if the browser is running on macOS or not.
*/
export const AltKey: React.FC = () => {
return isMac() ? <kbd></kbd> : <kbd>Alt</kbd>
}

View file

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { PropsWithChildren } from 'react'
import React from 'react'
import { Card, ListGroup } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
interface CategoryProps extends PropsWithChildren {
headerI18nKey: string
}
/**
* Renders a group of shortcut lines
*
* @param headerI18nKey The i18n key of the header
* @param children The lines to include
*/
export const CategoryCard: React.FC<CategoryProps> = ({ headerI18nKey, children }) => {
useTranslation()
return (
<Card key={headerI18nKey} className={'mb-4'}>
<Card.Header>
<Trans i18nKey={headerI18nKey} />
</Card.Header>
<ListGroup variant='flush'>{children}</ListGroup>
</Card>
)
}

View file

@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { isMac } from '../../editor-page/utils'
import React from 'react'
/**
* Renders a keyboard control/command key hint depending on if the browser is running on macOS or not.
*/
export const ModifierKey: React.FC = () => {
return isMac() ? <kbd></kbd> : <kbd>Ctrl</kbd>
}

View file

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ShowIf } from '../../common/show-if/show-if'
import { AltKey } from './alt-key'
import { ModifierKey } from './modifier-key'
import React from 'react'
import { ListGroup } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
interface ShortcutLineProps {
functionNameI18nKey: string
showModifierKey: boolean
showAltKey: boolean
functionKeyCode: string
}
/**
* Renders one shortcut hint for the modal
*
* @param functionNameI18nKey The i18n key of the function name that is associated to the shortcut
* @param showAltKey Defines if the shortcut requires the alt/option key
* @param showModifierKey Defines if the shortcut requires the control/command key
* @param functionKeyCode The actual key of the shortcut
*/
export const ShortcutLine: React.FC<ShortcutLineProps> = ({
functionNameI18nKey,
showAltKey,
showModifierKey,
functionKeyCode
}) => {
useTranslation()
return (
<ListGroup.Item className={'d-flex justify-content-between'}>
<span>
<Trans i18nKey={functionNameI18nKey} />
</span>
<span>
<ShowIf condition={showModifierKey}>
<ModifierKey />
<span> + </span>
</ShowIf>
<ShowIf condition={showAltKey}>
<AltKey />
<span> + </span>
</ShowIf>
<kbd>{functionKeyCode.toUpperCase()}</kbd>
</span>
</ListGroup.Item>
)
}

View file

@ -0,0 +1,78 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { CategoryCard } from './category-card'
import { ShortcutLine } from './shortcut-line'
import React from 'react'
import { Container } from 'react-bootstrap'
/**
* Renders a list of shortcuts usable in HedgeDoc.
*/
export const ShortcutsContent: React.FC = () => {
return (
<Container>
<CategoryCard headerI18nKey={'shortcuts.viewMode.header'}>
<ShortcutLine
functionNameI18nKey={'shortcuts.viewMode.view'}
showModifierKey={true}
showAltKey={true}
functionKeyCode={'v'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.viewMode.both'}
showModifierKey={true}
showAltKey={true}
functionKeyCode={'b'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.viewMode.edit'}
showModifierKey={true}
showAltKey={true}
functionKeyCode={'e'}
/>
</CategoryCard>
<CategoryCard headerI18nKey={'shortcuts.editor.header'}>
<ShortcutLine
functionNameI18nKey={'shortcuts.editor.bold'}
showModifierKey={true}
showAltKey={false}
functionKeyCode={'b'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.editor.italic'}
showModifierKey={true}
showAltKey={false}
functionKeyCode={'i'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.editor.underline'}
showModifierKey={true}
showAltKey={false}
functionKeyCode={'u'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.editor.strikethrough'}
showModifierKey={true}
showAltKey={false}
functionKeyCode={'d'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.editor.mark'}
showModifierKey={true}
showAltKey={false}
functionKeyCode={'m'}
/>
<ShortcutLine
functionNameI18nKey={'shortcuts.editor.link'}
showModifierKey={true}
showAltKey={false}
functionKeyCode={'k'}
/>
</CategoryCard>
</Container>
)
}

View file

@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import type { ModalVisibilityProps } from '../../common/modals/common-modal'
import { CommonModal } from '../../common/modals/common-modal'
import { ShortcutsContent } from './shortcuts-content'
import React from 'react'
import { Modal } from 'react-bootstrap'
import { QuestionCircle as IconQuestionCircle } from 'react-bootstrap-icons'
/**
* Renders the keyboard shortcuts overview modal.
* @param show True when the modal should be shown
* @param onHide Callback when the modal should be hidden
*/
export const ShortcutsModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
const title = useTranslatedText('shortcuts.title')
return (
<CommonModal titleIcon={IconQuestionCircle} show={show} onHide={onHide} showCloseButton={true} title={title}>
<Modal.Body>
<ShortcutsContent />
</Modal.Body>
</CommonModal>
)
}