Skip to content
This repository has been archived by the owner on Oct 20, 2022. It is now read-only.

Feature/184 add tag field to document #211

Merged
merged 13 commits into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from 6 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
15 changes: 15 additions & 0 deletions migrations/2021-10-22-02-create-tags-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Migration2021102202 {
constructor(db) {
this.db = db;
}

async up() {
await this.db.collection('documents').createIndex({ tags: 1 }, { unique: false, name: 'tagsIndex' });
}

async down() {
await this.db.collection('documents').dropIndex('tagsIndex');
}
}

export default Migration2021102202;
51 changes: 41 additions & 10 deletions src/components/document-metadata-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,30 @@ import React from 'react';
import urls from '../utils/urls';
import autoBind from 'auto-bind';
import PropTypes from 'prop-types';
import { Input, Radio } from 'antd';
import { Input, Radio, Tag, Space, Select, Form } from 'antd';
import { inject } from './container-context';
import { withTranslation } from 'react-i18next';
import { withSettings } from './settings-context';
import { withLanguage } from './language-context';
import LanguageSelect from './localization/language-select';
import { EyeOutlined, EditOutlined } from '@ant-design/icons';
import LanguageNameProvider from '../data/language-name-provider';
import CountryFlagAndName from './localization/country-flag-and-name';
import { documentRevisionShape, translationProps, languageProps } from '../ui/default-prop-types';
import { documentRevisionShape, translationProps, languageProps, settingsProps } from '../ui/default-prop-types';

const RadioButton = Radio.Button;
const RadioGroup = Radio.Group;

const MODE_EDIT = 'edit';
const MODE_PREVIEW = 'preview';

const toGermanTag = tag => `${tag.slice(0, 1).toUpperCase()}${tag.slice(1).toLowerCase()}`;

