Added support for creating new tags when bulk editing posts

refs https://github.com/TryGhost/Team/issues/2922
This commit is contained in:
Simon Backx 2023-04-13 17:05:42 +02:00
parent 76fae2a724
commit 788aa34c8b
3 changed files with 80 additions and 14 deletions

View File

@ -105,10 +105,49 @@ export default class PostsContextMenu extends Component {
*addTagToPostsTask(tags) {
const updatedModels = this.selectionList.availableModels;
yield this.performBulkEdit('addTag', {tags: tags.map(tag => tag.id)});
yield this.performBulkEdit('addTag', {
tags: tags.map((t) => {
return {
id: t.id,
name: t.name,
slug: t.slug
};
})
});
this.notifications.showNotification(this.#getToastMessage('tagsAdded'), {type: 'success'});
const serializedTags = tags.toArray().map((t) => {
return {
...t.serialize({includeId: true}),
type: 'tag'
};
});
// Destroy unsaved new tags (otherwise we could select them again)
this.store.peekAll('tag').forEach((tag) => {
if (tag.isNew) {
tag.destroyRecord();
}
});
// For new tags, attach the id to it, so we can link the new tag to the post
let allTags = null;
for (const tag of serializedTags) {
if (!tag.id) {
if (!allTags) {
// Update tags on the client side (we could have created new tags)
yield this.store.query('tag', {limit: 'all'});
allTags = this.store.peekAll('tag').toArray();
}
const createdTag = allTags.find(t => t.name === tag.name && t.id);
if (createdTag) {
tag.id = createdTag.id;
tag.slug = createdTag.slug;
}
}
}
// Update the models on the client side
for (const post of updatedModels) {
const newTags = post.tags.toArray().map((t) => {
@ -117,12 +156,9 @@ export default class PostsContextMenu extends Component {
type: 'tag'
};
});
for (const tag of tags) {
for (const tag of serializedTags) {
if (!newTags.find(t => t.id === tag.id)) {
newTags.push({
...tag.serialize({includeId: true}),
type: 'tag'
});
newTags.push(tag);
}
}

View File

@ -22,6 +22,13 @@ export default class AddTag extends Component {
// store and be updated when the above query returns
this.store.query('tag', {limit: 'all'});
this.#availableTags = this.store.peekAll('tag');
// Destroy unsaved new tags (otherwise we could select them again -> create them again)
this.#availableTags.forEach((tag) => {
if (tag.isNew) {
tag.destroyRecord();
}
});
}
@action
@ -58,11 +65,8 @@ export default class AddTag extends Component {
}
@action
shouldAllowCreate() {
return false;
// This is not supported by the backend yet
// return !this.#findTagByName(nameInput.trim(), this.#availableTags);
shouldAllowCreate(nameInput) {
return !this.#findTagByName(nameInput.trim(), this.#availableTags);
}
#findTagByName(name, tags) {

View File

@ -8,6 +8,7 @@ const messages = {
invalidVisibilityFilter: 'Invalid visibility filter.',
invalidVisibility: 'Invalid visibility value.',
invalidTiers: 'Invalid tiers value.',
invalidTags: 'Invalid tags value.',
invalidEmailSegment: 'The email segment parameter doesn\'t contain a valid filter',
unsupportedBulkAction: 'Unsupported bulk action'
};
@ -95,6 +96,23 @@ class PostsService {
return await this.#updatePosts({visibility: data.meta.visibility, tiers}, {filter: options.filter});
}
if (data.action === 'addTag') {
if (!Array.isArray(data.meta.tags)) {
throw new errors.IncorrectUsageError({
message: tpl(messages.invalidTags)
});
}
for (const tag of data.meta.tags) {
if (typeof tag !== 'object') {
throw new errors.IncorrectUsageError({
message: tpl(messages.invalidTags)
});
}
if (!tag.id && !tag.name) {
throw new errors.IncorrectUsageError({
message: tpl(messages.invalidTags)
});
}
}
return await this.#bulkAddTags({tags: data.meta.tags}, {filter: options.filter});
}
throw new errors.IncorrectUsageError({
@ -118,17 +136,25 @@ class PostsService {
});
}
// Create tags that don't exist
for (const tag of data.tags) {
if (!tag.id) {
const createdTag = await this.models.Tag.add(tag, {transacting: options.transacting});
tag.id = createdTag.id;
}
}
const postRows = await this.models.Post.getFilteredCollectionQuery({
filter: options.filter,
status: 'all'
}).select('posts.id');
const postTags = data.tags.reduce((pt, tagId) => {
const postTags = data.tags.reduce((pt, tag) => {
return pt.concat(postRows.map((post) => {
return {
id: (new ObjectId()).toHexString(),
post_id: post.id,
tag_id: tagId,
tag_id: tag.id,
sort_order: 0
};
}));