diff --git a/client/css/core-forms.styl b/client/css/core-forms.styl
index 5b62e0c..541de1b 100644
--- a/client/css/core-forms.styl
+++ b/client/css/core-forms.styl
@@ -17,6 +17,8 @@ form
.input li:first-child
padding-top: 0
margin-top: 0
+
+form:not(.horizontal)
.hint
margin-top: 0.2em
margin-bottom: 0
diff --git a/client/css/post-list-view.styl b/client/css/post-list-view.styl
index 4e797e0..b817c29 100644
--- a/client/css/post-list-view.styl
+++ b/client/css/post-list-view.styl
@@ -54,7 +54,7 @@
.icon:not(:first-of-type)
margin-left: 1em
- .masstag
+ .tag-flipper
position: absolute
top: 0.5em
left: 0.5em
@@ -117,24 +117,24 @@
margin-right: 0.25em
input[name=search-text]
width: 25em
- input[name=masstag]
- width: 12em
- .masstag-hint, .open-masstag
- margin-right: 1em
.append
font-size: 0.95em
color: $inactive-link-color
- .masstag
+ .bulk-edit-tags
&:not(.active)
[type=text],
.start-tagging,
.stop-tagging
display: none
- .masstag-hint
+ .hint
display: none
&.active
- .open-masstag
+ .open
display: none
+ input[name=tag]
+ width: 12em
+ .hint, .open
+ margin-right: 1em
.safety
margin-right: 0.25em
diff --git a/client/html/posts_header.tpl b/client/html/posts_header.tpl
index 54c9ab1..f90de34 100644
--- a/client/html/posts_header.tpl
+++ b/client/html/posts_header.tpl
@@ -9,13 +9,13 @@
%>'/><%
%><%
%>'>Syntax help<%
- %><% if (ctx.canMassTag) { %><%
+ %><% if (ctx.canBulkEditTags) { %><%
%><%
- %><%
- %>Tagging with:<%
- %>Mass tag<%
+ %><%
+ %>Tagging with:<%
+ %>Mass tag<%
%><%
- %><%= ctx.makeTextInput({name: 'masstag', value: ctx.parameters.tag}) %><%
+ %><%= ctx.makeTextInput({name: 'tag', value: ctx.parameters.tag}) %><%
%><%
%>Stop tagging<%
%><%
diff --git a/client/html/posts_page.tpl b/client/html/posts_page.tpl
index 79f31d9..1f0e664 100644
--- a/client/html/posts_page.tpl
+++ b/client/html/posts_page.tpl
@@ -33,8 +33,8 @@
<% } %>
- <% if (ctx.canMassTag && ctx.parameters && ctx.parameters.tag) { %>
-
+ <% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %>
+
<% } %>
diff --git a/client/js/controllers/post_list_controller.js b/client/js/controllers/post_list_controller.js
index bd570e9..a0973e1 100644
--- a/client/js/controllers/post_list_controller.js
+++ b/client/js/controllers/post_list_controller.js
@@ -31,8 +31,10 @@ class PostListController {
this._headerView = new PostsHeaderView({
hostNode: this._pageController.view.pageHeaderHolderNode,
parameters: ctx.parameters,
- canMassTag: api.hasPrivilege('tags:masstag'),
- massTagTags: this._massTagTags,
+ canBulkEditTags: api.hasPrivilege('posts:bulkEdit:tags'),
+ bulkEdit: {
+ tags: this._bulkEditTags
+ },
});
this._headerView.addEventListener(
'navigate', e => this._evtNavigate(e));
@@ -44,7 +46,7 @@ class PostListController {
this._pageController.showSuccess(message);
}
- get _massTagTags() {
+ get _bulkEditTags() {
return (this._ctx.parameters.tag || '').split(/\s+/).filter(s => s);
}
@@ -58,14 +60,14 @@ class PostListController {
}
_evtTag(e) {
- for (let tag of this._massTagTags) {
+ for (let tag of this._bulkEditTags) {
e.detail.post.addTag(tag);
}
e.detail.post.save().catch(error => window.alert(error.message));
}
_evtUntag(e) {
- for (let tag of this._massTagTags) {
+ for (let tag of this._bulkEditTags) {
e.detail.post.removeTag(tag);
}
e.detail.post.save().catch(error => window.alert(error.message));
@@ -103,8 +105,10 @@ class PostListController {
pageRenderer: pageCtx => {
Object.assign(pageCtx, {
canViewPosts: api.hasPrivilege('posts:view'),
- canMassTag: api.hasPrivilege('tags:masstag'),
- massTagTags: this._massTagTags,
+ canBulkEditTags: api.hasPrivilege('posts:bulkEdit:tags'),
+ bulkEdit: {
+ tags: this._bulkEditTags,
+ },
});
const view = new PostsPageView(pageCtx);
view.addEventListener('tag', e => this._evtTag(e));
diff --git a/client/js/views/posts_header_view.js b/client/js/views/posts_header_view.js
index 54b8cf0..0392ffd 100644
--- a/client/js/views/posts_header_view.js
+++ b/client/js/views/posts_header_view.js
@@ -23,10 +23,6 @@ class PostsHeaderView extends events.EventTarget {
this._queryAutoCompleteControl = new TagAutoCompleteControl(
this._queryInputNode,
{addSpace: true, transform: misc.escapeSearchTerm});
- if (this._massTagInputNode) {
- this._masstagAutoCompleteControl = new TagAutoCompleteControl(
- this._massTagInputNode, {addSpace: false});
- }
keyboard.bind('p', () => this._focusFirstPostNode());
search.searchInputNodeFocusHelper(this._queryInputNode);
@@ -38,19 +34,21 @@ class PostsHeaderView extends events.EventTarget {
this._formNode.addEventListener(
'submit', e => this._evtFormSubmit(e));
- if (this._massTagInputNode) {
- if (this._openMassTagLinkNode) {
- this._openMassTagLinkNode.addEventListener(
- 'click', e => this._evtMassTagClick(e));
+ if (this._bulkEditTagsInputNode) {
+ this._bulkEditTagsAutoCompleteControl = new TagAutoCompleteControl(
+ this._bulkEditTagsInputNode, {addSpace: false});
+ if (this._openBulkEditTagsLinkNode) {
+ this._openBulkEditTagsLinkNode.addEventListener(
+ 'click', e => this._evtBulkEditTagsClick(e));
}
- this._stopMassTagLinkNode.addEventListener(
+ this._stopBulkEditTagsLinkNode.addEventListener(
'click', e => this._evtStopTaggingClick(e));
- this._toggleMassTagVisibility(!!ctx.parameters.tag);
+ this._toggleBulkEditTagsVisibility(!!ctx.parameters.tag);
}
}
- _toggleMassTagVisibility(state) {
- this._formNode.querySelector('.masstag')
+ _toggleBulkEditTagsVisibility(state) {
+ this._formNode.querySelector('.bulk-edit-tags')
.classList.toggle('active', state);
}
@@ -66,27 +64,28 @@ class PostsHeaderView extends events.EventTarget {
return this._hostNode.querySelector('form [name=search-text]');
}
- get _massTagInputNode() {
- return this._hostNode.querySelector('form [name=masstag]');
+ get _bulkEditTagsInputNode() {
+ return this._hostNode.querySelector('form .bulk-edit-tags [name=tag]');
}
- get _openMassTagLinkNode() {
- return this._hostNode.querySelector('form .open-masstag');
+ get _openBulkEditTagsLinkNode() {
+ return this._hostNode.querySelector('form .bulk-edit-tags .open');
}
- get _stopMassTagLinkNode() {
- return this._hostNode.querySelector('form .stop-tagging');
+ get _stopBulkEditTagsLinkNode() {
+ return this._hostNode.querySelector(
+ 'form .bulk-edit-tags .stop-tagging');
}
- _evtMassTagClick(e) {
+ _evtBulkEditTagsClick(e) {
e.preventDefault();
- this._toggleMassTagVisibility(true);
+ this._toggleBulkEditTagsVisibility(true);
}
_evtStopTaggingClick(e) {
e.preventDefault();
- this._massTagInputNode.value = '';
- this._toggleMassTagVisibility(false);
+ this._bulkEditTagsInputNode.value = '';
+ this._toggleBulkEditTagsVisibility(false);
this.dispatchEvent(new CustomEvent('navigate', {detail: {parameters: {
query: this._ctx.parameters.query,
offset: this._ctx.parameters.offset,
@@ -116,15 +115,15 @@ class PostsHeaderView extends events.EventTarget {
_evtFormSubmit(e) {
e.preventDefault();
this._queryAutoCompleteControl.hide();
- if (this._masstagAutoCompleteControl) {
- this._masstagAutoCompleteControl.hide();
+ if (this._bulkEditTagsAutoCompleteControl) {
+ this._bulkEditTagsAutoCompleteControl.hide();
}
let parameters = {query: this._queryInputNode.value};
parameters.offset = parameters.query === this._ctx.parameters.query ?
this._ctx.parameters.offset : 0;
- if (this._massTagInputNode) {
- parameters.tag = this._massTagInputNode.value;
- this._massTagInputNode.blur();
+ if (this._bulkEditTagsInputNode) {
+ parameters.tag = this._bulkEditTagsInputNode.value;
+ this._bulkEditTagsInputNode.blur();
} else {
parameters.tag = null;
}
diff --git a/client/js/views/posts_page_view.js b/client/js/views/posts_page_view.js
index 3c53c84..b5ad5df 100644
--- a/client/js/views/posts_page_view.js
+++ b/client/js/views/posts_page_view.js
@@ -19,36 +19,40 @@ class PostsPageView extends events.EventTarget {
}
this._postIdToLinkNode = {};
- for (let linkNode of this._hostNode.querySelectorAll('.masstag')) {
+ for (let linkNode of this._tagFlipperNodes) {
const postId = linkNode.getAttribute('data-post-id');
const post = this._postIdToPost[postId];
this._postIdToLinkNode[postId] = linkNode;
linkNode.addEventListener(
- 'click', e => this._evtMassTagClick(e, post));
+ 'click', e => this._evtBulkEditTagsClick(e, post));
}
- this._syncMassTagHighlights();
+ this._syncBulkEditTagsHighlights();
+ }
+
+ get _tagFlipperNodes() {
+ return this._hostNode.querySelectorAll('.tag-flipper');
}
_evtPostChange(e) {
const linkNode = this._postIdToLinkNode[e.detail.post.id];
linkNode.removeAttribute('data-disabled');
- this._syncMassTagHighlights();
+ this._syncBulkEditTagsHighlights();
}
- _syncMassTagHighlights() {
- for (let linkNode of this._hostNode.querySelectorAll('.masstag')) {
+ _syncBulkEditTagsHighlights() {
+ for (let linkNode of this._tagFlipperNodes) {
const postId = linkNode.getAttribute('data-post-id');
const post = this._postIdToPost[postId];
let tagged = true;
- for (let tag of this._ctx.massTagTags) {
+ for (let tag of this._ctx.bulkEdit.tags) {
tagged = tagged & post.isTaggedWith(tag);
}
linkNode.classList.toggle('tagged', tagged);
}
}
- _evtMassTagClick(e, post) {
+ _evtBulkEditTagsClick(e, post) {
e.preventDefault();
const linkNode = e.target;
if (linkNode.getAttribute('data-disabled')) {
diff --git a/config.yaml.dist b/config.yaml.dist
index 1f4a230..2b03c0c 100644
--- a/config.yaml.dist
+++ b/config.yaml.dist
@@ -89,6 +89,7 @@ privileges:
'posts:score': regular
'posts:merge': moderator
'posts:favorite': regular
+ 'posts:bulk-edit:tags': power
'tags:create': regular
'tags:edit:names': power
@@ -98,7 +99,6 @@ privileges:
'tags:edit:suggestions': power
'tags:list': regular # note: will be available as data_url/tags.json anyway
'tags:view': anonymous
- 'tags:masstag': power
'tags:merge': moderator
'tags:delete': moderator