diff options
| author | Shyam Sunder <sgsunder1@gmail.com> | 2020-06-05 18:03:37 -0400 |
|---|---|---|
| committer | Shyam Sunder <sgsunder1@gmail.com> | 2020-06-06 08:58:23 -0400 |
| commit | 57193b57157b7a42896c887a2d5930493ac7b290 (patch) | |
| tree | 9555453a944caae64d8a00e4b073482d9e7fb244 /client/js/controls | |
| parent | c06aaa63af977e64ef08bfba7829214f0f0ed38e (diff) | |
client+server: implement code autoformatting using prettier and black
Diffstat (limited to 'client/js/controls')
| -rw-r--r-- | client/js/controls/auto_complete_control.js | 175 | ||||
| -rw-r--r-- | client/js/controls/comment_control.js | 191 | ||||
| -rw-r--r-- | client/js/controls/comment_list_control.js | 29 | ||||
| -rw-r--r-- | client/js/controls/expander_control.js | 47 | ||||
| -rw-r--r-- | client/js/controls/file_dropper_control.js | 94 | ||||
| -rw-r--r-- | client/js/controls/pool_auto_complete_control.js | 67 | ||||
| -rw-r--r-- | client/js/controls/pool_input_control.js | 128 | ||||
| -rw-r--r-- | client/js/controls/post_content_control.js | 48 | ||||
| -rw-r--r-- | client/js/controls/post_edit_sidebar_control.js | 457 | ||||
| -rw-r--r-- | client/js/controls/post_notes_overlay_control.js | 372 | ||||
| -rw-r--r-- | client/js/controls/post_readonly_sidebar_control.js | 181 | ||||
| -rw-r--r-- | client/js/controls/tag_auto_complete_control.js | 82 | ||||
| -rw-r--r-- | client/js/controls/tag_input_control.js | 318 |
13 files changed, 1230 insertions, 959 deletions
diff --git a/client/js/controls/auto_complete_control.js b/client/js/controls/auto_complete_control.js index a802209..6e9bf3d 100644 --- a/client/js/controls/auto_complete_control.js +++ b/client/js/controls/auto_complete_control.js @@ -1,6 +1,6 @@ -'use strict'; +"use strict"; -const views = require('../util/views.js'); +const views = require("../util/views.js"); const KEY_TAB = 9; const KEY_RETURN = 13; @@ -10,14 +10,14 @@ const KEY_UP = 38; const KEY_DOWN = 40; function _getSelectionStart(input) { - if ('selectionStart' in input) { + if ("selectionStart" in input) { return input.selectionStart; } if (document.selection) { input.focus(); const sel = document.selection.createRange(); const selLen = document.selection.createRange().text.length; - sel.moveStart('character', -input.value.length); + sel.moveStart("character", -input.value.length); return sel.text.length - selLen; } return 0; @@ -27,18 +27,22 @@ class AutoCompleteControl { constructor(sourceInputNode, options) { this._sourceInputNode = sourceInputNode; this._options = {}; - Object.assign(this._options, { - verticalShift: 2, - maxResults: 15, - getTextToFind: () => { - const value = sourceInputNode.value; - const start = _getSelectionStart(sourceInputNode); - return value.substring(0, start).replace(/.*\s+/, ''); + Object.assign( + this._options, + { + verticalShift: 2, + maxResults: 15, + getTextToFind: () => { + const value = sourceInputNode.value; + const start = _getSelectionStart(sourceInputNode); + return value.substring(0, start).replace(/.*\s+/, ""); + }, + confirm: null, + delete: null, + getMatches: null, }, - confirm: null, - delete: null, - getMatches: null, - }, options); + options + ); this._showTimeout = null; this._results = []; @@ -49,22 +53,22 @@ class AutoCompleteControl { hide() { window.clearTimeout(this._showTimeout); - this._suggestionDiv.style.display = 'none'; + this._suggestionDiv.style.display = "none"; this._isVisible = false; } replaceSelectedText(result, addSpace) { const start = _getSelectionStart(this._sourceInputNode); - let prefix = ''; + let prefix = ""; let suffix = this._sourceInputNode.value.substring(start); let middle = this._sourceInputNode.value.substring(0, start); - const index = middle.lastIndexOf(' '); + const index = middle.lastIndexOf(" "); if (index !== -1) { prefix = this._sourceInputNode.value.substring(0, index + 1); middle = this._sourceInputNode.value.substring(index + 1); } - this._sourceInputNode.value = ( - prefix + result.toString() + ' ' + suffix.trimLeft()); + this._sourceInputNode.value = + prefix + result.toString() + " " + suffix.trimLeft(); if (!addSpace) { this._sourceInputNode.value = this._sourceInputNode.value.trim(); } @@ -86,7 +90,7 @@ class AutoCompleteControl { } _show() { - this._suggestionDiv.style.display = 'block'; + this._suggestionDiv.style.display = "block"; this._isVisible = true; } @@ -101,29 +105,32 @@ class AutoCompleteControl { _install() { if (!this._sourceInputNode) { - throw new Error('Input element was not found'); + throw new Error("Input element was not found"); } - if (this._sourceInputNode.getAttribute('data-autocomplete')) { + if (this._sourceInputNode.getAttribute("data-autocomplete")) { throw new Error( - 'Autocompletion was already added for this element'); + "Autocompletion was already added for this element" + ); } - this._sourceInputNode.setAttribute('data-autocomplete', true); - this._sourceInputNode.setAttribute('autocomplete', 'off'); + this._sourceInputNode.setAttribute("data-autocomplete", true); + this._sourceInputNode.setAttribute("autocomplete", "off"); - this._sourceInputNode.addEventListener( - 'keydown', e => this._evtKeyDown(e)); - this._sourceInputNode.addEventListener( - 'blur', e => this._evtBlur(e)); + this._sourceInputNode.addEventListener("keydown", (e) => + this._evtKeyDown(e) + ); + this._sourceInputNode.addEventListener("blur", (e) => + this._evtBlur(e) + ); this._suggestionDiv = views.htmlToDom( - '<div class="autocomplete"><ul></ul></div>'); - this._suggestionList = this._suggestionDiv.querySelector('ul'); + '<div class="autocomplete"><ul></ul></div>' + ); + this._suggestionList = this._suggestionDiv.querySelector("ul"); document.body.appendChild(this._suggestionDiv); - views.monitorNodeRemoval( - this._sourceInputNode, () => { - this._uninstall(); - }); + views.monitorNodeRemoval(this._sourceInputNode, () => { + this._uninstall(); + }); } _uninstall() { @@ -174,10 +181,9 @@ class AutoCompleteControl { func(); } else { window.clearTimeout(this._showTimeout); - this._showTimeout = window.setTimeout( - () => { - this._showOrHide(); - }, 250); + this._showTimeout = window.setTimeout(() => { + this._showOrHide(); + }, 250); } } @@ -196,9 +202,11 @@ class AutoCompleteControl { } _selectPrevious() { - this._select(this._activeResult === -1 ? - this._results.length - 1 : - this._activeResult - 1); + this._select( + this._activeResult === -1 + ? this._results.length - 1 + : this._activeResult - 1 + ); } _selectNext() { @@ -206,15 +214,18 @@ class AutoCompleteControl { } _select(newActiveResult) { - this._activeResult = - newActiveResult.between(0, this._results.length - 1, true) ? - newActiveResult : - -1; + this._activeResult = newActiveResult.between( + 0, + this._results.length - 1, + true + ) + ? newActiveResult + : -1; this._refreshActiveResult(); } _updateResults(textToFind) { - this._options.getMatches(textToFind).then(matches => { + this._options.getMatches(textToFind).then((matches) => { const oldResults = this._results.slice(); this._results = matches.slice(0, this._options.maxResults); const oldResultsHash = JSON.stringify(oldResults); @@ -237,34 +248,30 @@ class AutoCompleteControl { } for (let [resultIndex, resultItem] of this._results.entries()) { let resultIndexWorkaround = resultIndex; - const listItem = document.createElement('li'); - const link = document.createElement('a'); + const listItem = document.createElement("li"); + const link = document.createElement("a"); link.innerHTML = resultItem.caption; - link.setAttribute('href', ''); - link.setAttribute('data-key', resultItem.value); - link.addEventListener( - 'mouseenter', - e => { - e.preventDefault(); - this._activeResult = resultIndexWorkaround; - this._refreshActiveResult(); - }); - link.addEventListener( - 'mousedown', - e => { - e.preventDefault(); - this._activeResult = resultIndexWorkaround; - this._confirm(this._getActiveSuggestion()); - this.hide(); - }); + link.setAttribute("href", ""); + link.setAttribute("data-key", resultItem.value); + link.addEventListener("mouseenter", (e) => { + e.preventDefault(); + this._activeResult = resultIndexWorkaround; + this._refreshActiveResult(); + }); + link.addEventListener("mousedown", (e) => { + e.preventDefault(); + this._activeResult = resultIndexWorkaround; + this._confirm(this._getActiveSuggestion()); + this.hide(); + }); listItem.appendChild(link); this._suggestionList.appendChild(listItem); } this._refreshActiveResult(); // display the suggestions offscreen to get the height - this._suggestionDiv.style.left = '-9999px'; - this._suggestionDiv.style.top = '-9999px'; + this._suggestionDiv.style.left = "-9999px"; + this._suggestionDiv.style.top = "-9999px"; this._show(); const verticalShift = this._options.verticalShift; const inputRect = this._sourceInputNode.getBoundingClientRect(); @@ -275,17 +282,23 @@ class AutoCompleteControl { // choose where to view the suggestions: if there's more space above // the input - draw the suggestions above it, otherwise below const direction = - inputRect.top + (inputRect.height / 2) < viewPortHeight / 2 ? 1 : -1; + inputRect.top + inputRect.height / 2 < viewPortHeight / 2 ? 1 : -1; let x = inputRect.left - bodyRect.left; - let y = direction === 1 ? - inputRect.bottom - bodyRect.top - verticalShift : - inputRect.top - bodyRect.top - listRect.height + verticalShift; + let y = + direction === 1 + ? inputRect.bottom - bodyRect.top - verticalShift + : inputRect.top - + bodyRect.top - + listRect.height + + verticalShift; // remove offscreen items until whole suggestion list can fit on the // screen - while ((y < 0 || y + listRect.height > viewPortHeight) && - this._suggestionList.childNodes.length) { + while ( + (y < 0 || y + listRect.height > viewPortHeight) && + this._suggestionList.childNodes.length + ) { this._suggestionList.removeChild(this._suggestionList.lastChild); const prevHeight = listRect.height; listRect = this._suggestionDiv.getBoundingClientRect(); @@ -295,19 +308,19 @@ class AutoCompleteControl { } } - this._suggestionDiv.style.left = x + 'px'; - this._suggestionDiv.style.top = y + 'px'; + this._suggestionDiv.style.left = x + "px"; + this._suggestionDiv.style.top = y + "px"; } _refreshActiveResult() { - let activeItem = this._suggestionList.querySelector('li.active'); + let activeItem = this._suggestionList.querySelector("li.active"); if (activeItem) { - activeItem.classList.remove('active'); + activeItem.classList.remove("active"); } if (this._activeResult >= 0) { - const allItems = this._suggestionList.querySelectorAll('li'); + const allItems = this._suggestionList.querySelectorAll("li"); activeItem = allItems[this._activeResult]; - activeItem.classList.add('active'); + activeItem.classList.add("active"); } } } diff --git a/client/js/controls/comment_control.js b/client/js/controls/comment_control.js index c105bc0..cdcfd53 100644 --- a/client/js/controls/comment_control.js +++ b/client/js/controls/comment_control.js @@ -1,12 +1,12 @@ -'use strict'; +"use strict"; -const api = require('../api.js'); -const misc = require('../util/misc.js'); -const events = require('../events.js'); -const views = require('../util/views.js'); +const api = require("../api.js"); +const misc = require("../util/misc.js"); +const events = require("../events.js"); +const views = require("../util/views.js"); -const template = views.getTemplate('comment'); -const scoreTemplate = views.getTemplate('score'); +const template = views.getTemplate("comment"); +const scoreTemplate = views.getTemplate("score"); class CommentControl extends events.EventTarget { constructor(hostNode, comment, onlyEditing) { @@ -16,104 +16,111 @@ class CommentControl extends events.EventTarget { this._onlyEditing = onlyEditing; if (comment) { - comment.addEventListener( - 'change', e => this._evtChange(e)); - comment.addEventListener( - 'changeScore', e => this._evtChangeScore(e)); + comment.addEventListener("change", (e) => this._evtChange(e)); + comment.addEventListener("changeScore", (e) => + this._evtChangeScore(e) + ); } const isLoggedIn = comment && api.isLoggedIn(comment.user); - const infix = isLoggedIn ? 'own' : 'any'; - views.replaceContent(this._hostNode, template({ - comment: comment, - user: comment ? comment.user : api.user, - canViewUsers: api.hasPrivilege('users:view'), - canEditComment: api.hasPrivilege(`comments:edit:${infix}`), - canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`), - onlyEditing: onlyEditing, - })); + const infix = isLoggedIn ? "own" : "any"; + views.replaceContent( + this._hostNode, + template({ + comment: comment, + user: comment ? comment.user : api.user, + canViewUsers: api.hasPrivilege("users:view"), + canEditComment: api.hasPrivilege(`comments:edit:${infix}`), + canDeleteComment: api.hasPrivilege(`comments:delete:${infix}`), + onlyEditing: onlyEditing, + }) + ); if (this._editButtonNodes) { for (let node of this._editButtonNodes) { - node.addEventListener('click', e => this._evtEditClick(e)); + node.addEventListener("click", (e) => this._evtEditClick(e)); } } if (this._deleteButtonNode) { - this._deleteButtonNode.addEventListener( - 'click', e => this._evtDeleteClick(e)); + this._deleteButtonNode.addEventListener("click", (e) => + this._evtDeleteClick(e) + ); } if (this._previewEditingButtonNode) { - this._previewEditingButtonNode.addEventListener( - 'click', e => this._evtPreviewEditingClick(e)); + this._previewEditingButtonNode.addEventListener("click", (e) => + this._evtPreviewEditingClick(e) + ); } if (this._saveChangesButtonNode) { - this._saveChangesButtonNode.addEventListener( - 'click', e => this._evtSaveChangesClick(e)); + this._saveChangesButtonNode.addEventListener("click", (e) => + this._evtSaveChangesClick(e) + ); } if (this._cancelEditingButtonNode) { - this._cancelEditingButtonNode.addEventListener( - 'click', e => this._evtCancelEditingClick(e)); + this._cancelEditingButtonNode.addEventListener("click", (e) => + this._evtCancelEditingClick(e) + ); } this._installScore(); if (onlyEditing) { - this._selectNav('edit'); - this._selectTab('edit'); + this._selectNav("edit"); + this._selectTab("edit"); } else { - this._selectNav('readonly'); - this._selectTab('preview'); + this._selectNav("readonly"); + this._selectTab("preview"); } } get _formNode() { - return this._hostNode.querySelector('form'); + return this._hostNode.querySelector("form"); } get _scoreContainerNode() { - return this._hostNode.querySelector('.score-container'); + return this._hostNode.querySelector(".score-container"); } get _editButtonNodes() { - return this._hostNode.querySelectorAll('li.edit>a, a.edit'); + return this._hostNode.querySelectorAll("li.edit>a, a.edit"); } get _previewEditingButtonNode() { - return this._hostNode.querySelector('li.preview>a'); + return this._hostNode.querySelector("li.preview>a"); } get _deleteButtonNode() { - return this._hostNode.querySelector('.delete'); + return this._hostNode.querySelector(".delete"); } get _upvoteButtonNode() { - return this._hostNode.querySelector('.upvote'); + return this._hostNode.querySelector(".upvote"); } get _downvoteButtonNode() { - return this._hostNode.querySelector('.downvote'); + return this._hostNode.querySelector(".downvote"); } get _saveChangesButtonNode() { - return this._hostNode.querySelector('.save-changes'); + return this._hostNode.querySelector(".save-changes"); } get _cancelEditingButtonNode() { - return this._hostNode.querySelector('.cancel-editing'); + return this._hostNode.querySelector(".cancel-editing"); } get _textareaNode() { - return this._hostNode.querySelector('.tab.edit textarea'); + return this._hostNode.querySelector(".tab.edit textarea"); } get _contentNode() { - return this._hostNode.querySelector('.tab.preview .comment-content'); + return this._hostNode.querySelector(".tab.preview .comment-content"); } get _heightKeeperNode() { - return this._hostNode.querySelector('.keep-height'); + return this._hostNode.querySelector(".keep-height"); } _installScore() { @@ -122,32 +129,35 @@ class CommentControl extends events.EventTarget { scoreTemplate({ score: this._comment ? this._comment.score : 0, ownScore: this._comment ? this._comment.ownScore : 0, - canScore: api.hasPrivilege('comments:score'), - })); + canScore: api.hasPrivilege("comments:score"), + }) + ); if (this._upvoteButtonNode) { - this._upvoteButtonNode.addEventListener( - 'click', e => this._evtScoreClick(e, 1)); + this._upvoteButtonNode.addEventListener("click", (e) => + this._evtScoreClick(e, 1) + ); } if (this._downvoteButtonNode) { - this._downvoteButtonNode.addEventListener( - 'click', e => this._evtScoreClick(e, -1)); + this._downvoteButtonNode.addEventListener("click", (e) => + this._evtScoreClick(e, -1) + ); } } enterEditMode() { - this._selectNav('edit'); - this._selectTab('edit'); + this._selectNav("edit"); + this._selectTab("edit"); } exitEditMode() { if (this._onlyEditing) { - this._selectNav('edit'); - this._selectTab('edit'); - this._setText(''); + this._selectNav("edit"); + this._selectTab("edit"); + this._setText(""); } else { - this._selectNav('readonly'); - this._selectTab('preview'); + this._selectNav("readonly"); + this._selectTab("preview"); this._setText(this._comment.text); } this._forgetHeight(); @@ -173,27 +183,31 @@ class CommentControl extends events.EventTarget { _evtScoreClick(e, score) { e.preventDefault(); - if (!api.hasPrivilege('comments:score')) { + if (!api.hasPrivilege("comments:score")) { return; } - this.dispatchEvent(new CustomEvent('score', { - detail: { - comment: this._comment, - score: this._comment.ownScore === score ? 0 : score, - }, - })); + this.dispatchEvent( + new CustomEvent("score", { + detail: { + comment: this._comment, + score: this._comment.ownScore === score ? 0 : score, + }, + }) + ); } _evtDeleteClick(e) { e.preventDefault(); - if (!window.confirm('Are you sure you want to delete this comment?')) { + if (!window.confirm("Are you sure you want to delete this comment?")) { return; } - this.dispatchEvent(new CustomEvent('delete', { - detail: { - comment: this._comment, - }, - })); + this.dispatchEvent( + new CustomEvent("delete", { + detail: { + comment: this._comment, + }, + }) + ); } _evtChange(e) { @@ -206,21 +220,24 @@ class CommentControl extends events.EventTarget { _evtPreviewEditingClick(e) { e.preventDefault(); - this._contentNode.innerHTML = - misc.formatMarkdown(this._textareaNode.value); - this._selectTab('edit'); - this._selectTab('preview'); + this._contentNode.innerHTML = misc.formatMarkdown( + this._textareaNode.value + ); + this._selectTab("edit"); + this._selectTab("preview"); } _evtSaveChangesClick(e) { e.preventDefault(); - this.dispatchEvent(new CustomEvent('submit', { - detail: { - target: this, - comment: this._comment, - text: this._textareaNode.value, - }, - })); + this.dispatchEvent( + new CustomEvent("submit", { + detail: { + target: this, + comment: this._comment, + text: this._textareaNode.value, + }, + }) + ); } _evtCancelEditingClick(e) { @@ -234,22 +251,22 @@ class CommentControl extends events.EventTarget { } _selectNav(modeName) { - for (let node of this._hostNode.querySelectorAll('nav')) { - node.classList.toggle('active', node.classList.contains(modeName)); + for (let node of this._hostNode.querySelectorAll("nav")) { + node.classList.toggle("active", node.classList.contains(modeName)); } } _selectTab(tabName) { this._ensureHeight(); - for (let node of this._hostNode.querySelectorAll('.tab, .tabs li')) { - node.classList.toggle('active', node.classList.contains(tabName)); + for (let node of this._hostNode.querySelectorAll(".tab, .tabs li")) { + node.classList.toggle("active", node.classList.contains(tabName)); } } _ensureHeight() { this._heightKeeperNode.style.minHeight = - this._heightKeeperNode.getBoundingClientRect().height + 'px'; + this._heightKeeperNode.getBoundingClientRect().height + "px"; } _forgetHeight() { diff --git a/client/js/controls/comment_list_control.js b/client/js/controls/comment_list_control.js index 4f57fd2..7717ee5 100644 --- a/client/js/controls/comment_list_control.js +++ b/client/js/controls/comment_list_control.js @@ -1,10 +1,10 @@ -'use strict'; +"use strict"; -const events = require('../events.js'); -const views = require('../util/views.js'); -const CommentControl = require('../controls/comment_control.js'); +const events = require("../events.js"); +const views = require("../util/views.js"); +const CommentControl = require("../controls/comment_control.js"); -const template = views.getTemplate('comment-list'); +const template = views.getTemplate("comment-list"); class CommentListControl extends events.EventTarget { constructor(hostNode, comments, reversed) { @@ -13,8 +13,8 @@ class CommentListControl extends events.EventTarget { this._comments = comments; this._commentIdToNode = {}; - comments.addEventListener('add', e => this._evtAdd(e)); - comments.addEventListener('remove', e => this._evtRemove(e)); + comments.addEventListener("add", (e) => this._evtAdd(e)); + comments.addEventListener("remove", (e) => this._evtRemove(e)); views.replaceContent(this._hostNode, template()); @@ -28,16 +28,19 @@ class CommentListControl extends events.EventTarget { } get _commentListNode() { - return this._hostNode.querySelector('ul'); + return this._hostNode.querySelector("ul"); } _installCommentNode(comment) { - const commentListItemNode = document.createElement('li'); + const commentListItemNode = document.createElement("li"); const commentControl = new CommentControl( - commentListItemNode, comment, false); - events.proxyEvent(commentControl, this, 'submit'); - events.proxyEvent(commentControl, this, 'score'); - events.proxyEvent(commentControl, this, 'delete'); + commentListItemNode, + comment, + false + ); + events.proxyEvent(commentControl, this, "submit"); + events.proxyEvent(commentControl, this, "score"); + events.proxyEvent(commentControl, this, "delete"); this._commentIdToNode[comment.id] = commentListItemNode; this._commentListNode.appendChild(commentListItemNode); } diff --git a/client/js/controls/expander_control.js b/client/js/controls/expander_control.js index 28a4ffb..11ad3ef 100644 --- a/client/js/controls/expander_control.js +++ b/client/js/controls/expander_control.js @@ -1,26 +1,28 @@ -'use strict'; +"use strict"; -const ICON_CLASS_OPENED = 'fa-chevron-down'; -const ICON_CLASS_CLOSED = 'fa-chevron-up'; +const ICON_CLASS_OPENED = "fa-chevron-down"; +const ICON_CLASS_CLOSED = "fa-chevron-up"; -const views = require('../util/views.js'); +const views = require("../util/views.js"); -const template = views.getTemplate('expander'); +const template = views.getTemplate("expander"); class ExpanderControl { constructor(name, title, nodes) { this._name = name; - nodes = Array.from(nodes).filter(n => n); + nodes = Array.from(nodes).filter((n) => n); if (!nodes.length) { return; } - const expanderNode = template({title: title}); - const toggleLinkNode = expanderNode.querySelector('a'); - const toggleIconNode = expanderNode.querySelector('i'); - const expanderContentNode = expanderNode.querySelector('div'); - toggleLinkNode.addEventListener('click', e => this._evtToggleClick(e)); + const expanderNode = template({ title: title }); + const toggleLinkNode = expanderNode.querySelector("a"); + const toggleIconNode = expanderNode.querySelector("i"); + const expanderContentNode = expanderNode.querySelector("div"); + toggleLinkNode.addEventListener("click", (e) => + this._evtToggleClick(e) + ); nodes[0].parentNode.insertBefore(expanderNode, nodes[0]); @@ -32,29 +34,30 @@ class ExpanderControl { this._toggleIconNode = toggleIconNode; expanderNode.classList.toggle( - 'collapsed', - this._allStates[this._name] === undefined ? - false : - !this._allStates[this._name]); + "collapsed", + this._allStates[this._name] === undefined + ? false + : !this._allStates[this._name] + ); this._syncIcon(); } // eslint-disable-next-line accessor-pairs set title(newTitle) { if (this._expanderNode) { - this._expanderNode - .querySelector('header span') - .textContent = newTitle; + this._expanderNode.querySelector( + "header span" + ).textContent = newTitle; } } get _isOpened() { - return !this._expanderNode.classList.contains('collapsed'); + return !this._expanderNode.classList.contains("collapsed"); } get _allStates() { try { - return JSON.parse(localStorage.getItem('expander')) || {}; + return JSON.parse(localStorage.getItem("expander")) || {}; } catch (e) { return {}; } @@ -63,12 +66,12 @@ class ExpanderControl { _save() { const newStates = Object.assign({}, this._allStates); newStates[this._name] = this._isOpened; - localStorage.setItem('expander', JSON.stringify(newStates)); + localStorage.setItem("expander", JSON.stringify(newStates)); } _evtToggleClick(e) { e.preventDefault(); - this._expanderNode.classList.toggle('collapsed'); + this._expanderNode.classList.toggle("collapsed"); this._save(); this._syncIcon(); } diff --git a/client/js/controls/file_dropper_control.js b/client/js/controls/file_dropper_control.js index a618720..2dfb492 100644 --- a/client/js/controls/file_dropper_control.js +++ b/client/js/controls/file_dropper_control.js @@ -1,9 +1,9 @@ -'use strict'; +"use strict"; -const events = require('../events.js'); -const views = require('../util/views.js'); +const events = require("../events.js"); +const views = require("../util/views.js"); -const template = views.getTemplate('file-dropper'); +const template = views.getTemplate("file-dropper"); const KEY_RETURN = 13; @@ -17,37 +17,42 @@ class FileDropperControl extends events.EventTarget { allowMultiple: options.allowMultiple, allowUrls: options.allowUrls, lock: options.lock, - id: 'file-' + Math.random().toString(36).substring(7), + id: "file-" + Math.random().toString(36).substring(7), urlPlaceholder: - options.urlPlaceholder || 'Alternatively, paste an URL here.', + options.urlPlaceholder || "Alternatively, paste an URL here.", }); - this._dropperNode = source.querySelector('.file-dropper'); - this._urlInputNode = source.querySelector('input[type=text]'); - this._urlConfirmButtonNode = source.querySelector('button'); - this._fileInputNode = source.querySelector('input[type=file]'); - this._fileInputNode.style.display = 'none'; + this._dropperNode = source.querySelector(".file-dropper"); + this._urlInputNode = source.querySelector("input[type=text]"); + this._urlConfirmButtonNode = source.querySelector("button"); + this._fileInputNode = source.querySelector("input[type=file]"); + this._fileInputNode.style.display = "none"; this._fileInputNode.multiple = options.allowMultiple || false; this._counter = 0; - this._dropperNode.addEventListener( - 'dragenter', e => this._evtDragEnter(e)); - this._dropperNode.addEventListener( - 'dragleave', e => this._evtDragLeave(e)); - this._dropperNode.addEventListener( - 'dragover', e => this._evtDragOver(e)); - this._dropperNode.addEventListener( - 'drop', e => this._evtDrop(e)); - this._fileInputNode.addEventListener( - 'change', e => this._evtFileChange(e)); + this._dropperNode.addEventListener("dragenter", (e) => + this._evtDragEnter(e) + ); + this._dropperNode.addEventListener("dragleave", (e) => + this._evtDragLeave(e) + ); + this._dropperNode.addEventListener("dragover", (e) => + this._evtDragOver(e) + ); + this._dropperNode.addEventListener("drop", (e) => this._evtDrop(e)); + this._fileInputNode.addEventListener("change", (e) => + this._evtFileChange(e) + ); if (this._urlInputNode) { - this._urlInputNode.addEventListener( - 'keydown', e => this._evtUrlInputKeyDown(e)); + this._urlInputNode.addEventListener("keydown", (e) => + this._evtUrlInputKeyDown(e) + ); } if (this._urlConfirmButtonNode) { - this._urlConfirmButtonNode.addEventListener( - 'click', e => this._evtUrlConfirmButtonClick(e)); + this._urlConfirmButtonNode.addEventListener("click", (e) => + this._evtUrlConfirmButtonClick(e) + ); } this._originalHtml = this._dropperNode.innerHTML; @@ -56,24 +61,27 @@ class FileDropperControl extends events.EventTarget { reset() { this._dropperNode.innerHTML = this._originalHtml; - this.dispatchEvent(new CustomEvent('reset')); + this.dispatchEvent(new CustomEvent("reset")); } _emitFiles(files) { files = Array.from(files); if (this._options.lock) { - this._dropperNode.innerText = - files.map(file => file.name).join(', '); + this._dropperNode.innerText = files + .map((file) => file.name) + .join(", "); } this.dispatchEvent( - new CustomEvent('fileadd', {detail: {files: files}})); + new CustomEvent("fileadd", { detail: { files: files } }) + ); } _emitUrls(urls) { - urls = Array.from(urls).map(url => url.trim()); + urls = Array.from(urls).map((url) => url.trim()); if (this._options.lock) { - this._dropperNode.innerText = - urls.map(url => url.split(/\//).reverse()[0]).join(', '); + this._dropperNode.innerText = urls + .map((url) => url.split(/\//).reverse()[0]) + .join(", "); } for (let url of urls) { if (!url) { @@ -84,18 +92,20 @@ class FileDropperControl extends events.EventTarget { return; } } - this.dispatchEvent(new CustomEvent('urladd', {detail: {urls: urls}})); + this.dispatchEvent( + new CustomEvent("urladd", { detail: { urls: urls } }) + ); } _evtDragEnter(e) { - this._dropperNode.classList.add('active'); + this._dropperNode.classList.add("active"); this._counter++; } _evtDragLeave(e) { this._counter--; if (this._counter === 0) { - this._dropperNode.classList.remove('active'); + this._dropperNode.classList.remove("active"); } } @@ -109,12 +119,12 @@ class FileDropperControl extends events.EventTarget { _evtDrop(e) { e.preventDefault(); - this._dropperNode.classList.remove('active'); + this._dropperNode.classList.remove("active"); if (!e.dataTransfer.files.length) { - window.alert('Only files are supported.'); + window.alert("Only files are supported."); } if (!this._options.allowMultiple && e.dataTransfer.files.length > 1) { - window.alert('Cannot select multiple files.'); + window.alert("Cannot select multiple files."); } this._emitFiles(e.dataTransfer.files); } @@ -124,16 +134,16 @@ class FileDropperControl extends events.EventTarget { return; } e.preventDefault(); - this._dropperNode.classList.remove('active'); + this._dropperNode.classList.remove("active"); this._emitUrls(this._urlInputNode.value.split(/[\r\n]/)); - this._urlInputNode.value = ''; + this._urlInputNode.value = ""; } _evtUrlConfirmButtonClick(e) { e.preventDefault(); - this._dropperNode.classList.remove('active'); + this._dropperNode.classList.remove("active"); this._emitUrls(this._urlInputNode.value.split(/[\r\n]/)); - this._urlInputNode.value = ''; + this._urlInputNode.value = ""; } } diff --git a/client/js/controls/pool_auto_complete_control.js b/client/js/controls/pool_auto_complete_control.js index f720833..97794a8 100644 --- a/client/js/controls/pool_auto_complete_control.js +++ b/client/js/controls/pool_auto_complete_control.js @@ -1,43 +1,54 @@ -'use strict'; +"use strict"; -const misc = require('../util/misc.js'); -const PoolList = require('../models/pool_list.js'); -const AutoCompleteControl = require('./auto_complete_control.js'); +const misc = require("../util/misc.js"); +const PoolList = require("../models/pool_list.js"); +const AutoCompleteControl = require("./auto_complete_control.js"); function _poolListToMatches(pools, options) { - return [...pools].sort((pool1, pool2) => { - return pool2.postCount - pool1.postCount; - }).map(pool => { - let cssName = misc.makeCssName(pool.category, 'pool'); - const caption = ( - '<span class="' + cssName + '">' - + misc.escapeHtml(pool.names[0] + ' (' + pool.postCount + ')') - + '</span>'); - return { - caption: caption, - value: pool, - }; - }); + return [...pools] + .sort((pool1, pool2) => { + return pool2.postCount - pool1.postCount; + }) + .map((pool) => { + let cssName = misc.makeCssName(pool.category, "pool"); + const caption = + '<span class="' + + cssName + + '">' + + misc.escapeHtml(pool.names[0] + " (" + pool.postCount + ")") + + "</span>"; + return { + caption: caption, + value: pool, + }; + }); } class PoolAutoCompleteControl extends AutoCompleteControl { constructor(input, options) { const minLengthForPartialSearch = 3; - options.getMatches = text => { + options.getMatches = (text) => { const term = misc.escapeSearchTerm(text); - const query = ( - text.length < minLengthForPartialSearch - ? term + '*' - : '*' + term + '*') + ' sort:post-count'; + const query = + (text.length < minLengthForPartialSearch + ? term + "*" + : "*" + term + "*") + " sort:post-count"; return new Promise((resolve, reject) => { - PoolList.search( - query, 0, this._options.maxResults, ['id', 'names', 'category', 'postCount', 'version']) - .then( - response => resolve( - _poolListToMatches(response.results, this._options)), - reject); + PoolList.search(query, 0, this._options.maxResults, [ + "id", + "names", + "category", + "postCount", + "version", + ]).then( + (response) => + resolve( + _poolListToMatches(response.results, this._options) + ), + reject + ); }); }; diff --git a/client/js/controls/pool_input_control.js b/client/js/controls/pool_input_control.js index af1c744..c8995da 100644 --- a/client/js/controls/pool_input_control.js +++ b/client/js/controls/pool_input_control.js @@ -1,24 +1,24 @@ -'use strict'; +"use strict"; -const api = require('../api.js'); -const pools = require('../pools.js'); -const misc = require('../util/misc.js'); -const uri = require('../util/uri.js'); -const Pool = require('../models/pool.js'); -const settings = require('../models/settings.js'); -const events = require('../events.js'); -const views = require('../util/views.js'); -const PoolAutoCompleteControl = require('./pool_auto_complete_control.js'); +const api = require("../api.js"); +const pools = require("../pools.js"); +const misc = require("../util/misc.js"); +const uri = require("../util/uri.js"); +const Pool = require("../models/pool.js"); +const settings = require("../models/settings.js"); +const events = require("../events.js"); +const views = require("../util/views.js"); +const PoolAutoCompleteControl = require("./pool_auto_complete_control.js"); const KEY_SPACE = 32; const KEY_RETURN = 13; -const SOURCE_INIT = 'init'; -const SOURCE_IMPLICATION = 'implication'; -const SOURCE_USER_INPUT = 'user-input'; -const SOURCE_CLIPBOARD = 'clipboard'; +const SOURCE_INIT = "init"; +const SOURCE_IMPLICATION = "implication"; +const SOURCE_USER_INPUT = "user-input"; +const SOURCE_CLIPBOARD = "clipboard"; -const template = views.getTemplate('pool-input'); +const template = views.getTemplate("pool-input"); function _fadeOutListItemNodeStatus(listItemNode) { if (listItemNode.classList.length) { @@ -27,8 +27,7 @@ function _fadeOutListItemNodeStatus(listItemNode) { } listItemNode.fadeTimeout = window.setTimeout(() => { while (listItemNode.classList.length) { - listItemNode.classList.remove( - listItemNode.classList.item(0)); + listItemNode.classList.remove(listItemNode.classList.item(0)); } listItemNode.fadeTimeout = null; }, 2500); @@ -45,29 +44,33 @@ class PoolInputControl extends events.EventTarget { // dom const editAreaNode = template(); this._editAreaNode = editAreaNode; - this._poolInputNode = editAreaNode.querySelector('input'); - this._poolListNode = editAreaNode.querySelector('ul.compact-pools'); + this._poolInputNode = editAreaNode.querySelector("input"); + this._poolListNode = editAreaNode.querySelector("ul.compact-pools"); this._autoCompleteControl = new PoolAutoCompleteControl( - this._poolInputNode, { + this._poolInputNode, + { getTextToFind: () => { return this._poolInputNode.value; }, - confirm: pool => { - this._poolInputNode.value = ''; + confirm: (pool) => { + this._poolInputNode.value = ""; this.addPool(pool, SOURCE_USER_INPUT); }, - delete: pool => { - this._poolInputNode.value = ''; + delete: (pool) => { + this._poolInputNode.value = ""; this.deletePool(pool); }, - verticalShift: -2 - }); + verticalShift: -2, + } + ); // show - this._hostNode.style.display = 'none'; + this._hostNode.style.display = "none"; this._hostNode.parentNode.insertBefore( - this._editAreaNode, hostNode.nextSibling); + this._editAreaNode, + hostNode.nextSibling + ); // add existing pools for (let pool of [...this.pools]) { @@ -81,19 +84,21 @@ class PoolInputControl extends events.EventTarget { return Promise.resolve(); } - this.pools.add(pool, false) + this.pools.add(pool, false); const listItemNode = this._createListItemNode(pool); if (!pool.category) { - listItemNode.classList.add('new'); + listItemNode.classList.add("new"); } this._poolListNode.prependChild(listItemNode); _fadeOutListItemNodeStatus(listItemNode); - this.dispatchEvent(new CustomEvent('add', { - detail: {pool: pool, source: source}, - })); - this.dispatchEvent(new CustomEvent('change')); + this.dispatchEvent( + new CustomEvent("add", { + detail: { pool: pool, source: source }, + }) + ); + this.dispatchEvent(new CustomEvent("change")); return Promise.resolve(); } @@ -107,52 +112,57 @@ class PoolInputControl extends events.EventTarget { this._deleteListItemNode(pool); - this.dispatchEvent(new CustomEvent('remove', { - detail: {pool: pool}, - })); - this.dispatchEvent(new CustomEvent('change')); + this.dispatchEvent( + new CustomEvent("remove", { + detail: { pool: pool }, + }) + ); + this.dispatchEvent(new CustomEvent("change")); } _createListItemNode(pool) { - const className = pool.category ? - misc.makeCssName(pool.category, 'pool') : - null; + const className = pool.category + ? misc.makeCssName(pool.category, "pool") + : null; - const poolLinkNode = document.createElement('a'); + const poolLinkNode = document.createElement("a"); if (className) { poolLinkNode.classList.add(className); } poolLinkNode.setAttribute( - 'href', uri.formatClientLink('pool', pool.names[0])); + "href", + uri.formatClientLink("pool", pool.names[0]) + ); - const poolIconNode = document.createElement('i'); - poolIconNode.classList.add('fa'); - poolIconNode.classList.add('fa-pool'); + const poolIconNode = document.createElement("i"); + poolIconNode.classList.add("fa"); + poolIconNode.classList.add("fa-pool"); poolLinkNode.appendChild(poolIconNode); - const searchLinkNode = document.createElement('a'); + const searchLinkNode = document.createElement("a"); if (className) { searchLinkNode.classList.add(className); } searchLinkNode.setAttribute( - 'href', uri.formatClientLink( - 'posts', {query: "pool:" + pool.id})); - searchLinkNode.textContent = pool.names[0] + ' '; + "href", + uri.formatClientLink("posts", { query: "pool:" + pool.id }) + ); + searchLinkNode.textContent = pool.names[0] + " "; - const usagesNode = document.createElement('span'); - usagesNode.classList.add('pool-usages'); - usagesNode.setAttribute('data-pseudo-content', pool.postCount); + const usagesNode = document.createElement("span"); + usagesNode.classList.add("pool-usages"); + usagesNode.setAttribute("data-pseudo-content", pool.postCount); - const removalLinkNode = document.createElement('a'); - removalLinkNode.classList.add('remove-pool'); - removalLinkNode.setAttribute('href', ''); - removalLinkNode.setAttribute('data-pseudo-content', '×'); - removalLinkNode.addEventListener('click', e => { + const removalLinkNode = document.createElement("a"); + removalLinkNode.classList.add("remove-pool"); + removalLinkNode.setAttribute("href", ""); + removalLinkNode.setAttribute("data-pseudo-content", "×"); + removalLinkNode.addEventListener("click", (e) => { e.preventDefault(); this.deletePool(pool); }); - const listItemNode = document.createElement('li'); + const listItemNode = document.createElement("li"); listItemNode.appendChild(removalLinkNode); listItemNode.appendChild(poolLinkNode); listItemNode.appendChild(searchLinkNode); diff --git a/client/js/controls/post_content_control.js b/client/js/controls/post_content_control.js index d1848b3..55daca7 100644 --- a/client/js/controls/post_content_control.js +++ b/client/js/controls/post_content_control.js @@ -1,36 +1,38 @@ -'use strict'; +"use strict"; -const settings = require('../models/settings.js'); -const views = require('../util/views.js'); -const optimizedResize = require('../util/optimized_resize.js'); +const settings = require("../models/settings.js"); +const views = require("../util/views.js"); +const optimizedResize = require("../util/optimized_resize.js"); class PostContentControl { constructor(hostNode, post, viewportSizeCalculator, fitFunctionOverride) { this._post = post; this._viewportSizeCalculator = viewportSizeCalculator; this._hostNode = hostNode; - this._template = views.getTemplate('post-content'); + this._template = views.getTemplate("post-content"); let fitMode = settings.get().fitMode; - if (typeof fitFunctionOverride !== 'undefined') { + if (typeof fitFunctionOverride !== "undefined") { fitMode = fitFunctionOverride; } - this._currentFitFunction = { - 'fit-both': this.fitBoth, - 'fit-original': this.fitOriginal, - 'fit-width': this.fitWidth, - 'fit-height': this.fitHeight, - }[fitMode] || this.fitBoth; + this._currentFitFunction = + { + "fit-both": this.fitBoth, + "fit-original": this.fitOriginal, + "fit-width": this.fitWidth, + "fit-height": this.fitHeight, + }[fitMode] || this.fitBoth; this._install(); - this._post.addEventListener( - 'changeContent', e => this._evtPostContentChange(e)); + this._post.addEventListener("changeContent", (e) => + this._evtPostContentChange(e) + ); } disableOverlay() { - this._hostNode.querySelector('.post-overlay').style.display = 'none'; + this._hostNode.querySelector(".post-overlay").style.display = "none"; } fitWidth() { @@ -92,10 +94,11 @@ class PostContentControl { _resize(width, height) { const resizeListenerNodes = [this._postContentNode].concat( - ...this._postContentNode.querySelectorAll('.resize-listener')); + ...this._postContentNode.querySelectorAll(".resize-listener") + ); for (let node of resizeListenerNodes) { - node.style.width = width + 'px'; - node.style.height = height + 'px'; + node.style.width = width + "px"; + node.style.height = height + "px"; } } @@ -106,10 +109,9 @@ class PostContentControl { _install() { this._reinstall(); optimizedResize.add(() => this._refreshSize()); - views.monitorNodeRemoval( - this._hostNode, () => { - this._uninstall(); - }); + views.monitorNodeRemoval(this._hostNode, () => { + this._uninstall(); + }); } _reinstall() { @@ -118,7 +120,7 @@ class PostContentControl { autoplay: settings.get().autoplayVideos, }); if (settings.get().transparencyGrid) { - newNode.classList.add('transparency-grid'); + newNode.classList.add("transparency-grid"); } if (this._postContentNode) { this._hostNode.replaceChild(newNode, this._postContentNode); diff --git a/client/js/controls/post_edit_sidebar_control.js b/client/js/controls/post_edit_sidebar_control.js index 495dcdb..c6b7c22 100644 --- a/client/js/controls/post_edit_sidebar_control.js +++ b/client/js/controls/post_edit_sidebar_control.js @@ -1,17 +1,17 @@ -'use strict'; +"use strict"; -const api = require('../api.js'); -const events = require('../events.js'); -const misc = require('../util/misc.js'); -const views = require('../util/views.js'); -const Note = require('../models/note.js'); -const Point = require('../models/point.js'); -const TagInputControl = require('./tag_input_control.js'); -const PoolInputControl = require('./pool_input_control.js'); -const ExpanderControl = require('../controls/expander_control.js'); -const FileDropperControl = require('../controls/file_dropper_control.js'); +const api = require("../api.js"); +const events = require("../events.js"); +const misc = require("../util/misc.js"); +const views = require("../util/views.js"); +const Note = require("../models/note.js"); +const Point = require("../models/point.js"); +const TagInputControl = require("./tag_input_control.js"); +const PoolInputControl = require("./pool_input_control.js"); +const ExpanderControl = require("../controls/expander_control.js"); +const FileDropperControl = require("../controls/file_dropper_control.js"); -const template = views.getTemplate('post-edit-sidebar'); +const template = views.getTemplate("post-edit-sidebar"); class PostEditSidebarControl extends events.EventTarget { constructor(hostNode, post, postContentControl, postNotesOverlayControl) { @@ -24,182 +24,222 @@ class PostEditSidebarControl extends events.EventTarget { this._postNotesOverlayControl.switchToPassiveEdit(); - views.replaceContent(this._hostNode, template({ - post: this._post, - enableSafety: api.safetyEnabled(), - hasClipboard: document.queryCommandSupported('copy'), - canEditPostSafety: api.hasPrivilege('posts:edit:safety'), - canEditPostSource: api.hasPrivilege('posts:edit:source'), - canEditPostTags: api.hasPrivilege('posts:edit:tags'), - canEditPostRelations: api.hasPrivilege('posts:edit:relations'), - canEditPostNotes: api.hasPrivilege('posts:edit:notes') && - post.type !== 'video' && - post.type !== 'flash', - canEditPostFlags: api.hasPrivilege('posts:edit:flags'), - canEditPostContent: api.hasPrivilege('posts:edit:content'), - canEditPostThumbnail: api.hasPrivilege('posts:edit:thumbnail'), - canEditPoolPosts: api.hasPrivilege('pools:edit:posts'), - canCreateAnonymousPosts: api.hasPrivilege('posts:create:anonymous'), - canDeletePosts: api.hasPrivilege('posts:delete'), - canFeaturePosts: api.hasPrivilege('posts:feature'), - canMergePosts: api.hasPrivilege('posts:merge'), - })); + views.replaceContent( + this._hostNode, + template({ + post: this._post, + enableSafety: api.safetyEnabled(), + hasClipboard: document.queryCommandSupported("copy"), + canEditPostSafety: api.hasPrivilege("posts:edit:safety"), + canEditPostSource: api.hasPrivilege("posts:edit:source"), + canEditPostTags: api.hasPrivilege("posts:edit:tags"), + canEditPostRelations: api.hasPrivilege("posts:edit:relations"), + canEditPostNotes: + api.hasPrivilege("posts:edit:notes") && + post.type !== "video" && + post.type !== "flash", + canEditPostFlags: api.hasPrivilege("posts:edit:flags"), + canEditPostContent: api.hasPrivilege("posts:edit:content"), + canEditPostThumbnail: api.hasPrivilege("posts:edit:thumbnail"), + canEditPoolPosts: api.hasPrivilege("pools:edit:posts"), + canCreateAnonymousPosts: api.hasPrivilege( + "posts:create:anonymous" + ), + canDeletePosts: api.hasPrivilege("posts:delete"), + canFeaturePosts: api.hasPrivilege("posts:feature"), + canMergePosts: api.hasPrivilege("posts:merge"), + }) + ); new ExpanderControl( - 'post-info', - 'Basic info', - this._hostNode.querySelectorAll('.safety, .relations, .flags, .post-source')); + "post-info", + "Basic info", + this._hostNode.querySelectorAll( + ".safety, .relations, .flags, .post-source" + ) + ); this._tagsExpander = new ExpanderControl( - 'post-tags', + "post-tags", `Tags (${this._post.tags.length})`, - this._hostNode.querySelectorAll('.tags')); + this._hostNode.querySelectorAll(".tags") + ); this._notesExpander = new ExpanderControl( - 'post-notes', - 'Notes', - this._hostNode.querySelectorAll('.notes')); + "post-notes", + "Notes", + this._hostNode.querySelectorAll(".notes") + ); this._poolsExpander = new ExpanderControl( - 'post-pools', + "post-pools", `Pools (${this._post.pools.length})`, - this._hostNode.querySelectorAll('.pools')); + this._hostNode.querySelectorAll(".pools") + ); new ExpanderControl( - 'post-content', - 'Content', - this._hostNode.querySelectorAll('.post-content, .post-thumbnail')); + "post-content", + "Content", + this._hostNode.querySelectorAll(".post-content, .post-thumbnail") + ); new ExpanderControl( - 'post-management', - 'Management', - this._hostNode.querySelectorAll('.management')); + "post-management", + "Management", + this._hostNode.querySelectorAll(".management") + ); this._syncExpanderTitles(); if (this._formNode) { - this._formNode.addEventListener('submit', e => this._evtSubmit(e)); + this._formNode.addEventListener("submit", (e) => + this._evtSubmit(e) + ); } if (this._tagInputNode) { this._tagControl = new TagInputControl( - this._tagInputNode, post.tags); + this._tagInputNode, + post.tags + ); } if (this._poolInputNode) { this._poolControl = new PoolInputControl( - this._poolInputNode, post.pools); + this._poolInputNode, + post.pools + ); } if (this._contentInputNode) { this._contentFileDropper = new FileDropperControl( - this._contentInputNode, {allowUrls: true, + this._contentInputNode, + { + allowUrls: true, lock: true, - urlPlaceholder: '...or paste an URL here.'}); - this._contentFileDropper.addEventListener('fileadd', e => { + urlPlaceholder: "...or paste an URL here.", + } + ); + this._contentFileDropper.addEventListener("fileadd", (e) => { this._newPostContent = e.detail.files[0]; }); - this._contentFileDropper.addEventListener('urladd', e => { + this._contentFileDropper.addEventListener("urladd", (e) => { this._newPostContent = e.detail.urls[0]; }); } if (this._thumbnailInputNode) { this._thumbnailFileDropper = new FileDropperControl( - this._thumbnailInputNode, {lock: true}); - this._thumbnailFileDropper.addEventListener('fileadd', e => { + this._thumbnailInputNode, + { lock: true } + ); + this._thumbnailFileDropper.addEventListener("fileadd", (e) => { this._newPostThumbnail = e.detail.files[0]; - this._thumbnailRemovalLinkNode.style.display = 'block'; + this._thumbnailRemovalLinkNode.style.display = "block"; }); } if (this._thumbnailRemovalLinkNode) { - this._thumbnailRemovalLinkNode.addEventListener( - 'click', e => this._evtRemoveThumbnailClick(e)); - this._thumbnailRemovalLinkNode.style.display = - this._post.hasCustomThumbnail ? 'block' : 'none'; + this._thumbnailRemovalLinkNode.addEventListener("click", (e) => + this._evtRemoveThumbnailClick(e) + ); + this._thumbnailRemovalLinkNode.style.display = this._post + .hasCustomThumbnail + ? "block" + : "none"; } if (this._addNoteLinkNode) { - this._addNoteLinkNode.addEventListener( - 'click', e => this._evtAddNoteClick(e)); + this._addNoteLinkNode.addEventListener("click", (e) => + this._evtAddNoteClick(e) + ); } if (this._copyNotesLinkNode) { - this._copyNotesLinkNode.addEventListener( - 'click', e => this._evtCopyNotesClick(e)); + this._copyNotesLinkNode.addEventListener("click", (e) => + this._evtCopyNotesClick(e) + ); } if (this._pasteNotesLinkNode) { - this._pasteNotesLinkNode.addEventListener( - 'click', e => this._evtPasteNotesClick(e)); + this._pasteNotesLinkNode.addEventListener("click", (e) => + this._evtPasteNotesClick(e) + ); } if (this._deleteNoteLinkNode) { - this._deleteNoteLinkNode.addEventListener( - 'click', e => this._evtDeleteNoteClick(e)); + this._deleteNoteLinkNode.addEventListener("click", (e) => + this._evtDeleteNoteClick(e) + ); } if (this._featureLinkNode) { - this._featureLinkNode.addEventListener( - 'click', e => this._evtFeatureClick(e)); + this._featureLinkNode.addEventListener("click", (e) => + this._evtFeatureClick(e) + ); } if (this._mergeLinkNode) { - this._mergeLinkNode.addEventListener( - 'click', e => this._evtMergeClick(e)); + this._mergeLinkNode.addEventListener("click", (e) => + this._evtMergeClick(e) + ); } if (this._deleteLinkNode) { - this._deleteLinkNode.addEventListener( - 'click', e => this._evtDeleteClick(e)); + this._deleteLinkNode.addEventListener("click", (e) => + this._evtDeleteClick(e) + ); } - this._postNotesOverlayControl.addEventListener( - 'blur', e => this._evtNoteBlur(e)); + this._postNotesOverlayControl.addEventListener("blur", (e) => + this._evtNoteBlur(e) + ); - this._postNotesOverlayControl.addEventListener( - 'focus', e => this._evtNoteFocus(e)); + this._postNotesOverlayControl.addEventListener("focus", (e) => + this._evtNoteFocus(e) + ); - this._post.addEventListener( - 'changeContent', e => this._evtPostContentChange(e)); + this._post.addEventListener("changeContent", (e) => + this._evtPostContentChange(e) + ); - this._post.addEventListener( - 'changeThumbnail', e => this._evtPostThumbnailChange(e)); + this._post.addEventListener("changeThumbnail", (e) => + this._evtPostThumbnailChange(e) + ); if (this._formNode) { const inputNodes = this._formNode.querySelectorAll( - 'input, textarea'); + "input, textarea" + ); for (let node of inputNodes) { - node.addEventListener( - 'change', - e => this.dispatchEvent(new CustomEvent('change'))); + node.addEventListener("change", (e) => + this.dispatchEvent(new CustomEvent("change")) + ); } - this._postNotesOverlayControl.addEventListener( - 'change', - e => this.dispatchEvent(new CustomEvent('change'))); + this._postNotesOverlayControl.addEventListener("change", (e) => + this.dispatchEvent(new CustomEvent("change")) + ); } - for (let eventType of ['add', 'remove']) { - this._post.notes.addEventListener(eventType, e => { + for (let eventType of ["add", "remove"]) { + this._post.notes.addEventListener(eventType, (e) => { this._syncExpanderTitles(); }); - this._post.pools.addEventListener(eventType, e => { + this._post.pools.addEventListener(eventType, (e) => { this._syncExpanderTitles(); }); } - this._tagControl.addEventListener( - 'change', e => { - this.dispatchEvent(new CustomEvent('change')); - this._syncExpanderTitles(); - }); + this._tagControl.addEventListener("change", (e) => { + this.dispatchEvent(new CustomEvent("change")); + this._syncExpanderTitles(); + }); if (this._noteTextareaNode) { - this._noteTextareaNode.addEventListener( - 'change', e => this._evtNoteTextChangeRequest(e)); + this._noteTextareaNode.addEventListener("change", (e) => + this._evtNoteTextChangeRequest(e) + ); } - this._poolControl.addEventListener( - 'change', e => { - this.dispatchEvent(new CustomEvent('change')); - this._syncExpanderTitles(); - }); + this._poolControl.addEventListener("change", (e) => { + this.dispatchEvent(new CustomEvent("change")); + this._syncExpanderTitles(); + }); } _syncExpanderTitles() { @@ -220,37 +260,43 @@ class PostEditSidebarControl extends events.EventTarget { e.preventDefault(); this._thumbnailFileDropper.reset(); this._newPostThumbnail = null; - this._thumbnailRemovalLinkNode.style.display = 'none'; + this._thumbnailRemovalLinkNode.style.display = "none"; } _evtFeatureClick(e) { e.preventDefault(); - if (confirm('Are you sure you want to feature this post?')) { - this.dispatchEvent(new CustomEvent('feature', { - detail: { - post: this._post, - }, - })); + if (confirm("Are you sure you want to feature this post?")) { + this.dispatchEvent( + new CustomEvent("feature", { + detail: { + post: this._post, + }, + }) + ); } } _evtMergeClick(e) { e.preventDefault(); - this.dispatchEvent(new CustomEvent('merge', { - detail: { - post: this._post, - }, - })); + this.dispatchEvent( + new CustomEvent("merge", { + detail: { + post: this._post, + }, + }) + ); } _evtDeleteClick(e) { e.preventDefault(); - if (confirm('Are you sure you want to delete this post?')) { - this.dispatchEvent(new CustomEvent('delete', { - detail: { - post: this._post, - }, - })); + if (confirm("Are you sure you want to delete this post?")) { + this.dispatchEvent( + new CustomEvent("delete", { + detail: { + post: this._post, + }, + }) + ); } } @@ -262,60 +308,64 @@ class PostEditSidebarControl extends events.EventTarget { _evtNoteFocus(e) { this._editedNote = e.detail.note; - this._addNoteLinkNode.classList.remove('inactive'); - this._deleteNoteLinkNode.classList.remove('inactive'); - this._noteTextareaNode.removeAttribute('disabled'); + this._addNoteLinkNode.classList.remove("inactive"); + this._deleteNoteLinkNode.classList.remove("inactive"); + this._noteTextareaNode.removeAttribute("disabled"); this._noteTextareaNode.value = e.detail.note.text; } _evtNoteBlur(e) { this._evtNoteTextChangeRequest(null); - this._addNoteLinkNode.classList.remove('inactive'); - this._deleteNoteLinkNode.classList.add('inactive'); + this._addNoteLinkNode.classList.remove("inactive"); + this._deleteNoteLinkNode.classList.add("inactive"); this._noteTextareaNode.blur(); - this._noteTextareaNode.setAttribute('disabled', 'disabled'); - this._noteTextareaNode.value = ''; + this._noteTextareaNode.setAttribute("disabled", "disabled"); + this._noteTextareaNode.value = ""; } _evtAddNoteClick(e) { e.preventDefault(); - if (e.target.classList.contains('inactive')) { + if (e.target.classList.contains("inactive")) { return; } - this._addNoteLinkNode.classList.add('inactive'); + this._addNoteLinkNode.classList.add("inactive"); this._postNotesOverlayControl.switchToDrawing(); } _evtCopyNotesClick(e) { e.preventDefault(); - let textarea = document.createElement('textarea'); - textarea.style.position = 'fixed'; - textarea.style.opacity = '0'; - textarea.value = JSON.stringify([...this._post.notes].map(note => ({ - polygon: [...note.polygon].map( - point => [point.x, point.y]), - text: note.text, - }))); + let textarea = document.createElement("textarea"); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + textarea.value = JSON.stringify( + [...this._post.notes].map((note) => ({ + polygon: [...note.polygon].map((point) => [point.x, point.y]), + text: note.text, + })) + ); document.body.appendChild(textarea); textarea.select(); let success = false; try { - success = document.execCommand('copy'); + success = document.execCommand("copy"); } catch (err) { // continue regardless of error } textarea.blur(); document.body.removeChild(textarea); - alert(success - ? 'Notes copied to clipboard.' - : 'Failed to copy the text to clipboard. Sorry.'); + alert( + success + ? "Notes copied to clipboard." + : "Failed to copy the text to clipboard. Sorry." + ); } _evtPasteNotesClick(e) { e.preventDefault(); const text = window.prompt( - 'Please enter the exported notes snapshot:'); + "Please enter the exported notes snapshot:" + ); if (!text) { return; } @@ -333,7 +383,7 @@ class PostEditSidebarControl extends events.EventTarget { _evtDeleteNoteClick(e) { e.preventDefault(); - if (e.target.classList.contains('inactive')) { + if (e.target.classList.contains("inactive")) { return; } this._post.notes.remove(this._editedNote); @@ -342,72 +392,78 @@ class PostEditSidebarControl extends events.EventTarget { _evtSubmit(e) { e.preventDefault(); - this.dispatchEvent(new CustomEvent('submit', { - detail: { - post: this._post, + this.dispatchEvent( + new CustomEvent("submit", { + detail: { + post: this._post, - safety: this._safetyButtonNodes.length ? - Array.from(this._safetyButtonNodes) - .filter(node => node.checked)[0] - .value.toLowerCase() : - undefined, + safety: this._safetyButtonNodes.length + ? Array.from(this._safetyButtonNodes) + .filter((node) => node.checked)[0] + .value.toLowerCase() + : undefined, - flags: this._videoFlags, + flags: this._videoFlags, - tags: this._tagInputNode ? - misc.splitByWhitespace(this._tagInputNode.value) : - undefined, + tags: this._tagInputNode + ? misc.splitByWhitespace(this._tagInputNode.value) + : undefined, - pools: this._poolInputNode ? - misc.splitByWhitespace(this._poolInputNode.value) : - undefined, + pools: this._poolInputNode + ? misc.splitByWhitespace(this._poolInputNode.value) + : undefined, - relations: this._relationsInputNode ? - misc.splitByWhitespace(this._relationsInputNode.value) - .map(x => parseInt(x)) : - undefined, + relations: this._relationsInputNode + ? misc + .splitByWhitespace( + this._relationsInputNode.value + ) + .map((x) => parseInt(x)) + : undefined, - content: this._newPostContent ? - this._newPostContent : - undefined, + content: this._newPostContent + ? this._newPostContent + : undefined, - thumbnail: this._newPostThumbnail !== undefined ? - this._newPostThumbnail : - undefined, + thumbnail: + this._newPostThumbnail !== undefined + ? this._newPostThumbnail + : undefined, - source: this._sourceInputNode ? - this._sourceInputNode.value : - undefined, - }, - })); + source: this._sourceInputNode + ? this._sourceInputNode.value + : undefined, + }, + }) + ); } get _formNode() { - return this._hostNode.querySelector('form'); + return this._hostNode.querySelector("form"); } get _submitButtonNode() { - return this._hostNode.querySelector('.submit'); + return this._hostNode.querySelector(".submit"); } get _safetyButtonNodes() { - return this._formNode.querySelectorAll('.safety input'); + return this._formNode.querySelectorAll(".safety input"); } get _tagInputNode() { - return this._formNode.querySelector('.tags input'); + return this._formNode.querySelector(".tags input"); } get _poolInputNode() { - return this._formNode.querySelector('.pools input'); + return this._formNode.querySelector(".pools input"); } get _loopVideoInputNode() { - return this._formNode.querySelector('.flags input[name=loop]'); + return this._formNode.querySelector(".flags input[name=loop]"); } get _soundVideoInputNode() { - return this._formNode.querySelector('.flags input[name=sound]'); + return this._formNode.querySelector(".flags input[name=sound]"); } get _videoFlags() { @@ -416,65 +472,68 @@ class PostEditSidebarControl extends events.EventTarget { } let ret = []; if (this._loopVideoInputNode.checked) { - ret.push('loop'); + ret.push("loop"); } if (this._soundVideoInputNode.checked) { - ret.push('sound'); + ret.push("sound"); } return ret; } get _relationsInputNode() { - return this._formNode.querySelector('.relations input'); + return this._formNode.querySelector(".relations input"); } get _contentInputNode() { - return this._formNode.querySelector('.post-content .dropper-container'); + return this._formNode.querySelector( + ".post-content .dropper-container" + ); } get _thumbnailInputNode() { return this._formNode.querySelector( - '.post-thumbnail .dropper-container'); + ".post-thumbnail .dropper-container" + ); } get _thumbnailRemovalLinkNode() { - return this._formNode.querySelector('.post-thumbnail a'); + return this._formNode.querySelector(".post-thumbnail a"); } get _sourceInputNode() { - return this._formNode.querySelector('.post-source textarea'); + return this._formNode.querySelector(".post-source textarea"); } get _featureLinkNode() { - return this._formNode.querySelector('.management .feature'); + return this._formNode.querySelector(".management .feature"); } get _mergeLinkNode() { - return this._formNode.querySelector('.management .merge'); + return this._formNode.querySelector(".management .merge"); } get _deleteLinkNode() { - return this._formNode.querySelector('.management .delete'); + return this._formNode.querySelector(".management .delete"); } get _addNoteLinkNode() { - return this._formNode.querySelector('.notes .add'); + return this._formNode.querySelector(".notes .add"); } get _copyNotesLinkNode() { - return this._formNode.querySelector('.notes .copy'); + return this._formNode.querySelector(".notes .copy"); } get _pasteNotesLinkNode() { - return this._formNode.querySelector('.notes .paste'); + return this._formNode.querySelector(".notes .paste"); } get _deleteNoteLinkNode() { - return this._formNode.querySelector('.notes .delete'); + return this._formNode.querySelector(".notes .delete"); } get _noteTextareaNode() { - return this._formNode.querySelector('.notes textarea'); + return this._formNode.querySelector(".notes textarea"); } enableForm() { diff --git a/client/js/controls/post_notes_overlay_control.js b/client/js/controls/post_notes_overlay_control.js index 7bf69b2..030f7f2 100644 --- a/client/js/controls/post_notes_overlay_control.js +++ b/client/js/controls/post_notes_overlay_control.js @@ -1,13 +1,13 @@ -'use strict'; +"use strict"; -const keyboard = require('../util/keyboard.js'); -const views = require('../util/views.js'); -const events = require('../events.js'); -const misc = require('../util/misc.js'); -const Note = require('../models/note.js'); -const Point = require('../models/point.js'); +const keyboard = require("../util/keyboard.js"); +const views = require("../util/views.js"); +const events = require("../events.js"); +const misc = require("../util/misc.js"); +const Note = require("../models/note.js"); +const Point = require("../models/point.js"); -const svgNS = 'http://www.w3.org/2000/svg'; +const svgNS = "http://www.w3.org/2000/svg"; const snapThreshold = 10; const circleSize = 10; @@ -22,19 +22,19 @@ const KEY_RETURN = 13; function _getDistance(point1, point2) { return Math.sqrt( - Math.pow(point1.x - point2.x, 2) + - Math.pow(point1.y - point2.y, 2)); + Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2) + ); } function _setNodeState(node, stateName) { if (node === null) { return; } - node.setAttribute('data-state', stateName); + node.setAttribute("data-state", stateName); } function _clearEditedNote(hostNode) { - const node = hostNode.querySelector('[data-state=\'editing\']'); + const node = hostNode.querySelector("[data-state='editing']"); _setNodeState(node, null); return node !== null; } @@ -48,7 +48,7 @@ function _getNoteCentroid(note) { const y0 = note.polygon.at(i).y; const x1 = note.polygon.at((i + 1) % vertexCount).x; const y1 = note.polygon.at((i + 1) % vertexCount).y; - const a = (x0 * y1) - (x1 * y0); + const a = x0 * y1 - x1 * y0; signedArea += a; centroid.x += (x0 + x1) * a; centroid.y += (y0 + y1) * a; @@ -82,32 +82,30 @@ class State { return false; } - evtCanvasKeyDown(e) { - } + evtCanvasKeyDown(e) {} - evtNoteMouseDown(e, hoveredNote) { - } + evtNoteMouseDown(e, hoveredNote) {} - evtCanvasMouseDown(e) { - } + evtCanvasMouseDown(e) {} - evtCanvasMouseMove(e) { - } + evtCanvasMouseMove(e) {} - evtCanvasMouseUp(e) { - } + evtCanvasMouseUp(e) {} _getScreenPoint(point) { return new Point( point.x * this._control.boundingBox.width, - point.y * this._control.boundingBox.height); + point.y * this._control.boundingBox.height + ); } _snapPoints(targetPoint, referencePoint) { const targetScreenPoint = this._getScreenPoint(targetPoint); const referenceScreenPoint = this._getScreenPoint(referencePoint); - if (_getDistance(targetScreenPoint, referenceScreenPoint) < - snapThreshold) { + if ( + _getDistance(targetScreenPoint, referenceScreenPoint) < + snapThreshold + ) { targetPoint.x = referencePoint.x; targetPoint.y = referencePoint.y; } @@ -124,15 +122,16 @@ class State { (e.clientX - this._control.boundingBox.left) / this._control.boundingBox.width, (e.clientY - this._control.boundingBox.top) / - this._control.boundingBox.height); + this._control.boundingBox.height + ); } } class ReadOnlyState extends State { constructor(control) { - super(control, 'read-only'); + super(control, "read-only"); if (_clearEditedNote(control._hostNode)) { - this._control.dispatchEvent(new CustomEvent('blur')); + this._control.dispatchEvent(new CustomEvent("blur")); } keyboard.unpause(); } @@ -144,9 +143,9 @@ class ReadOnlyState extends State { class PassiveState extends State { constructor(control) { - super(control, 'passive'); + super(control, "passive"); if (_clearEditedNote(control._hostNode)) { - this._control.dispatchEvent(new CustomEvent('blur')); + this._control.dispatchEvent(new CustomEvent("blur")); } keyboard.unpause(); } @@ -164,23 +163,24 @@ class ActiveState extends State { constructor(control, note, stateName) { super(control, stateName); if (_clearEditedNote(control._hostNode)) { - this._control.dispatchEvent(new CustomEvent('blur')); + this._control.dispatchEvent(new CustomEvent("blur")); } keyboard.pause(); if (note !== null) { this._note = note; this._control.dispatchEvent( - new CustomEvent('focus', { - detail: {note: note}, - })); - _setNodeState(this._note.groupNode, 'editing'); + new CustomEvent("focus", { + detail: { note: note }, + }) + ); + _setNodeState(this._note.groupNode, "editing"); } } } class SelectedState extends ActiveState { constructor(control, note) { - super(control, note, 'selected'); + super(control, note, "selected"); this._clickTimeout = null; this._control._hideNoteText(); } @@ -211,27 +211,40 @@ class SelectedState extends ActiveState { const mouseScreenPoint = this._getScreenPoint(mousePoint); if (e.shiftKey) { this._control._state = new ScalingNoteState( - this._control, this._note, mousePoint); + this._control, + this._note, + mousePoint + ); return; } if (this._note !== hoveredNote) { - this._control._state = - new SelectedState(this._control, hoveredNote); + this._control._state = new SelectedState( + this._control, + hoveredNote + ); return; } this._clickTimeout = window.setTimeout(() => { for (let polygonPoint of this._note.polygon) { const distance = _getDistance( mouseScreenPoint, - this._getScreenPoint(polygonPoint)); + this._getScreenPoint(polygonPoint) + ); if (distance < circleSize) { this._control._state = new MovingPointState( - this._control, this._note, polygonPoint, mousePoint); + this._control, + this._note, + polygonPoint, + mousePoint + ); return; } } this._control._state = new MovingNoteState( - this._control, this._note, mousePoint); + this._control, + this._note, + mousePoint + ); }, 100); } @@ -241,9 +254,12 @@ class SelectedState extends ActiveState { for (let polygonPoint of this._note.polygon) { const distance = _getDistance( mouseScreenPoint, - this._getScreenPoint(polygonPoint)); + this._getScreenPoint(polygonPoint) + ); polygonPoint.edgeNode.classList.toggle( - 'nearby', distance < circleSize); + "nearby", + distance < circleSize + ); } } @@ -252,16 +268,24 @@ class SelectedState extends ActiveState { const mouseScreenPoint = this._getScreenPoint(mousePoint); if (e.shiftKey) { this._control._state = new ScalingNoteState( - this._control, this._note, mousePoint); + this._control, + this._note, + mousePoint + ); return; } for (let polygonPoint of this._note.polygon) { const distance = _getDistance( mouseScreenPoint, - this._getScreenPoint(polygonPoint)); + this._getScreenPoint(polygonPoint) + ); if (distance < circleSize) { this._control._state = new MovingPointState( - this._control, this._note, polygonPoint, mousePoint); + this._control, + this._note, + polygonPoint, + mousePoint + ); return; } } @@ -283,32 +307,37 @@ class SelectedState extends ActiveState { const origin = _getNoteCentroid(this._note); const originalSize = _getNoteSize(this._note); const targetSize = new Point( - originalSize.x + (x / this._control.boundingBox.width), - originalSize.y + (y / this._control.boundingBox.height)); + originalSize.x + x / this._control.boundingBox.width, + originalSize.y + y / this._control.boundingBox.height + ); const scale = new Point( targetSize.x / originalSize.x, - targetSize.y / originalSize.y); + targetSize.y / originalSize.y + ); for (let point of this._note.polygon) { - point.x = origin.x + ((point.x - origin.x) * scale.x); - point.y = origin.y + ((point.y - origin.y) * scale.y); + point.x = origin.x + (point.x - origin.x) * scale.x; + point.y = origin.y + (point.y - origin.y) * scale.y; } } } class MovingPointState extends ActiveState { constructor(control, note, notePoint, mousePoint) { - super(control, note, 'moving-point'); + super(control, note, "moving-point"); this._notePoint = notePoint; - this._originalNotePoint = {x: notePoint.x, y: notePoint.y}; + this._originalNotePoint = { x: notePoint.x, y: notePoint.y }; this._originalPosition = mousePoint; - _setNodeState(this._note.groupNode, 'editing'); + _setNodeState(this._note.groupNode, "editing"); } evtCanvasKeyDown(e) { if (e.which === KEY_ESCAPE) { this._notePoint.x = this._originalNotePoint.x; this._notePoint.y = this._originalNotePoint.y; - this._control._state = new SelectedState(this._control, this._note); + this._control._state = new SelectedState( + this._control, + this._note + ); } } @@ -326,9 +355,11 @@ class MovingPointState extends ActiveState { class MovingNoteState extends ActiveState { constructor(control, note, mousePoint) { - super(control, note, 'moving-note'); - this._originalPolygon = [...note.polygon].map( - point => ({x: point.x, y: point.y})); + super(control, note, "moving-note"); + this._originalPolygon = [...note.polygon].map((point) => ({ + x: point.x, + y: point.y, + })); this._originalPosition = mousePoint; } @@ -338,7 +369,10 @@ class MovingNoteState extends ActiveState { this._note.polygon.at(i).x = this._originalPolygon[i].x; this._note.polygon.at(i).y = this._originalPolygon[i].y; } - this._control._state = new SelectedState(this._control, this._note); + this._control._state = new SelectedState( + this._control, + this._note + ); } } @@ -358,9 +392,11 @@ class MovingNoteState extends ActiveState { class ScalingNoteState extends ActiveState { constructor(control, note, mousePoint) { - super(control, note, 'scaling-note'); - this._originalPolygon = [...note.polygon].map( - point => ({x: point.x, y: point.y})); + super(control, note, "scaling-note"); + this._originalPolygon = [...note.polygon].map((point) => ({ + x: point.x, + y: point.y, + })); this._originalMousePoint = mousePoint; this._originalSize = _getNoteSize(note); } @@ -371,7 +407,10 @@ class ScalingNoteState extends ActiveState { this._note.polygon.at(i).x = this._originalPolygon[i].x; this._note.polygon.at(i).y = this._originalPolygon[i].y; } - this._control._state = new SelectedState(this._control, this._note); + this._control._state = new SelectedState( + this._control, + this._note + ); } } @@ -384,12 +423,16 @@ class ScalingNoteState extends ActiveState { const originalPolygonPoint = this._originalPolygon[i]; polygonPoint.x = originalMousePoint.x + - ((originalPolygonPoint.x - originalMousePoint.x) * - (1 + ((mousePoint.x - originalMousePoint.x) / originalSize.x))); + (originalPolygonPoint.x - originalMousePoint.x) * + (1 + + (mousePoint.x - originalMousePoint.x) / + originalSize.x); polygonPoint.y = originalMousePoint.y + - ((originalPolygonPoint.y - originalMousePoint.y) * - (1 + ((mousePoint.y - originalMousePoint.y) / originalSize.y))); + (originalPolygonPoint.y - originalMousePoint.y) * + (1 + + (mousePoint.y - originalMousePoint.y) / + originalSize.y); } } @@ -400,7 +443,7 @@ class ScalingNoteState extends ActiveState { class ReadyToDrawState extends ActiveState { constructor(control) { - super(control, null, 'ready-to-draw'); + super(control, null, "ready-to-draw"); } evtNoteMouseDown(e, hoveredNote) { @@ -411,23 +454,27 @@ class ReadyToDrawState extends ActiveState { const mousePoint = this._getPointFromEvent(e); if (e.shiftKey) { this._control._state = new DrawingRectangleState( - this._control, mousePoint); + this._control, + mousePoint + ); } else { this._control._state = new DrawingPolygonState( - this._control, mousePoint); + this._control, + mousePoint + ); } } } class DrawingRectangleState extends ActiveState { constructor(control, mousePoint) { - super(control, null, 'drawing-rectangle'); + super(control, null, "drawing-rectangle"); this._note = this._createNote(); this._note.polygon.add(new Point(mousePoint.x, mousePoint.y)); this._note.polygon.add(new Point(mousePoint.x, mousePoint.y)); this._note.polygon.add(new Point(mousePoint.x, mousePoint.y)); this._note.polygon.add(new Point(mousePoint.x, mousePoint.y)); - _setNodeState(this._note.groupNode, 'drawing'); + _setNodeState(this._note.groupNode, "drawing"); } evtCanvasMouseUp(e) { @@ -443,7 +490,10 @@ class DrawingRectangleState extends ActiveState { this._control._state = new ReadyToDrawState(this._control); } else { this._control._post.notes.add(this._note); - this._control._state = new SelectedState(this._control, this._note); + this._control._state = new SelectedState( + this._control, + this._note + ); } } @@ -458,11 +508,11 @@ class DrawingRectangleState extends ActiveState { class DrawingPolygonState extends ActiveState { constructor(control, mousePoint) { - super(control, null, 'drawing-polygon'); + super(control, null, "drawing-polygon"); this._note = this._createNote(); this._note.polygon.add(new Point(mousePoint.x, mousePoint.y)); this._note.polygon.add(new Point(mousePoint.x, mousePoint.y)); - _setNodeState(this._note.groupNode, 'drawing'); + _setNodeState(this._note.groupNode, "drawing"); } evtCanvasKeyDown(e) { @@ -502,11 +552,16 @@ class DrawingPolygonState extends ActiveState { } if (e.shiftKey && secondLastPoint) { - const direction = (Math.round( - Math.atan2( - secondLastPoint.y - mousePoint.y, - secondLastPoint.x - mousePoint.x) / - (2 * Math.PI / 4)) + 4) % 4; + const direction = + (Math.round( + Math.atan2( + secondLastPoint.y - mousePoint.y, + secondLastPoint.x - mousePoint.x + ) / + ((2 * Math.PI) / 4) + ) + + 4) % + 4; if (direction === 0 || direction === 2) { lastPoint.x = mousePoint.x; lastPoint.y = secondLastPoint.y; @@ -533,7 +588,10 @@ class DrawingPolygonState extends ActiveState { } else { this._control._deleteDomNode(this._note); this._control._post.notes.add(this._note); - this._control._state = new SelectedState(this._control, this._note); + this._control._state = new SelectedState( + this._control, + this._note + ); } } } @@ -544,48 +602,51 @@ class PostNotesOverlayControl extends events.EventTarget { this._post = post; this._hostNode = hostNode; - this._svgNode = document.createElementNS(svgNS, 'svg'); - this._svgNode.classList.add('resize-listener'); - this._svgNode.classList.add('notes-overlay'); - this._svgNode.setAttribute('preserveAspectRatio', 'none'); - this._svgNode.setAttribute('viewBox', '0 0 1 1'); + this._svgNode = document.createElementNS(svgNS, "svg"); + this._svgNode.classList.add("resize-listener"); + this._svgNode.classList.add("notes-overlay"); + this._svgNode.setAttribute("preserveAspectRatio", "none"); + this._svgNode.setAttribute("viewBox", "0 0 1 1"); for (let note of this._post.notes) { this._createPolygonNode(note); } this._hostNode.appendChild(this._svgNode); - this._post.addEventListener('change', e => this._evtPostChange(e)); - this._post.notes.addEventListener('remove', e => { + this._post.addEventListener("change", (e) => this._evtPostChange(e)); + this._post.notes.addEventListener("remove", (e) => { this._deleteDomNode(e.detail.note); }); - this._post.notes.addEventListener('add', e => { + this._post.notes.addEventListener("add", (e) => { this._createPolygonNode(e.detail.note); }); - const keyHandler = e => this._evtCanvasKeyDown(e); - document.addEventListener('keydown', keyHandler); - this._svgNode.addEventListener( - 'mousedown', e => this._evtCanvasMouseDown(e)); - this._svgNode.addEventListener( - 'mouseup', e => this._evtCanvasMouseUp(e)); - this._svgNode.addEventListener( - 'mousemove', e => this._evtCanvasMouseMove(e)); + const keyHandler = (e) => this._evtCanvasKeyDown(e); + document.addEventListener("keydown", keyHandler); + this._svgNode.addEventListener("mousedown", (e) => + this._evtCanvasMouseDown(e) + ); + this._svgNode.addEventListener("mouseup", (e) => + this._evtCanvasMouseUp(e) + ); + this._svgNode.addEventListener("mousemove", (e) => + this._evtCanvasMouseMove(e) + ); - const wrapperNode = document.createElement('div'); - wrapperNode.classList.add('wrapper'); - this._textNode = document.createElement('div'); - this._textNode.classList.add('note-text'); + const wrapperNode = document.createElement("div"); + wrapperNode.classList.add("wrapper"); + this._textNode = document.createElement("div"); + this._textNode.classList.add("note-text"); this._textNode.appendChild(wrapperNode); - this._textNode.addEventListener( - 'mouseleave', e => this._evtNoteMouseLeave(e)); + this._textNode.addEventListener("mouseleave", (e) => + this._evtNoteMouseLeave(e) + ); document.body.appendChild(this._textNode); - views.monitorNodeRemoval( - this._hostNode, () => { - this._hostNode.removeChild(this._svgNode); - document.removeEventListener('keydown', keyHandler); - document.body.removeChild(this._textNode); - this._state = new ReadOnlyState(this); - }); + views.monitorNodeRemoval(this._hostNode, () => { + this._hostNode.removeChild(this._svgNode); + document.removeEventListener("keydown", keyHandler); + document.body.removeChild(this._textNode); + this._state = new ReadOnlyState(this); + }); this._state = new ReadOnlyState(this); } @@ -613,7 +674,7 @@ class PostNotesOverlayControl extends events.EventTarget { } _evtCanvasKeyDown(e) { - const illegalNodeNames = ['textarea', 'input', 'select']; + const illegalNodeNames = ["textarea", "input", "select"]; if (illegalNodeNames.includes(e.target.nodeName.toLowerCase())) { return; } @@ -655,53 +716,58 @@ class PostNotesOverlayControl extends events.EventTarget { _evtNoteMouseLeave(e) { const newElement = e.relatedTarget; - if (newElement === this._svgNode || - (!this._svgNode.contains(newElement) && + if ( + newElement === this._svgNode || + (!this._svgNode.contains(newElement) && !this._textNode.contains(newElement) && - newElement !== this._textNode)) { + newElement !== this._textNode) + ) { this._hideNoteText(); } } _showNoteText(note) { - this._textNode.querySelector('.wrapper').innerHTML = - misc.formatMarkdown(note.text); - this._textNode.style.display = 'block'; + this._textNode.querySelector( + ".wrapper" + ).innerHTML = misc.formatMarkdown(note.text); + this._textNode.style.display = "block"; const bodyRect = document.body.getBoundingClientRect(); const noteRect = this._textNode.getBoundingClientRect(); const svgRect = this.boundingBox; const centroid = _getNoteCentroid(note); - const x = ( + const x = -bodyRect.left + svgRect.left + - (svgRect.width * centroid.x) - - (noteRect.width / 2)); - const y = ( + svgRect.width * centroid.x - + noteRect.width / 2; + const y = -bodyRect.top + svgRect.top + - (svgRect.height * centroid.y) - - (noteRect.height / 2)); - this._textNode.style.left = x + 'px'; - this._textNode.style.top = y + 'px'; + svgRect.height * centroid.y - + noteRect.height / 2; + this._textNode.style.left = x + "px"; + this._textNode.style.top = y + "px"; } _hideNoteText() { - this._textNode.style.display = 'none'; + this._textNode.style.display = "none"; } _updatePolygonNotePoints(note) { note.polygonNode.setAttribute( - 'points', - [...note.polygon].map( - point => [point.x, point.y].join(',')).join(' ')); + "points", + [...note.polygon] + .map((point) => [point.x, point.y].join(",")) + .join(" ") + ); } _createEdgeNode(point, groupNode) { - const node = document.createElementNS(svgNS, 'ellipse'); - node.setAttribute('cx', point.x); - node.setAttribute('cy', point.y); - node.setAttribute('rx', circleSize / 2 / this.boundingBox.width); - node.setAttribute('ry', circleSize / 2 / this.boundingBox.height); + const node = document.createElementNS(svgNS, "ellipse"); + node.setAttribute("cx", point.x); + node.setAttribute("cy", point.y); + node.setAttribute("rx", circleSize / 2 / this.boundingBox.width); + node.setAttribute("ry", circleSize / 2 / this.boundingBox.height); point.edgeNode = node; groupNode.appendChild(node); } @@ -713,8 +779,8 @@ class PostNotesOverlayControl extends events.EventTarget { _updateEdgeNode(point, note) { this._updatePolygonNotePoints(note); - point.edgeNode.setAttribute('cx', point.x); - point.edgeNode.setAttribute('cy', point.y); + point.edgeNode.setAttribute("cx", point.x); + point.edgeNode.setAttribute("cy", point.y); } _deleteDomNode(note) { @@ -722,17 +788,19 @@ class PostNotesOverlayControl extends events.EventTarget { } _createPolygonNode(note) { - const groupNode = document.createElementNS(svgNS, 'g'); + const groupNode = document.createElementNS(svgNS, "g"); note.groupNode = groupNode; { - const node = document.createElementNS(svgNS, 'polygon'); + const node = document.createElementNS(svgNS, "polygon"); note.polygonNode = node; - node.setAttribute('vector-effect', 'non-scaling-stroke'); - node.setAttribute('stroke-alignment', 'inside'); - node.addEventListener( - 'mouseenter', e => this._evtNoteMouseEnter(e, note)); - node.addEventListener( - 'mouseleave', e => this._evtNoteMouseLeave(e)); + node.setAttribute("vector-effect", "non-scaling-stroke"); + node.setAttribute("stroke-alignment", "inside"); + node.addEventListener("mouseenter", (e) => + this._evtNoteMouseEnter(e, note) + ); + node.addEventListener("mouseleave", (e) => + this._evtNoteMouseLeave(e) + ); this._updatePolygonNotePoints(note); groupNode.appendChild(node); } @@ -740,17 +808,17 @@ class PostNotesOverlayControl extends events.EventTarget { this._createEdgeNode(point, groupNode); } - note.polygon.addEventListener('change', e => { + note.polygon.addEventListener("change", (e) => { this._updateEdgeNode(e.detail.point, note); - this.dispatchEvent(new CustomEvent('change')); + this.dispatchEvent(new CustomEvent("change")); }); - note.polygon.addEventListener('remove', e => { + note.polygon.addEventListener("remove", (e) => { this._deleteEdgeNode(e.detail.point, note); - this.dispatchEvent(new CustomEvent('change')); + this.dispatchEvent(new CustomEvent("change")); }); - note.polygon.addEventListener('add', e => { + note.polygon.addEventListener("add", (e) => { this._createEdgeNode(e.detail.point, groupNode); - this.dispatchEvent(new CustomEvent('change')); + this.dispatchEvent(new CustomEvent("change")); }); this._svgNode.appendChild(groupNode); diff --git a/client/js/controls/post_readonly_sidebar_control.js b/client/js/controls/post_readonly_sidebar_control.js index 388c238..580ef43 100644 --- a/client/js/controls/post_readonly_sidebar_control.js +++ b/client/js/controls/post_readonly_sidebar_control.js @@ -1,14 +1,14 @@ -'use strict'; +"use strict"; -const api = require('../api.js'); -const events = require('../events.js'); -const views = require('../util/views.js'); -const uri = require('../util/uri.js'); -const misc = require('../util/misc.js'); +const api = require("../api.js"); +const events = require("../events.js"); +const views = require("../util/views.js"); +const uri = require("../util/uri.js"); +const misc = require("../util/misc.js"); -const template = views.getTemplate('post-readonly-sidebar'); -const scoreTemplate = views.getTemplate('score'); -const favTemplate = views.getTemplate('fav'); +const template = views.getTemplate("post-readonly-sidebar"); +const scoreTemplate = views.getTemplate("score"); +const favTemplate = views.getTemplate("fav"); class PostReadonlySidebarControl extends events.EventTarget { constructor(hostNode, post, postContentControl) { @@ -17,19 +17,22 @@ class PostReadonlySidebarControl extends events.EventTarget { this._post = post; this._postContentControl = postContentControl; - post.addEventListener('changeFavorite', e => this._evtChangeFav(e)); - post.addEventListener('changeScore', e => this._evtChangeScore(e)); + post.addEventListener("changeFavorite", (e) => this._evtChangeFav(e)); + post.addEventListener("changeScore", (e) => this._evtChangeScore(e)); - views.replaceContent(this._hostNode, template({ - post: this._post, - enableSafety: api.safetyEnabled(), - canListPosts: api.hasPrivilege('posts:list'), - canEditPosts: api.hasPrivilege('posts:edit'), - canViewTags: api.hasPrivilege('tags:view'), - escapeColons: uri.escapeColons, - extractRootDomain: uri.extractRootDomain, - getPrettyTagName: misc.getPrettyTagName, - })); + views.replaceContent( + this._hostNode, + template({ + post: this._post, + enableSafety: api.safetyEnabled(), + canListPosts: api.hasPrivilege("posts:list"), + canEditPosts: api.hasPrivilege("posts:edit"), + canViewTags: api.hasPrivilege("tags:view"), + escapeColons: uri.escapeColons, + extractRootDomain: uri.extractRootDomain, + getPrettyTagName: misc.getPrettyTagName, + }) + ); this._installFav(); this._installScore(); @@ -38,58 +41,62 @@ class PostReadonlySidebarControl extends events.EventTarget { } get _scoreContainerNode() { - return this._hostNode.querySelector('.score-container'); + return this._hostNode.querySelector(".score-container"); } get _favContainerNode() { - return this._hostNode.querySelector('.fav-container'); + return this._hostNode.querySelector(".fav-container"); } get _upvoteButtonNode() { - return this._hostNode.querySelector('.upvote'); + return this._hostNode.querySelector(".upvote"); } get _downvoteButtonNode() { - return this._hostNode.querySelector('.downvote'); + return this._hostNode.querySelector(".downvote"); } get _addFavButtonNode() { - return this._hostNode.querySelector('.add-favorite'); + return this._hostNode.querySelector(".add-favorite"); } get _remFavButtonNode() { - return this._hostNode.querySelector('.remove-favorite'); + return this._hostNode.querySelector(".remove-favorite"); } get _fitBothButtonNode() { - return this._hostNode.querySelector('.fit-both'); + return this._hostNode.querySelector(".fit-both"); } get _fitOriginalButtonNode() { - return this._hostNode.querySelector('.fit-original'); + return this._hostNode.querySelector(".fit-original"); } get _fitWidthButtonNode() { - return this._hostNode.querySelector('.fit-width'); + return this._hostNode.querySelector(".fit-width"); } get _fitHeightButtonNode() { - return this._hostNode.querySelector('.fit-height'); + return this._hostNode.querySelector(".fit-height"); } _installFitButtons() { this._fitBothButtonNode.addEventListener( - 'click', this._eventZoomProxy( - () => this._postContentControl.fitBoth())); + "click", + this._eventZoomProxy(() => this._postContentControl.fitBoth()) + ); this._fitOriginalButtonNode.addEventListener( - 'click', this._eventZoomProxy( - () => this._postContentControl.fitOriginal())); + "click", + this._eventZoomProxy(() => this._postContentControl.fitOriginal()) + ); this._fitWidthButtonNode.addEventListener( - 'click', this._eventZoomProxy( - () => this._postContentControl.fitWidth())); + "click", + this._eventZoomProxy(() => this._postContentControl.fitWidth()) + ); this._fitHeightButtonNode.addEventListener( - 'click', this._eventZoomProxy( - () => this._postContentControl.fitHeight())); + "click", + this._eventZoomProxy(() => this._postContentControl.fitHeight()) + ); } _installFav() { @@ -98,16 +105,19 @@ class PostReadonlySidebarControl extends events.EventTarget { favTemplate({ favoriteCount: this._post.favoriteCount, ownFavorite: this._post.ownFavorite, - canFavorite: api.hasPrivilege('posts:favorite'), - })); + canFavorite: api.hasPrivilege("posts:favorite"), + }) + ); if (this._addFavButtonNode) { - this._addFavButtonNode.addEventListener( - 'click', e => this._evtAddToFavoritesClick(e)); + this._addFavButtonNode.addEventListener("click", (e) => + this._evtAddToFavoritesClick(e) + ); } if (this._remFavButtonNode) { - this._remFavButtonNode.addEventListener( - 'click', e => this._evtRemoveFromFavoritesClick(e)); + this._remFavButtonNode.addEventListener("click", (e) => + this._evtRemoveFromFavoritesClick(e) + ); } } @@ -117,77 +127,88 @@ class PostReadonlySidebarControl extends events.EventTarget { scoreTemplate({ score: this._post.score, ownScore: this._post.ownScore, - canScore: api.hasPrivilege('posts:score'), - })); + canScore: api.hasPrivilege("posts:score"), + }) + ); if (this._upvoteButtonNode) { - this._upvoteButtonNode.addEventListener( - 'click', e => this._evtScoreClick(e, 1)); + this._upvoteButtonNode.addEventListener("click", (e) => + this._evtScoreClick(e, 1) + ); } if (this._downvoteButtonNode) { - this._downvoteButtonNode.addEventListener( - 'click', e => this._evtScoreClick(e, -1)); + this._downvoteButtonNode.addEventListener("click", (e) => + this._evtScoreClick(e, -1) + ); } } _eventZoomProxy(func) { - return e => { + return (e) => { e.preventDefault(); e.target.blur(); func(); this._syncFitButton(); - this.dispatchEvent(new CustomEvent('fitModeChange', { - detail: { - mode: this._getFitMode(), - }, - })); + this.dispatchEvent( + new CustomEvent("fitModeChange", { + detail: { + mode: this._getFitMode(), + }, + }) + ); }; } _getFitMode() { const funcToName = {}; - funcToName[this._postContentControl.fitBoth] = 'fit-both'; - funcToName[this._postContentControl.fitOriginal] = 'fit-original'; - funcToName[this._postContentControl.fitWidth] = 'fit-width'; - funcToName[this._postContentControl.fitHeight] = 'fit-height'; + funcToName[this._postContentControl.fitBoth] = "fit-both"; + funcToName[this._postContentControl.fitOriginal] = "fit-original"; + funcToName[this._postContentControl.fitWidth] = "fit-width"; + funcToName[this._postContentControl.fitHeight] = "fit-height"; return funcToName[this._postContentControl._currentFitFunction]; } _syncFitButton() { const className = this._getFitMode(); - const oldNode = this._hostNode.querySelector('.zoom a.active'); + const oldNode = this._hostNode.querySelector(".zoom a.active"); const newNode = this._hostNode.querySelector(`.zoom a.${className}`); if (oldNode) { - oldNode.classList.remove('active'); + oldNode.classList.remove("active"); } - newNode.classList.add('active'); + newNode.classList.add("active"); } _evtAddToFavoritesClick(e) { e.preventDefault(); - this.dispatchEvent(new CustomEvent('favorite', { - detail: { - post: this._post, - }, - })); + this.dispatchEvent( + new CustomEvent("favorite", { + detail: { + post: this._post, + }, + }) + ); } _evtRemoveFromFavoritesClick(e) { e.preventDefault(); - this.dispatchEvent(new CustomEvent('unfavorite', { - detail: { - post: this._post, - }, - })); + this.dispatchEvent( + new CustomEvent("unfavorite", { + detail: { + post: this._post, + }, + }) + ); } _evtScoreClick(e, score) { e.preventDefault(); - this.dispatchEvent(new CustomEvent('score', { - detail: { - post: this._post, - score: this._post.ownScore === score ? 0 : score, - }, - })); + this.dispatchEvent( + new CustomEvent("score", { + detail: { + post: this._post, + score: this._post.ownScore === score ? 0 : score, + }, + }) + ); } _evtChangeFav(e) { diff --git a/client/js/controls/tag_auto_complete_control.js b/client/js/controls/tag_auto_complete_control.js index f8d3f6c..8ee6aa8 100644 --- a/client/js/controls/tag_auto_complete_control.js +++ b/client/js/controls/tag_auto_complete_control.js @@ -1,51 +1,63 @@ -'use strict'; +"use strict"; -const misc = require('../util/misc.js'); -const views = require('../util/views.js'); -const TagList = require('../models/tag_list.js'); -const AutoCompleteControl = require('./auto_complete_control.js'); +const misc = require("../util/misc.js"); +const views = require("../util/views.js"); +const TagList = require("../models/tag_list.js"); +const AutoCompleteControl = require("./auto_complete_control.js"); function _tagListToMatches(tags, options) { - return [...tags].sort((tag1, tag2) => { - return tag2.usages - tag1.usages; - }).map(tag => { - let cssName = misc.makeCssName(tag.category, 'tag'); - if (options.isTaggedWith(tag.names[0])) { - cssName += ' disabled'; - } - const caption = ( - '<span class="' + cssName + '">' - + misc.escapeHtml(tag.names[0] + ' (' + tag.postCount + ')') - + '</span>'); - return { - caption: caption, - value: tag, - }; - }); + return [...tags] + .sort((tag1, tag2) => { + return tag2.usages - tag1.usages; + }) + .map((tag) => { + let cssName = misc.makeCssName(tag.category, "tag"); + if (options.isTaggedWith(tag.names[0])) { + cssName += " disabled"; + } + const caption = + '<span class="' + + cssName + + '">' + + misc.escapeHtml(tag.names[0] + " (" + tag.postCount + ")") + + "</span>"; + return { + caption: caption, + value: tag, + }; + }); } class TagAutoCompleteControl extends AutoCompleteControl { constructor(input, options) { const minLengthForPartialSearch = 3; - options = Object.assign({ - isTaggedWith: tag => false, - }, options); + options = Object.assign( + { + isTaggedWith: (tag) => false, + }, + options + ); - options.getMatches = text => { + options.getMatches = (text) => { const term = misc.escapeSearchTerm(text); - const query = ( - text.length < minLengthForPartialSearch - ? term + '*' - : '*' + term + '*') + ' sort:usages'; + const query = + (text.length < minLengthForPartialSearch + ? term + "*" + : "*" + term + "*") + " sort:usages"; return new Promise((resolve, reject) => { - TagList.search( - query, 0, this._options.maxResults, ['names', 'category', 'usages']) - .then( - response => resolve( - _tagListToMatches(response.results, this._options)), - reject); + TagList.search(query, 0, this._options.maxResults, [ + "names", + "category", + "usages", + ]).then( + (response) => + resolve( + _tagListToMatches(response.results, this._options) + ), + reject + ); }); }; diff --git a/client/js/controls/tag_input_control.js b/client/js/controls/tag_input_control.js index e0b1339..7d069f9 100644 --- a/client/js/controls/tag_input_control.js +++ b/client/js/controls/tag_input_control.js @@ -1,25 +1,25 @@ -'use strict'; +"use strict"; -const api = require('../api.js'); -const tags = require('../tags.js'); -const misc = require('../util/misc.js'); -const uri = require('../util/uri.js'); -const Tag = require('../models/tag.js'); -const settings = require('../models/settings.js'); -const events = require('../events.js'); -const views = require('../util/views.js'); -const TagAutoCompleteControl = require('./tag_auto_complete_control.js'); +const api = require("../api.js"); +const tags = require("../tags.js"); +const misc = require("../util/misc.js"); +const uri = require("../util/uri.js"); +const Tag = require("../models/tag.js"); +const settings = require("../models/settings.js"); +const events = require("../events.js"); +const views = require("../util/views.js"); +const TagAutoCompleteControl = require("./tag_auto_complete_control.js"); const KEY_SPACE = 32; const KEY_RETURN = 13; -const SOURCE_INIT = 'init'; -const SOURCE_IMPLICATION = 'implication'; -const SOURCE_USER_INPUT = 'user-input'; -const SOURCE_SUGGESTION = 'suggestions'; -const SOURCE_CLIPBOARD = 'clipboard'; +const SOURCE_INIT = "init"; +const SOURCE_IMPLICATION = "implication"; +const SOURCE_USER_INPUT = "user-input"; +const SOURCE_SUGGESTION = "suggestions"; +const SOURCE_CLIPBOARD = "clipboard"; -const template = views.getTemplate('tag-input'); +const template = views.getTemplate("tag-input"); function _fadeOutListItemNodeStatus(listItemNode) { if (listItemNode.classList.length) { @@ -28,8 +28,7 @@ function _fadeOutListItemNodeStatus(listItemNode) { } listItemNode.fadeTimeout = window.setTimeout(() => { while (listItemNode.classList.length) { - listItemNode.classList.remove( - listItemNode.classList.item(0)); + listItemNode.classList.remove(listItemNode.classList.item(0)); } listItemNode.fadeTimeout = null; }, 2500); @@ -51,7 +50,9 @@ class SuggestionList { } set(suggestion, weight) { - if (Object.prototype.hasOwnProperty.call(this._suggestions, suggestion)) { + if ( + Object.prototype.hasOwnProperty.call(this._suggestions, suggestion) + ) { weight = Math.max(weight, this._suggestions[suggestion]); } this._suggestions[suggestion] = weight; @@ -74,8 +75,8 @@ class SuggestionList { let nameDiff = a[0].localeCompare(b[0]); return weightDiff === 0 ? nameDiff : weightDiff; }); - return tuples.map(tuple => { - return {tagName: tuple[0], weight: tuple[1]}; + return tuples.map((tuple) => { + return { tagName: tuple[0], weight: tuple[1] }; }); } } @@ -91,45 +92,58 @@ class TagInputControl extends events.EventTarget { // dom const editAreaNode = template(); this._editAreaNode = editAreaNode; - this._tagInputNode = editAreaNode.querySelector('input'); - this._suggestionsNode = editAreaNode.querySelector('.tag-suggestions'); - this._tagListNode = editAreaNode.querySelector('ul.compact-tags'); + this._tagInputNode = editAreaNode.querySelector("input"); + this._suggestionsNode = editAreaNode.querySelector(".tag-suggestions"); + this._tagListNode = editAreaNode.querySelector("ul.compact-tags"); this._autoCompleteControl = new TagAutoCompleteControl( - this._tagInputNode, { + this._tagInputNode, + { getTextToFind: () => { return this._tagInputNode.value; }, - confirm: tag => { - this._tagInputNode.value = ''; + confirm: (tag) => { + this._tagInputNode.value = ""; // note: tags from autocomplete don't contain implications // so they need to be looked up in API this.addTagByName(tag.names[0], SOURCE_USER_INPUT); }, - delete: tag => { - this._tagInputNode.value = ''; + delete: (tag) => { + this._tagInputNode.value = ""; this.deleteTag(tag); }, verticalShift: -2, - isTaggedWith: tagName => this.tags.isTaggedWith(tagName), - }); + isTaggedWith: (tagName) => this.tags.isTaggedWith(tagName), + } + ); // dom events - this._tagInputNode.addEventListener( - 'keydown', e => this._evtInputKeyDown(e)); - this._tagInputNode.addEventListener( - 'paste', e => this._evtInputPaste(e)); - this._editAreaNode.querySelector('a.opacity').addEventListener( - 'click', e => this._evtToggleSuggestionsPopupOpacityClick(e)); - this._editAreaNode.querySelector('a.close').addEventListener( - 'click', e => this._evtCloseSuggestionsPopupClick(e)); - this._editAreaNode.querySelector('button').addEventListener( - 'click', e => this._evtAddTagButtonClick(e)); + this._tagInputNode.addEventListener("keydown", (e) => + this._evtInputKeyDown(e) + ); + this._tagInputNode.addEventListener("paste", (e) => + this._evtInputPaste(e) + ); + this._editAreaNode + .querySelector("a.opacity") + .addEventListener("click", (e) => + this._evtToggleSuggestionsPopupOpacityClick(e) + ); + this._editAreaNode + .querySelector("a.close") + .addEventListener("click", (e) => + this._evtCloseSuggestionsPopupClick(e) + ); + this._editAreaNode + .querySelector("button") + .addEventListener("click", (e) => this._evtAddTagButtonClick(e)); // show - this._hostNode.style.display = 'none'; + this._hostNode.style.display = "none"; this._hostNode.parentNode.insertBefore( - this._editAreaNode, hostNode.nextSibling); + this._editAreaNode, + hostNode.nextSibling + ); // add existing tags for (let tag of [...this.tags]) { @@ -139,7 +153,10 @@ class TagInputControl extends events.EventTarget { } addTagByText(text, source) { - for (let tagName of text.split(/\s+/).filter(word => word).reverse()) { + for (let tagName of text + .split(/\s+/) + .filter((word) => word) + .reverse()) { this.addTagByName(tagName, source); } } @@ -149,48 +166,60 @@ class TagInputControl extends events.EventTarget { if (!name) { return; } - return Tag.get(name).then(tag => { - return this.addTag(tag, source); - }, () => { - const tag = new Tag(); - tag.names = [name]; - tag.category = null; - return this.addTag(tag, source); - }); + return Tag.get(name).then( + (tag) => { + return this.addTag(tag, source); + }, + () => { + const tag = new Tag(); + tag.names = [name]; + tag.category = null; + return this.addTag(tag, source); + } + ); } addTag(tag, source) { if (source !== SOURCE_INIT && this.tags.isTaggedWith(tag.names[0])) { const listItemNode = this._getListItemNode(tag); if (source !== SOURCE_IMPLICATION) { - listItemNode.classList.add('duplicate'); + listItemNode.classList.add("duplicate"); _fadeOutListItemNodeStatus(listItemNode); } return Promise.resolve(); } - return this.tags.addByName(tag.names[0], false).then(() => { - const listItemNode = this._createListItemNode(tag); - if (!tag.category) { - listItemNode.classList.add('new'); - } - if (source === SOURCE_IMPLICATION) { - listItemNode.classList.add('implication'); - } - this._tagListNode.prependChild(listItemNode); - _fadeOutListItemNodeStatus(listItemNode); + return this.tags + .addByName(tag.names[0], false) + .then(() => { + const listItemNode = this._createListItemNode(tag); + if (!tag.category) { + listItemNode.classList.add("new"); + } + if (source === SOURCE_IMPLICATION) { + listItemNode.classList.add("implication"); + } + this._tagListNode.prependChild(listItemNode); + _fadeOutListItemNodeStatus(listItemNode); - return Promise.all( - tag.implications.map( - implication => this.addTagByName( - implication.names[0], SOURCE_IMPLICATION))); - }).then(() => { - this.dispatchEvent(new CustomEvent('add', { - detail: {tag: tag, source: source}, - })); - this.dispatchEvent(new CustomEvent('change')); - return Promise.resolve(); - }); + return Promise.all( + tag.implications.map((implication) => + this.addTagByName( + implication.names[0], + SOURCE_IMPLICATION + ) + ) + ); + }) + .then(() => { + this.dispatchEvent( + new CustomEvent("add", { + detail: { tag: tag, source: source }, + }) + ); + this.dispatchEvent(new CustomEvent("change")); + return Promise.resolve(); + }); } deleteTag(tag) { @@ -202,25 +231,27 @@ class TagInputControl extends events.EventTarget { this._deleteListItemNode(tag); - this.dispatchEvent(new CustomEvent('remove', { - detail: {tag: tag}, - })); - this.dispatchEvent(new CustomEvent('change')); + this.dispatchEvent( + new CustomEvent("remove", { + detail: { tag: tag }, + }) + ); + this.dispatchEvent(new CustomEvent("change")); } _evtInputPaste(e) { e.preventDefault(); - const pastedText = window.clipboardData ? - window.clipboardData.getData('Text') : - (e.originalEvent || e).clipboardData.getData('text/plain'); + const pastedText = window.clipboardData + ? window.clipboardData.getData("Text") + : (e.originalEvent || e).clipboardData.getData("text/plain"); if (pastedText.length > 2000) { - window.alert('Pasted text is too long.'); + window.alert("Pasted text is too long."); return; } this._hideAutoComplete(); this.addTagByText(pastedText, SOURCE_CLIPBOARD); - this._tagInputNode.value = ''; + this._tagInputNode.value = ""; } _evtCloseSuggestionsPopupClick(e) { @@ -231,7 +262,7 @@ class TagInputControl extends events.EventTarget { _evtAddTagButtonClick(e) { e.preventDefault(); this.addTagByName(this._tagInputNode.value, SOURCE_USER_INPUT); - this._tagInputNode.value = ''; + this._tagInputNode.value = ""; } _evtToggleSuggestionsPopupOpacityClick(e) { @@ -244,36 +275,41 @@ class TagInputControl extends events.EventTarget { e.preventDefault(); this._hideAutoComplete(); this.addTagByText(this._tagInputNode.value, SOURCE_USER_INPUT); - this._tagInputNode.value = ''; + this._tagInputNode.value = ""; } } _createListItemNode(tag) { - const className = tag.category ? - misc.makeCssName(tag.category, 'tag') : - null; + const className = tag.category + ? misc.makeCssName(tag.category, "tag") + : null; - const tagLinkNode = document.createElement('a'); + const tagLinkNode = document.createElement("a"); if (className) { tagLinkNode.classList.add(className); } tagLinkNode.setAttribute( - 'href', uri.formatClientLink('tag', tag.names[0])); + "href", + uri.formatClientLink("tag", tag.names[0]) + ); - const tagIconNode = document.createElement('i'); - tagIconNode.classList.add('fa'); - tagIconNode.classList.add('fa-tag'); + const tagIconNode = document.createElement("i"); + tagIconNode.classList.add("fa"); + tagIconNode.classList.add("fa-tag"); tagLinkNode.appendChild(tagIconNode); - const searchLinkNode = document.createElement('a'); + const searchLinkNode = document.createElement("a"); if (className) { searchLinkNode.classList.add(className); } searchLinkNode.setAttribute( - 'href', uri.formatClientLink( - 'posts', {query: uri.escapeColons(tag.names[0])})); - searchLinkNode.textContent = tag.names[0] + ' '; - searchLinkNode.addEventListener('click', e => { + "href", + uri.formatClientLink("posts", { + query: uri.escapeColons(tag.names[0]), + }) + ); + searchLinkNode.textContent = tag.names[0] + " "; + searchLinkNode.addEventListener("click", (e) => { e.preventDefault(); this._suggestions.clear(); if (tag.postCount > 0) { @@ -284,20 +320,20 @@ class TagInputControl extends events.EventTarget { } }); - const usagesNode = document.createElement('span'); - usagesNode.classList.add('tag-usages'); - usagesNode.setAttribute('data-pseudo-content', tag.postCount); + const usagesNode = document.createElement("span"); + usagesNode.classList.add("tag-usages"); + usagesNode.setAttribute("data-pseudo-content", tag.postCount); - const removalLinkNode = document.createElement('a'); - removalLinkNode.classList.add('remove-tag'); - removalLinkNode.setAttribute('href', ''); - removalLinkNode.setAttribute('data-pseudo-content', '×'); - removalLinkNode.addEventListener('click', e => { + const removalLinkNode = document.createElement("a"); + removalLinkNode.classList.add("remove-tag"); + removalLinkNode.setAttribute("href", ""); + removalLinkNode.setAttribute("data-pseudo-content", "×"); + removalLinkNode.addEventListener("click", (e) => { e.preventDefault(); this.deleteTag(tag); }); - const listItemNode = document.createElement('li'); + const listItemNode = document.createElement("li"); listItemNode.appendChild(removalLinkNode); listItemNode.appendChild(tagLinkNode); listItemNode.appendChild(searchLinkNode); @@ -327,20 +363,25 @@ class TagInputControl extends events.EventTarget { if (!browsingSettings.tagSuggestions) { return; } - api.get( - uri.formatApiLink('tag-siblings', tag.names[0]), - {noProgress: true}) - .then(response => { - return Promise.resolve(response.results); - }, response => { - return Promise.resolve([]); - }).then(siblings => { - const args = siblings.map(s => s.occurrences); + api.get(uri.formatApiLink("tag-siblings", tag.names[0]), { + noProgress: true, + }) + .then( + (response) => { + return Promise.resolve(response.results); + }, + (response) => { + return Promise.resolve([]); + } + ) + .then((siblings) => { + const args = siblings.map((s) => s.occurrences); let maxSiblingOccurrences = Math.max(1, ...args); for (let sibling of siblings) { this._suggestions.set( sibling.tag.names[0], - sibling.occurrences * 4.9 / maxSiblingOccurrences); + (sibling.occurrences * 4.9) / maxSiblingOccurrences + ); } for (let suggestion of tag.suggestions || []) { this._suggestions.set(suggestion, 5); @@ -354,10 +395,10 @@ class TagInputControl extends events.EventTarget { } _refreshSuggestionsPopup() { - if (!this._suggestionsNode.classList.contains('shown')) { + if (!this._suggestionsNode.classList.contains("shown")) { return; } - const listNode = this._suggestionsNode.querySelector('ul'); + const listNode = this._suggestionsNode.querySelector("ul"); listNode.scrollTop = 0; while (listNode.firstChild) { listNode.removeChild(listNode.firstChild); @@ -369,35 +410,36 @@ class TagInputControl extends events.EventTarget { continue; } - const addLinkNode = document.createElement('a'); + const addLinkNode = document.createElement("a"); addLinkNode.textContent = tagName; - addLinkNode.classList.add('add-tag'); - addLinkNode.setAttribute('href', ''); - Tag.get(tagName).then(tag => { + addLinkNode.classList.add("add-tag"); + addLinkNode.setAttribute("href", ""); + Tag.get(tagName).then((tag) => { addLinkNode.classList.add( - misc.makeCssName(tag.category, 'tag')); + misc.makeCssName(tag.category, "tag") + ); }); - addLinkNode.addEventListener('click', e => { + addLinkNode.addEventListener("click", (e) => { e.preventDefault(); listNode.removeChild(listItemNode); this.addTagByName(tagName, SOURCE_SUGGESTION); }); - const weightNode = document.createElement('span'); - weightNode.classList.add('tag-weight'); - weightNode.setAttribute('data-pseudo-content', weight); + const weightNode = document.createElement("span"); + weightNode.classList.add("tag-weight"); + weightNode.setAttribute("data-pseudo-content", weight); - const removalLinkNode = document.createElement('a'); - removalLinkNode.classList.add('remove-tag'); - removalLinkNode.setAttribute('href', ''); - removalLinkNode.setAttribute('data-pseudo-content', '×'); - removalLinkNode.addEventListener('click', e => { + const removalLinkNode = document.createElement("a"); + removalLinkNode.classList.add("remove-tag"); + removalLinkNode.setAttribute("href", ""); + removalLinkNode.setAttribute("data-pseudo-content", "×"); + removalLinkNode.addEventListener("click", (e) => { e.preventDefault(); listNode.removeChild(listItemNode); this._suggestions.ban(tagName); }); - const listItemNode = document.createElement('li'); + const listItemNode = document.createElement("li"); listItemNode.appendChild(removalLinkNode); listItemNode.appendChild(weightNode); listItemNode.appendChild(addLinkNode); @@ -407,19 +449,19 @@ class TagInputControl extends events.EventTarget { _closeSuggestionsPopup() { this._suggestions.clear(); - this._suggestionsNode.classList.remove('shown'); + this._suggestionsNode.classList.remove("shown"); } _removeSuggestionsPopupOpacity() { - this._suggestionsNode.classList.remove('translucent'); + this._suggestionsNode.classList.remove("translucent"); } _toggleSuggestionsPopupOpacity() { - this._suggestionsNode.classList.toggle('translucent'); + this._suggestionsNode.classList.toggle("translucent"); } _openSuggestionsPopup() { - this._suggestionsNode.classList.add('shown'); + this._suggestionsNode.classList.add("shown"); this._refreshSuggestionsPopup(); } |