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:
parent
0e4e994431
commit
0dc7a4058e
5 changed files with 146 additions and 106 deletions
|
@ -54,33 +54,35 @@
|
||||||
.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
|
||||||
display: inline-block
|
|
||||||
padding: 0.5em
|
.tag-flipper
|
||||||
box-sizing: border-box
|
|
||||||
border: 0
|
|
||||||
&:after
|
|
||||||
display: inline-block
|
display: inline-block
|
||||||
width: 1em
|
padding: 0.5em
|
||||||
height: 1em
|
box-sizing: border-box
|
||||||
text-align: center
|
border: 0
|
||||||
line-height: 1em
|
|
||||||
font-size: 20pt
|
|
||||||
&.tagged
|
|
||||||
background: rgba(0, 230, 0, 0.7)
|
|
||||||
&:after
|
&:after
|
||||||
color: white
|
display: inline-block
|
||||||
content: '-'
|
width: 1em
|
||||||
&:not(.tagged)
|
height: 1em
|
||||||
background: rgba(255, 0, 0, 0.7)
|
text-align: center
|
||||||
&:after
|
line-height: 1em
|
||||||
color: white
|
font-size: 20pt
|
||||||
content: '+'
|
&.tagged
|
||||||
&[data-disabled]
|
background: rgba(0, 230, 0, 0.7)
|
||||||
background: rgba(200, 200, 200, 0.7)
|
&:after
|
||||||
|
color: white
|
||||||
|
content: '-'
|
||||||
|
&:not(.tagged)
|
||||||
|
background: rgba(255, 0, 0, 0.7)
|
||||||
|
&:after
|
||||||
|
color: white
|
||||||
|
content: '+'
|
||||||
|
&[data-disabled]
|
||||||
|
background: rgba(200, 200, 200, 0.7)
|
||||||
|
|
||||||
.thumbnail
|
.thumbnail
|
||||||
background-position: 50% 30%
|
background-position: 50% 30%
|
||||||
|
@ -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]
|
||||||
|
|
|
@ -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><%
|
||||||
%><% if (ctx.canBulkEditTags) { %><%
|
|
||||||
%><wbr/><%
|
|
||||||
%><span class='bulk-edit-tags'><%
|
|
||||||
%><span class='append hint'>Tagging with:</span><%
|
|
||||||
%><a href class='mousetrap button append open'>Mass tag</a><%
|
|
||||||
%><wbr/><%
|
|
||||||
%><%= ctx.makeTextInput({name: 'tag', value: ctx.parameters.tag}) %><%
|
|
||||||
%><input class='mousetrap start-tagging' type='submit' value='Start tagging'/><%
|
|
||||||
%><a href class='mousetrap button append stop-tagging'>Stop tagging</a><%
|
|
||||||
%></span><%
|
|
||||||
%><% } %><%
|
|
||||||
%></form><%
|
%></form><%
|
||||||
|
%><% if (ctx.canBulkEditTags) { %><%
|
||||||
|
%><form class='horizontal bulk-edit-tags'><%
|
||||||
|
%><span class='append hint'>Tagging with:</span><%
|
||||||
|
%><a href class='mousetrap button append open'>Mass tag</a><%
|
||||||
|
%><wbr/><%
|
||||||
|
%><%= ctx.makeTextInput({name: 'tag', value: ctx.parameters.tag}) %><%
|
||||||
|
%><input class='mousetrap start' type='submit' value='Start tagging'/><%
|
||||||
|
%><a href class='mousetrap button append close'>Stop tagging</a><%
|
||||||
|
%></form><%
|
||||||
|
%><% } %><%
|
||||||
%></div>
|
%></div>
|
||||||
|
|
|
@ -33,10 +33,12 @@
|
||||||
</span>
|
</span>
|
||||||
<% } %>
|
<% } %>
|
||||||
</a>
|
</a>
|
||||||
<% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %>
|
<span class='edit-overlay'>
|
||||||
<a href data-post-id='<%= post.id %>' class='tag-flipper'>
|
<% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %>
|
||||||
</a>
|
<a href data-post-id='<%= post.id %>' class='tag-flipper'>
|
||||||
<% } %>
|
</a>
|
||||||
|
<% } %>
|
||||||
|
</span>
|
||||||
</li>
|
</li>
|
||||||
<% } %>
|
<% } %>
|
||||||
<%= ctx.makeFlexboxAlign() %>
|
<%= ctx.makeFlexboxAlign() %>
|
||||||
|
|
|
@ -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._stopBulkEditTagsLinkNode.addEventListener(
|
this._navigate();
|
||||||
'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._navigate();
|
||||||
|
}
|
||||||
|
|
||||||
|
_navigate() {
|
||||||
this._queryAutoCompleteControl.hide();
|
this._queryAutoCompleteControl.hide();
|
||||||
if (this._bulkEditTagsAutoCompleteControl) {
|
|
||||||
this._bulkEditTagsAutoCompleteControl.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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue