client/posts: refactor bulk tag editor

Extract the state that controls mass tag form in the posts list header
to a separate class.

It's not exactly a 100% reusable control (the .tpl is shared), but it
should greatly simplify reading the JS.
This commit is contained in:
rr- 2017-02-11 20:12:44 +01:00
parent 0e4e994431
commit 0dc7a4058e
5 changed files with 146 additions and 106 deletions

View file

@ -54,10 +54,12 @@
.icon:not(:first-of-type) .icon:not(:first-of-type)
margin-left: 1em margin-left: 1em
.tag-flipper .edit-overlay
position: absolute position: absolute
top: 0.5em top: 0.5em
left: 0.5em left: 0.5em
.tag-flipper
display: inline-block display: inline-block
padding: 0.5em padding: 0.5em
box-sizing: border-box box-sizing: border-box
@ -121,14 +123,14 @@
font-size: 0.95em font-size: 0.95em
color: $inactive-link-color color: $inactive-link-color
.bulk-edit-tags .bulk-edit-tags
&:not(.active) &:not(.opened)
[type=text], [type=text],
.start-tagging, .start,
.stop-tagging .close
display: none display: none
.hint .hint
display: none display: none
&.active &.opened
.open .open
display: none display: none
input[name=tag] input[name=tag]

View file

@ -1,5 +1,5 @@
<div class='post-list-header'><% <div class='post-list-header'><%
%><form class='horizontal'><% %><form class='horizontal search'><%
%><%= ctx.makeTextInput({text: 'Search query', id: 'search-text', name: 'search-text', value: ctx.parameters.query}) %><% %><%= ctx.makeTextInput({text: 'Search query', id: 'search-text', name: 'search-text', value: ctx.parameters.query}) %><%
%><wbr/><% %><wbr/><%
%><input class='mousetrap' type='submit' value='Search'/><% %><input class='mousetrap' type='submit' value='Search'/><%
@ -9,16 +9,15 @@
%><input data-safety=unsafe type='button' class='mousetrap safety safety-unsafe <%- ctx.settings.listPosts.unsafe ? '' : 'disabled' %>'/><% %><input data-safety=unsafe type='button' class='mousetrap safety safety-unsafe <%- ctx.settings.listPosts.unsafe ? '' : 'disabled' %>'/><%
%><wbr/><% %><wbr/><%
%><a class='mousetrap button append' href='<%- ctx.formatClientLink('help', 'search', 'posts') %>'>Syntax help</a><% %><a class='mousetrap button append' href='<%- ctx.formatClientLink('help', 'search', 'posts') %>'>Syntax help</a><%
%></form><%
%><% if (ctx.canBulkEditTags) { %><% %><% if (ctx.canBulkEditTags) { %><%
%><wbr/><% %><form class='horizontal bulk-edit-tags'><%
%><span class='bulk-edit-tags'><%
%><span class='append hint'>Tagging with:</span><% %><span class='append hint'>Tagging with:</span><%
%><a href class='mousetrap button append open'>Mass tag</a><% %><a href class='mousetrap button append open'>Mass tag</a><%
%><wbr/><% %><wbr/><%
%><%= ctx.makeTextInput({name: 'tag', value: ctx.parameters.tag}) %><% %><%= ctx.makeTextInput({name: 'tag', value: ctx.parameters.tag}) %><%
%><input class='mousetrap start-tagging' type='submit' value='Start tagging'/><% %><input class='mousetrap start' type='submit' value='Start tagging'/><%
%><a href class='mousetrap button append stop-tagging'>Stop tagging</a><% %><a href class='mousetrap button append close'>Stop tagging</a><%
%></span><%
%><% } %><%
%></form><% %></form><%
%><% } %><%
%></div> %></div>

View file

@ -33,10 +33,12 @@
</span> </span>
<% } %> <% } %>
</a> </a>
<span class='edit-overlay'>
<% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %> <% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %>
<a href data-post-id='<%= post.id %>' class='tag-flipper'> <a href data-post-id='<%= post.id %>' class='tag-flipper'>
</a> </a>
<% } %> <% } %>
</span>
</li> </li>
<% } %> <% } %>
<%= ctx.makeFlexboxAlign() %> <%= ctx.makeFlexboxAlign() %>

View file

