From 7d35e998ecda36ad7314ae670b86156497168ece Mon Sep 17 00:00:00 2001 From: jessop Date: Fri, 18 Oct 2019 14:01:53 -0400 Subject: [PATCH] solves 3 issues with tags --- src/ui/component/publishForm/view.jsx | 12 +++-- src/ui/component/tagsSearch/view.jsx | 71 ++++++++++++++++----------- src/ui/component/tagsSelect/view.jsx | 7 ++- src/ui/util/set-operations.js | 15 ++++++ static/app-strings.json | 5 +- 5 files changed, 72 insertions(+), 38 deletions(-) create mode 100644 src/ui/util/set-operations.js diff --git a/src/ui/component/publishForm/view.jsx b/src/ui/component/publishForm/view.jsx index 43fa0f2cb..d80aca708 100644 --- a/src/ui/component/publishForm/view.jsx +++ b/src/ui/component/publishForm/view.jsx @@ -141,10 +141,14 @@ function PublishForm(props: Props) { help={__('The better your tags are, the easier it will be for people to discover your content.')} empty={__('No tags added')} placeholder={__('Add a tag')} - onSelect={newTag => { - if (!tags.map(savedTag => savedTag.name).includes(newTag.name)) { - updatePublishForm({ tags: [...tags, newTag] }); - } + onSelect={newTags => { + const validatedTags = []; + newTags.forEach(newTag => { + if (!tags.some(tag => tag.name === newTag.name)) { + validatedTags.push(newTag); + } + }); + updatePublishForm({ tags: [...tags, ...validatedTags] }); }} onRemove={clickedTag => { const newTags = tags.slice().filter(tag => tag.name !== clickedTag.name); diff --git a/src/ui/component/tagsSearch/view.jsx b/src/ui/component/tagsSearch/view.jsx index ff0fce5ad..c58421fba 100644 --- a/src/ui/component/tagsSearch/view.jsx +++ b/src/ui/component/tagsSearch/view.jsx @@ -2,9 +2,10 @@ import React, { useState } from 'react'; import { Form, FormField } from 'component/common/form'; import Tag from 'component/tag'; +import { setUnion, setDifference } from 'util/set-operations'; type Props = { - tagsPasssedIn: Array, + tagsPassedIn: Array, unfollowedTags: Array, followedTags: Array, doToggleTagFollow: string => void, @@ -15,9 +16,16 @@ type Props = { placeholder?: string, }; +/* + We display tagsPassedIn + onClick gets the tag when a tag is clicked + onSubmit gets an array of tags in object form + We suggest tags based on followed, unfollowed, and passedIn + */ + export default function TagsSearch(props: Props) { const { - tagsPasssedIn, + tagsPassedIn, unfollowedTags = [], followedTags = [], doToggleTagFollow, @@ -28,23 +36,26 @@ export default function TagsSearch(props: Props) { placeholder, } = props; const [newTag, setNewTag] = useState(''); - - let tags = unfollowedTags.slice(); - if (newTag) { - tags.unshift({ name: newTag }); - } - const doesTagMatch = name => { const nextTag = newTag.substr(newTag.lastIndexOf(',') + 1, newTag.length).trim(); return newTag ? name.toLowerCase().includes(nextTag.toLowerCase()) : true; }; + // Make sure there are no duplicates, then trim - const suggestedTagsSet = new Set(tags.map(tag => tag.name)); + // suggestedTags = (followedTags - tagsPassedIn) + unfollowedTags + + const followedTagsSet = new Set(followedTags.map(tag => tag.name)); + const selectedTagsSet = new Set(tagsPassedIn.map(tag => tag.name)); + const unfollowedTagsSet = new Set(unfollowedTags.map(tag => tag.name)); + const remainingFollowedTagsSet = setDifference(followedTagsSet, selectedTagsSet); + const suggestedTagsSet = setUnion(remainingFollowedTagsSet, unfollowedTagsSet); + const suggestedTags = Array.from(suggestedTagsSet) .filter(doesTagMatch) .slice(0, 5); - if (!newTag && suggestMature) { + // tack 'mature' onto the end if it's not already in the list + if (!newTag && suggestMature && !suggestedTags.some(tag => tag === 'mature')) { suggestedTags.push('mature'); } @@ -62,12 +73,16 @@ export default function TagsSearch(props: Props) { setNewTag(''); + const newTagsArr = [...new Set(tags.split(',').map(newTag => newTag.trim().toLowerCase()))]; + // Split into individual tags, normalize the tags, and remove duplicates with a set. - tags = [...new Set(tags.split(',').map(newTag => newTag.trim().toLowerCase()))]; - tags.forEach(tag => { - if (onSelect) { - onSelect({ name: tag }); - } else { + if (onSelect) { + const arrOfObjectTags = newTagsArr.map(tag => { + return { name: tag }; + }); + onSelect(arrOfObjectTags); + } else { + newTagsArr.forEach(tag => { if (!unfollowedTags.map(({ name }) => name).includes(tag)) { doAddTag(tag); } @@ -75,29 +90,25 @@ export default function TagsSearch(props: Props) { if (!followedTags.map(({ name }) => name).includes(tag)) { doToggleTagFollow(tag); } - } - }); + }); + } } - function handleTagClick(tags) { - tags = tags.split(',').map(newTag => newTag.trim()); - - tags.forEach(tag => { - if (onSelect) { - onSelect({ name: tag }); - } else { - doToggleTagFollow(tag); - } - }); + function handleTagClick(tag: string) { + if (onSelect) { + onSelect([{ name: tag }]); + } else { + doToggleTagFollow(tag); + } } return (
    - {tagsPasssedIn.map(tag => ( + {tagsPassedIn.map(tag => ( { @@ -119,7 +130,7 @@ export default function TagsSearch(props: Props) {
      {suggestedTags.map(tag => ( - handleTagClick(tag)} /> + handleTagClick(tag)} /> ))} {!suggestedTags.length &&

      No suggested tags

      }
    diff --git a/src/ui/component/tagsSelect/view.jsx b/src/ui/component/tagsSelect/view.jsx index ce5ca8b23..7a9363a4c 100644 --- a/src/ui/component/tagsSelect/view.jsx +++ b/src/ui/component/tagsSelect/view.jsx @@ -18,11 +18,14 @@ type Props = { title?: string | boolean, help?: string, tagsChosen?: Array, - onSelect?: Tag => void, + onSelect?: (Array) => void, onRemove?: Tag => void, placeholder?: string, }; +/* + Displays tagsChosen if it exists, otherwise followedTags. + */ export default function TagsSelect(props: Props) { const { showClose, @@ -89,7 +92,7 @@ export default function TagsSelect(props: Props) { onRemove={handleTagClick} onSelect={onSelect} suggestMature={suggestMature && !hasMatureTag} - tagsPasssedIn={tagsToDisplay} + tagsPassedIn={tagsToDisplay} placeholder={placeholder} /> diff --git a/src/ui/util/set-operations.js b/src/ui/util/set-operations.js new file mode 100644 index 000000000..0f8545620 --- /dev/null +++ b/src/ui/util/set-operations.js @@ -0,0 +1,15 @@ +export const setDifference = (setA, setB) => { + let _difference = new Set(setA); + for (let el of setB) { + _difference.delete(el); + } + return _difference; +}; + +export const setUnion = (setA, setB) => { + let _union = new Set(setA); + for (let el of setB) { + _union.add(el); + } + return _union; +}; diff --git a/static/app-strings.json b/static/app-strings.json index 5f36878d9..6023e8d40 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -830,5 +830,6 @@ "To enable this feature, check 'Save Password' the next time you start the app.": "To enable this feature, check 'Save Password' the next time you start the app.", "An email address is required to sync your account.": "An email address is required to sync your account.", "Sign Out": "Sign Out", - "Follow more tags": "Follow more tags" -} + "Follow more tags": "Follow more tags", + "Portuguese": "Portuguese" +} \ No newline at end of file