From 0dc7a4058e8e00b1dd878343f89b8c7a10cfcdaf Mon Sep 17 00:00:00 2001 From: rr- Date: Sat, 11 Feb 2017 20:12:44 +0100 Subject: [PATCH] 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. --- client/css/post-list-view.styl | 54 ++++++----- client/html/posts_header.tpl | 23 +++-- client/html/posts_page.tpl | 10 +- client/js/views/posts_header_view.js | 137 +++++++++++++++++---------- client/js/views/posts_page_view.js | 28 +++--- 5 files changed, 146 insertions(+), 106 deletions(-) diff --git a/client/css/post-list-view.styl b/client/css/post-list-view.styl index b817c29..ed57fe9 100644 --- a/client/css/post-list-view.styl +++ b/client/css/post-list-view.styl @@ -54,33 +54,35 @@ .icon:not(:first-of-type) margin-left: 1em - .tag-flipper + .edit-overlay position: absolute top: 0.5em left: 0.5em - display: inline-block - padding: 0.5em - box-sizing: border-box - border: 0 - &:after + + .tag-flipper display: inline-block - width: 1em - height: 1em - text-align: center - line-height: 1em - font-size: 20pt - &.tagged - background: rgba(0, 230, 0, 0.7) + padding: 0.5em + box-sizing: border-box + border: 0 &: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) + display: inline-block + width: 1em + height: 1em + text-align: center + line-height: 1em + font-size: 20pt + &.tagged + background: rgba(0, 230, 0, 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 background-position: 50% 30% @@ -121,14 +123,14 @@ font-size: 0.95em color: $inactive-link-color .bulk-edit-tags - &:not(.active) + &:not(.opened) [type=text], - .start-tagging, - .stop-tagging + .start, + .close display: none .hint display: none - &.active + &.opened .open display: none input[name=tag] diff --git a/client/html/posts_header.tpl b/client/html/posts_header.tpl index f90de34..b85b784 100644 --- a/client/html/posts_header.tpl +++ b/client/html/posts_header.tpl @@ -1,5 +1,5 @@
<% - %>
<% + %><% %><%= ctx.makeTextInput({text: 'Search query', id: 'search-text', name: 'search-text', value: ctx.parameters.query}) %><% %><% %><% @@ -9,16 +9,15 @@ %>'/><% %><% %>'>Syntax help<% - %><% if (ctx.canBulkEditTags) { %><% - %><% - %><% - %>Tagging with:<% - %>Mass tag<% - %><% - %><%= ctx.makeTextInput({name: 'tag', value: ctx.parameters.tag}) %><% - %><% - %>Stop tagging<% - %><% - %><% } %><% %>
<% + %><% if (ctx.canBulkEditTags) { %><% + %>
<% + %>Tagging with:<% + %>Mass 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 1f0e664..a6c6cc5 100644 --- a/client/html/posts_page.tpl +++ b/client/html/posts_page.tpl @@ -33,10 +33,12 @@ <% } %> - <% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %> - - - <% } %> + + <% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %> + + + <% } %> + <% } %> <%= ctx.makeFlexboxAlign() %> diff --git a/client/js/views/posts_header_view.js b/client/js/views/posts_header_view.js index 0392ffd..0bc1dce 100644 --- a/client/js/views/posts_header_view.js +++ b/client/js/views/posts_header_view.js @@ -11,6 +11,74 @@ const TagAutoCompleteControl = 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 { constructor(ctx) { super(); @@ -34,26 +102,20 @@ class PostsHeaderView extends events.EventTarget { this._formNode.addEventListener( 'submit', e => this._evtFormSubmit(e)); - if (this._bulkEditTagsInputNode) { - this._bulkEditTagsAutoCompleteControl = new TagAutoCompleteControl( - this._bulkEditTagsInputNode, {addSpace: false}); - if (this._openBulkEditTagsLinkNode) { - this._openBulkEditTagsLinkNode.addEventListener( - 'click', e => this._evtBulkEditTagsClick(e)); - } - this._stopBulkEditTagsLinkNode.addEventListener( - 'click', e => this._evtStopTaggingClick(e)); - this._toggleBulkEditTagsVisibility(!!ctx.parameters.tag); + if (this._bulkEditTagsNode) { + this._bulkTagEditor = new BulkTagEditor(this._bulkEditTagsNode); + this._bulkTagEditor.toggleOpen(!!ctx.parameters.tag); + this._bulkTagEditor.addEventListener('submit', e => { + this._navigate(); + }); + this._bulkTagEditor.addEventListener('close', e => { + this._navigate(); + }); } } - _toggleBulkEditTagsVisibility(state) { - this._formNode.querySelector('.bulk-edit-tags') - .classList.toggle('active', state); - } - get _formNode() { - return this._hostNode.querySelector('form'); + return this._hostNode.querySelector('form.search'); } get _safetyButtonNodes() { @@ -64,34 +126,8 @@ class PostsHeaderView extends events.EventTarget { return this._hostNode.querySelector('form [name=search-text]'); } - get _bulkEditTagsInputNode() { - return this._hostNode.querySelector('form .bulk-edit-tags [name=tag]'); - } - - 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, - }}})); + get _bulkEditTagsNode() { + return this._hostNode.querySelector('.bulk-edit-tags'); } _evtSafetyButtonClick(e, url) { @@ -114,16 +150,17 @@ class PostsHeaderView extends events.EventTarget { _evtFormSubmit(e) { e.preventDefault(); + this._navigate(); + } + + _navigate() { this._queryAutoCompleteControl.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._bulkEditTagsInputNode) { - parameters.tag = this._bulkEditTagsInputNode.value; - this._bulkEditTagsInputNode.blur(); + if (this._bulkTagEditor && this._bulkTagEditor.opened) { + parameters.tag = this._bulkTagEditor.value; + this._bulkTagEditor.blur(); } else { parameters.tag = null; } diff --git a/client/js/views/posts_page_view.js b/client/js/views/posts_page_view.js index b5ad5df..c2dbe90 100644 --- a/client/js/views/posts_page_view.js +++ b/client/js/views/posts_page_view.js @@ -27,7 +27,7 @@ class PostsPageView extends events.EventTarget { 'click', e => this._evtBulkEditTagsClick(e, post)); } - this._syncBulkEditTagsHighlights(); + this._syncTagFlippersHighlights(); } get _tagFlipperNodes() { @@ -37,19 +37,7 @@ class PostsPageView extends events.EventTarget { _evtPostChange(e) { const linkNode = this._postIdToLinkNode[e.detail.post.id]; linkNode.removeAttribute('data-disabled'); - this._syncBulkEditTagsHighlights(); - } - - _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); - } + this._syncTagFlippersHighlights(); } _evtBulkEditTagsClick(e, post) { @@ -64,6 +52,18 @@ class PostsPageView extends events.EventTarget { linkNode.classList.contains('tagged') ? 'untag' : 'tag', {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;