diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 95c6ce81..793003bb 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -393,6 +393,10 @@
"message": "Install",
"description": "Label for install button"
},
+ "installButtonInstalled": {
+ "message": "Installed",
+ "description": "Text displayed when the style is successfully installed"
+ },
"installButtonUpdate": {
"message": "Update",
"description": "Label for update button"
diff --git a/content/install-user-css.js b/content/install-user-css.js
index 29d7bc47..dc542c5c 100644
--- a/content/install-user-css.js
+++ b/content/install-user-css.js
@@ -68,7 +68,10 @@ function createSourceLoader() {
}
function initUsercssInstall() {
- const pendingSource = createSourceLoader().load();
+ const sourceLoader = createSourceLoader();
+ const pendingSource = sourceLoader.load();
+ let watcher;
+
chrome.runtime.onConnect.addListener(port => {
// FIXME: is this the correct way to reject a connection?
// https://developer.chrome.com/extensions/messaging#connect
@@ -83,6 +86,19 @@ function initUsercssInstall() {
port.postMessage({method: msg.method + 'Response', error: err.message || String(err)})
);
break;
+
+ case 'liveReloadStart':
+ if (!watcher) {
+ watcher = sourceLoader.watch(sourceCode => {
+ port.postMessage({method: 'sourceCodeChanged', sourceCode});
+ });
+ }
+ watcher.start();
+ break;
+
+ case 'liveReloadStop':
+ watcher.stop();
+ break;
}
});
});
diff --git a/install-usercss.html b/install-usercss.html
index b04f9be0..b2f6b78a 100644
--- a/install-usercss.html
+++ b/install-usercss.html
@@ -54,6 +54,10 @@
+
diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css
index b9bf9307..5e373b30 100644
--- a/install-usercss/install-usercss.css
+++ b/install-usercss/install-usercss.css
@@ -85,6 +85,18 @@ h1 small {
overflow: hidden;
overflow-wrap: break-word;
min-width: 0;
+
+ display: flex;
+ flex-direction: column;
+}
+
+.main > :first-child {
+ flex: 0 0 auto;
+}
+
+.main > :last-child {
+ flex: 1 1 auto;
+ min-height: 0;
}
.main .code,
diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js
index fd2ef383..d4597092 100644
--- a/install-usercss/install-usercss.js
+++ b/install-usercss/install-usercss.js
@@ -4,6 +4,8 @@
(function () {
const params = getParams();
+ let liveReload = false;
+ let installed = false;
const port = chrome.tabs.connect(
Number(params.tabId),
@@ -19,11 +21,104 @@
initSourceCode(msg.sourceCode);
}
break;
+ case 'sourceCodeChanged':
+ if (msg.error) {
+ alert(msg.error);
+ } else {
+ liveReloadUpdate(msg.sourceCode);
+ }
+ break;
}
});
port.onDisconnect.addListener(closeCurrentTab);
const cm = CodeMirror.fromTextArea($('.code textarea'), {readOnly: true});
+ let liveReloadPending = Promise.resolve();
+
+ function liveReloadUpdate(sourceCode) {
+ liveReloadPending = liveReloadPending.then(() => {
+ const scrollInfo = cm.getScrollInfo();
+ const cursor = cm.getCursor();
+ cm.setValue(sourceCode);
+ cm.setCursor(cursor);
+ cm.scrollTo(scrollInfo.left, scrollInfo.top);
+
+ return runtimeSend({
+ method: 'saveUsercss',
+ reason: 'update',
+ sourceCode
+ }).then(updateMeta).catch(showError);
+ });
+ }
+
+ function updateMeta(style, dup) {
+ $$('.main .warning').forEach(e => e.remove());
+
+ const data = style.usercssData;
+ const dupData = dup && dup.usercssData;
+ const versionTest = dup && semverCompare(data.version, dupData.version);
+
+ // update editor
+ cm.setPreprocessor(data.preprocessor);
+
+ // update metas
+ document.title = `${installButtonLabel()} ${data.name}`;
+
+ $('.install').textContent = installButtonLabel();
+ $('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || '';
+ $('.meta-name').textContent = data.name;
+ $('.meta-version').textContent = data.version;
+ $('.meta-description').textContent = data.description;
+
+ $('.meta-author').parentNode.style.display = data.author ? '' : 'none';
+ $('.meta-author').textContent = data.author;
+
+ $('.meta-license').parentNode.style.display = data.license ? '' : 'none';
+ $('.meta-license').textContent = data.license;
+
+ $('.applies-to').textContent = '';
+ getAppliesTo(style).forEach(pattern =>
+ $('.applies-to').appendChild($element({tag: 'li', textContent: pattern}))
+ );
+
+ $('.external-link').textContent = '';
+ const externalLink = makeExternalLink();
+ if (externalLink) {
+ $('.external-link').appendChild(externalLink);
+ }
+
+ function makeExternalLink() {
+ const urls = [];
+ if (data.homepageURL) {
+ urls.push([data.homepageURL, t('externalHomepage')]);
+ }
+ if (data.supportURL) {
+ urls.push([data.supportURL, t('externalSupport')]);
+ }
+ if (urls.length) {
+ return $element({appendChild: [
+ $element({tag: 'h3', textContent: t('externalLink')}),
+ $element({tag: 'ul', appendChild: urls.map(args =>
+ $element({tag: 'li', appendChild: makeLink(...args)})
+ )})
+ ]});
+ }
+ }
+
+ function installButtonLabel() {
+ return t(
+ installed ? 'installButtonInstalled' :
+ !dup ? 'installButton' :
+ versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall'
+ );
+ }
+ }
+
+ function showError(err) {
+ $$('.main .warning').forEach(e => e.remove());
+ const main = $('.main');
+ main.insertBefore(buildWarning(err), main.firstChild);
+ }
function runtimeSend(request) {
return new Promise((resolve, reject) => {
@@ -41,15 +136,25 @@
});
return runtimeSend(request)
.then(result => {
+ installed = true;
+
$$('.warning')
.forEach(el => el.remove());
- $('.install').textContent = 'Installed';
$('.install').disabled = true;
$('.install').classList.add('installed');
$('.set-update-url input[type=checkbox]').disabled = true;
$('.set-update-url').title = result.updateUrl ?
t('installUpdateFrom', result.updateUrl) : '';
- window.dispatchEvent(new CustomEvent('installed', {detail: result}));
+
+ updateMeta(result);
+
+ if (liveReload) {
+ port.postMessage({method: 'liveReloadStart'});
+ }
+ $('.live-reload').addEventListener('change', () => {
+ const method = 'liveReload' + (liveReload ? 'Start' : 'Stop');
+ port.postMessage({method});
+ });
})
.catch(err => {
alert(chrome.i18n.getMessage('styleInstallFailed', String(err)));
@@ -81,6 +186,8 @@
const dupData = dup && dup.usercssData;
const versionTest = dup && semverCompare(data.version, dupData.version);
+ updateMeta(style, dup);
+
// update UI
if (versionTest < 0) {
$('.actions').parentNode.insertBefore(
@@ -120,59 +227,14 @@
}
};
- // update editor
- cm.setPreprocessor(data.preprocessor);
-
- // update metas
- document.title = `${installButtonLabel()} ${data.name}`;
-
- $('.install').textContent = installButtonLabel();
- $('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || '';
- $('.meta-name').textContent = data.name;
- $('.meta-version').textContent = data.version;
- $('.meta-description').textContent = data.description;
-
- if (data.author) {
- $('.meta-author').textContent = data.author;
+ // live reload
+ const setLiveReload = $('.live-reload input[type=checkbox]');
+ if (updateUrl.protocol !== 'file:') {
+ setLiveReload.parentNode.remove();
} else {
- $('.meta-author').parentNode.remove();
- }
- if (data.license) {
- $('.meta-license').textContent = data.license;
- } else {
- $('.meta-license').parentNode.remove();
- }
-
- getAppliesTo(style).forEach(pattern =>
- $('.applies-to').appendChild($element({tag: 'li', textContent: pattern}))
- );
-
- const externalLink = makeExternalLink();
- if (externalLink) {
- $('.external-link').appendChild(externalLink);
- }
-
- function makeExternalLink() {
- const urls = [];
- if (data.homepageURL) {
- urls.push([data.homepageURL, t('externalHomepage')]);
- }
- if (data.supportURL) {
- urls.push([data.supportURL, t('externalSupport')]);
- }
- if (urls.length) {
- return $element({appendChild: [
- $element({tag: 'h3', textContent: t('externalLink')}),
- $element({tag: 'ul', appendChild: urls.map(args =>
- $element({tag: 'li', appendChild: makeLink(...args)})
- )})
- ]});
- }
- }
-
- function installButtonLabel() {
- return t(!dup ? 'installButton' :
- versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall');
+ setLiveReload.addEventListener('change', () => {
+ liveReload = setLiveReload.checked;
+ });
}
}