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

Commit 2a63a01

Browse files
committed
started work on metadata GUI
Signed-off-by: Philip Molares <[email protected]>
1 parent 6ac60cf commit 2a63a01

15 files changed

+372
-4
lines changed

public/locales/en.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,9 @@
347347
"usersContributed": "<0></0> users contributed to this document",
348348
"revisions": "<0></0> revisions are saved"
349349
},
350+
"metadataEditor": {
351+
"title": "Metadata"
352+
},
350353
"gistImport": {
351354
"title": "Import from Gist",
352355
"insertGistUrl": "Paste your gist url here…"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { useCallback } from 'react'
8+
import { InputLabelProps } from './input-label'
9+
import { MetadataInputFieldProps } from './metadata-editor'
10+
11+
export const BooleanMetadataInput: React.FC<MetadataInputFieldProps<boolean> & InputLabelProps> = ({ label, content, onContentChange }) => {
12+
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
13+
onContentChange(event.currentTarget.checked)
14+
}, [onContentChange])
15+
16+
return (
17+
<div className='d-flex flex-row'>
18+
<input type="checkbox"
19+
className="left"
20+
checked={content}
21+
onChange={onChange}
22+
/>
23+
&nbsp;
24+
{label}
25+
</div>
26+
)
27+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { Fragment, useCallback } from 'react'
8+
import { iso6391 } from '../../yaml-metadata/yaml-metadata'
9+
import { MetadataInputFieldProps, SelectMetadataOptions } from './metadata-editor'
10+
11+
export const DatalistMetadataInput: React.FC<MetadataInputFieldProps<string> & SelectMetadataOptions<iso6391>> = ({ id, content, onContentChange, options }) => {
12+
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
13+
onContentChange(event.currentTarget.value)
14+
}, [onContentChange])
15+
16+
return (
17+
<Fragment>
18+
<input list={id} onChange={onChange} value={content}/>
19+
<datalist id={id}>
20+
{options.map((option: iso6391) => {
21+
return (
22+
<option value={option}>
23+
{option}
24+
</option>
25+
)
26+
})}
27+
</datalist>
28+
</Fragment>
29+
)
30+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
.tighter {
8+
margin-bottom: -0.5px !important;
9+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React from 'react'
8+
import './input-label.scss'
9+
10+
export interface InputLabelProps {
11+
id: string
12+
label: string
13+
}
14+
15+
export const InputLabel: React.FC<InputLabelProps> = ({ id, label, children }) => {
16+
return (
17+
<div className='d-flex flex-column pb-3'>
18+
<label className='small font-weight-lighter tighter' htmlFor={id}>{label}</label>
19+
{children}
20+
</div>
21+
)
22+
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { useState } from 'react'
8+
import { Modal } from 'react-bootstrap'
9+
import { CommonModal } from '../../../common/modals/common-modal'
10+
import { TextDirection, YAMLMetaData } from '../../yaml-metadata/yaml-metadata'
11+
import { BooleanMetadataInput } from './boolean-metadata-input'
12+
import { InputLabel } from './input-label'
13+
import { DatalistMetadataInput } from './datalist-metadata-input'
14+
import { TextDirectionMetadataInput } from './text-direction-metadata-input'
15+
import { StringMetadataInput } from './string-metadata-input'
16+
import { StringMetadataTextarea } from './string-metadata-textarea'
17+
import { TagsMetadataInput } from './tags-metadata-input'
18+
19+
export interface MetadataEditorProps {
20+
show: boolean,
21+
onHide: () => void
22+
}
23+
24+
export interface MetadataInputFieldProps<T> {
25+
id: string
26+
content: T
27+
onContentChange: (newContent: T) => void
28+
}
29+
30+
export interface SelectMetadataOptions<T> {
31+
options: T[]
32+
}
33+
34+
export const MetadataEditor: React.FC<MetadataEditorProps> = ({ show, onHide }) => {
35+
const [yamlMetadata, setYamlMetadata] = useState<Omit<YAMLMetaData, 'opengraph'>>({
36+
title: "Test Title",
37+
description: "Test Description\nwith two lines",
38+
tags: ["tag1", "tag2"],
39+
robots: "",
40+
lang: "de-at",
41+
dir: TextDirection.LTR,
42+
breaks: false,
43+
GA: "test GA string",
44+
disqus: "test disqus string",
45+
type: '',
46+
deprecatedTagsSyntax: false
47+
})
48+
49+
return (
50+
<CommonModal
51+
show={show}
52+
onHide={onHide}
53+
closeButton={true}
54+
titleI18nKey={'editor.modal.metadataEditor.title'}>
55+
<Modal.Body>
56+
<InputLabel id={'title'} label={"title"}>
57+
<StringMetadataInput id={'title'} content={yamlMetadata.title} onContentChange={title => setYamlMetadata({...yamlMetadata, title})}/>
58+
</InputLabel>
59+
<InputLabel id={'description'} label={"description"}>
60+
<StringMetadataTextarea id={'description'} content={yamlMetadata.description} onContentChange={description => setYamlMetadata({...yamlMetadata, description})}/>
61+
</InputLabel>
62+
<InputLabel id={'tags'} label={"tags"}>
63+
<TagsMetadataInput id={'tags'} content={yamlMetadata.tags} onContentChange={tags => setYamlMetadata({...yamlMetadata, tags})}/>
64+
</InputLabel>
65+
<InputLabel id={'robots'} label={"robots"}>
66+
<StringMetadataInput id={'robots'} content={yamlMetadata.robots} onContentChange={robots => setYamlMetadata({...yamlMetadata, robots})}/>
67+
</InputLabel>
68+
<InputLabel id={'lang'} label={"lang"}>
69+
<DatalistMetadataInput id={'lang'} options={['aa', 'ab', 'af', 'de-at']} content={yamlMetadata.lang} onContentChange={lang => setYamlMetadata({...yamlMetadata, lang})}/>
70+
</InputLabel>
71+
<InputLabel id={'dir'} label={"dir"}>
72+
<TextDirectionMetadataInput
73+
id={'dir'}
74+
content={yamlMetadata.dir}
75+
onContentChange={dir => setYamlMetadata({...yamlMetadata, dir })}
76+
/>
77+
</InputLabel>
78+
<InputLabel id={'breaks'} label={"breaks"}>
79+
<BooleanMetadataInput id={'breaks'} label={"breaks"} content={yamlMetadata.breaks} onContentChange={breaks => setYamlMetadata({...yamlMetadata, breaks})}/>
80+
</InputLabel>
81+
<InputLabel id={'GA'} label={"GA"}>
82+
<StringMetadataInput id={'GA'} content={yamlMetadata.GA} onContentChange={GA => setYamlMetadata({...yamlMetadata, GA})}/>
83+
</InputLabel>
84+
<InputLabel id={'disqus'} label={"disqus"}>
85+
<StringMetadataInput id={'disqus'} content={yamlMetadata.disqus} onContentChange={disqus => setYamlMetadata({...yamlMetadata, disqus})}/>
86+
</InputLabel>
87+
</Modal.Body>
88+
</CommonModal>
89+
)
90+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { useCallback } from 'react'
8+
import { MetadataInputFieldProps } from './metadata-editor'
9+
10+
export const StringMetadataInput: React.FC<MetadataInputFieldProps<string>> = ({ id, content, onContentChange }) => {
11+
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
12+
onContentChange(event.currentTarget.value)
13+
}, [onContentChange])
14+
15+
return (
16+
<input id={id}
17+
type="text"
18+
value={content}
19+
onChange={onChange}
20+
/>
21+
)
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { useCallback } from 'react'
8+
import { MetadataInputFieldProps } from './metadata-editor'
9+
10+
export const StringMetadataTextarea: React.FC<MetadataInputFieldProps<string>> = ({ id, content, onContentChange }) => {
11+
const onChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
12+
onContentChange(event.currentTarget.value)
13+
}, [onContentChange])
14+
15+
return (
16+
<textarea
17+
id={id}
18+
value={content}
19+
onChange={onChange}
20+
/>
21+
)
22+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
.tag-bubble {
8+
white-space: nowrap;
9+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { Fragment, useCallback, useState } from 'react'
8+
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
9+
import { MetadataInputFieldProps } from './metadata-editor'
10+
import './tags-metadata-input.scss'
11+
12+
export const TagsMetadataInput: React.FC<MetadataInputFieldProps<string[]>> = ({ id, content, onContentChange }) => {
13+
14+
const [newTag, setNewTag] = useState<string>('');
15+
16+
const addTag = useCallback(() => {
17+
if (!/^\s*$/.test(newTag)) {
18+
onContentChange([...content, newTag]);
19+
setNewTag('');
20+
}
21+
}, [content, newTag, onContentChange])
22+
23+
const onKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
24+
if (event.code === 'Enter') {
25+
addTag();
26+
}
27+
}, [addTag])
28+
29+
const onChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
30+
setNewTag(event.currentTarget.value);
31+
}, [])
32+
33+
return (
34+
<Fragment>
35+
<div className='pl-1 d-flex flex-row mb-2 mt-1 overflow-x-scroll'>
36+
{
37+
content.map(tag => {
38+
return (
39+
<div className='rounded-pill mr-1 px-2 bg-primary tag-bubble' key={tag}>
40+
{tag}
41+
<ForkAwesomeIcon icon={'times'} className='pl-1'/>
42+
</div>
43+
)
44+
})
45+
}
46+
</div>
47+
<input type="text"
48+
id={id}
49+
className='w-100 px-2'
50+
value={newTag}
51+
onKeyDown={onKeyDown}
52+
onChange={onChange}
53+
/>
54+
</Fragment>
55+
)
56+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
4+
SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React from 'react'
8+
import { ToggleButton, ToggleButtonGroup } from 'react-bootstrap'
9+
import { ForkAwesomeIcon } from '../../../common/fork-awesome/fork-awesome-icon'
10+
import { TextDirection } from '../../yaml-metadata/yaml-metadata'
11+
import { MetadataInputFieldProps } from './metadata-editor'
12+
13+
14+
export const TextDirectionMetadataInput: React.FC<MetadataInputFieldProps<TextDirection>> = ({ id, content, onContentChange }) => {
15+
return (
16+
<ToggleButtonGroup
17+
type="radio"
18+
name={id}
19+
id={id}
20+
value={content}
21+
className="ml-2"
22+
>
23+
<ToggleButton
24+
value={TextDirection.LTR}
25+
variant="outline-secondary"
26+
title={'ltr'}
27+
onChange={() => onContentChange(TextDirection.LTR)}
28+
>
29+
<ForkAwesomeIcon icon={'align-left'}/>
30+
&nbsp;
31+
left to right
32+
</ToggleButton>
33+
<ToggleButton
34+
value={TextDirection.RTL}
35+
variant="outline-secondary"
36+
title={'ltr'}
37+
onChange={() => onContentChange(TextDirection.RTL)}
38+
>
39+
<ForkAwesomeIcon icon={'align-right'}/>
40+
&nbsp;
41+
right to left
42+
</ToggleButton>
43+
</ToggleButtonGroup>
44+
)
45+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
3+
*
4+
* SPDX-License-Identifier: AGPL-3.0-only
5+
*/
6+
7+
import React, { Fragment, useState } from 'react'
8+
import { Trans, useTranslation } from 'react-i18next'
9+
import { MetadataEditor } from '../document-bar/metadata-editor/metadata-editor'
10+
import { SidebarButton } from './sidebar-button'
11+
import { SpecificSidebarEntryProps } from './types'
12+
13+
export const MetadataEditorSidebarEntry: React.FC<SpecificSidebarEntryProps> = ({className, hide}) => {
14+
const [showModal, setShowModal] = useState(false)
15+
useTranslation()
16+
17+
return (
18+
<Fragment>
19+
<SidebarButton hide={hide} className={className} icon={"tags"} onClick={() => setShowModal(true)}>
20+
<Trans i18nKey={'editor.modal.metadataEditor.title'} />
21+
</SidebarButton>
22+
<MetadataEditor show={showModal} onHide={() => setShowModal(false)}/>
23+
</Fragment>
24+
)
25+
}

src/components/editor/sidebar/sidebar.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { DeleteNoteSidebarEntry } from './delete-note-sidebar-entry'
1010
import { DocumentInfoSidebarEntry } from './document-info-sidebar-entry'
1111
import { ExportMenuSidebarMenu } from './export-menu-sidebar-menu'
1212
import { ImportMenuSidebarMenu } from './import-menu-sidebar-menu'
13+
import { MetadataEditorSidebarEntry } from './metadata-editor-sidebar-entry'
1314
import { PermissionsSidebarEntry } from './permissions-sidebar-entry'
1415
import { PinNoteSidebarEntry } from './pin-note-sidebar-entry'
1516
import { RevisionSidebarEntry } from './revision-sidebar-entry'
@@ -40,6 +41,7 @@ export const Sidebar: React.FC = () => {
4041
<UsersOnlineSidebarMenu menuId={DocumentSidebarMenuSelection.USERS_ONLINE}
4142
selectedMenuId={selectedMenu} onClick={toggleValue}/>
4243
<DocumentInfoSidebarEntry hide={selectionIsNotNone}/>
44+
<MetadataEditorSidebarEntry hide={selectionIsNotNone}/>
4345
<RevisionSidebarEntry hide={selectionIsNotNone}/>
4446
<PermissionsSidebarEntry hide={selectionIsNotNone}/>
4547
<ImportMenuSidebarMenu menuId={DocumentSidebarMenuSelection.IMPORT}

0 commit comments

Comments
 (0)