stylus/install-usercss/install-usercss.js

365 lines
11 KiB
JavaScript
Raw Normal View History

/* global CodeMirror semverCompare makeLink closeCurrentTab */
/* global messageBox download chromeLocal */
2017-09-24 03:39:04 +00:00
'use strict';
2017-10-29 17:22:10 +00:00
(() => {
// TODO: remove .replace(/^\?/, '') when minimum_chrome_version >= 52 (https://crbug.com/601425)
const params = new URLSearchParams(location.search.replace(/^\?/, ''));
2017-09-25 12:01:50 +00:00
let liveReload = false;
let installed = false;
2017-09-24 03:39:04 +00:00
const tabId = Number(params.get('tabId'));
let port;
2017-11-25 17:24:15 +00:00
if (params.has('direct')) {
$('.live-reload').remove();
getCodeDirectly();
} else {
port = chrome.tabs.connect(tabId);
port.postMessage({method: 'getSourceCode'});
port.onMessage.addListener(msg => {
switch (msg.method) {
case 'getSourceCodeResponse':
if (msg.error) {
messageBox.alert(msg.error);
} else {
initSourceCode(msg.sourceCode);
}
break;
case 'sourceCodeChanged':
if (msg.error) {
messageBox.alert(msg.error);
} else {
liveReloadUpdate(msg.sourceCode);
}
break;
}
});
port.onDisconnect.addListener(closeCurrentTab);
}
2017-09-24 03:39:04 +00:00
const cm = CodeMirror($('.main'), {readOnly: true});
2017-09-25 12:01:50 +00:00
let liveReloadPending = Promise.resolve();
window.addEventListener('resize', adjustCodeHeight);
2017-09-25 12:01:50 +00:00
setTimeout(() => {
if (!installed) {
const div = $element({});
$('.header').appendChild($element({
className: 'lds-spinner',
appendChild: new Array(12).fill(div).map(e => e.cloneNode()),
}));
}
}, 200);
2017-09-25 12:01:50 +00:00
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 sendMessage({
2017-10-07 10:14:49 +00:00
id: installed.id,
2017-09-25 12:01:50 +00:00
method: 'saveUsercss',
reason: 'update',
sourceCode
2017-11-09 05:00:08 +00:00
}).then(updateMeta)
.catch(showError);
2017-09-25 12:01:50 +00:00
});
}
function updateMeta(style, dup) {
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;
if (data.author) {
$('.meta-author').parentNode.style.display = '';
$('.meta-author').textContent = '';
$('.meta-author').appendChild(makeAuthor(data.author));
} else {
$('.meta-author').parentNode.style.display = 'none';
}
2017-09-25 12:01:50 +00:00
$('.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);
}
$('.header').classList.add('meta-init');
$('.header').classList.remove('meta-init-error');
setTimeout(() => $('.lds-spinner').remove(), 1000);
showError('');
requestAnimationFrame(adjustCodeHeight);
function makeAuthor(text) {
const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))$/);
if (!match) {
return document.createTextNode(text);
}
const [, name, email, url] = match;
const frag = document.createDocumentFragment();
if (email) {
2017-10-12 08:15:45 +00:00
frag.appendChild(makeLink(`mailto:${email}`, name));
} else {
frag.appendChild($element({
tag: 'span',
textContent: name
}));
}
if (url) {
2017-10-12 08:15:45 +00:00
frag.appendChild(makeLink(
url,
$element({
2017-11-09 00:01:45 +00:00
tag: 'svg#svg',
viewBox: '0 0 20 20',
2017-11-29 10:37:37 +00:00
class: 'svg-icon',
2017-11-09 00:01:45 +00:00
appendChild: $element({
tag: 'svg#path',
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z'
})
})
2017-10-12 08:15:45 +00:00
));
}
return frag;
}
2017-09-25 12:01:50 +00:00
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) {
$('.warnings').textContent = '';
if (err) {
$('.warnings').appendChild(buildWarning(err));
}
$('.warnings').classList.toggle('visible', Boolean(err));
$('.container').classList.toggle('has-warnings', Boolean(err));
adjustCodeHeight();
2017-09-25 12:01:50 +00:00
}
2017-09-24 03:39:04 +00:00
function install(style) {
installed = style;
2017-09-25 12:01:50 +00:00
$$('.warning')
.forEach(el => el.remove());
$('.install').disabled = true;
$('.install').classList.add('installed');
$('.set-update-url input[type=checkbox]').disabled = true;
$('.set-update-url').title = style.updateUrl ?
t('installUpdateFrom', style.updateUrl) : '';
2017-09-25 12:01:50 +00:00
updateMeta(style);
2017-09-25 12:01:50 +00:00
if (!liveReload && !prefs.get('openEditInWindow')) {
chrome.tabs.update({url: '/edit.html?id=' + style.id});
} else {
BG.openEditor(style.id);
if (!liveReload) {
closeCurrentTab();
}
}
2017-10-06 08:29:06 +00:00
window.dispatchEvent(new CustomEvent('installed'));
2017-09-24 03:39:04 +00:00
}
function initSourceCode(sourceCode) {
cm.setValue(sourceCode);
cm.refresh();
sendMessage({method: 'buildUsercss', sourceCode, checkDup: true})
.then(init)
.catch(err => {
$('.header').classList.add('meta-init-error');
showError(err);
});
2017-09-24 03:39:04 +00:00
}
function buildWarning(err) {
return $element({className: 'warning', appendChild: [
t('parseUsercssError'),
$element({tag: 'pre', textContent: String(err)})
]});
}
function init({style, dup}) {
const data = style.usercssData;
const dupData = dup && dup.usercssData;
const versionTest = dup && semverCompare(data.version, dupData.version);
2017-09-25 12:01:50 +00:00
updateMeta(style, dup);
2017-09-24 08:54:21 +00:00
// update UI
2017-09-24 03:39:04 +00:00
if (versionTest < 0) {
$('.actions').parentNode.insertBefore(
$element({className: 'warning', textContent: t('versionInvalidOlder')}),
$('.actions')
);
}
$('button.install').onclick = () => {
(!dup ?
Promise.resolve(true) :
messageBox.confirm(t('styleInstallOverwrite', [
data.name,
dupData.version,
data.version,
]))
).then(ok => ok &&
sendMessage(Object.assign(style, {method: 'saveUsercss', reason: 'update'}))
.then(install)
.catch(err => messageBox.alert(t('styleInstallFailed', err))));
2017-09-24 03:39:04 +00:00
};
2017-09-24 08:54:21 +00:00
// set updateUrl
2017-09-24 03:39:04 +00:00
const setUpdate = $('.set-update-url input[type=checkbox]');
2017-11-01 01:26:53 +00:00
const updateUrl = new URL(params.get('updateUrl'));
$('.set-update-url > span').textContent = t('installUpdateFromLabel');
2017-09-24 03:39:04 +00:00
if (dup && dup.updateUrl === updateUrl.href) {
setUpdate.checked = true;
// there is no way to "unset" updateUrl, you can only overwrite it.
setUpdate.disabled = true;
2017-10-11 13:45:17 +00:00
} else if (updateUrl.protocol !== 'file:') {
2017-09-24 03:39:04 +00:00
setUpdate.checked = true;
style.updateUrl = updateUrl.href;
}
setUpdate.onchange = e => {
if (e.target.checked) {
style.updateUrl = updateUrl.href;
} else {
delete style.updateUrl;
}
};
if (!port) {
return;
}
2017-09-25 12:01:50 +00:00
// live reload
const setLiveReload = $('.live-reload input[type=checkbox]');
if (updateUrl.protocol !== 'file:') {
setLiveReload.parentNode.remove();
2017-09-24 03:39:04 +00:00
} else {
2017-09-25 12:01:50 +00:00
setLiveReload.addEventListener('change', () => {
liveReload = setLiveReload.checked;
if (installed) {
const method = 'liveReload' + (liveReload ? 'Start' : 'Stop');
port.postMessage({method});
}
});
window.addEventListener('installed', () => {
if (liveReload) {
port.postMessage({method: 'liveReloadStart'});
}
2017-09-25 12:01:50 +00:00
});
2017-09-24 03:39:04 +00:00
}
}
function getAppliesTo(style) {
function *_gen() {
for (const section of style.sections) {
for (const type of ['urls', 'urlPrefixes', 'domains', 'regexps']) {
if (section[type]) {
yield *section[type];
}
}
}
}
const result = [..._gen()];
if (!result.length) {
result.push(chrome.i18n.getMessage('appliesToEverything'));
}
return result;
}
function adjustCodeHeight() {
// Chrome-only bug (apparently): it doesn't limit the scroller element height
const scroller = cm.display.scroller;
const prevWindowHeight = adjustCodeHeight.prevWindowHeight;
if (scroller.scrollHeight === scroller.clientHeight ||
prevWindowHeight && window.innerHeight !== prevWindowHeight) {
adjustCodeHeight.prevWindowHeight = window.innerHeight;
cm.setSize(null, $('.main').offsetHeight - $('.warnings').offsetHeight);
}
}
function getCodeDirectly() {
// FF applies page CSP even to content scripts, https://bugzil.la/1267027
// To circumvent that, the bg process downloads the code directly
2017-11-25 17:24:15 +00:00
const key = 'tempUsercssCode' + tabId;
chrome.storage.local.get(key, data => {
const code = data && data[key];
// bg already downloaded the code
if (typeof code === 'string') {
initSourceCode(code);
chrome.storage.local.remove(key);
return;
}
// bg still downloads the code
if (code && code.loading) {
const waitForCodeInStorage = (changes, area) => {
if (area === 'local' && key in changes) {
initSourceCode(changes[key].newValue);
chrome.storage.onChanged.removeListener(waitForCodeInStorage);
chrome.storage.local.remove(key);
}
};
chrome.storage.onChanged.addListener(waitForCodeInStorage);
return;
}
// on the off-chance dbExecChromeStorage.getAll ran right after bg download was saved
download(params.get('updateUrl'))
.then(initSourceCode)
.catch(err => messageBox.alert(t('styleInstallFailed', String(err))));
});
}
2017-09-24 03:39:04 +00:00
})();