allow live-reload without reinstalling

This commit is contained in:
tophf 2018-01-04 13:36:27 +03:00
parent c9e60dc19b
commit a339b50e27
5 changed files with 150 additions and 162 deletions

View File

@ -588,6 +588,10 @@
"message": "Live reload", "message": "Live reload",
"description": "The label of live-reload feature" "description": "The label of live-reload feature"
}, },
"liveReloadInstallHint": {
"message": "Live reload is enabled so the installed style will be auto-updated on external changes while both this tab and the source file tab are open.",
"description": "The label of live-reload feature"
},
"liveReloadError": { "liveReloadError": {
"message": "An error occurred while watching the file", "message": "An error occurred while watching the file",
"description": "The label of live-reload error" "description": "The label of live-reload error"

View File

@ -1,7 +1,64 @@
'use strict'; 'use strict';
function createSourceLoader() { (() => {
let source; // some weird bug in new Chrome: the content script gets injected multiple times
if (typeof window.initUsercssInstall === 'function') return;
if (!/text\/(css|plain)/.test(document.contentType) ||
!/==userstyle==/i.test(document.body.textContent)) {
return;
}
window.initUsercssInstall = () => {};
orphanCheck();
const DELAY = 500;
const url = location.href;
let sourceCode, port, timer;
chrome.runtime.onConnect.addListener(onConnected);
chrome.runtime.sendMessage({method: 'installUsercss', url}, r =>
r && r.__ERROR__ && alert(r.__ERROR__));
function onConnected(newPort) {
port = newPort;
port.onDisconnect.addListener(stop);
port.onMessage.addListener(onMessage);
}
function onMessage(msg, port) {
switch (msg.method) {
case 'getSourceCode':
fetchText(url)
.then(text => {
sourceCode = sourceCode || text;
port.postMessage({
method: msg.method + 'Response',
sourceCode,
});
})
.catch(err => port.postMessage({
method: msg.method + 'Response',
error: err.message || String(err),
}));
break;
case 'liveReloadStart':
start();
break;
case 'liveReloadStop':
stop();
break;
case 'closeTab':
if (history.length > 1) {
history.back();
} else {
chrome.runtime.sendMessage({method: 'closeTab'});
}
break;
}
}
function fetchText(url) { function fetchText(url) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -14,104 +71,40 @@ function createSourceLoader() {
}); });
} }
function load() { function start() {
return fetchText(location.href).then(newSource => { timer = timer || setTimeout(check, DELAY);
source = newSource;
return source;
});
} }
function watch(cb) { function stop() {
let timer; clearTimeout(timer);
const DELAY = 1000; timer = null;
function start() {
if (timer) {
return;
}
timer = setTimeout(check, DELAY);
}
function stop() {
clearTimeout(timer);
timer = null;
}
function check() {
fetchText(location.href)
.then(newSource => {
if (source !== newSource) {
source = newSource;
return cb(source);
}
})
.catch(error => {
console.log(chrome.i18n.getMessage('liveReloadError', error));
})
.then(() => {
timer = setTimeout(check, DELAY);
});
}
return {start, stop};
} }
return {load, watch, source: () => source}; function check() {
} fetchText(url)
.then(text => {
function initUsercssInstall() { if (sourceCode === text) return;
const sourceLoader = createSourceLoader(); sourceCode = text;
const pendingSource = sourceLoader.load(); port.postMessage({method: 'sourceCodeChanged', sourceCode});
let watcher; })
.catch(error => {
chrome.runtime.onConnect.addListener(port => { console.log(chrome.i18n.getMessage('liveReloadError', error));
port.onMessage.addListener(msg => { })
switch (msg.method) { .then(() => {
case 'getSourceCode': timer = null;
pendingSource start();
.then(sourceCode => port.postMessage({method: msg.method + 'Response', sourceCode})) });
.catch(err => 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;
case 'closeTab':
if (history.length > 1) {
history.back();
} else {
chrome.runtime.sendMessage({method: 'closeTab'});
}
break;
}
});
});
chrome.runtime.sendMessage({
method: 'installUsercss',
url: location.href,
}, r => r && r.__ERROR__ && alert(r.__ERROR__));
}
function isUsercss() {
if (!/text\/(css|plain)/.test(document.contentType)) {
return false;
} }
if (!/==userstyle==/i.test(document.body.textContent)) {
return false;
}
return true;
}
if (isUsercss()) { function orphanCheck() {
initUsercssInstall(); const eventName = chrome.runtime.id + '-install-hook-usercss';
} const orphanCheckRequest = () => {
if (chrome.i18n && chrome.i18n.getUILanguage()) return true;
// In Chrome content script is orphaned on an extension update/reload
// so we need to detach event listeners
removeEventListener(eventName, orphanCheckRequest, true);
};
dispatchEvent(new Event(eventName));
addEventListener(eventName, orphanCheckRequest, true);
}
})();

