diff --git a/content/install-user-css.css b/content/install-user-css.css
index 4cba66ab..8d8c030d 100644
--- a/content/install-user-css.css
+++ b/content/install-user-css.css
@@ -44,8 +44,15 @@ h1 small {
margin: 15px 0;
}
-.actions > * {
- display: inline-block;
+.live-reload {
+ width: fit-content;
+ display: flex;
+ align-items: center;
+ margin: 0.5em auto;
+}
+
+.live-reload input {
+ margin: 0 0.5em 0 0;
}
.external {
@@ -68,3 +75,7 @@ button.install {
font-family: monospace;
white-space: pre-wrap;
}
+
+.main {
+ flex-grow: 1;
+}
diff --git a/content/install-user-css.js b/content/install-user-css.js
index 2cd17e95..8dce7539 100644
--- a/content/install-user-css.js
+++ b/content/install-user-css.js
@@ -4,17 +4,6 @@
let pendingResource;
-function fetchText(url) {
- return new Promise((resolve, reject) => {
- // you can't use fetch in Chrome under 'file:' protocol
- const xhr = new XMLHttpRequest();
- xhr.open('GET', url);
- xhr.addEventListener('load', () => resolve(xhr.responseText));
- xhr.addEventListener('error', () => reject(xhr));
- xhr.send();
- });
-}
-
function install(style) {
const request = Object.assign(style, {
method: 'saveUsercss',
@@ -22,11 +11,12 @@ function install(style) {
updateUrl: location.href
});
return communicate(request)
- .then(() => {
+ .then(result => {
$$('.warning')
.forEach(el => el.remove());
$('button.install').textContent = 'Installed';
$('button.install').disabled = true;
+ window.dispatchEvent(new CustomEvent('installed', {detail: result}));
})
.catch(err => {
alert(chrome.i18n.getMessage('styleInstallFailed', String(err)));
@@ -62,7 +52,7 @@ function getAppliesTo(style) {
return result;
}
-function initInstallPage({style, dup}) {
+function initInstallPage({style, dup}, sourceLoader) {
return pendingResource.then(() => {
const versionTest = dup && semverCompare(style.version, dup.version);
document.body.innerHTML = '';
@@ -88,7 +78,9 @@ function initInstallPage({style, dup}) {
Support
-
+
`));
if (versionTest < 0) {
@@ -109,9 +101,64 @@ function initInstallPage({style, dup}) {
install(style);
}
};
+
+ if (location.protocol === 'file:') {
+ initLiveReload(sourceLoader);
+ }
});
}
+function initLiveReload(sourceLoader) {
+ let installed;
+ const watcher = sourceLoader.watch(source => {
+ $('.code').textContent = source;
+ return communicate({
+ method: 'saveUsercss',
+ id: installed.id,
+ source: source
+ }).then(() => {
+ $$('.main .warning').forEach(e => e.remove());
+ }).catch(err => {
+ const oldWarning = $('.main .warning');
+ // FIXME: i18n
+ const warning = tHTML(`
+
+ Stylus failed to parse usercss:
+
${err}
+
+ `);
+ if (oldWarning) {
+ oldWarning.replaceWith(warning);
+ } else {
+ $('.main').prepend(warning);
+ }
+ });
+ });
+ window.addEventListener('installed', ({detail: {style}}) => {
+ installed = style;
+ if ($('.live-reload-checkbox').checked) {
+ watcher.start();
+ }
+ });
+ // FIXME: i18n
+ $('.actions').append(tHTML(`
+
+ `));
+ $('.live-reload-checkbox').onchange = e => {
+ if (!installed) {
+ return;
+ }
+ if (e.target.checked) {
+ watcher.start();
+ } else {
+ watcher.stop();
+ }
+ };
+}
+
function initErrorPage(err, source) {
return pendingResource.then(() => {
document.body.innerHTML = '';
@@ -127,8 +174,65 @@ function initErrorPage(err, source) {
});
}
-function initUsercssInstall() {
+function createSourceLoader() {
let source;
+
+ function fetchText(url) {
+ return new Promise((resolve, reject) => {
+ // you can't use fetch in Chrome under 'file:' protocol
+ const xhr = new XMLHttpRequest();
+ xhr.open('GET', url);
+ xhr.addEventListener('load', () => resolve(xhr.responseText));
+ xhr.addEventListener('error', () => reject(xhr));
+ xhr.send();
+ });
+ }
+
+ function load() {
+ return fetchText(location.href)
+ .then(_source => {
+ source = _source;
+ return source;
+ });
+ }
+
+ function watch(cb) {
+ let timer;
+ const DELAY = 1000;
+
+ function start() {
+ if (timer) {
+ return;
+ }
+ timer = setTimeout(check, DELAY);
+ }
+
+ function stop() {
+ clearTimeout(timer);
+ timer = null;
+ }
+
+ function check() {
+ fetchText(location.href)
+ .then(_source => {
+ if (source !== _source) {
+ source = _source;
+ return cb(source);
+ }
+ })
+ .catch(console.log)
+ .then(() => {
+ timer = setTimeout(check, DELAY);
+ });
+ }
+
+ return {start, stop};
+ }
+
+ return {load, watch, source: () => source};
+}
+
+function initUsercssInstall() {
pendingResource = communicate({
method: 'injectResource',
resources: [
@@ -139,17 +243,18 @@ function initUsercssInstall() {
'/content/install-user-css.css'
]
});
- fetchText(location.href)
- .then(_source => {
- source = _source;
- return communicate({
+
+ const sourceLoader = createSourceLoader();
+ sourceLoader.load()
+ .then(() =>
+ communicate({
method: 'filterUsercss',
- source,
+ source: sourceLoader.source(),
checkDup: true
- });
- })
- .then(initInstallPage)
- .catch(err => initErrorPage(err, source));
+ })
+ )
+ .then(result => initInstallPage(result, sourceLoader))
+ .catch(err => initErrorPage(err, sourceLoader.source()));
}
function isUsercss() {