refactor(frontend): make terminology of cheatsheet more clear

Also add additional documentation to explain how cheatsheets work

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2023-06-28 22:44:09 +02:00
parent 81927b88f2
commit 7a365acdb9
11 changed files with 61 additions and 45 deletions

View file

@ -3,8 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { CheatsheetEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
import type { CheatsheetSingleEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
import { hasCheatsheetTopics } from '../../cheatsheet/cheatsheet-extension'
import { CategoryAccordion } from './category-accordion'
import { CheatsheetEntryPane } from './cheatsheet-entry-pane'
import { CheatsheetSearch } from './cheatsheet-search'
@ -20,11 +20,11 @@ import { Trans } from 'react-i18next'
export const CheatsheetContent: React.FC = () => {
const [visibleExtensions, setVisibleExtensions] = useState<CheatsheetExtension[]>([])
const [selectedExtension, setSelectedExtension] = useState<CheatsheetExtension>()
const [selectedEntry, setSelectedEntry] = useState<CheatsheetEntry>()
const [selectedEntry, setSelectedEntry] = useState<CheatsheetSingleEntry>()
const changeExtension = useCallback((value: CheatsheetExtension) => {
setSelectedExtension(value)
setSelectedEntry(isCheatsheetGroup(value) ? value.entries[0] : value)
setSelectedEntry(hasCheatsheetTopics(value) ? value.topics[0] : value)
}, [])
return (
@ -46,7 +46,7 @@ export const CheatsheetContent: React.FC = () => {
/>
{selectedEntry !== undefined ? (
<CheatsheetEntryPane
rootI18nKey={isCheatsheetGroup(selectedExtension) ? selectedExtension.i18nKey : undefined}
rootI18nKey={hasCheatsheetTopics(selectedExtension) ? selectedExtension.i18nKey : undefined}
extension={selectedEntry}
/>
) : (

View file

@ -8,7 +8,7 @@ import { HtmlToReact } from '../../../common/html-to-react/html-to-react'
import { RendererIframe } from '../../../common/renderer-iframe/renderer-iframe'
import { ExtensionEventEmitterProvider } from '../../../markdown-renderer/hooks/use-extension-event-emitter'
import { RendererType } from '../../../render-page/window-post-message-communicator/rendering-message'
import type { CheatsheetEntry } from '../../cheatsheet/cheatsheet-extension'
import type { CheatsheetSingleEntry } from '../../cheatsheet/cheatsheet-extension'
import { EditorToRendererCommunicatorContextProvider } from '../../render-context/editor-to-renderer-communicator-context-provider'
import { ReadMoreLinkItem } from './read-more-link-item'
import { useComponentsFromAppExtensions } from './use-components-from-app-extensions'
@ -19,7 +19,7 @@ import { Trans, useTranslation } from 'react-i18next'
interface CheatsheetRendererProps {
rootI18nKey?: string
extension: CheatsheetEntry
extension: CheatsheetSingleEntry
}
/**

View file

@ -7,6 +7,7 @@ import { allAppExtensions } from '../../../../extensions/all-app-extensions'
import type { SearchIndexEntry } from '../../../../hooks/common/use-document-search'
import { useDocumentSearch } from '../../../../hooks/common/use-document-search'
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { useTranslatedText } from '../../../../hooks/common/use-translated-text'
import { UiIcon } from '../../../common/icons/ui-icon'
import type { CheatsheetSingleEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
import { hasCheatsheetTopics } from '../../cheatsheet/cheatsheet-extension'
@ -47,8 +48,8 @@ export const CheatsheetSearch: React.FC<CheatsheetSearchProps> = ({ setVisibleEx
() => allAppExtensions.flatMap((extension) => extension.buildCheatsheetExtensions()),
[]
)
const buildSearchIndexDocument = useCallback(
(entry: CheatsheetEntry, rootI18nKey: string | undefined = undefined): CheatsheetSearchIndexEntry => {
const buildSearchIndexEntry = useCallback(
(entry: CheatsheetSingleEntry, rootI18nKey: string | undefined = undefined): CheatsheetSearchIndexEntry => {
const rootI18nKeyWithDot = rootI18nKey ? `${rootI18nKey}.` : ''
return {
id: rootI18nKey ? rootI18nKey : entry.i18nKey,
@ -59,15 +60,16 @@ export const CheatsheetSearch: React.FC<CheatsheetSearchProps> = ({ setVisibleEx
},
[t]
)
const placeholderText = useTranslatedText('cheatsheet.search')
const cheatsheetSearchIndexEntries = useMemo(
() =>
allCheatsheetExtensions.flatMap((entry) => {
if (hasCheatsheetTopics(entry)) {
return entry.topics.map((innerEntry) => buildSearchIndexEntry(innerEntry, entry.i18nKey))
}
return buildSearchIndexDocument(entry)
return buildSearchIndexEntry(entry)
}),
[buildSearchIndexDocument, allCheatsheetExtensions]
[buildSearchIndexEntry, allCheatsheetExtensions]
)
const searchResults = useDocumentSearch(cheatsheetSearchIndexEntries, searchOptions, searchTerm)
useEffect(() => {
@ -81,21 +83,14 @@ export const CheatsheetSearch: React.FC<CheatsheetSearchProps> = ({ setVisibleEx
})
setVisibleExtensions(extensionResults)
}, [allCheatsheetExtensions, searchResults, searchTerm, setVisibleExtensions])
const onChange = useOnInputChange((search) => {
setSearchTerm(search)
})
const onChange = useOnInputChange(setSearchTerm)
const clearSearch = useCallback(() => {
setSearchTerm('')
}, [setSearchTerm])
return (
<InputGroup className='mb-3'>
<FormControl
placeholder={t('cheatsheet.search') ?? undefined}
aria-label={t('cheatsheet.search') ?? undefined}
onChange={onChange}
value={searchTerm}
/>
<FormControl placeholder={placeholderText} aria-label={placeholderText} onChange={onChange} value={searchTerm} />
<button className={styles.innerBtn} onClick={clearSearch}>
<UiIcon icon={X} />
</button>

View file

@ -3,16 +3,16 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { CheatsheetEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
import type { CheatsheetSingleEntry, CheatsheetExtension } from '../../cheatsheet/cheatsheet-extension'
import { hasCheatsheetTopics } from '../../cheatsheet/cheatsheet-extension'
import React, { useMemo } from 'react'
import { Button, ButtonGroup, ListGroupItem } from 'react-bootstrap'
import { Trans } from 'react-i18next'
interface EntrySelectionProps {
extension: CheatsheetExtension | undefined
selectedEntry: CheatsheetEntry | undefined
setSelectedEntry: (value: CheatsheetEntry) => void
selectedEntry: CheatsheetSingleEntry | undefined
setSelectedEntry: (value: CheatsheetSingleEntry) => void
}
/**
@ -25,10 +25,10 @@ interface EntrySelectionProps {
*/
export const TopicSelection: React.FC<EntrySelectionProps> = ({ extension, selectedEntry, setSelectedEntry }) => {
const listItems = useMemo(() => {
if (!isCheatsheetGroup(extension)) {
if (!hasCheatsheetTopics(extension)) {
return null
}
return extension.entries.map((entry) => (
return extension.topics.map((entry) => (
<Button
key={entry.i18nKey}
variant={selectedEntry?.i18nKey === entry.i18nKey ? 'primary' : 'outline-primary'}

View file

@ -5,7 +5,7 @@
*/
import { allAppExtensions } from '../../../../extensions/all-app-extensions'
import type { CheatsheetExtensionComponentProps } from '../../cheatsheet/cheatsheet-extension'
import { isCheatsheetGroup } from '../../cheatsheet/cheatsheet-extension'
import { hasCheatsheetTopics } from '../../cheatsheet/cheatsheet-extension'
import type { ReactElement } from 'react'
import React, { Fragment, useMemo } from 'react'
@ -20,7 +20,7 @@ export const useComponentsFromAppExtensions = (
<Fragment key={'app-extensions'}>
{allAppExtensions
.flatMap((extension) => extension.buildCheatsheetExtensions())
.flatMap((extension) => (isCheatsheetGroup(extension) ? extension.entries : extension))
.flatMap((extension) => (hasCheatsheetTopics(extension) ? extension.topics : extension))
.map((extension) => {
if (extension.cheatsheetExtensionComponent) {
return React.createElement(extension.cheatsheetExtensionComponent, { key: extension.i18nKey, setContent })

View file

@ -9,19 +9,40 @@ export interface CheatsheetExtensionComponentProps {
setContent: (dispatcher: string | ((prevState: string) => string)) => void
}
export type CheatsheetExtension = CheatsheetEntry | CheatsheetGroup
export type CheatsheetExtension = CheatsheetSingleEntry | CheatsheetEntryWithTopics
export const isCheatsheetGroup = (extension: CheatsheetExtension | undefined): extension is CheatsheetGroup => {
return (extension as CheatsheetGroup)?.entries !== undefined
/**
* Determine if a given {@link CheatsheetExtension} is a {@link CheatsheetEntryWithTopics} or just a {@link CheatsheetSingleEntry}.
*
* @param extension The extension in question
* @return boolean
*/
export const hasCheatsheetTopics = (
extension: CheatsheetExtension | undefined
): extension is CheatsheetEntryWithTopics => {
return (extension as CheatsheetEntryWithTopics)?.topics !== undefined
}
export interface CheatsheetGroup {
/**
* This is an entry with just a name and a bunch of different topics to discuss.
*
* e.g 'basics.headlines' with the topics 'hashtag' and 'equal'
*/
export interface CheatsheetEntryWithTopics {
i18nKey: string
categoryI18nKey?: string
entries: CheatsheetEntry[]
topics: CheatsheetSingleEntry[]
}
export interface CheatsheetEntry {
/**
* This is an entry that describes something completely.
*
* In the translations you'll find both 'description' containing an explanation and 'example' containing a demonstration in markdown under the i18nKey.
* If this entry is a topic of some other entry the i18nKey needs to be prefixed with the i18nKey of the other entry.
*
* e.g 'basics.basicFormatting'
*/
export interface CheatsheetSingleEntry {
i18nKey: string
categoryI18nKey?: string
cheatsheetExtensionComponent?: React.FC<CheatsheetExtensionComponentProps>

View file

@ -33,7 +33,7 @@ export class BasicMarkdownSyntaxAppExtension extends AppExtension {
{
i18nKey: 'basics.headlines',
categoryI18nKey: 'basic',
entries: [
topics: [
{
i18nKey: 'hashtag'
},
@ -45,17 +45,17 @@ export class BasicMarkdownSyntaxAppExtension extends AppExtension {
{
i18nKey: 'basics.code',
categoryI18nKey: 'basic',
entries: [{ i18nKey: 'inline' }, { i18nKey: 'block' }]
topics: [{ i18nKey: 'inline' }, { i18nKey: 'block' }]
},
{
i18nKey: 'basics.lists',
categoryI18nKey: 'basic',
entries: [{ i18nKey: 'unordered' }, { i18nKey: 'ordered' }]
topics: [{ i18nKey: 'unordered' }, { i18nKey: 'ordered' }]
},
{
i18nKey: 'basics.images',
categoryI18nKey: 'basic',
entries: [{ i18nKey: 'basic' }, { i18nKey: 'size' }]
topics: [{ i18nKey: 'basic' }, { i18nKey: 'size' }]
},
{
i18nKey: 'basics.links',

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
*/
@ -22,7 +22,7 @@ export class BlockquoteAppExtension extends AppExtension {
}
buildCheatsheetExtensions(): CheatsheetExtension[] {
return [{ i18nKey: 'blockquoteTags', entries: [{ i18nKey: 'name' }, { i18nKey: 'color' }, { i18nKey: 'time' }] }]
return [{ i18nKey: 'blockquoteTags', topics: [{ i18nKey: 'name' }, { i18nKey: 'color' }, { i18nKey: 'time' }] }]
}
buildAutocompletion(): CompletionSource[] {

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
*/
@ -22,7 +22,7 @@ export class CsvTableAppExtension extends AppExtension {
}
buildCheatsheetExtensions(): CheatsheetExtension[] {
return [{ i18nKey: 'csv', entries: [{ i18nKey: 'table' }, { i18nKey: 'header' }] }]
return [{ i18nKey: 'csv', topics: [{ i18nKey: 'table' }, { i18nKey: 'header' }] }]
}
buildAutocompletion(): CompletionSource[] {

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
*/
@ -23,7 +23,7 @@ export class HighlightedCodeFenceAppExtension extends AppExtension {
return [
{
i18nKey: 'codeHighlighting',
entries: [{ i18nKey: 'language' }, { i18nKey: 'lineNumbers' }, { i18nKey: 'lineWrapping' }]
topics: [{ i18nKey: 'language' }, { i18nKey: 'lineNumbers' }, { i18nKey: 'lineWrapping' }]
}
]
}

View file

@ -21,7 +21,7 @@ export class TableOfContentsAppExtension extends AppExtension {
return [
{
i18nKey: 'toc',
entries: [
topics: [
{
i18nKey: 'basic'
},