View File

@ -5,50 +5,37 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge"> <meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Loading...</title> <title>Loading...</title>
<link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="/install-usercss/install-usercss.css"> <link href="global.css" rel="stylesheet">
<script src="/js/messaging.js"></script> <link href="install-usercss/install-usercss.css" rel="stylesheet">
<script src="/js/prefs.js"></script>
<script src="/js/dom.js"></script> <script src="js/messaging.js"></script>
<script src="/js/localization.js"></script> <script src="js/prefs.js"></script>
<script src="/content/apply.js"></script> <script src="js/dom.js"></script>
<script src="/vendor/node-semver/semver.js"></script> <script src="js/localization.js"></script>
<script src="js/script-loader.js"></script> <script src="js/script-loader.js"></script>
<script src="content/apply.js"></script>
<script src="vendor/node-semver/semver.js"></script>
<script src="/msgbox/msgbox.js"></script> <link href="msgbox/msgbox.css" rel="stylesheet">
<link rel="stylesheet" href="/msgbox/msgbox.css"> <script src="msgbox/msgbox.js"></script>
<script src="/vendor/codemirror/lib/codemirror.js"></script> <link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
<script src="/vendor/codemirror/keymap/sublime.js"></script> <script src="vendor/codemirror/lib/codemirror.js"></script>
<script src="/vendor/codemirror/keymap/emacs.js"></script> <script src="vendor/codemirror/keymap/sublime.js"></script>
<script src="/vendor/codemirror/keymap/vim.js"></script> <script src="vendor/codemirror/keymap/emacs.js"></script>
<script src="vendor/codemirror/keymap/vim.js"></script>
<link rel="stylesheet" href="/vendor/codemirror/lib/codemirror.css"> <script src="vendor/codemirror/mode/css/css.js"></script>
<script src="/vendor/codemirror/mode/css/css.js"></script> <script src="vendor/codemirror/addon/search/searchcursor.js"></script>
<link rel="stylesheet" href="/vendor/codemirror/addon/dialog/dialog.css">
<link rel="stylesheet" href="/vendor/codemirror/addon/search/matchesonscrollbar.css">
<script src="/vendor/codemirror/addon/scroll/annotatescrollbar.js"></script>
<script src="/vendor/codemirror/addon/search/matchesonscrollbar.js"></script>
<script src="/vendor/codemirror/addon/search/match-highlighter.js"></script>
<script src="/vendor/codemirror/addon/dialog/dialog.js"></script>
<script src="/vendor/codemirror/addon/search/searchcursor.js"></script>
<script src="/vendor/codemirror/addon/search/search.js"></script>
<script src="/vendor/codemirror/addon/comment/comment.js"></script>
<script src="/vendor/codemirror/addon/selection/active-line.js"></script>
<link rel="stylesheet" href="/vendor/codemirror/addon/fold/foldgutter.css" />
<script src="/vendor/codemirror/addon/fold/foldcode.js"></script>
<script src="/vendor/codemirror/addon/fold/foldgutter.js"></script>
<script src="/vendor/codemirror/addon/fold/brace-fold.js"></script>
<script src="/vendor/codemirror/addon/fold/comment-fold.js"></script>
<script src="/vendor/codemirror/addon/edit/matchbrackets.js"></script>
<link rel="stylesheet" href="/vendor/codemirror/addon/lint/lint.css" />
<link rel="stylesheet" href="/vendor/codemirror/addon/hint/show-hint.css" />
<script src="/vendor/codemirror/addon/hint/show-hint.js"></script>
<script src="/vendor/codemirror/addon/hint/css-hint.js"></script>
<script src="/edit/match-highlighter-helper.js"></script> <link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet" />
<script src="/edit/codemirror-default.js"></script> <script src="vendor/codemirror/addon/fold/foldcode.js"></script>
<link rel="stylesheet" href="/edit/codemirror-default.css"> <script src="vendor/codemirror/addon/fold/foldgutter.js"></script>
<script src="vendor/codemirror/addon/fold/brace-fold.js"></script>
<script src="edit/codemirror-default.js"></script>
<link rel="stylesheet" href="edit/codemirror-default.css">
</head> </head>
<body id="stylus-install-usercss"> <body id="stylus-install-usercss">
<div class="container"> <div class="container">
@ -60,6 +47,7 @@
<div class="actions"> <div class="actions">
<h2 class="installed" i18n-text="installButtonInstalled"></h2> <h2 class="installed" i18n-text="installButtonInstalled"></h2>
<button class="install" i18n-text="installButton"></button> <button class="install" i18n-text="installButton"></button>
<p id="live-reload-install-hint" i18n-text="liveReloadInstallHint" class="hidden"></p>
<label class="set-update-url"> <label class="set-update-url">
<input type="checkbox"> <input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
@ -91,7 +79,7 @@
</div> </div>
</div> </div>
<script src="/install-usercss/install-usercss.js"></script> <script src="install-usercss/install-usercss.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;"> <svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000"> <symbol id="svg-icon-checked" viewBox="0 0 1000 1000">

View File

@ -156,12 +156,18 @@ h1 small {
background-position: center center; background-position: center center;
} }
.install:hover { .install:hover:not(:disabled) {
filter: brightness(1.1); filter: brightness(1.1);
color: #eee; color: #eee;
text-shadow: none; text-shadow: none;
} }
.install:disabled {
opacity: .25;
color: white;
cursor: auto;
}
.install.reinstall:after { .install.reinstall:after {
background-color: #333; background-color: #333;
filter: grayscale(100%); filter: grayscale(100%);

View File

@ -6,7 +6,8 @@
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425) // TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, '')); const params = new URLSearchParams(location.search.replace(/^\?/, ''));
let liveReload = false; let liveReload = false;
let installed = false; let installed = null;
let installedDup = null;
const tabId = Number(params.get('tabId')); const tabId = Number(params.get('tabId'));
let tabUrl; let tabUrl;
@ -65,7 +66,7 @@
cm.scrollTo(scrollInfo.left, scrollInfo.top); cm.scrollTo(scrollInfo.left, scrollInfo.top);
return sendMessage({ return sendMessage({
id: installed.id, id: (installed || installedDup).id,
method: 'saveUsercss', method: 'saveUsercss',
reason: 'update', reason: 'update',
sourceCode sourceCode
@ -74,19 +75,27 @@
}); });
} }
function updateMeta(style, dup) { function updateMeta(style, dup = installedDup) {
installedDup = dup;
const data = style.usercssData; const data = style.usercssData;
const dupData = dup && dup.usercssData; const dupData = dup && dup.usercssData;
const versionTest = dup && semverCompare(data.version, dupData.version); const versionTest = dup && semverCompare(data.version, dupData.version);
// update editor
cm.setPreprocessor(data.preprocessor); cm.setPreprocessor(data.preprocessor);
// update metas const installButtonLabel = t(
document.title = `${installButtonLabel()} ${data.name}`; installed ? 'installButtonInstalled' :
!dup ? 'installButton' :
versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall'
);
document.title = `${installButtonLabel} ${data.name}`;
$('.install').textContent = installButtonLabel(); $('.install').textContent = installButtonLabel;
$('.install').classList.add(installButtonClass()); $('.install').classList.add(
installed ? 'installed' :
!dup ? 'install' :
versionTest > 0 ? 'update' :
'reinstall');
$('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || ''; $('.set-update-url').title = dup && dup.updateUrl && t('installUpdateFrom', dup.updateUrl) || '';
$('.meta-name').textContent = data.name; $('.meta-name').textContent = data.name;
$('.meta-version').textContent = data.version; $('.meta-version').textContent = data.version;
@ -158,20 +167,6 @@
)) ))
])); ]));
} }
function installButtonClass() {
return installed ? 'installed' :
!dup ? 'install' :
versionTest > 0 ? 'update' : 'reinstall';
}
function installButtonLabel() {
return t(
installed ? 'installButtonInstalled' :
!dup ? 'installButton' :
versionTest > 0 ? 'installButtonUpdate' : 'installButtonReinstall'
);
}
} }
function showError(err) { function showError(err) {
@ -213,7 +208,7 @@
cm.setValue(sourceCode); cm.setValue(sourceCode);
cm.refresh(); cm.refresh();
API.buildUsercss({sourceCode, checkDup: true}) API.buildUsercss({sourceCode, checkDup: true})
.then(r => init(r instanceof Object ? r : deepCopy(r))) .then(init)
.catch(err => { .catch(err => {
$('.header').classList.add('meta-init-error'); $('.header').classList.add('meta-init-error');
showError(err); showError(err);
@ -324,9 +319,11 @@
} else { } else {
setLiveReload.addEventListener('change', () => { setLiveReload.addEventListener('change', () => {
liveReload = setLiveReload.checked; liveReload = setLiveReload.checked;
if (installed) { if (installed || installedDup) {
const method = 'liveReload' + (liveReload ? 'Start' : 'Stop'); const method = 'liveReload' + (liveReload ? 'Start' : 'Stop');
port.postMessage({method}); port.postMessage({method});
$('.install').disabled = liveReload;
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload);
} }
}); });
window.addEventListener('installed', () => { window.addEventListener('installed', () => {