From 4936426fa3beac8c07c8566bb414be8c5eba9a8f Mon Sep 17 00:00:00 2001 From: tophf Date: Sat, 25 Mar 2017 08:54:58 +0300 Subject: [PATCH] dom.js: extract common DOM functions --- .eslintrc | 6 +++++ backup/fileSaveLoad.js | 19 ++++++------- dom.js | 61 ++++++++++++++++++++++++++++++++++++++++++ manage.html | 1 + manage.js | 53 +++++------------------------------- messaging.js | 13 --------- msgbox/confirm.js | 5 +--- msgbox/msgbox.js | 7 +---- popup.html | 1 + popup.js | 19 ------------- 10 files changed, 88 insertions(+), 97 deletions(-) create mode 100644 dom.js diff --git a/.eslintrc b/.eslintrc index c32b677b..a646543e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -35,6 +35,12 @@ globals: importStyles: true getActiveTabRealURL: true openURL: true + $: true + $$: true + animateElement: true + scrollElementIntoView: true + getClickedStyleElement: true + getClickedStyleId: true onDOMready: true getDomains: true webSqlStorage: true diff --git a/backup/fileSaveLoad.js b/backup/fileSaveLoad.js index 7d7528ae..f835bb47 100644 --- a/backup/fileSaveLoad.js +++ b/backup/fileSaveLoad.js @@ -1,9 +1,10 @@ -/* globals getStyles, saveStyle, invalidateCache, refreshAllTabs, handleUpdate */ +/* globals getStyles, saveStyle, invalidateCache, refreshAllTabs */ 'use strict'; const STYLISH_DUMP_FILE_EXT = '.txt'; const STYLUS_BACKUP_FILE_EXT = '.json'; + function importFromFile({fileTypeFilter, file} = {}) { return new Promise(resolve => { const fileInput = document.createElement('input'); @@ -44,6 +45,7 @@ function importFromFile({fileTypeFilter, file} = {}) { }); } + function importFromString(jsonString) { const json = runTryCatch(() => Array.from(JSON.parse(jsonString))) || []; const oldStyles = json.length && deepCopyStyles(); @@ -177,14 +179,14 @@ function importFromString(jsonString) { } function bindClick(box) { - for (let block of [...box.querySelectorAll('details')]) { + for (let block of $$('details')) { if (block.dataset.id != 'invalid') { block.style.cursor = 'pointer'; block.onclick = event => { const styleElement = $(`[style-id="${event.target.dataset.id}"]`); if (styleElement) { scrollElementIntoView(styleElement); - highlightElement(styleElement); + animateElement(styleElement, {className: 'highlight'}); } }; } @@ -221,7 +223,8 @@ function importFromString(jsonString) { } } -document.getElementById('file-all-styles').onclick = () => { + +$('#file-all-styles').onclick = () => { getStyles({}, function (styles) { const text = JSON.stringify(styles, null, '\t'); const fileName = generateFileName(); @@ -248,7 +251,7 @@ document.getElementById('file-all-styles').onclick = () => { }; -document.getElementById('unfile-all-styles').onclick = () => { +$('#unfile-all-styles').onclick = () => { importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT}); }; @@ -264,11 +267,9 @@ Object.assign(document.body, { } }, ondragend(event) { - this.classList.add('fadeout'); - this.addEventListener('animationend', function _() { - this.removeEventListener('animationend', _); + animateElement(this, {className: 'fadeout'}).then(() => { this.style.animationDuration = ''; - this.classList.remove('dropzone', 'fadeout'); + this.classList.remove('dropzone'); }); }, ondragleave(event) { diff --git a/dom.js b/dom.js new file mode 100644 index 00000000..403d1449 --- /dev/null +++ b/dom.js @@ -0,0 +1,61 @@ +'use strict'; + +function onDOMready() { + if (document.readyState != 'loading') { + return Promise.resolve(); + } + return new Promise(resolve => { + document.addEventListener('DOMContentLoaded', function _() { + document.removeEventListener('DOMContentLoaded', _); + resolve(); + }); + }); +} + + +function getClickedStyleId(event) { + return (getClickedStyleElement(event) || {}).styleId; +} + + +function getClickedStyleElement(event) { + return event.target.closest('.entry'); +} + + +function scrollElementIntoView(element) { + // align to the top/bottom of the visible area if wasn't visible + const bounds = element.getBoundingClientRect(); + if (bounds.top < 0 || bounds.top > innerHeight - bounds.height) { + element.scrollIntoView(bounds.top < 0); + } +} + + +function animateElement(element, {className, remove = false}) { + return new Promise(resolve => { + element.addEventListener('animationend', function _() { + element.removeEventListener('animationend', _); + element.classList.remove(className); + // TODO: investigate why animation restarts if the elements is removed in .then() + if (remove) { + element.remove(); + } + resolve(); + }); + element.classList.add(className); + }); +} + + +function $(selector, base = document) { + // we have ids with . like #manage.onlyEdited which look like #id.class + // so since getElementById is superfast we'll try it anyway + const byId = selector.startsWith('#') && document.getElementById(selector.slice(1)); + return byId || base.querySelector(selector); +} + + +function $$(selector, base = document) { + return [...base.querySelectorAll(selector)]; +} diff --git a/manage.html b/manage.html index 4726eeec..7e39d6d6 100644 --- a/manage.html +++ b/manage.html @@ -53,6 +53,7 @@ + diff --git a/manage.js b/manage.js index f6c4ed1c..6b19ea37 100644 --- a/manage.js +++ b/manage.js @@ -230,7 +230,7 @@ class EntryOnClick { function handleUpdate(style, {reason} = {}) { const element = createStyleElement(style); const oldElement = $(`[style-id="${style.id}"]`, installed); - highlightElement(element); + animateElement(element, {className: 'highlight'}); if (!oldElement) { installed.appendChild(element); } else { @@ -260,12 +260,11 @@ function applyUpdateAll() { btnApply.disabled = false; }, 1000); - [...document.querySelectorAll('.can-update .update')] - .forEach(button => { - // align to the bottom of the visible area if wasn't visible - button.scrollIntoView(false); - button.click(); - }); + $$('.can-update .update').forEach(button => { + // align to the bottom of the visible area if wasn't visible + button.scrollIntoView(false); + button.click(); + }); } @@ -278,8 +277,7 @@ function checkUpdateAll() { btnApply.classList.add('hidden'); noUpdates.classList.add('hidden'); - const elements = document.querySelectorAll('[style-update-url]'); - Promise.all([...elements].map(checkUpdate)) + Promise.all($$('[style-update-url]').map(checkUpdate)) .then(updatables => { btnCheck.disabled = false; if (updatables.includes(true)) { @@ -445,43 +443,6 @@ function searchStyles(immediately, bin) { } -function getClickedStyleId(event) { - return (getClickedStyleElement(event) || {}).styleId; -} - - -function getClickedStyleElement(event) { - return event.target.closest('.entry'); -} - - -function scrollElementIntoView(element) { - // align to the top/bottom of the visible area if wasn't visible - const bounds = element.getBoundingClientRect(); - if (bounds.top < 0 || bounds.top > innerHeight - bounds.height) { - element.scrollIntoView(bounds.top < 0); - } -} - - -function highlightElement(element) { - element.addEventListener('animationend', function _() { - element.removeEventListener('animationend', _); - element.classList.remove('highlight'); - }); - element.classList.add('highlight'); -} - - function rememberScrollPosition() { history.replaceState({scrollY}, document.title); } - - -function $(selector, base = document) { - if (selector.startsWith('#') && /^#[^,\s]+$/.test(selector)) { - return document.getElementById(selector.slice(1)); - } else { - return base.querySelector(selector); - } -} diff --git a/messaging.js b/messaging.js index f3f24364..dd09681f 100644 --- a/messaging.js +++ b/messaging.js @@ -163,19 +163,6 @@ function openURL({url}) { } -function onDOMready() { - if (document.readyState != 'loading') { - return Promise.resolve(); - } - return new Promise(resolve => { - document.addEventListener('DOMContentLoaded', function _() { - document.removeEventListener('DOMContentLoaded', _); - resolve(); - }); - }); -} - - function stringAsRegExp(s, flags) { return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, '\\$&'), flags); } diff --git a/msgbox/confirm.js b/msgbox/confirm.js index 2094c5f3..f77cc489 100644 --- a/msgbox/confirm.js +++ b/msgbox/confirm.js @@ -29,11 +29,8 @@ function confirmDelete(event, {float = false} = {}) { function doDelete(confirmed) { window.removeEventListener('keydown', onKey); window.removeEventListener('scroll', preventScroll); - box.classList.add('lights-on'); - box.addEventListener('animationend', function _() { - box.removeEventListener('animationend', _); + animateElement(box, {className: 'lights-on'}).then(() => { box.dataset.display = false; - box.classList.remove('lights-on'); }); Promise.resolve(confirmed && deleteStyle(id)) .then(resolveMe); diff --git a/msgbox/msgbox.js b/msgbox/msgbox.js index 3cafe8e6..af5f3617 100644 --- a/msgbox/msgbox.js +++ b/msgbox/msgbox.js @@ -31,12 +31,7 @@ function messageBox({title, contents, buttons, onclick}) { || event.type == 'click' || event.keyCode == 27 && !event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey) && messageBox.element) { - const box = messageBox.element; - box.classList.add('fadeout'); - box.addEventListener('animationend', function _() { - box.removeEventListener('animationend', _); - box.remove(); - }); + animateElement(messageBox.element, {className: 'fadeout', remove: true}); document.removeEventListener('keydown', messageBox.close); $(`#${id}-buttons`).onclick = null; messageBox.element = null; diff --git a/popup.html b/popup.html index d0084d96..9ef10321 100644 --- a/popup.html +++ b/popup.html @@ -43,6 +43,7 @@ + diff --git a/popup.js b/popup.js index 01e86ba6..079737b1 100644 --- a/popup.js +++ b/popup.js @@ -199,16 +199,6 @@ class EntryOnClick { } -function getClickedStyleId(event) { - return (getClickedStyleElement(event) || {}).styleId; -} - - -function getClickedStyleElement(event) { - return event.target.closest('.entry'); -} - - function openLinkInTabOrWindow(event) { if (!prefs.get('openEditInWindow', false)) { openURLandHide(event); @@ -272,12 +262,3 @@ function handleDelete(id) { installed.removeChild(styleElement); } } - - -function $(selector, base = document) { - if (selector.startsWith('#') && /^#[^,\s]+$/.test(selector)) { - return document.getElementById(selector.slice(1)); - } else { - return base.querySelector(selector); - } -}