diff --git a/migrations/2021-10-22-02-create-tags-index.js b/migrations/2021-10-22-02-create-tags-index.js new file mode 100644 index 00000000..5935e72f --- /dev/null +++ b/migrations/2021-10-22-02-create-tags-index.js @@ -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; diff --git a/src/components/document-metadata-editor.js b/src/components/document-metadata-editor.js index 9641ae30..5d2e37ba 100644 --- a/src/components/document-metadata-editor.js +++ b/src/components/document-metadata-editor.js @@ -2,15 +2,17 @@ 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 validators from '../utils/input-validators'; 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; @@ -18,11 +20,13 @@ const RadioGroup = Radio.Group; const MODE_EDIT = 'edit'; const MODE_PREVIEW = 'preview'; +const { isValidTag } = validators; + class DocumentMetadataEditor extends React.Component { constructor(props) { super(props); autoBind(this); - this.state = { mode: MODE_PREVIEW }; + this.state = { mode: MODE_PREVIEW, tagsValidationStatus: '' }; } handleEditClick() { @@ -39,22 +43,31 @@ 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(selectedValues) { + const { onChanged, documentRevision } = this.props; + const areTagsValid = selectedValues.length > 0 && selectedValues.every(tag => isValidTag({ tag })); + this.setState({ tagsValidationStatus: areTagsValid ? '' : 'error' }); + onChanged({ metadata: { ...documentRevision, tags: selectedValues }, invalidMetadata: !areTagsValid }); } 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]); let docLanguage; let componentToShow; @@ -68,6 +81,8 @@ class DocumentMetadataEditor extends React.Component { {t('language')}:
{t('slug')}: {documentRevision.slug ? {urls.getArticleUrl(documentRevision.slug)} : ({t('unassigned')})} +
+ {t('tags')}: {documentRevision.tags.map(item => ({item}))} ); break; @@ -80,6 +95,17 @@ class DocumentMetadataEditor extends React.Component { {t('language')}:
{t('slug')}: + {t('tags')}: + +