solves 3 issues with tags

This commit is contained in:
jessop 2019-10-18 14:01:53 -04:00 committed by Sean Yesmunt
parent 4fe31f318b
commit 7d35e998ec
5 changed files with 72 additions and 38 deletions

View file

@ -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);

View file

@ -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<Tag>,
tagsPassedIn: Array<Tag>,
unfollowedTags: Array<Tag>,
followedTags: Array<Tag>,
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 (
<React.Fragment>
<Form className="tags__input-wrapper" onSubmit={handleSubmit}>
<ul className="tags--remove">
{tagsPasssedIn.map(tag => (
{tagsPassedIn.map(tag => (
<Tag
key={tag.name}
key={`passed${tag.name}`}
name={tag.name}
type="remove"
onClick={() => {
@ -119,7 +130,7 @@ export default function TagsSearch(props: Props) {
</Form>
<ul className="tags">
{suggestedTags.map(tag => (
<Tag key={tag} name={tag} type="add" onClick={() => handleTagClick(tag)} />
<Tag key={`suggested${tag}`} name={tag} type="add" onClick={() => handleTagClick(tag)} />
))}
{!suggestedTags.length && <p className="empty tags__empty-message">No suggested tags</p>}
</ul>

View file

@ -18,11 +18,14 @@ type Props = {
title?: string | boolean,
help?: string,
tagsChosen?: Array<Tag>,
onSelect?: Tag => void,
onSelect?: (Array<Tag>) => 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}
/>
</React.Fragment>

View file

@ -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;
};

View file

@ -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"
}