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);
}
},