From 82ca3fd54f048aaefcbb909c48cfec03241bf72f Mon Sep 17 00:00:00 2001 From: Hunternif Date: Wed, 10 Apr 2019 23:16:27 +0700 Subject: [PATCH] client/tag_input: When adding new tags, infer category via syntax "category:tag" --- client/js/controllers/post_main_controller.js | 20 ++++++++++ .../js/controls/post_edit_sidebar_control.js | 2 + client/js/controls/tag_input_control.js | 39 +++++++++++++++---- client/js/models/tag_list.js | 13 ++++--- 4 files changed, 62 insertions(+), 12 deletions(-) diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index 19148526..b1a3e1c7 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -6,6 +6,7 @@ const uri = require('../util/uri.js'); const misc = require('../util/misc.js'); const settings = require('../models/settings.js'); const Comment = require('../models/comment.js'); +const Tag = require('../models/tag.js'); const Post = require('../models/post.js'); const PostList = require('../models/post_list.js'); const PostMainView = require('../views/post_main_view.js'); @@ -153,6 +154,25 @@ class PostMainController extends BasePostController { post.save() .then(() => { this._view.sidebarControl.showSuccess('Post saved.'); + // now save the new tags with categories + let tagPromises = []; + for (let newTag of e.detail.newTags) { + // load the tag that was created during updating the post + tagPromises.push( + Tag.get(newTag.names[0]).then(tag => { + tag.category = newTag.category; + return tag.save(); + } + ).then(() => { + e.detail.newTags.removeByName(newTag.names[0]); + }, error => { + this._view.sidebarControl.showError(error.message); + }) + ); + } + return Promise.all(tagPromises); + }) + .then(() => { this._view.sidebarControl.enableForm(); misc.disableExitConfirmation(); }, error => { diff --git a/client/js/controls/post_edit_sidebar_control.js b/client/js/controls/post_edit_sidebar_control.js index 2180d4b5..fc42dd92 100644 --- a/client/js/controls/post_edit_sidebar_control.js +++ b/client/js/controls/post_edit_sidebar_control.js @@ -338,6 +338,8 @@ class PostEditSidebarControl extends events.EventTarget { misc.splitByWhitespace(this._tagInputNode.value) : undefined, + newTags: this._tagControl.newTags, + relations: this._relationsInputNode ? misc.splitByWhitespace(this._relationsInputNode.value) .map(x => parseInt(x)) : diff --git a/client/js/controls/tag_input_control.js b/client/js/controls/tag_input_control.js index 1ad0957e..5bd964a1 100644 --- a/client/js/controls/tag_input_control.js +++ b/client/js/controls/tag_input_control.js @@ -5,6 +5,7 @@ const tags = require('../tags.js'); const misc = require('../util/misc.js'); const uri = require('../util/uri.js'); const Tag = require('../models/tag.js'); +const TagList = require('../models/tag_list.js'); const settings = require('../models/settings.js'); const events = require('../events.js'); const views = require('../util/views.js'); @@ -84,6 +85,7 @@ class TagInputControl extends events.EventTarget { constructor(hostNode, tagList) { super(); this.tags = tagList; + this.newTags = new TagList(); this._hostNode = hostNode; this._suggestions = new SuggestionList(); this._tagToListItemNode = new Map(); @@ -139,27 +141,44 @@ class TagInputControl extends events.EventTarget { } addTagByText(text, source) { + // try to find category for new tags in the format "category:name" + text = text.replace(/:\s+/, ':'); for (let tagName of text.split(/\s+/).filter(word => word).reverse()) { - this.addTagByName(tagName, source); + let nameAndCat = tagName.split(':'); + if (nameAndCat.length > 1) { + // "cat:my:tag" should parse to category "cat" and tag "my:tag" + let categoryName = nameAndCat.shift(); + let joinedTagName = nameAndCat.join(':'); + this.addTagByCategoryAndName(categoryName, joinedTagName, source); + } else { + this.addTagByName(tagName, source); + } } } addTagByName(name, source) { + // use the default category + return this.addTagByCategoryAndName(null, name, source); + } + + addTagByCategoryAndName(category, name, source) { + category = category ? category.trim() : null; name = name.trim(); if (!name) { return; } + // if tag with this name already exists, existing category will be used return Tag.get(name).then(tag => { return this.addTag(tag, source); }, () => { const tag = new Tag(); tag.names = [name]; - tag.category = null; - return this.addTag(tag, source); + tag.category = category; + return this.addTag(tag, source, true); }); } - addTag(tag, source) { + addTag(tag, source, isNew) { if (source != SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) { const listItemNode = this._getListItemNode(tag); if (source !== SOURCE_IMPLICATION) { @@ -169,9 +188,14 @@ class TagInputControl extends events.EventTarget { return Promise.resolve(); } - return this.tags.addByName(tag.names[0], false).then(() => { + return this.tags.addByTag(tag, false).then(() => { + // new tags with categories will be saved separately after the post + return isNew && tag.category + ? this.newTags.add(tag) + : Promise.resolve(); + }).then( () => { const listItemNode = this._createListItemNode(tag); - if (!tag.category) { + if (isNew === true) { listItemNode.classList.add('new'); } if (source === SOURCE_IMPLICATION) { @@ -197,6 +221,7 @@ class TagInputControl extends events.EventTarget { if (!this.tags.isTaggedWith(tag.names[0])) { return; } + this.newTags.removeByName(tag.names[0]); this.tags.removeByName(tag.names[0]); this._hideAutoComplete(); @@ -230,7 +255,7 @@ class TagInputControl extends events.EventTarget { _evtAddTagButtonClick(e) { e.preventDefault(); - this.addTagByName(this._tagInputNode.value, SOURCE_USER_INPUT); + this.addTagByText(this._tagInputNode.value, SOURCE_USER_INPUT); this._tagInputNode.value = ''; } diff --git a/client/js/models/tag_list.js b/client/js/models/tag_list.js index d87b694e..c55662aa 100644 --- a/client/js/models/tag_list.js +++ b/client/js/models/tag_list.js @@ -35,17 +35,20 @@ class TagList extends AbstractList { } addByName(tagName, addImplications) { - if (this.isTaggedWith(tagName)) { - return Promise.resolve(); - } - const tag = new Tag(); tag.names = [tagName]; + return this.addByTag(tag, addImplications); + } + + addByTag(tag, addImplications) { + if (this.isTaggedWith(tag.names[0])) { + return Promise.resolve(); + } this.add(tag); if (addImplications !== false) { - return Tag.get(tagName).then(actualTag => { + return Tag.get(tag.names[0]).then(actualTag => { return Promise.all( actualTag.implications.map( relation => this.addByName(relation.names[0], true)));