From b4662f1523f2dc0e0903322568a43651f83199bb Mon Sep 17 00:00:00 2001 From: eight Date: Sat, 1 Feb 2020 07:26:30 +0800 Subject: [PATCH] Breaking: add router, keep search params --- content/apply.js | 6 +++- js/router.js | 74 ++++++++++++++++++++++++++++++++++++++++++++++ manage.html | 1 + manage/filters.js | 36 ++++++++++++++-------- manage/manage.js | 70 ++++++++++++++++++------------------------- options/options.js | 4 +-- popup/popup.js | 2 +- 7 files changed, 135 insertions(+), 58 deletions(-) create mode 100644 js/router.js diff --git a/content/apply.js b/content/apply.js index 2fcd2260..02c4f6be 100644 --- a/content/apply.js +++ b/content/apply.js @@ -265,7 +265,11 @@ const APPLY = (() => { if (IS_OWN_PAGE) { // FIXME: currently we only do this in our own page. Is it safe to do // it on all pages? - history.pushState({preUrl: location.href}, null, ' '); + try { + // history.replaceState(null, null, ' '); + // eslint-disable-next-line no-undef + router.updateHash(''); + } catch (err) {} } break; } diff --git a/js/router.js b/js/router.js new file mode 100644 index 00000000..38d74246 --- /dev/null +++ b/js/router.js @@ -0,0 +1,74 @@ +/* global deepEqual */ +/* exported router */ +'use strict'; + +const router = (() => { + // FIXME: this only works with one history + const buffer = []; + const watchers = []; + document.addEventListener('DOMContentLoaded', () => update()); + window.addEventListener('popstate', () => update()); + window.addEventListener('hashchange', () => update()); + return {watch, updateSearch, getSearch, updateHash}; + + function watch(options, callback) { + watchers.push({options, callback}); + } + + function updateSearch(key, value) { + const search = new URLSearchParams(location.search.replace(/^\?/, '')); + if (!value) { + search.delete(key); + } else { + search.set(key, value); + } + const finalSearch = search.toString(); + if (finalSearch) { + history.replaceState(history.state, null, `?${finalSearch}${location.hash}`); + } else { + history.replaceState(history.state, null, `${location.pathname}${location.hash}`); + } + update(true); + } + + function updateHash(hash) { + if (buffer.length > 1) { + if (!hash && !buffer[buffer.length - 2].includes('#') || buffer[buffer.length - 2].endsWith(hash)) { + buffer.pop(); + history.back(); + return; + } + } + if (!hash) { + hash = ' '; + } + history.pushState(history.state, null, hash); + update(); + } + + function getSearch(key) { + return new URLSearchParams(location.search.replace(/^\?/, '')).get(key); + } + + function update(replace) { + if (!buffer.length || buffer[buffer.length - 1] !== location.href && !replace) { + buffer.push(location.href); + } else if (buffer.length && replace) { + buffer[buffer.length - 1] = location.href; + } + for (const {options, callback} of watchers) { + let state; + if (options.hash) { + state = options.hash === location.hash; + } else if (options.search) { + // TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425) + const search = new URLSearchParams(location.search.replace(/^\?/, '')); + state = options.search.map(key => search.get(key)); + } + if (!deepEqual(state, options.currentState)) { + options.currentState = state; + callback(state); + } + } + } +})(); diff --git a/manage.html b/manage.html index bbf58fc7..1134343a 100644 --- a/manage.html +++ b/manage.html @@ -150,6 +150,7 @@ + diff --git a/manage/filters.js b/manage/filters.js index b09c3193..04212cbc 100644 --- a/manage/filters.js +++ b/manage/filters.js @@ -1,4 +1,4 @@ -/* global installed messageBox sorter $ $$ $create t debounce prefs API onDOMready */ +/* global installed messageBox sorter $ $$ $create t debounce prefs API router */ /* exported filterAndAppend */ 'use strict'; @@ -9,11 +9,18 @@ const filtersSelector = { numTotal: 0, }; -// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425) -const urlFilterParam = new URLSearchParams(location.search.replace(/^\?/, '')).get('url'); -if (location.search) { - history.replaceState(0, document.title, location.origin + location.pathname); -} +let initialized = false; + +router.watch({search: ['search']}, ([search]) => { + if (search != null) { + $('#search').value = search; + } + if (!initialized) { + init(); + } else { + searchStyles(); + } +}); HTMLSelectElement.prototype.adjustWidth = function () { const option0 = this.selectedOptions[0]; @@ -30,11 +37,11 @@ HTMLSelectElement.prototype.adjustWidth = function () { parent.replaceChild(this, singleSelect); }; -onDOMready().then(() => { - $('#search').oninput = searchStyles; - if (urlFilterParam) { - $('#search').value = 'url:' + urlFilterParam; - } +function init() { + $('#search').oninput = e => { + router.updateSearch('search', e.target.value); + }; + $('#search-help').onclick = event => { event.preventDefault(); messageBox({ @@ -120,6 +127,7 @@ onDOMready().then(() => { } } filterOnChange({forceRefilter: true}); + router.updateSearch('search', ''); }; // Adjust width after selects are visible @@ -130,8 +138,10 @@ onDOMready().then(() => { } }); + initialized = true; + filterOnChange({forceRefilter: true}); -}); +} function filterOnChange({target: el, forceRefilter}) { @@ -271,7 +281,7 @@ function showFiltersStats() { } -function searchStyles({immediately, container}) { +function searchStyles({immediately, container} = {}) { const el = $('#search'); const query = el.value.trim(); if (query === el.lastValue && !immediately && !container) { diff --git a/manage/manage.js b/manage/manage.js index ed9ff34c..455a9029 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -1,13 +1,13 @@ /* global messageBox getStyleWithNoCode - filterAndAppend urlFilterParam showFiltersStats + filterAndAppend showFiltersStats checkUpdate handleUpdateInstalled objectDiff configDialog sorter msg prefs API onDOMready $ $$ $create template setupLivePrefs URLS enforceInputRange t tWordBreak formatDate getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce - scrollElementIntoView CHROME VIVALDI FIREFOX + scrollElementIntoView CHROME VIVALDI FIREFOX router */ 'use strict'; @@ -35,7 +35,8 @@ const handleEvent = {}; Promise.all([ API.getAllStyles(true), - urlFilterParam && API.searchDB({query: 'url:' + urlFilterParam}), + // FIXME: integrate this into filter.js + router.getSearch('search') && API.searchDB({query: router.getSearch('search')}), Promise.all([ onDOMready(), prefs.initializing, @@ -58,10 +59,6 @@ Promise.all([ msg.onExtension(onRuntimeMessage); function onRuntimeMessage(msg) { - if (msg === 'options-open' || msg === 'options-close') { - toggleOptions(msg); - return; - } switch (msg.method) { case 'styleUpdated': case 'styleAdded': @@ -84,8 +81,9 @@ function onRuntimeMessage(msg) { function initGlobalEvents() { installed = $('#installed'); installed.onclick = handleEvent.entryClicked; - $('#manage-options-button').onclick = () => - history.replaceState('', document.title, location.href + '#stylus-options'); + $('#manage-options-button').onclick = () => { + router.updateHash('#stylus-options'); + }; { const btn = $('#manage-shortcuts-button'); btn.onclick = btn.onclick || (() => openURL({url: URLS.configureCommands})); @@ -717,41 +715,31 @@ function embedOptions() { } } - -function removeOptions() { +function unembedOptions() { const options = $('#stylus-embedded-options'); - if (options) options.remove(); -} + if (options) { + options.contentWindow.document.body.classList.add('scaleout'); + options.classList.add('fadeout'); + animateElement(options, { + className: 'fadeout', + onComplete: removeOptions, + }); + } -// wait for possible filter params to be removed -onDOMready().then(() => { - chrome.tabs.onUpdated.addListener((tabId, changeInfo) => { - if (changeInfo.url) { - if (location.hash === '#stylus-options') { - embedOptions(); - } else { - const options = $('#stylus-embedded-options'); - if (options) { - options.contentWindow.document.body.classList.add('scaleout'); - options.classList.add('fadeout'); - animateElement(options, { - className: 'fadeout', - onComplete: removeOptions, - }); - } - } - } - }); -}); - - -function toggleOptions(msg) { - if (msg === 'options-open' && location.hash !== '#stylus-options') { - history.replaceState('', document.title, location.href + '#stylus-options'); - } else if (msg === 'options-close' && location.hash === '#stylus-options') { - history.replaceState('', document.title, location.origin + location.pathname); + function removeOptions() { + const options = $('#stylus-embedded-options'); + if (options) options.remove(); } } +router.watch({hash: '#stylus-options'}, state => { + if (state) { + embedOptions(); + } else { + unembedOptions(); + } +}); -if (location.hash === '#stylus-options') embedOptions(); +window.addEventListener('closeOptions', () => { + router.updateHash(''); +}); diff --git a/options/options.js b/options/options.js index 6feb02c4..2a849146 100644 --- a/options/options.js +++ b/options/options.js @@ -40,7 +40,7 @@ if (FIREFOX && 'update' in (chrome.commands || {})) { // actions $('#options-close-icon').onclick = () => { - top.history.replaceState('', top.document.title, top.location.origin + top.location.pathname); + top.dispatchEvent(new CustomEvent('closeOptions')); }; document.onclick = e => { @@ -299,7 +299,7 @@ function customizeHotkeys() { window.onkeydown = event => { if (event.keyCode === 27) { - top.history.replaceState('', top.document.title, top.location.origin + top.location.pathname); + top.dispatchEvent(new CustomEvent('closeOptions')); } }; diff --git a/popup/popup.js b/popup/popup.js index af72688c..da54f588 100644 --- a/popup/popup.js +++ b/popup/popup.js @@ -593,7 +593,7 @@ Object.assign(handleEvent, { if (!this.eventHandled) { this.eventHandled = true; this.dataset.href += event.shiftKey || event.button === 2 ? - '?url=' + encodeURIComponent(tabURL) : ''; + '?search=' + encodeURIComponent(`url:${tabURL}`) : ''; handleEvent.openURLandHide.call(this, event); } },