@ -11,6 +11,74 @@ const TagAutoCompleteControl =
const template = views.getTemplate('posts-header'); const template = views.getTemplate('posts-header');
class BulkTagEditor 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');
}
get _openLinkNode() {
return this._hostNode.querySelector('.open');
}
get _closeLinkNode() {
return this._hostNode.querySelector('.close');
}
get _inputNode() {
return this._hostNode.querySelector('input[name=tag]');
}
focus() {
this._inputNode.focus();
}
blur() {
this._autoCompleteControl.hide();
this._inputNode.blur();
}
toggleOpen(state) {
this._hostNode.classList.toggle('opened', state);
}
_evtFormSubmit(e) {
e.preventDefault();
this.dispatchEvent(new CustomEvent('submit', {detail: {}}));
}
_evtOpenLinkClick(e) {
e.preventDefault();
this.toggleOpen(true);
this.focus();
this.dispatchEvent(new CustomEvent('open', {detail: {}}));
}
_evtCloseLinkClick(e) {
e.preventDefault();
this._inputNode.value = '';
this.toggleOpen(false);
this.blur();
this.dispatchEvent(new CustomEvent('close', {detail: {}}));
}
}
class PostsHeaderView extends events.EventTarget { class PostsHeaderView extends events.EventTarget {
constructor(ctx) { constructor(ctx) {
super(); super();
@ -34,26 +102,20 @@ class PostsHeaderView extends events.EventTarget {
this._formNode.addEventListener( this._formNode.addEventListener(
'submit', e => this._evtFormSubmit(e)); 'submit', e => this._evtFormSubmit(e));
if (this._bulkEditTagsInputNode) { if (this._bulkEditTagsNode) {
this._bulkEditTagsAutoCompleteControl = new TagAutoCompleteControl( this._bulkTagEditor = new BulkTagEditor(this._bulkEditTagsNode);
this._bulkEditTagsInputNode, {addSpace: false}); this._bulkTagEditor.toggleOpen(!!ctx.parameters.tag);
if (this._openBulkEditTagsLinkNode) { this._bulkTagEditor.addEventListener('submit', e => {
this._openBulkEditTagsLinkNode.addEventListener( this._navigate();
'click', e => this._evtBulkEditTagsClick(e)); });
this._bulkTagEditor.addEventListener('close', e => {
this._navigate();
});
} }
this._stopBulkEditTagsLinkNode.addEventListener(
'click', e => this._evtStopTaggingClick(e));
this._toggleBulkEditTagsVisibility(!!ctx.parameters.tag);
}
}
_toggleBulkEditTagsVisibility(state) {
this._formNode.querySelector('.bulk-edit-tags')
.classList.toggle('active', state);
} }
get _formNode() { get _formNode() {
return this._hostNode.querySelector('form'); return this._hostNode.querySelector('form.search');
} }
get _safetyButtonNodes() { get _safetyButtonNodes() {
@ -64,34 +126,8 @@ class PostsHeaderView extends events.EventTarget {
return this._hostNode.querySelector('form [name=search-text]'); return this._hostNode.querySelector('form [name=search-text]');
} }
get _bulkEditTagsInputNode() { get _bulkEditTagsNode() {
return this._hostNode.querySelector('form .bulk-edit-tags [name=tag]'); return this._hostNode.querySelector('.bulk-edit-tags');
}
get _openBulkEditTagsLinkNode() {
return this._hostNode.querySelector('form .bulk-edit-tags .open');
}
get _stopBulkEditTagsLinkNode() {
return this._hostNode.querySelector(
'form .bulk-edit-tags .stop-tagging');
}
_evtBulkEditTagsClick(e) {
e.preventDefault();
this._toggleBulkEditTagsVisibility(true);
}
_evtStopTaggingClick(e) {
e.preventDefault();
this._bulkEditTagsInputNode.value = '';
this._toggleBulkEditTagsVisibility(false);
this.dispatchEvent(new CustomEvent('navigate', {detail: {parameters: {
query: this._ctx.parameters.query,
offset: this._ctx.parameters.offset,
limit: this._ctx.parameters.limit,
tag: null,
}}}));
} }
_evtSafetyButtonClick(e, url) { _evtSafetyButtonClick(e, url) {
@ -114,16 +150,17 @@ class PostsHeaderView extends events.EventTarget {
_evtFormSubmit(e) { _evtFormSubmit(e) {
e.preventDefault(); e.preventDefault();
this._queryAutoCompleteControl.hide(); this._navigate();
if (this._bulkEditTagsAutoCompleteControl) {
this._bulkEditTagsAutoCompleteControl.hide();
} }
_navigate() {
this._queryAutoCompleteControl.hide();
let parameters = {query: this._queryInputNode.value}; let parameters = {query: this._queryInputNode.value};
parameters.offset = parameters.query === this._ctx.parameters.query ? parameters.offset = parameters.query === this._ctx.parameters.query ?
this._ctx.parameters.offset : 0; this._ctx.parameters.offset : 0;
if (this._bulkEditTagsInputNode) { if (this._bulkTagEditor && this._bulkTagEditor.opened) {
parameters.tag = this._bulkEditTagsInputNode.value; parameters.tag = this._bulkTagEditor.value;
this._bulkEditTagsInputNode.blur(); this._bulkTagEditor.blur();
} else { } else {
parameters.tag = null; parameters.tag = null;
} }

View file

@ -27,7 +27,7 @@ class PostsPageView extends events.EventTarget {
'click', e => this._evtBulkEditTagsClick(e, post)); 'click', e => this._evtBulkEditTagsClick(e, post));
} }
this._syncBulkEditTagsHighlights(); this._syncTagFlippersHighlights();
} }
get _tagFlipperNodes() { get _tagFlipperNodes() {
@ -37,19 +37,7 @@ class PostsPageView extends events.EventTarget {
_evtPostChange(e) { _evtPostChange(e) {
const linkNode = this._postIdToLinkNode[e.detail.post.id]; const linkNode = this._postIdToLinkNode[e.detail.post.id];
linkNode.removeAttribute('data-disabled'); linkNode.removeAttribute('data-disabled');
this._syncBulkEditTagsHighlights(); this._syncTagFlippersHighlights();
}
_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.bulkEdit.tags) {
tagged = tagged & post.isTaggedWith(tag);
}
linkNode.classList.toggle('tagged', tagged);
}
} }
_evtBulkEditTagsClick(e, post) { _evtBulkEditTagsClick(e, post) {
@ -64,6 +52,18 @@ class PostsPageView extends events.EventTarget {
linkNode.classList.contains('tagged') ? 'untag' : 'tag', linkNode.classList.contains('tagged') ? 'untag' : 'tag',
{detail: {post: post}})); {detail: {post: post}}));
} }
_syncTagFlippersHighlights() {
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.bulkEdit.tags) {
tagged = tagged & post.isTaggedWith(tag);
}
linkNode.classList.toggle('tagged', tagged);
}
}
} }
module.exports = PostsPageView; module.exports = PostsPageView;