Skip to content
This repository was archived by the owner on Nov 23, 2022. It is now read-only.

metadata GUI #986

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@typescript-eslint/parser": "4.22.0",
"abcjs": "6.0.0-beta.32",
"bootstrap": "4.6.0",

"codemirror": "5.60.0",
"copy-webpack-plugin": "6.4.1",
"d3-graphviz": "3.2.0",
Expand All @@ -56,6 +57,7 @@
"i18next": "20.2.1",
"i18next-browser-languagedetector": "6.1.0",
"i18next-http-backend": "1.2.1",
"iso-639-1": "2.1.8",
"js-yaml": "4.0.0",
"katex": "0.13.2",
"luxon": "1.26.0",
Expand Down
57 changes: 38 additions & 19 deletions public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -339,26 +339,45 @@
"modal": {
"snippetImport": {
"title": "Import from Snippet",
"selectProject": "Select From Available Projects",
"selectSnippet": "Select From Available Snippets"
"selectProject": "Select From Available Projects",
"selectSnippet": "Select From Available Snippets"
},
"documentInfo": {
"title": "Document info",
"created": "<0></0> created this note <1></1>",
"edited": "<0></0> was the last editor <1></1>",
"usersContributed": "<0></0> users contributed to this document",
"revisions": "<0></0> revisions are saved"
},
"gistImport": {
"title": "Import from Gist",
"insertGistUrl": "Paste your gist url here…"
},
"snippetExport": {
"title": "Export to Snippet",
"visibilityLevel": "Select Visibility Level"
},
"revision": {
"title": "Revisions",
"documentInfo": {
"title": "Document info",
"created": "<0></0> created this note <1></1>",
"edited": "<0></0> was the last editor <1></1>",
"usersContributed": "<0></0> users contributed to this document",
"revisions": "<0></0> revisions are saved"
},
"metadataEditor": {
"title": "Edit Metadata",
"labels": {
"title": "Title",
"type": "Document type",
"description": "Description",
"tags": "Tags",
"lang": "Language",
"dir": "Text direction",
"breaks": "New line style",
"robots": "Robots",
"GA": "Google Analytics",
"disqus": "Disqus",
"LTR": "left to right",
"RTL": "right to left",
"breaksOn": "hedgedoc style",
"breaksOff": "markdown style"
}
},
"gistImport": {
"title": "Import from Gist",
"insertGistUrl": "Paste your gist url here…"
},
"snippetExport": {
"title": "Export to Snippet",
"visibilityLevel": "Select Visibility Level"
},
"revision": {
"title": "Revisions",
"revertButton": "Revert",
"error": "An error occurred while fetching the revisions of this note.",
"length": "Length",
Expand Down
13 changes: 7 additions & 6 deletions src/components/common/fork-awesome/fork-awesome-icon.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/*
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)

SPDX-License-Identifier: AGPL-3.0-only
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React from 'react'
import React, { MouseEventHandler } from 'react'
import { IconName, IconSize } from './types'

export interface ForkAwesomeIconProps {
Expand All @@ -13,14 +13,15 @@ export interface ForkAwesomeIconProps {
fixedWidth?: boolean
size?: IconSize
stacked?: boolean
onClick?: MouseEventHandler<HTMLElement>
}

export const ForkAwesomeIcon: React.FC<ForkAwesomeIconProps> = ({ icon, fixedWidth = false, size, className, stacked = false }) => {
export const ForkAwesomeIcon: React.FC<ForkAwesomeIconProps> = ({ icon, fixedWidth = false, size, className, stacked = false, onClick }) => {
const fixedWithClass = fixedWidth ? 'fa-fw' : ''
const sizeClass = size ? `-${ size }` : (stacked ? '-1x' : '')
const stackClass = stacked ? '-stack' : ''
const extraClasses = `${ className ?? '' } ${ sizeClass || stackClass ? `fa${ stackClass }${ sizeClass }` : '' }`
return (
<i className={ `fa ${ fixedWithClass } fa-${ icon } ${ extraClasses }` }/>
<i className={ `fa ${ fixedWithClass } fa-${ icon } ${ extraClasses }` } onClick={ onClick }/>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const ShareModal: React.FC<ShareModalProps> = ({ show, onHide }) => {
<CopyableField content={ `${ baseUrl }/p/${ id }` } nativeShareButton={ true }
url={ `${ baseUrl }/p/${ id }` }/>
</ShowIf>
<ShowIf condition={ noteFrontmatter.type === '' }>
<ShowIf condition={ noteFrontmatter.type === NoteType.DOCUMENT }>
<Trans i18nKey={ 'editor.modal.shareLink.viewOnlyDescription' }/>
<CopyableField content={ `${ baseUrl }/s/${ id }` } nativeShareButton={ true }
url={ `${ baseUrl }/s/${ id }` }/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { useCallback } from 'react'
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { MetadataInputFieldProps } from './metadata-editor'

enum ButtonState {
ON = 1,
OFF = 0
}

export const BreaksMetadataInput: React.FC<MetadataInputFieldProps<boolean>> = ({ frontmatterKey, content, onContentChange }) => {
const { t } = useTranslation()

const toggleButtonClick = useCallback((value: ButtonState) => {
onContentChange({ breaks: value === ButtonState.ON })
}, [onContentChange])

return (
<ToggleButtonGroup
type="radio"
name={ frontmatterKey }
id={ frontmatterKey }
value={ content ? ButtonState.ON : ButtonState.OFF }
className={ 'd-block' }
onChange={ toggleButtonClick }>
<ToggleButton
value={ ButtonState.ON }
variant="outline-secondary"
title={ t('editor.modal.metadataEditor.labels.breaksOn') }>
<Trans i18nKey={ 'editor.modal.metadataEditor.labels.breaksOn' }/>
</ToggleButton>
<ToggleButton
value={ ButtonState.OFF }
variant="outline-secondary"
title={ t('editor.modal.metadataEditor.labels.breaksOff') }>
<Trans i18nKey={ 'editor.modal.metadataEditor.labels.breaksOff' }/>
</ToggleButton>
</ToggleButtonGroup>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { useCallback } from 'react'
import { MetadataInputFieldProps, SelectMetadataOptions } from './metadata-editor'
import { Dropdown, DropdownButton } from 'react-bootstrap'

export const DatalistMetadataInput: React.FC<MetadataInputFieldProps<string> & SelectMetadataOptions<string>> = ({ frontmatterKey, content, onContentChange, options }) => {
const onSelect = useCallback((eventKey: string | null) => {
if (eventKey === null) {
return
}
onContentChange({ [frontmatterKey]: eventKey })
}, [frontmatterKey, onContentChange])

return (
<DropdownButton onSelect={ onSelect } id={ frontmatterKey } title={ content }>
{
options.map((option) => <Dropdown.Item eventKey={ option } key={ option }>{ option }</Dropdown.Item>)
}
</DropdownButton>
)
}
/*

<!--<Fragment>
<input list={ frontmatterKey } onChange={ onChange } value={ content } className={ 'form-control' }/>
<datalist id={ frontmatterKey }>
{ options.map(option => (
<option key={ option } value={ option }>
{ option }
</option>
)) }
</datalist>
</Fragment>-->
*/
9 changes: 9 additions & 0 deletions src/components/editor-page/metadata-editor/input-label.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*!
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

.tighter {
margin-bottom: -0.5px !important;
}
23 changes: 23 additions & 0 deletions src/components/editor-page/metadata-editor/input-label.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React from 'react'
import './input-label.scss'
import { Form } from 'react-bootstrap'

export interface InputLabelProps {
id: string
label: string
}

export const InputLabel: React.FC<InputLabelProps> = ({ id, label, children }) => {
return (
<Form.Group className={ 'pb-3' }>
<label className='small font-weight-lighter tighter' htmlFor={ id }>{ label }</label>
{ children }
</Form.Group>
)
}
119 changes: 119 additions & 0 deletions src/components/editor-page/metadata-editor/metadata-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import ISO from 'iso-639-1'
import React, { useCallback } from 'react'
import { Col, Modal, Row } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import { useSelector } from 'react-redux'
import { ApplicationState } from '../../../redux'
import { replaceFrontmatterInMarkdownContentAction } from '../../../redux/note-details/methods'
import { CommonModal } from '../../common/modals/common-modal'
import { NoteType, RawNoteFrontmatter } from '../note-frontmatter/note-frontmatter'
import { BreaksMetadataInput } from './breaks-metadata-input'
import { DatalistMetadataInput } from './datalist-metadata-input'
import { InputLabel } from './input-label'
import { StringMetadataInput } from './string-metadata-input'
import { StringMetadataTextarea } from './string-metadata-textarea'
import { TagsMetadataInput } from './tags-metadata-input'
import { TextDirectionMetadataInput } from './text-direction-metadata-input'

export interface MetadataEditorProps {
show: boolean,
onHide: () => void
}

export interface MetadataInputFieldProps<T> {
content: T
frontmatterKey: keyof RawNoteFrontmatter
onContentChange: (frontmatter: RawNoteFrontmatter) => void
}

export interface SelectMetadataOptions<T> {
options: T[]
}

export const MetadataEditor: React.FC<MetadataEditorProps> = ({ show, onHide }) => {
const { t } = useTranslation()
const yamlMetadata = useSelector((state: ApplicationState) => state.noteDetails.frontmatter)
/*const [yamlMetadata, setNoteFrontmatter] = useState<Omit<YAMLMetaData, 'opengraph'>>({
title: "Test Title",
description: "Test Description\nwith two lines",
tags: ["tag1", "tag2"],
robots: "",
lang: "de-at",
dir: TextDirection.LTR,
breaks: false,
GA: "test GA string",
disqus: "test disqus string",
type: '',
deprecatedTagsSyntax: false
})*/

const updateFrontmatter = useCallback((frontmatter: RawNoteFrontmatter): void => {
replaceFrontmatterInMarkdownContentAction(frontmatter)
}, [])

return (
<CommonModal
size='lg'
show={ show }
onHide={ onHide }
closeButton={ true }
titleI18nKey={ 'editor.modal.metadataEditor.title' }>
<Modal.Body>
<Row>
<Col lg={ 6 }>
<InputLabel id={ 'title' } label={ t('editor.modal.metadataEditor.labels.title') }>
<StringMetadataInput frontmatterKey={ 'title' } content={ yamlMetadata.title }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'type' } label={ t('editor.modal.metadataEditor.labels.type') }>
<DatalistMetadataInput frontmatterKey={ 'type' } options={ Object.values(NoteType) }
content={ yamlMetadata.type }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'dir' } label={ t('editor.modal.metadataEditor.labels.dir') }>
<TextDirectionMetadataInput frontmatterKey={ 'dir' } content={ yamlMetadata.dir }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'description' } label={ t('editor.modal.metadataEditor.labels.description') }>
<StringMetadataTextarea frontmatterKey={ 'description' } content={ yamlMetadata.description }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'disqus' } label={ t('editor.modal.metadataEditor.labels.disqus') }>
<StringMetadataInput frontmatterKey={ 'disqus' } content={ yamlMetadata.disqus }
onContentChange={ updateFrontmatter }/>
</InputLabel>
</Col>
<Col lg={ 6 }>
<InputLabel id={ 'lang' } label={ t('editor.modal.metadataEditor.labels.lang') }>
<DatalistMetadataInput frontmatterKey={ 'lang' } options={ ISO.getAllCodes() }
content={ yamlMetadata.lang }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'robots' } label={ t('editor.modal.metadataEditor.labels.robots') }>
<StringMetadataInput frontmatterKey={ 'robots' } content={ yamlMetadata.robots }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'breaks' } label={ t('editor.modal.metadataEditor.labels.breaks') }>
<BreaksMetadataInput frontmatterKey={ 'breaks' } content={ yamlMetadata.breaks }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'tags' } label={ t('editor.modal.metadataEditor.labels.tags') }>
<TagsMetadataInput frontmatterKey={ 'tags' } content={ yamlMetadata.tags }
onContentChange={ updateFrontmatter }/>
</InputLabel>
<InputLabel id={ 'GA' } label={ t('editor.modal.metadataEditor.labels.GA') }>
<StringMetadataInput frontmatterKey={ 'GA' } content={ yamlMetadata.GA }
onContentChange={ updateFrontmatter }/>
</InputLabel>
</Col>
</Row>
</Modal.Body>
</CommonModal>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { useCallback } from 'react'
import { MetadataInputFieldProps } from './metadata-editor'

export const StringMetadataInput: React.FC<MetadataInputFieldProps<string>> = ({ content, onContentChange, frontmatterKey }) => {
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
onContentChange({ [frontmatterKey]: event.currentTarget.value })
}, [frontmatterKey, onContentChange])

return (
<input
id={ frontmatterKey }
type="text"
className={ 'form-control' }
value={ content }
onChange={ onChange }
/>
)
}
Loading