client/tag_input: When adding new tags, infer category via syntax "category:tag"
This commit is contained in:
parent
8e1e6af232
commit
82ca3fd54f
4 changed files with 62 additions and 12 deletions
|
@ -6,6 +6,7 @@ const uri = require('../util/uri.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const settings = require('../models/settings.js');
|
const settings = require('../models/settings.js');
|
||||||
const Comment = require('../models/comment.js');
|
const Comment = require('../models/comment.js');
|
||||||
|
const Tag = require('../models/tag.js');
|
||||||
const Post = require('../models/post.js');
|
const Post = require('../models/post.js');
|
||||||
const PostList = require('../models/post_list.js');
|
const PostList = require('../models/post_list.js');
|
||||||
const PostMainView = require('../views/post_main_view.js');
|
const PostMainView = require('../views/post_main_view.js');
|
||||||
|
@ -153,6 +154,25 @@ class PostMainController extends BasePostController {
|
||||||
post.save()
|
post.save()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this._view.sidebarControl.showSuccess('Post saved.');
|
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();
|
this._view.sidebarControl.enableForm();
|
||||||
misc.disableExitConfirmation();
|
misc.disableExitConfirmation();
|
||||||
}, error => {
|
}, error => {
|
||||||
|
|
|
@ -338,6 +338,8 @@ class PostEditSidebarControl extends events.EventTarget {
|
||||||
misc.splitByWhitespace(this._tagInputNode.value) :
|
misc.splitByWhitespace(this._tagInputNode.value) :
|
||||||
undefined,
|
undefined,
|
||||||
|
|
||||||
|
newTags: this._tagControl.newTags,
|
||||||
|
|
||||||
relations: this._relationsInputNode ?
|
relations: this._relationsInputNode ?
|
||||||
misc.splitByWhitespace(this._relationsInputNode.value)
|
misc.splitByWhitespace(this._relationsInputNode.value)
|
||||||
.map(x => parseInt(x)) :
|
.map(x => parseInt(x)) :
|
||||||
|
|
|
@ -5,6 +5,7 @@ const tags = require('../tags.js');
|
||||||
const misc = require('../util/misc.js');
|
const misc = require('../util/misc.js');
|
||||||
const uri = require('../util/uri.js');
|
const uri = require('../util/uri.js');
|
||||||
const Tag = require('../models/tag.js');
|
const Tag = require('../models/tag.js');
|
||||||
|
const TagList = require('../models/tag_list.js');
|
||||||
const settings = require('../models/settings.js');
|
const settings = require('../models/settings.js');
|
||||||
const events = require('../events.js');
|
const events = require('../events.js');
|
||||||
const views = require('../util/views.js');
|
const views = require('../util/views.js');
|
||||||
|
@ -84,6 +85,7 @@ class TagInputControl extends events.EventTarget {
|
||||||
constructor(hostNode, tagList) {
|
constructor(hostNode, tagList) {
|
||||||
super();
|
super();
|
||||||
this.tags = tagList;
|
this.tags = tagList;
|
||||||
|
this.newTags = new TagList();
|
||||||
this._hostNode = hostNode;
|
this._hostNode = hostNode;
|
||||||
this._suggestions = new SuggestionList();
|
this._suggestions = new SuggestionList();
|
||||||
this._tagToListItemNode = new Map();
|
this._tagToListItemNode = new Map();
|
||||||
|
@ -139,27 +141,44 @@ class TagInputControl extends events.EventTarget {
|
||||||
}
|
}
|
||||||
|
|
||||||
addTagByText(text, source) {
|
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()) {
|
for (let tagName of text.split(/\s+/).filter(word => word).reverse()) {
|
||||||
|
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);
|
this.addTagByName(tagName, source);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addTagByName(name, 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();
|
name = name.trim();
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// if tag with this name already exists, existing category will be used
|
||||||
return Tag.get(name).then(tag => {
|
return Tag.get(name).then(tag => {
|
||||||
return this.addTag(tag, source);
|
return this.addTag(tag, source);
|
||||||
}, () => {
|
}, () => {
|
||||||
const tag = new Tag();
|
const tag = new Tag();
|
||||||
tag.names = [name];
|
tag.names = [name];
|
||||||
tag.category = null;
|
tag.category = category;
|
||||||
return this.addTag(tag, source);
|
return this.addTag(tag, source, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
addTag(tag, source) {
|
addTag(tag, source, isNew) {
|
||||||
if (source != SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) {
|
if (source != SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) {
|
||||||
const listItemNode = this._getListItemNode(tag);
|
const listItemNode = this._getListItemNode(tag);
|
||||||
if (source !== SOURCE_IMPLICATION) {
|
if (source !== SOURCE_IMPLICATION) {
|
||||||
|
@ -169,9 +188,14 @@ class TagInputControl extends events.EventTarget {
|
||||||
return Promise.resolve();
|
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);
|
const listItemNode = this._createListItemNode(tag);
|
||||||
if (!tag.category) {
|
if (isNew === true) {
|
||||||
listItemNode.classList.add('new');
|
listItemNode.classList.add('new');
|
||||||
}
|
}
|
||||||
if (source === SOURCE_IMPLICATION) {
|
if (source === SOURCE_IMPLICATION) {
|
||||||
|
@ -197,6 +221,7 @@ class TagInputControl extends events.EventTarget {
|
||||||
if (!this.tags.isTaggedWith(tag.names[0])) {
|
if (!this.tags.isTaggedWith(tag.names[0])) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.newTags.removeByName(tag.names[0]);
|
||||||
this.tags.removeByName(tag.names[0]);
|
this.tags.removeByName(tag.names[0]);
|
||||||
this._hideAutoComplete();
|
this._hideAutoComplete();
|
||||||
|
|
||||||
|
@ -230,7 +255,7 @@ class TagInputControl extends events.EventTarget {
|
||||||
|
|
||||||
_evtAddTagButtonClick(e) {
|
_evtAddTagButtonClick(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.addTagByName(this._tagInputNode.value, SOURCE_USER_INPUT);
|
this.addTagByText(this._tagInputNode.value, SOURCE_USER_INPUT);
|
||||||
this._tagInputNode.value = '';
|
this._tagInputNode.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,17 +35,20 @@ class TagList extends AbstractList {
|
||||||
}
|
}
|
||||||
|
|
||||||
addByName(tagName, addImplications) {
|
addByName(tagName, addImplications) {
|
||||||
if (this.isTaggedWith(tagName)) {
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
const tag = new Tag();
|
const tag = new Tag();
|
||||||
tag.names = [tagName];
|
tag.names = [tagName];
|
||||||
|
return this.addByTag(tag, addImplications);
|
||||||
|
}
|
||||||
|
|
||||||
|
addByTag(tag, addImplications) {
|
||||||
|
if (this.isTaggedWith(tag.names[0])) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
this.add(tag);
|
this.add(tag);
|
||||||
|
|
||||||
if (addImplications !== false) {
|
if (addImplications !== false) {
|
||||||
return Tag.get(tagName).then(actualTag => {
|
return Tag.get(tag.names[0]).then(actualTag => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
actualTag.implications.map(
|
actualTag.implications.map(
|
||||||
relation => this.addByName(relation.names[0], true)));
|
relation => this.addByName(relation.names[0], true)));
|
||||||
|
|
Reference in a new issue