class DocumentMetadataEditor extends React.Component {
constructor(props) {
super(props);
autoBind(this);
this.state = { mode: MODE_PREVIEW };
this.state = { mode: MODE_PREVIEW, tagsValidationStatus: '' };
}

handleEditClick() {
Expand All @@ -39,22 +42,35 @@ class DocumentMetadataEditor extends React.Component {

handleTitleChange(event) {
const { onChanged, documentRevision } = this.props;
onChanged({ ...documentRevision, title: event.target.value });
onChanged({ metadata: { ...documentRevision, title: event.target.value } });
}

handleLanguageChange(value) {
const { onChanged, documentRevision } = this.props;
onChanged({ ...documentRevision, language: value });
onChanged({ metadata: { ...documentRevision, language: value } });
}

handleSlugChange(event) {
const { onChanged, documentRevision } = this.props;
onChanged({ ...documentRevision, slug: event.target.value });
onChanged({ metadata: { ...documentRevision, slug: event.target.value } });
}

handleTagsChange(selectedValue, language) {
const { onChanged, documentRevision } = this.props;
const invalidMetadata = selectedValue.length === 0 || selectedValue.some(tag => tag.length < 3 || tag.length > 30);
adrianaluchian marked this conversation as resolved.
Show resolved Hide resolved
this.setState({ tagsValidationStatus: invalidMetadata ? 'error' : '' });
adrianaluchian marked this conversation as resolved.
Show resolved Hide resolved

const languageMapper = language === 'de' ? toGermanTag : word => word;
adrianaluchian marked this conversation as resolved.
Show resolved Hide resolved

onChanged({ metadata: { ...documentRevision, tags: selectedValue.map(languageMapper) }, invalidMetadata });
}

render() {
const { mode } = this.state;
const { documentRevision, languageNameProvider, language, t } = this.props;
const { mode, tagsValidationStatus } = this.state;
const { documentRevision, languageNameProvider, language, t, settings } = this.props;

const mergedTags = new Set(settings.defaultTags);
documentRevision.tags.forEach(tag => mergedTags.add(tag));
adrianaluchian marked this conversation as resolved.
Show resolved Hide resolved

let docLanguage;
let componentToShow;
Expand All @@ -68,6 +84,8 @@ class DocumentMetadataEditor extends React.Component {
<span>{t('language')}:</span> <span><CountryFlagAndName code={docLanguage.flag} name={docLanguage.name} /></span>
<br />
<span>{t('slug')}:</span> {documentRevision.slug ? <span>{urls.getArticleUrl(documentRevision.slug)}</span> : <i>({t('unassigned')})</i>}
<br />
<span>{t('tags')}</span>: {documentRevision.tags.map(item => (<Space key={item}><Tag key={item}>{item}</Tag></Space>))}
</div>
);
break;
Expand All @@ -80,6 +98,18 @@ class DocumentMetadataEditor extends React.Component {
<span>{t('language')}:</span> <LanguageSelect value={documentRevision.language} onChange={this.handleLanguageChange} />
<br />
<span>{t('slug')}:</span> <Input addonBefore={urls.articlesPrefix} value={documentRevision.slug || ''} onChange={this.handleSlugChange} />
<span>{t('tags')}</span>:
<Form.Item validateStatus={tagsValidationStatus} help={tagsValidationStatus && t('invalidTags')}>
<Select
mode="tags"
tokenSeparators={[' ', ' ']}
adrianaluchian marked this conversation as resolved.
Show resolved Hide resolved
value={documentRevision.tags}
style={{ width: '100%' }}
placeholder="Tags Mode"
adrianaluchian marked this conversation as resolved.
Show resolved Hide resolved
onChange={selectedValue => this.handleTagsChange(selectedValue, documentRevision.language)}
options={Array.from(mergedTags).map(tag => ({ value: tag, key: tag }))}
/>
</Form.Item>
</div>
);
break;
Expand Down Expand Up @@ -113,11 +143,12 @@ class DocumentMetadataEditor extends React.Component {
DocumentMetadataEditor.propTypes = {
...translationProps,
...languageProps,
...settingsProps,
documentRevision: documentRevisionShape.isRequired,
languageNameProvider: PropTypes.instanceOf(LanguageNameProvider).isRequired,
onChanged: PropTypes.func.isRequired
};

export default withTranslation('documentMetadataEditor')(withLanguage(inject({
export default withTranslation('documentMetadataEditor')(withSettings(withLanguage(inject({
languageNameProvider: LanguageNameProvider
}, DocumentMetadataEditor)));
}, DocumentMetadataEditor))));
6 changes: 6 additions & 0 deletions src/components/document-metadata-editor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ slug:
unassigned:
en: unassigned
de: nicht zugewiesen
tags:
en: Tags
de: Tags
invalidTags:
en: At least one tag must be provided and all tags must be between 3 and 30 characters
de: Mindestens ein Tag muss angegeben werden und Tags dürfen nicht kürzer als 3 und nicht länger als 30 Zeichen sein
10 changes: 6 additions & 4 deletions src/components/pages/edit-doc.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,8 @@ class EditDoc extends React.Component {
editedDocumentRevision: clonedRevision,
isDirty: false,
proposedSectionKeys,
invalidSectionKeys: []
invalidSectionKeys: [],
invalidMetadata: false
};
}

Expand Down Expand Up @@ -147,10 +148,11 @@ class EditDoc extends React.Component {
}
}

handleMetadataChanged(metadata) {
handleMetadataChanged({ metadata, invalidMetadata }) {
this.setState(prevState => {
return {
...prevState,
invalidMetadata,
editedDocumentRevision: { ...prevState.editedDocumentRevision, ...metadata },
isDirty: true
};
Expand Down Expand Up @@ -326,7 +328,7 @@ class EditDoc extends React.Component {

render() {
const { t } = this.props;
const { editedDocumentRevision, isDirty, invalidSectionKeys, proposedSectionKeys } = this.state;
const { editedDocumentRevision, isDirty, invalidSectionKeys, proposedSectionKeys, invalidMetadata } = this.state;

const newSectionMenu = (
<Menu>
Expand All @@ -345,7 +347,7 @@ class EditDoc extends React.Component {
);

const headerActions = [];
if (isDirty && !invalidSectionKeys.length) {
if (isDirty && !invalidSectionKeys.length && !invalidMetadata) {
headerActions.push({
key: 'save',
type: 'primary',
Expand Down