client/posts: add bulk safety editing (#122)

This commit is contained in:
rr-
2017-02-11 21:58:18 +01:00
parent 0dc7a4058e
commit 1caf76b1b2
7 changed files with 242 additions and 47 deletions

View File

@ -11,7 +11,7 @@ const PostsPageView = require('../views/posts_page_view.js');
const EmptyView = require('../views/empty_view.js');
const fields = [
'id', 'thumbnailUrl', 'type',
'id', 'thumbnailUrl', 'type', 'safety',
'score', 'favoriteCount', 'commentCount', 'tags', 'version'];
class PostListController {
@ -32,6 +32,7 @@ class PostListController {
hostNode: this._pageController.view.pageHeaderHolderNode,
parameters: ctx.parameters,
canBulkEditTags: api.hasPrivilege('posts:bulkEdit:tags'),
canBulkEditSafety: api.hasPrivilege('posts:bulkEdit:safety'),
bulkEdit: {
tags: this._bulkEditTags
},
@ -73,6 +74,11 @@ class PostListController {
e.detail.post.save().catch(error => window.alert(error.message));
}
_evtChangeSafety(e) {
e.detail.post.safety = e.detail.safety;
e.detail.post.save().catch(error => window.alert(error.message));
}
_decorateSearchQuery(text) {
const browsingSettings = settings.get();
let disabledSafety = [];
@ -106,6 +112,8 @@ class PostListController {
Object.assign(pageCtx, {
canViewPosts: api.hasPrivilege('posts:view'),
canBulkEditTags: api.hasPrivilege('posts:bulkEdit:tags'),
canBulkEditSafety:
api.hasPrivilege('posts:bulkEdit:safety'),
bulkEdit: {
tags: this._bulkEditTags,
},
@ -113,6 +121,8 @@ class PostListController {
const view = new PostsPageView(pageCtx);
view.addEventListener('tag', e => this._evtTag(e));
view.addEventListener('untag', e => this._evtUntag(e));
view.addEventListener(
'changeSafety', e => this._evtChangeSafety(e));
return view;
},
});

View File

@ -11,26 +11,19 @@ const TagAutoCompleteControl =
const template = views.getTemplate('posts-header');
class BulkTagEditor extends events.EventTarget {
class BulkEditor extends events.EventTarget {
constructor(hostNode) {
super();
this._hostNode = hostNode;
this._autoCompleteControl = new TagAutoCompleteControl(
this._inputNode, {addSpace: false});
this._openLinkNode.addEventListener(
'click', e => this._evtOpenLinkClick(e));
this._closeLinkNode.addEventListener(
'click', e => this._evtCloseLinkClick(e));
this._hostNode.addEventListener('submit', e => this._evtFormSubmit(e));
}
get value() {
return this._inputNode.value;
}
get opened() {
return this._hostNode.classList.contains('opened');
return this._hostNode.classList.contains('opened') &&
!this._hostNode.classList.contains('hidden');
}
get _openLinkNode() {
@ -41,6 +34,53 @@ class BulkTagEditor extends events.EventTarget {
return this._hostNode.querySelector('.close');
}
toggleOpen(state) {
this._hostNode.classList.toggle('opened', state);
}
toggleHide(state) {
this._hostNode.classList.toggle('hidden', state);
}
_evtOpenLinkClick(e) {
throw new Error('Not implemented');
}
_evtCloseLinkClick(e) {
throw new Error('Not implemented');
}
}
class BulkSafetyEditor extends BulkEditor {
constructor(hostNode) {
super(hostNode);
}
_evtOpenLinkClick(e) {
e.preventDefault();
this.toggleOpen(true);
this.dispatchEvent(new CustomEvent('open', {detail: {}}));
}
_evtCloseLinkClick(e) {
e.preventDefault();
this.toggleOpen(false);
this.dispatchEvent(new CustomEvent('close', {detail: {}}));
}
}
class BulkTagEditor extends BulkEditor {
constructor(hostNode) {
super(hostNode);
this._autoCompleteControl = new TagAutoCompleteControl(
this._inputNode, {addSpace: false});
this._hostNode.addEventListener('submit', e => this._evtFormSubmit(e));
}
get value() {
return this._inputNode.value;
}
get _inputNode() {
return this._hostNode.querySelector('input[name=tag]');
}
@ -54,10 +94,6 @@ class BulkTagEditor extends events.EventTarget {
this._inputNode.blur();
}
toggleOpen(state) {
this._hostNode.classList.toggle('opened', state);
}
_evtFormSubmit(e) {
e.preventDefault();
this.dispatchEvent(new CustomEvent('submit', {detail: {}}));
@ -99,18 +135,38 @@ class PostsHeaderView extends events.EventTarget {
safetyButtonNode.addEventListener(
'click', e => this._evtSafetyButtonClick(e));
}
this._formNode.addEventListener(
'submit', e => this._evtFormSubmit(e));
this._formNode.addEventListener('submit', e => this._evtFormSubmit(e));
this._bulkEditors = [];
if (this._bulkEditTagsNode) {
this._bulkTagEditor = new BulkTagEditor(this._bulkEditTagsNode);
this._bulkTagEditor.toggleOpen(!!ctx.parameters.tag);
this._bulkEditors.push(this._bulkTagEditor);
}
if (this._bulkEditSafetyNode) {
this._bulkSafetyEditor = new BulkSafetyEditor(
this._bulkEditSafetyNode);
this._bulkEditors.push(this._bulkSafetyEditor);
}
for (let editor of this._bulkEditors) {
this._bulkTagEditor.addEventListener('submit', e => {
this._navigate();
});
this._bulkTagEditor.addEventListener('close', e => {
editor.addEventListener('open', e => {
this._hideBulkEditorsExcept(editor);
this._navigate();
});
editor.addEventListener('close', e => {
this._closeAndShowAllBulkEditors();
this._navigate();
});
}
if (ctx.parameters.tag && this._bulkTagEditor) {
this._openBulkEditor(this._bulkTagEditor);
} else if (ctx.parameters.safety && this._bulkSafetyEditor) {
this._openBulkEditor(this._bulkSafetyEditor);
}
}
@ -130,6 +186,31 @@ class PostsHeaderView extends events.EventTarget {
return this._hostNode.querySelector('.bulk-edit-tags');
}
get _bulkEditSafetyNode() {
return this._hostNode.querySelector('.bulk-edit-safety');
}
_openBulkEditor(editor) {
editor.toggleOpen(true);
this._hideBulkEditorsExcept(editor);
}
_hideBulkEditorsExcept(editor) {
for (let otherEditor of this._bulkEditors) {
if (otherEditor !== editor) {
otherEditor.toggleOpen(false);
otherEditor.toggleHide(true);
}
}
}
_closeAndShowAllBulkEditors() {
for (let otherEditor of this._bulkEditors) {
otherEditor.toggleOpen(false);
otherEditor.toggleHide(false);
}
}
_evtSafetyButtonClick(e, url) {
e.preventDefault();
e.target.classList.toggle('disabled');
@ -164,6 +245,9 @@ class PostsHeaderView extends events.EventTarget {
} else {
parameters.tag = null;
}
parameters.safety = (
this._bulkSafetyEditor &&
this._bulkSafetyEditor.opened ? '1' : null);
this.dispatchEvent(
new CustomEvent('navigate', {detail: {parameters: parameters}}));
}

View File

@ -18,26 +18,48 @@ class PostsPageView extends events.EventTarget {
post.addEventListener('change', e => this._evtPostChange(e));
}
this._postIdToLinkNode = {};
for (let linkNode of this._tagFlipperNodes) {
const postId = linkNode.getAttribute('data-post-id');
this._postIdToListItemNode = {};
for (let listItemNode of this._listItemNodes) {
const postId = listItemNode.getAttribute('data-post-id');
const post = this._postIdToPost[postId];
this._postIdToLinkNode[postId] = linkNode;
linkNode.addEventListener(
'click', e => this._evtBulkEditTagsClick(e, post));
this._postIdToListItemNode[postId] = listItemNode;
const tagFlipperNode = this._getTagFlipperNode(listItemNode);
if (tagFlipperNode) {
tagFlipperNode.addEventListener(
'click', e => this._evtBulkEditTagsClick(e, post));
}
const safetyFlipperNode = this._getSafetyFlipperNode(listItemNode);
if (safetyFlipperNode) {
for (let linkNode of safetyFlipperNode.querySelectorAll('a')) {
linkNode.addEventListener(
'click', e => this._evtBulkEditSafetyClick(e, post));
}
}
}
this._syncTagFlippersHighlights();
this._syncBulkEditorsHighlights();
}
get _tagFlipperNodes() {
return this._hostNode.querySelectorAll('.tag-flipper');
get _listItemNodes() {
return this._hostNode.querySelectorAll('li');
}
_getTagFlipperNode(listItemNode) {
return listItemNode.querySelector('.tag-flipper');
}
_getSafetyFlipperNode(listItemNode) {
return listItemNode.querySelector('.safety-flipper');
}
_evtPostChange(e) {
const linkNode = this._postIdToLinkNode[e.detail.post.id];
linkNode.removeAttribute('data-disabled');
this._syncTagFlippersHighlights();
const listItemNode = this._postIdToListItemNode[e.detail.post.id];
for (let node of listItemNode.querySelectorAll('[data-disabled]')) {
node.removeAttribute('data-disabled');
}
this._syncBulkEditorsHighlights();
}
_evtBulkEditTagsClick(e, post) {
@ -53,15 +75,43 @@ class PostsPageView extends events.EventTarget {
{detail: {post: post}}));
}
_syncTagFlippersHighlights() {
for (let linkNode of this._tagFlipperNodes) {
const postId = linkNode.getAttribute('data-post-id');
_evtBulkEditSafetyClick(e, post) {
e.preventDefault();
const linkNode = e.target;
if (linkNode.getAttribute('data-disabled')) {
return;
}
const newSafety = linkNode.getAttribute('data-safety');
if (post.safety === newSafety) {
return;
}
linkNode.setAttribute('data-disabled', true);
this.dispatchEvent(
new CustomEvent(
'changeSafety', {detail: {post: post, safety: newSafety}}));
}
_syncBulkEditorsHighlights() {
for (let listItemNode of this._listItemNodes) {
const postId = listItemNode.getAttribute('data-post-id');
const post = this._postIdToPost[postId];
let tagged = true;
for (let tag of this._ctx.bulkEdit.tags) {
tagged = tagged & post.isTaggedWith(tag);
const tagFlipperNode = this._getTagFlipperNode(listItemNode);
if (tagFlipperNode) {
let tagged = true;
for (let tag of this._ctx.bulkEdit.tags) {
tagged = tagged & post.isTaggedWith(tag);
}
tagFlipperNode.classList.toggle('tagged', tagged);
}
const safetyFlipperNode = this._getSafetyFlipperNode(listItemNode);
if (safetyFlipperNode) {
for (let linkNode of safetyFlipperNode.querySelectorAll('a')) {
const safety = linkNode.getAttribute('data-safety');
linkNode.classList.toggle('active', post.safety == safety);
}
}
linkNode.classList.toggle('tagged', tagged);
}
}
}