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
					
				@ -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]
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
<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}) %><%
 | 
			
		||||
        %><wbr/><%
 | 
			
		||||
        %><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' %>'/><%
 | 
			
		||||
        %><wbr/><%
 | 
			
		||||
        %><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><%
 | 
			
		||||
    %><% 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>
 | 
			
		||||
 | 
			
		||||
@ -33,10 +33,12 @@
 | 
			
		||||
                            </span>
 | 
			
		||||
                        <% } %>
 | 
			
		||||
                    </a>
 | 
			
		||||
                    <% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %>
 | 
			
		||||
                        <a href data-post-id='<%= post.id %>' class='tag-flipper'>
 | 
			
		||||
                        </a>
 | 
			
		||||
                    <% } %>
 | 
			
		||||
                    <span class='edit-overlay'>
 | 
			
		||||
                        <% if (ctx.canBulkEditTags && ctx.parameters && ctx.parameters.tag) { %>
 | 
			
		||||
                            <a href data-post-id='<%= post.id %>' class='tag-flipper'>
 | 
			
		||||
                            </a>
 | 
			
		||||
                        <% } %>
 | 
			
		||||
                    </span>
 | 
			
		||||
                </li>
 | 
			
		||||
            <% } %>
 | 
			
		||||
            <%= ctx.makeFlexboxAlign() %>
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user