" +
state.marked.map(function(mark) {
var info = mark.__annotation;
var isActiveLine = info.from.line == cm.getCursor().line;
@@ -938,7 +980,7 @@ function renderLintReport(someBlockChanged) {
var newContent = content.cloneNode(false);
var issueCount = 0;
editors.forEach(function(cm, index) {
- if (cm.state.lint.html) {
+ if (cm.state.lint && cm.state.lint.html) {
var newBlock = newContent.appendChild(document.createElement("table"));
var html = "" + label + " " + (index+1) + "" + cm.state.lint.html;
newBlock.innerHTML = html;
@@ -993,7 +1035,7 @@ function beautify(event) {
doBeautify();
} else {
var script = document.head.appendChild(document.createElement("script"));
- script.src = "beautify/beautify-css.js";
+ script.src = "beautify/beautify-css-mod.js";
script.onload = doBeautify;
}
function doBeautify() {
@@ -1023,6 +1065,7 @@ function beautify(event) {
if (cm.beautifyChange && cm.beautifyChange[cm.changeGeneration()]) {
delete cm.beautifyChange[cm.changeGeneration()];
cm.undo();
+ cm.scrollIntoView(cm.getCursor());
undoable |= cm.beautifyChange[cm.changeGeneration()];
}
});
@@ -1031,6 +1074,9 @@ function beautify(event) {
scope.forEach(function(cm) {
setTimeout(function() {
+ const pos = options.translate_positions =
+ [].concat.apply([], cm.doc.sel.ranges.map(r =>
+ [Object.assign({}, r.anchor), Object.assign({}, r.head)]));
var text = cm.getValue();
var newText = exports.css_beautify(text, options);
if (newText != text) {
@@ -1039,6 +1085,11 @@ function beautify(event) {
cm.beautifyChange = {};
}
cm.setValue(newText);
+ const selections = [];
+ for (let i = 0; i < pos.length; i += 2) {
+ selections.push({anchor: pos[i], head: pos[i + 1]});
+ }
+ cm.setSelections(selections);
cm.beautifyChange[cm.changeGeneration()] = true;
undoButton.disabled = false;
}
@@ -1065,48 +1116,67 @@ function beautify(event) {
}
}
-window.addEventListener("load", init, false);
+document.addEventListener("DOMContentLoaded", init);
function init() {
+ initCodeMirror();
var params = getParams();
if (!params.id) { // match should be 2 - one for the whole thing, one for the parentheses
// This is an add
+ tE("heading", "addStyleTitle");
var section = {code: ""}
for (var i in CssToProperty) {
if (params[i]) {
section[CssToProperty[i]] = [params[i]];
}
}
- addSection(null, section);
- // default to enabled
- document.getElementById("enabled").checked = true
- tE("heading", "addStyleTitle");
- initHooks();
+ window.onload = () => {
+ window.onload = null;
+ addSection(null, section);
+ // default to enabled
+ document.getElementById("enabled").checked = true
+ initHooks();
+ };
return;
}
// This is an edit
tE("heading", "editStyleHeading", null, false);
- requestStyle();
- function requestStyle() {
- chrome.runtime.sendMessage({method: "getStyles", id: params.id}, function callback(styles) {
- if (!styles) { // Chrome is starting up and shows edit.html
- requestStyle();
- return;
- }
- var style = styles[0];
- styleId = style.id;
- initWithStyle(style);
- });
- }
+ getStylesSafe({id: params.id}).then(styles => {
+ let style = styles[0];
+ if (!style) {
+ style = {id: null, sections: []};
+ history.replaceState({}, document.title, location.pathname);
+ }
+ styleId = style.id;
+ setStyleMeta(style);
+ window.onload = () => {
+ window.onload = null;
+ initWithStyle({style});
+ };
+ if (document.readyState != 'loading') {
+ window.onload();
+ }
+ });
}
-function initWithStyle(style) {
+function setStyleMeta(style) {
document.getElementById("name").value = style.name;
document.getElementById("enabled").checked = style.enabled;
document.getElementById("url").href = style.url;
+}
+
+function initWithStyle({style, codeIsUpdated}) {
+ setStyleMeta(style);
+
+ if (codeIsUpdated === false) {
+ setCleanGlobal();
+ updateTitle();
+ return;
+ }
+
// if this was done in response to an update, we need to clear existing sections
getSections().forEach(function(div) { div.remove(); });
- var queue = style.sections.length ? style.sections : [{code: ""}];
+ var queue = style.sections.length ? style.sections.slice() : [{code: ""}];
var queueStart = new Date().getTime();
// after 100ms the sections will be added asynchronously
while (new Date().getTime() - queueStart <= 100 && queue.length) {
@@ -1123,7 +1193,11 @@ function initWithStyle(style) {
function add() {
var sectionDiv = addSection(null, queue.shift());
maximizeCodeHeight(sectionDiv, !queue.length);
- updateLintReport(sectionDiv.CodeMirror, prefs.get("editor.lintDelay"));
+ const cm = sectionDiv.CodeMirror;
+ setTimeout(() => {
+ cm.setOption('lint', CodeMirror.defaults.lint);
+ updateLintReport(cm, 0);
+ }, prefs.get("editor.lintDelay"));
}
}
@@ -1149,11 +1223,28 @@ function initHooks() {
document.querySelector("#lint h2").addEventListener("click", toggleLintReport);
}
+ document.querySelectorAll(
+ 'input:not([type]), input[type="text"], input[type="search"], input[type="number"]')
+ .forEach(e => e.addEventListener('mousedown', toggleContextMenuDelete));
+
setupGlobalSearch();
setCleanGlobal();
updateTitle();
}
+
+function toggleContextMenuDelete(event) {
+ if (event.button == 2 && prefs.get('editor.contextDelete')) {
+ chrome.contextMenus.update('editor.contextDelete', {
+ enabled: Boolean(
+ this.selectionStart != this.selectionEnd ||
+ this.somethingSelected && this.somethingSelected()
+ ),
+ }, ignoreChromeError);
+ }
+}
+
+
function maximizeCodeHeight(sectionDiv, isLast) {
var cm = sectionDiv.CodeMirror;
var stats = maximizeCodeHeight.stats = maximizeCodeHeight.stats || {totalHeight: 0, deltas: []};
@@ -1254,14 +1345,14 @@ function save() {
}
var name = document.getElementById("name").value;
var enabled = document.getElementById("enabled").checked;
- var request = {
- method: "saveStyle",
+ saveStyleSafe({
id: styleId,
name: name,
enabled: enabled,
+ reason: 'editSave',
sections: getSectionsHashes()
- };
- chrome.runtime.sendMessage(request, saveComplete);
+ })
+ .then(saveComplete);
}
function getSectionsHashes() {
@@ -1348,7 +1439,7 @@ function fromMozillaFormat() {
function doImport() {
var replaceOldStyle = this.name == "import-replace";
- popup.querySelector(".close-icon").click();
+ popup.querySelector(".dismiss").onclick();
var mozStyle = trimNewLines(popup.codebox.getValue());
var parser = new parserlib.css.Parser(), lines = mozStyle.split("\n");
var sectionStack = [{code: "", start: {line: 1, col: 1}}];
@@ -1423,15 +1514,20 @@ function fromMozillaFormat() {
}
}
function doAddSection(section) {
+ section.code = section.code.trim();
+ // don't add empty sections
+ if (!section.code
+ && !section.urls
+ && !section.urlPrefixes
+ && !section.domains
+ && !section.regexps) {
+ return;
+ }
if (!firstAddedCM) {
if (!initFirstSection(section)) {
return;
}
}
- // don't add empty sections
- if (!(section.code || section.urls || section.urlPrefixes || section.domains || section.regexps)) {
- return;
- }
setCleanItem(addSection(null, section), false);
firstAddedCM = firstAddedCM || editors.last;
}
@@ -1532,8 +1628,8 @@ function showKeyMapHelp() {
cell.innerHTML = cell.textContent;
});
}
- function mergeKeyMaps(merged) {
- [].slice.call(arguments, 1).forEach(function(keyMap) {
+ function mergeKeyMaps(merged, ...more) {
+ more.forEach(keyMap => {
if (typeof keyMap == "string") {
keyMap = CodeMirror.keyMap[keyMap];
}
@@ -1567,6 +1663,111 @@ function showLintHelp() {
);
}
+function showRegExpTester(event, section = getSectionForChild(this)) {
+ const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
+ const OWN_ICON = chrome.runtime.getManifest().icons['16'];
+ const cachedRegexps = showRegExpTester.cachedRegexps =
+ showRegExpTester.cachedRegexps || new Map();
+ const regexps = [...section.querySelector('.applies-to-list').children]
+ .map(item =>
+ !item.matches('.applies-to-everything') &&
+ item.querySelector('.applies-type').value == 'regexp' &&
+ item.querySelector('.applies-value').value.trim())
+ .filter(item => item)
+ .map(text => {
+ const rxData = Object.assign({text}, cachedRegexps.get(text));
+ if (!rxData.urls) {
+ cachedRegexps.set(text, Object.assign(rxData, {
+ rx: tryRegExp(text),
+ urls: new Map(),
+ }));
+ }
+ return rxData;
+ });
+ chrome.tabs.onUpdated.addListener(function _(tabId, info) {
+ if (document.querySelector('.regexp-report')) {
+ if (info.url) {
+ showRegExpTester(event, section);
+ }
+ } else {
+ chrome.tabs.onUpdated.removeListener(_);
+ }
+ });
+ chrome.tabs.query({}, tabs => {
+ const supported = tabs.map(tab => tab.url)
+ .filter(url => URLS.supported.test(url));
+ const unique = [...new Set(supported).values()];
+ for (const rxData of regexps) {
+ const {rx, urls} = rxData;
+ if (rx) {
+ const urlsNow = new Map();
+ for (const url of unique) {
+ const match = urls.get(url) || (url.match(rx) || [])[0];
+ if (match) {
+ urlsNow.set(url, match);
+ }
+ }
+ rxData.urls = urlsNow;
+ }
+ }
+ const moreInfoLink = template.regexpTestPartial.outerHTML;
+ const stats = {
+ full: {data: [], label: t('styleRegexpTestFull')},
+ partial: {data: [], label: t('styleRegexpTestPartial') + moreInfoLink},
+ none: {data: [], label: t('styleRegexpTestNone')},
+ invalid: {data: [], label: t('styleRegexpTestInvalid')},
+ };
+ for (const {text, rx, urls} of regexps) {
+ if (!rx) {
+ stats.invalid.data.push({text});
+ continue;
+ }
+ if (!urls.size) {
+ stats.none.data.push({text});
+ continue;
+ }
+ const full = [];
+ const partial = [];
+ for (const [url, match] of urls.entries()) {
+ const faviconUrl = url.startsWith(URLS.ownOrigin)
+ ? OWN_ICON
+ : GET_FAVICON_URL + new URL(url).hostname;
+ const icon = ``;
+ if (match.length == url.length) {
+ full.push(`${icon + url}
`);
+ } else {
+ partial.push(`${icon}${match}` +
+ url.substr(match.length) + '
');
+ }
+ }
+ if (full.length) {
+ stats.full.data.push({text, urls: full});
+ }
+ if (partial.length) {
+ stats.partial.data.push({text, urls: partial});
+ }
+ }
+ showHelp(t('styleRegexpTestTitle'),
+ '' +
+ Object.keys(stats).map(type => (!stats[type].data.length ? '' :
+ `
+ ${stats[type].label}
` +
+ stats[type].data.map(({text, urls}) => (!urls ? text :
+ `${text}
${urls.join('')} `
+ )).join('
') +
+ ' '
+ )).join('') +
+ '
');
+ document.querySelector('.regexp-report').onclick = event => {
+ const target = event.target.closest('a, .regexp-report div');
+ if (target) {
+ openURL({url: target.href || target.textContent});
+ event.preventDefault();
+ }
+ };
+ });
+}
+
function showHelp(title, text) {
var div = document.getElementById("help-popup");
div.classList.remove("big");
@@ -1575,14 +1776,16 @@ function showHelp(title, text) {
if (getComputedStyle(div).display == "none") {
document.addEventListener("keydown", closeHelp);
- div.querySelector(".close-icon").onclick = closeHelp; // avoid chaining on multiple showHelp() calls
+ div.querySelector(".dismiss").onclick = closeHelp; // avoid chaining on multiple showHelp() calls
}
div.style.display = "block";
return div;
function closeHelp(e) {
- if (e.type == "click" || (e.keyCode == 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey)) {
+ if (!e
+ || e.type == "click"
+ || ((e.keyCode || e.which) == 27 && !e.altKey && !e.ctrlKey && !e.shiftKey && !e.metaKey)) {
div.style.display = "";
document.querySelector(".contents").innerHTML = "";
document.removeEventListener("keydown", closeHelp);
@@ -1625,11 +1828,21 @@ function getParams() {
return params;
}
-chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
+chrome.runtime.onMessage.addListener(onRuntimeMessage);
+
+function onRuntimeMessage(request) {
switch (request.method) {
case "styleUpdated":
- if (styleId && styleId == request.id) {
- initWithStyle(request.style);
+ if (styleId && styleId == request.style.id && request.reason != 'editSave') {
+ if ((request.style.sections[0] || {}).code === null) {
+ // the code-less style came from notifyAllTabs
+ onBackgroundReady().then(() => {
+ request.style = BG.cachedStyles.byId.get(request.style.id);
+ initWithStyle(request);
+ });
+ } else {
+ initWithStyle(request);
+ }
}
break;
case "styleDeleted":
@@ -1640,14 +1853,93 @@ chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
}
break;
case "prefChanged":
- if (request.prefName == "editor.smartIndent") {
- CodeMirror.setOption("smartIndent", request.value);
+ if ('editor.smartIndent' in request.prefs) {
+ CodeMirror.setOption('smartIndent', request.prefs['editor.smartIndent']);
}
+ break;
+ case 'editDeleteText':
+ document.execCommand('delete');
+ break;
}
-});
+}
function getComputedHeight(el) {
var compStyle = getComputedStyle(el);
return el.getBoundingClientRect().height +
parseFloat(compStyle.marginTop) + parseFloat(compStyle.marginBottom);
}
+
+
+function getCodeMirrorThemes() {
+ if (!chrome.runtime.getPackageDirectoryEntry) {
+ const themes = Promise.resolve([
+ chrome.i18n.getMessage('defaultTheme'),
+ '3024-day',
+ '3024-night',
+ 'abcdef',
+ 'ambiance',
+ 'ambiance-mobile',
+ 'base16-dark',
+ 'base16-light',
+ 'bespin',
+ 'blackboard',
+ 'cobalt',
+ 'colorforth',
+ 'dracula',
+ 'duotone-dark',
+ 'duotone-light',
+ 'eclipse',
+ 'elegant',
+ 'erlang-dark',
+ 'hopscotch',
+ 'icecoder',
+ 'isotope',
+ 'lesser-dark',
+ 'liquibyte',
+ 'material',
+ 'mbo',
+ 'mdn-like',
+ 'midnight',
+ 'monokai',
+ 'neat',
+ 'neo',
+ 'night',
+ 'panda-syntax',
+ 'paraiso-dark',
+ 'paraiso-light',
+ 'pastel-on-dark',
+ 'railscasts',
+ 'rubyblue',
+ 'seti',
+ 'solarized',
+ 'the-matrix',
+ 'tomorrow-night-bright',
+ 'tomorrow-night-eighties',
+ 'ttcn',
+ 'twilight',
+ 'vibrant-ink',
+ 'xq-dark',
+ 'xq-light',
+ 'yeti',
+ 'zenburn',
+ ]);
+ localStorage.codeMirrorThemes = themes.join(' ');
+ }
+ return new Promise(resolve => {
+ chrome.runtime.getPackageDirectoryEntry(rootDir => {
+ rootDir.getDirectory('codemirror/theme', {create: false}, themeDir => {
+ themeDir.createReader().readEntries(entries => {
+ const themes = [
+ chrome.i18n.getMessage('defaultTheme')
+ ].concat(
+ entries.filter(entry => entry.isFile)
+ .sort((a, b) => (a.name < b.name ? -1 : 1))
+ .map(entry => entry.name.replace(/\.css$/, ''))
+ );
+ localStorage.codeMirrorThemes = themes.join(' ');
+ resolve(themes);
+ });
+ });
+ });
+ });
+}
diff --git a/health.js b/health.js
deleted file mode 100644
index 857e0736..00000000
--- a/health.js
+++ /dev/null
@@ -1,11 +0,0 @@
-healthCheck();
-
-function healthCheck() {
- chrome.runtime.sendMessage({method: "healthCheck"}, function(ok) {
- if (ok === undefined) { // Chrome is starting up
- healthCheck();
- } else if (!ok && confirm(t("dbError"))) {
- window.open("http://userstyles.org/dberror");
- }
- });
-}
diff --git a/help.png b/images/help.png
similarity index 100%
rename from help.png
rename to images/help.png
diff --git a/128.png b/images/icon/128.png
similarity index 100%
rename from 128.png
rename to images/icon/128.png
diff --git a/16.png b/images/icon/16.png
similarity index 100%
rename from 16.png
rename to images/icon/16.png
diff --git a/16w.png b/images/icon/16w.png
similarity index 100%
rename from 16w.png
rename to images/icon/16w.png
diff --git a/16x.png b/images/icon/16x.png
similarity index 100%
rename from 16x.png
rename to images/icon/16x.png
diff --git a/19.png b/images/icon/19.png
similarity index 100%
rename from 19.png
rename to images/icon/19.png
diff --git a/19w.png b/images/icon/19w.png
similarity index 100%
rename from 19w.png
rename to images/icon/19w.png
diff --git a/19x.png b/images/icon/19x.png
similarity index 100%
rename from 19x.png
rename to images/icon/19x.png
diff --git a/32.png b/images/icon/32.png
similarity index 100%
rename from 32.png
rename to images/icon/32.png
diff --git a/32w.png b/images/icon/32w.png
similarity index 100%
rename from 32w.png
rename to images/icon/32w.png
diff --git a/32x.png b/images/icon/32x.png
similarity index 100%
rename from 32x.png
rename to images/icon/32x.png
diff --git a/38.png b/images/icon/38.png
similarity index 100%
rename from 38.png
rename to images/icon/38.png
diff --git a/38w.png b/images/icon/38w.png
similarity index 100%
rename from 38w.png
rename to images/icon/38w.png
diff --git a/38x.png b/images/icon/38x.png
similarity index 100%
rename from 38x.png
rename to images/icon/38x.png
diff --git a/48.png b/images/icon/48.png
similarity index 100%
rename from 48.png
rename to images/icon/48.png
diff --git a/images/world_go.png b/images/world_go.png
new file mode 100644
index 00000000..79239a2c
Binary files /dev/null and b/images/world_go.png differ
diff --git a/install.js b/install.js
index cc1ffb7d..61d031c5 100644
--- a/install.js
+++ b/install.js
@@ -1,180 +1,212 @@
-chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
- if (response.length == 0) {
- sendEvent("styleCanBeInstalledChrome");
- } else {
- var installedStyle = response[0];
- // maybe an update is needed
- // use the md5 if available
- var md5Url = getMeta("stylish-md5-url");
- if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
- getResource(md5Url, function(md5) {
- if (md5 == installedStyle.originalMd5) {
- sendEvent("styleAlreadyInstalledChrome", {updateUrl: installedStyle.updateUrl});
- } else {
- sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
- }
- });
- } else {
- getResource(getMeta("stylish-code-chrome"), function(code) {
- // this would indicate a failure (a style with settings?).
- if (code == null) {
- sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
- }
- var json = JSON.parse(code);
- if (json.sections.length == installedStyle.sections.length) {
- if (json.sections.every(function(section) {
- return installedStyle.sections.some(function(installedSection) {
- return sectionsAreEqual(section, installedSection);
- });
- })) {
- // everything's the same
- sendEvent("styleAlreadyInstalledChrome", {updateUrl: installedStyle.updateUrl});
- return;
- };
- }
- sendEvent("styleCanBeUpdatedChrome", {updateUrl: installedStyle.updateUrl});
- });
- }
- }
+'use strict';
+
+document.addEventListener('stylishUpdateChrome', onUpdateClicked);
+document.addEventListener('stylishInstallChrome', onInstallClicked);
+
+chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
+ // orphaned content script check
+ if (msg.method == 'ping') {
+ sendResponse(true);
+ }
});
-function sectionsAreEqual(a, b) {
- if (a.code != b.code) {
- return false;
- }
- return ["urls", "urlPrefixes", "domains", "regexps"].every(function(attribute) {
- return arraysAreEqual(a[attribute], b[attribute]);
- });
+new MutationObserver((mutations, observer) => {
+ if (document.body) {
+ observer.disconnect();
+ chrome.runtime.sendMessage({
+ method: 'getStyles',
+ url: getMeta('stylish-id-url') || location.href
+ }, checkUpdatability);
+ }
+}).observe(document.documentElement, {childList: true});
+
+
+function checkUpdatability([installedStyle]) {
+ if (!installedStyle) {
+ sendEvent('styleCanBeInstalledChrome');
+ return;
+ }
+ const md5Url = getMeta('stylish-md5-url');
+ if (md5Url && installedStyle.md5Url && installedStyle.originalMd5) {
+ getResource(md5Url).then(md5 => {
+ reportUpdatable(md5 != installedStyle.originalMd5);
+ });
+ } else {
+ getResource(getMeta('stylish-code-chrome')).then(code => {
+ reportUpdatable(code === null ||
+ !styleSectionsEqual(JSON.parse(code), installedStyle));
+ });
+ }
+
+ function reportUpdatable(isUpdatable) {
+ sendEvent(
+ isUpdatable
+ ? 'styleCanBeUpdatedChrome'
+ : 'styleAlreadyInstalledChrome',
+ {
+ updateUrl: installedStyle.updateUrl
+ }
+ );
+ }
}
-function arraysAreEqual(a, b) {
- // treat empty array and undefined as equivalent
- if (typeof a == "undefined")
- return (typeof b == "undefined") || (b.length == 0);
- if (typeof b == "undefined")
- return (typeof a == "undefined") || (a.length == 0);
- if (a.length != b.length) {
- return false;
- }
- return a.every(function(entry) {
- return b.indexOf(entry) != -1;
- });
+
+function sendEvent(type, detail = null) {
+ detail = {detail};
+ if (typeof cloneInto != 'undefined') {
+ // Firefox requires explicit cloning, however USO can't process our messages anyway
+ // because USO tries to use a global "event" variable deprecated in Firefox
+ detail = cloneInto(detail, document); // eslint-disable-line no-undef
+ }
+ onDOMready().then(() => {
+ document.dispatchEvent(new CustomEvent(type, detail));
+ });
}
-function sendEvent(type, data) {
- if (typeof data == "undefined") {
- data = null;
- }
- var stylishEvent = new CustomEvent(type, {detail: data});
- document.dispatchEvent(stylishEvent);
+
+function onInstallClicked() {
+ if (!orphanCheck || !orphanCheck()) {
+ return;
+ }
+ getResource(getMeta('stylish-description'))
+ .then(name => saveStyleCode('styleInstall', name))
+ .then(() => getResource(getMeta('stylish-install-ping-url-chrome')));
}
-document.addEventListener("stylishUpdateChrome", stylishUpdateChrome);
-function stylishInstallChrome() {
- orphanCheck();
- getResource(getMeta("stylish-description"), function(name) {
- if (confirm(chrome.i18n.getMessage('styleInstall', [name]))) {
- getResource(getMeta("stylish-code-chrome"), function(code) {
- // check for old style json
- var json = JSON.parse(code);
- json.method = "saveStyle";
- chrome.runtime.sendMessage(json, function(response) {
- sendEvent("styleInstalledChrome");
- });
- });
- getResource(getMeta("stylish-install-ping-url-chrome"));
- }
- });
+
+function onUpdateClicked() {
+ if (!orphanCheck || !orphanCheck()) {
+ return;
+ }
+ chrome.runtime.sendMessage({
+ method: 'getStyles',
+ url: getMeta('stylish-id-url') || location.href,
+ }, ([style]) => {
+ saveStyleCode('styleUpdate', style.name, {id: style.id});
+ });
}
-document.addEventListener("stylishInstallChrome", stylishInstallChrome);
-function stylishUpdateChrome() {
- orphanCheck();
- chrome.runtime.sendMessage({method: "getStyles", url: getMeta("stylish-id-url") || location.href}, function(response) {
- var style = response[0];
- if (confirm(chrome.i18n.getMessage('styleUpdate', [style.name]))) {
- getResource(getMeta("stylish-code-chrome"), function(code) {
- var json = JSON.parse(code);
- json.method = "saveStyle";
- json.id = style.id;
- chrome.runtime.sendMessage(json, function() {
- sendEvent("styleInstalledChrome");
- });
- });
- }
- });
+
+function saveStyleCode(message, name, addProps) {
+ return new Promise(resolve => {
+ if (!confirm(chrome.i18n.getMessage(message, [name]))) {
+ return;
+ }
+ getResource(getMeta('stylish-code-chrome')).then(code => {
+ chrome.runtime.sendMessage(
+ Object.assign(JSON.parse(code), addProps, {
+ method: 'saveStyle',
+ reason: 'update',
+ }),
+ () => sendEvent('styleInstalledChrome')
+ );
+ resolve();
+ });
+ });
}
+
function getMeta(name) {
- var e = document.querySelector("link[rel='" + name + "']");
- return e ? e.getAttribute("href") : null;
+ const e = document.querySelector(`link[rel="${name}"]`);
+ return e ? e.getAttribute('href') : null;
}
-function getResource(url, callback) {
- if (url.indexOf("#") == 0) {
- if (callback) {
- callback(document.getElementById(url.substring(1)).innerText);
- }
- return;
- }
- var xhr = new XMLHttpRequest();
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4 && callback) {
- if (xhr.status >= 400) {
- callback(null);
- } else {
- callback(xhr.responseText);
- }
- }
- }
- if (url.length > 2000) {
- var parts = url.split("?");
- xhr.open("POST", parts[0], true);
- xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
- xhr.send(parts[1]);
- } else {
- xhr.open("GET", url, true);
- xhr.send();
- }
+
+function getResource(url) {
+ return new Promise(resolve => {
+ if (url.startsWith('#')) {
+ resolve(document.getElementById(url.slice(1)).textContent);
+ } else {
+ chrome.runtime.sendMessage({method: 'download', url}, resolve);
+ }
+ });
}
-/* stylish to stylus; https://github.com/schomery/stylish-chrome/issues/12 */
-(function (es) {
- es.forEach(e => {
- [...e.childNodes].filter(n => n.nodeType == 3).forEach(n => {
- n.nodeValue = n.nodeValue.replace('Stylish', 'Stylus');
- });
- });
-})([
- ...document.querySelectorAll('div[id^="stylish-installed-style-not-installed-"]'),
- ...document.querySelectorAll('div[id^="stylish-installed-style-needs-update-"]')
-]);
-// orphaned content script check
+function styleSectionsEqual({sections: a}, {sections: b}) {
+ if (!a || !b) {
+ return undefined;
+ }
+ if (a.length != b.length) {
+ return false;
+ }
+ const checkedInB = [];
+ return a.every(sectionA => b.some(sectionB => {
+ if (!checkedInB.includes(sectionB) && propertiesEqual(sectionA, sectionB)) {
+ checkedInB.push(sectionB);
+ return true;
+ }
+ }));
+
+ function propertiesEqual(secA, secB) {
+ for (const name of ['urlPrefixes', 'urls', 'domains', 'regexps']) {
+ if (!equalOrEmpty(secA[name], secB[name], 'every', arrayMirrors)) {
+ return false;
+ }
+ }
+ return equalOrEmpty(secA.code, secB.code, 'substr', (a, b) => a == b);
+ }
+
+ function equalOrEmpty(a, b, telltale, comparator) {
+ const typeA = a && typeof a[telltale] == 'function';
+ const typeB = b && typeof b[telltale] == 'function';
+ return (
+ (a === null || a === undefined || (typeA && !a.length)) &&
+ (b === null || b === undefined || (typeB && !b.length))
+ ) || typeA && typeB && a.length == b.length && comparator(a, b);
+ }
+
+ function arrayMirrors(array1, array2) {
+ for (const el of array1) {
+ if (array2.indexOf(el) < 0) {
+ return false;
+ }
+ }
+ for (const el of array2) {
+ if (array1.indexOf(el) < 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+
+function onDOMready() {
+ if (document.readyState != 'loading') {
+ return Promise.resolve();
+ }
+ return new Promise(resolve => {
+ document.addEventListener('DOMContentLoaded', function _() {
+ document.removeEventListener('DOMContentLoaded', _);
+ resolve();
+ });
+ });
+}
-chrome.runtime.onMessage.addListener((msg, sender, sendResponse) =>
- msg.method == 'ping' && sendResponse(true));
function orphanCheck() {
- var port = chrome.runtime.connect();
- if (port) {
- port.disconnect();
- return;
- }
- // we're orphaned due to an extension update
- // we can detach event listeners
- document.removeEventListener('stylishUpdateChrome', stylishUpdateChrome);
- document.removeEventListener('stylishInstallChrome', stylishInstallChrome);
- // we can't detach chrome.runtime.onMessage because it's no longer connected internally
- // we can destroy global functions in this context to free up memory
- [
- 'arraysAreEqual',
- 'getMeta',
- 'getResource',
- 'orphanCheck',
- 'sectionsAreEqual',
- 'sendEvent',
- 'stylishUpdateChrome',
- 'stylishInstallChrome'
- ].forEach(fn => window[fn] = null);
+ const port = chrome.runtime.connect();
+ if (port) {
+ port.disconnect();
+ return true;
+ }
+ // we're orphaned due to an extension update
+ // we can detach event listeners
+ document.removeEventListener('stylishUpdateChrome', onUpdateClicked);
+ document.removeEventListener('stylishInstallChrome', onInstallClicked);
+ // we can't detach chrome.runtime.onMessage because it's no longer connected internally
+ // we can destroy global functions in this context to free up memory
+ [
+ 'checkUpdatability',
+ 'getMeta',
+ 'getResource',
+ 'onDOMready',
+ 'onInstallClicked',
+ 'onUpdateClicked',
+ 'orphanCheck',
+ 'saveStyleCode',
+ 'sendEvent',
+ 'styleSectionsEqual',
+ ].forEach(fn => (window[fn] = null));
}
diff --git a/localization.js b/localization.js
index f30f814c..2a6ac4c4 100644
--- a/localization.js
+++ b/localization.js
@@ -1,82 +1,121 @@
-var template = {};
+'use strict';
+
+const template = {};
tDocLoader();
+
function t(key, params) {
- var s = chrome.i18n.getMessage(key, params)
- if (s == "") {
- throw "Missing string '" + key + "'.";
- }
- return s;
-}
-function o(key) {
- document.write(t(key));
+ const cache = !params && t.cache[key];
+ const s = cache || chrome.i18n.getMessage(key, params);
+ if (s == '') {
+ throw `Missing string "${key}"`;
+ }
+ if (!params && !cache) {
+ t.cache[key] = s;
+ }
+ return s;
}
+
+
function tE(id, key, attr, esc) {
- if (attr) {
- document.getElementById(id).setAttribute(attr, t(key));
- } else if (typeof esc == "undefined" || esc) {
- document.getElementById(id).appendChild(document.createTextNode(t(key)));
- } else {
- document.getElementById(id).innerHTML = t(key);
- }
+ if (attr) {
+ document.getElementById(id).setAttribute(attr, t(key));
+ } else if (typeof esc == 'undefined' || esc) {
+ document.getElementById(id).appendChild(document.createTextNode(t(key)));
+ } else {
+ document.getElementById(id).innerHTML = t(key);
+ }
}
+
function tHTML(html) {
- var node = document.createElement("div");
- node.innerHTML = html.replace(/>\s+<'); // spaces are removed; use for an explicit space
- tNodeList(node.querySelectorAll("*"));
- var child = node.removeChild(node.firstElementChild);
- node.remove();
- return child;
+ const node = document.createElement('div');
+ node.innerHTML = html.replace(/>\s+<'); // spaces are removed; use for an explicit space
+ if (html.includes('i18n-')) {
+ tNodeList(node.getElementsByTagName('*'));
+ }
+ return node.firstElementChild;
}
+
function tNodeList(nodes) {
- for (var n = 0; n < nodes.length; n++) {
- var node = nodes[n];
- if (node.nodeType != 1) { // not an ELEMENT_NODE
- continue;
- }
- if (node.localName == "template") {
- tNodeList(node.content.querySelectorAll("*"));
- template[node.dataset.id] = node.content.firstElementChild;
- continue;
- }
- for (var a = node.attributes.length - 1; a >= 0; a--) {
- var attr = node.attributes[a];
- var name = attr.nodeName;
- if (name.indexOf("i18n-") != 0) {
- continue;
- }
- name = name.substr(5); // "i18n-".length
- var value = t(attr.value);
- switch (name) {
- case "text":
- node.insertBefore(document.createTextNode(value), node.firstChild);
- break;
- case "html":
- node.insertAdjacentHTML("afterbegin", value);
- break;
- default:
- node.setAttribute(name, value);
- }
- node.removeAttribute(attr.nodeName);
- }
- }
+ const PREFIX = 'i18n-';
+ for (let n = nodes.length; --n >= 0;) {
+ const node = nodes[n];
+ // skip non-ELEMENT_NODE
+ if (node.nodeType != 1) {
+ continue;
+ }
+ if (node.localName == 'template') {
+ const elements = node.content.querySelectorAll('*');
+ tNodeList(elements);
+ template[node.dataset.id] = elements[0];
+ // compress inter-tag whitespace to reduce number of DOM nodes by 25%
+ const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
+ const toRemove = [];
+ while (walker.nextNode()) {
+ const textNode = walker.currentNode;
+ if (!textNode.nodeValue.trim()) {
+ toRemove.push(textNode);
+ }
+ }
+ toRemove.forEach(el => el.remove());
+ continue;
+ }
+ for (let a = node.attributes.length; --a >= 0;) {
+ const attr = node.attributes[a];
+ const name = attr.nodeName;
+ if (!name.startsWith(PREFIX)) {
+ continue;
+ }
+ const type = name.substr(PREFIX.length);
+ const value = t(attr.value);
+ switch (type) {
+ case 'text':
+ node.insertBefore(document.createTextNode(value), node.firstChild);
+ break;
+ case 'text-append':
+ node.appendChild(document.createTextNode(value));
+ break;
+ case 'html':
+ node.insertAdjacentHTML('afterbegin', value);
+ break;
+ default:
+ node.setAttribute(type, value);
+ }
+ node.removeAttribute(name);
+ }
+ }
}
+
function tDocLoader() {
- // localize HEAD
- tNodeList(document.querySelectorAll("*"));
+ t.cache = tryJSONparse(localStorage.L10N) || {};
+ const cacheLength = Object.keys(t.cache).length;
+ // localize HEAD
+ tNodeList(document.getElementsByTagName('*'));
- // localize BODY
- var observer = new MutationObserver(function(mutations) {
- for (var m = 0; m < mutations.length; m++) {
- tNodeList(mutations[m].addedNodes);
- }
- });
- observer.observe(document, {subtree: true, childList: true});
- document.addEventListener("DOMContentLoaded", function() {
- observer.disconnect();
- tNodeList(document.querySelectorAll("*"));
- });
+ // localize BODY
+ const process = mutations => {
+ for (const mutation of mutations) {
+ tNodeList(mutation.addedNodes);
+ }
+ };
+ const observer = new MutationObserver(process);
+ const onLoad = () => {
+ tDocLoader.stop();
+ process(observer.takeRecords());
+ if (cacheLength != Object.keys(t.cache).length) {
+ localStorage.L10N = JSON.stringify(t.cache);
+ }
+ };
+ tDocLoader.start = () => {
+ observer.observe(document, {subtree: true, childList: true});
+ };
+ tDocLoader.stop = () => {
+ observer.disconnect();
+ document.removeEventListener('DOMContentLoaded', onLoad);
+ };
+ tDocLoader.start();
+ document.addEventListener('DOMContentLoaded', onLoad);
}
diff --git a/manage.css b/manage.css
new file mode 100644
index 00000000..f576411e
--- /dev/null
+++ b/manage.css
@@ -0,0 +1,779 @@
+body {
+ margin: 0;
+ font: 12px arial, sans-serif;
+ /* Firefox: fill the entire page for drag'n'drop to work */
+ display: flex;
+ height: 100%;
+}
+
+a {
+ color: #000;
+ transition: color .5s;
+ text-decoration-skip: ink;
+}
+
+a:hover {
+ color: #666;
+}
+
+#header {
+ width: 280px;
+ height: 100vh;
+ position: fixed;
+ top: 0;
+ padding: 15px;
+ border-right: 1px dashed #AAA;
+ -webkit-box-shadow: 0 0 50px -18px black;
+ overflow: auto;
+ box-sizing: border-box;
+ z-index: 9;
+}
+
+#header h1 {
+ margin-top: 0;
+}
+
+#header a[href^="edit"] {
+ text-decoration: none;
+}
+
+.firefox .chromium-only {
+ display: none;
+}
+
+#installed {
+ position: relative;
+ padding-left: 280px;
+ box-sizing: border-box;
+ width: 100%;
+}
+
+.entry {
+ margin: 0;
+ padding: 1.25em 2em;
+ border-top: 1px solid #ddd;
+}
+
+.entry:first-child {
+ border-top: none;
+}
+
+.svg-icon {
+ cursor: pointer;
+ vertical-align: middle;
+ transition: fill .5s;
+ width: 20px;
+ height: 20px;
+}
+
+.svg-icon:hover {
+ fill: #000;
+}
+
+.svg-icon {
+ fill: #666;
+}
+
+.svg-icon.info {
+ width: 14px;
+ height: 16px;
+ margin-left: .5ex;
+}
+
+.homepage {
+ margin-left: 0.1em;
+ margin-right: 0.1em;
+}
+
+.homepage[href=""] {
+ display: none;
+}
+
+.homepage .svg-icon {
+ margin-top: -4px;
+ margin-left: .5ex;
+}
+
+.style-name {
+ margin-top: .25em;
+ word-break: break-word;
+}
+
+.style-name a, .style-edit-link {
+ text-decoration: none;
+}
+
+.style-name-link:hover {
+ text-decoration: underline;
+ color: #000;
+}
+
+.applies-to {
+ word-break: break-word;
+}
+
+.applies-to,
+.actions {
+ padding-left: 15px;
+ margin-bottom: 0;
+}
+
+.actions {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+}
+
+.actions > * {
+ margin-bottom: .25rem;
+}
+
+.actions > *:not(:last-child) {
+ margin-right: .25rem;
+}
+
+.applies-to label {
+ margin-right: .5ex;
+}
+
+.applies-to .target:hover {
+ background-color: rgba(128, 128, 128, .15);
+}
+
+.applies-to-extra:not([open]) {
+ display: inline;
+ margin-left: 1ex;
+}
+
+summary {
+ font-weight: bold;
+ cursor: pointer;
+ outline: none;
+}
+
+.applies-to-extra summary {
+ list-style-type: none; /* for FF, allegedly */
+}
+
+.applies-to-extra summary::-webkit-details-marker {
+ display: none;
+}
+
+.disabled h2::after {
+ content: "__MSG_genericDisabledLabel__";
+ font-weight: normal;
+ font-size: 11px;
+ text-transform: lowercase;
+ background: rgba(128, 128, 128, .2);
+ padding: 2px 5px 3px;
+ border-radius: 4px;
+ margin-left: 1ex;
+}
+
+.disabled {
+ opacity: 0.5;
+}
+
+.disabled .disable {
+ display: none;
+}
+
+.enabled .enable {
+ display: none;
+}
+
+/* compact layout */
+
+.newUI #installed {
+ display: table;
+ margin-top: .75rem;
+ margin-bottom: .75rem;
+}
+
+.newUI .disabled {
+ opacity: 1;
+}
+
+.newUI .disabled .style-name,
+.newUI .disabled .applies-to {
+ opacity: .5;
+}
+
+.newUI .entry {
+ display: table-row;
+}
+
+.newUI .entry:nth-child(2n) {
+ background-color: rgba(128, 128, 128, 0.05);
+}
+
+.newUI .entry > * {
+ padding: .9rem 0 1rem;
+ margin: 0;
+ display: table-cell;
+ vertical-align: middle;
+}
+
+.newUI .checker {
+ position: relative;
+ top: 1px;
+ margin-right: 1ex;
+}
+
+.newUI .style-name {
+ font-size: 14px;
+ font-family: sans-serif;
+ text-indent: -2em;
+ padding-left: 3em;
+ padding-right: 30px;
+}
+
+.newUI .homepage .svg-icon {
+ position: absolute;
+ margin-top: 0;
+ margin-left: -28px;
+}
+
+.newUI .actions {
+ width: 60px;
+ height: 20px;
+ white-space: nowrap;
+}
+
+.newUI .actions > * {
+ margin: 0;
+}
+
+.newUI .actions .svg-icon {
+ margin-right: 8px;
+}
+
+.newUI .updater-icons > * {
+ transition: opacity 1s;
+ display: none;
+}
+
+.newUI .entry .svg-icon {
+ fill: #999;
+}
+
+.newUI .entry:hover .svg-icon {
+ fill: #666;
+}
+
+.newUI .entry:hover .svg-icon:hover {
+ fill: #000;
+}
+
+.newUI .checking-update .check-update {
+ opacity: 0;
+ display: inline;
+ pointer-events: none;
+}
+
+.newUI .can-update .update,
+.newUI .no-update:not(.update-problem):not(.update-done) .up-to-date,
+.newUI .no-update.update-problem .check-update,
+.newUI .update-done .updated {
+ display: inline;
+}
+
+.newUI .update-done .updated svg {
+ top: -4px;
+ position: relative;
+ /* unprefixed since Chrome 53 */
+ -webkit-filter: drop-shadow(0 4px 0 currentColor);
+ filter: drop-shadow(0 5px 0 currentColor);
+}
+
+.newUI .can-update .update,
+.newUI .no-update.update-problem .check-update {
+ cursor: pointer;
+}
+
+.newUI .can-update[data-details$="locally edited"] .update svg,
+.newUI .update-problem .check-update svg {
+ fill: #ef6969;
+}
+
+.newUI .can-update[data-details$="locally edited"]:hover .update svg,
+.newUI .entry.update-problem:hover .check-update svg {
+ fill: #fd4040;
+}
+
+.newUI .can-update[data-details$="locally edited"]:hover .update svg:hover,
+.newUI .entry.update-problem:hover .check-update svg:hover {
+ fill: red;
+}
+
+.newUI .updater-icons > :not(.check-update):after {
+ content: attr(title);
+ position: absolute;
+ margin-top: 18px;
+ margin-left: -36px;
+ padding: 1ex 1.5ex;
+ border: 1px solid #ded597;
+ background-color: #fffbd6;
+ border-radius: 4px;
+ box-shadow: 2px 3px 10px rgba(0,0,0,.25);
+ font-size: 90%;
+ animation: fadeout 10s;
+ animation-fill-mode: both;
+ z-index: 999;
+}
+
+.newUI .update-problem .check-update:after {
+ background-color: red;
+ border: 1px solid #d40000;
+ color: white;
+ animation: none;
+}
+
+.newUI .can-update .update:after {
+ animation: none;
+}
+
+.newUI .can-update:not([data-details$="locally edited"]) .update:after {
+ background-color: #c0fff0;
+ border: 1px solid #89cac9;
+}
+
+.newUI .applies-to {
+ padding-top: .25rem;
+ padding-bottom: .25rem;
+}
+
+.newUI .targets {
+ overflow: hidden;
+}
+
+.newUI .applies-to.expanded .targets {
+ max-height: 100vh;
+}
+
+.newUI .target {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: calc(100vw - 280px - 60px - 25vw - 3rem);
+ box-sizing: border-box;
+ padding-right: 1rem;
+ line-height: 18px;
+}
+
+.newUI .applies-to .expander {
+ margin: 0;
+ cursor: pointer;
+ font-size: 3ex;
+ line-height: .5ex;
+ vertical-align: super;
+ letter-spacing: .1ex;
+}
+
+.newUI .applies-to:not(.has-more) .expander {
+ display: none;
+}
+
+.newUI .has-favicons .applies-to .expander {
+ padding-left: 20px;
+}
+
+.newUI .target:hover {
+ background-color: inherit;
+}
+
+.newUI .target img {
+ width: 16px;
+ height: 16px;
+ vertical-align: sub;
+ margin-left: -20px;
+ margin-right: 4px;
+ transition: opacity .5s, filter .5s;
+ /* unprefixed since Chrome 53 */
+ -webkit-filter: grayscale(1);
+ filter: grayscale(1);
+ /* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
+ backface-visibility: hidden;
+ opacity: .25;
+ display: none;
+}
+
+.newUI .has-favicons .target {
+ padding-left: 20px;
+}
+
+.newUI .has-favicons .target img[src] {
+ display: inline;
+}
+
+.newUI .entry:hover .target img {
+ opacity: 1;
+ /* unprefixed since Chrome 53 */
+ -webkit-filter: grayscale(0);
+ filter: grayscale(0);
+}
+
+#newUIoptions {
+ display: none;
+}
+
+.newUI #newUIoptions {
+ display: initial;
+}
+
+#newUIoptions > * {
+ display: flex;
+ align-items: center;
+ margin-bottom: auto;
+ flex-wrap: wrap;
+}
+
+#newUIoptions input[type="number"] {
+ width: 3em;
+ margin-right: .5em;
+}
+
+input[id^="manage.newUI"] {
+ margin-left: 0;
+}
+
+#faviconsHelp {
+ overflow-y: auto;
+ font-size: 90%;
+ padding: 1ex 0 2ex 16px;
+}
+
+#faviconsHelp div {
+ display: flex;
+ align-items: center;
+ margin-top: 1ex;
+}
+
+/* Default, no update buttons */
+.update,
+.check-update {
+ display: none;
+}
+
+/* Check update button for things that can*/
+.updatable .check-update {
+ display: inline;
+}
+
+/* Update check in progress */
+.checking-update .check-update {
+ display: none;
+}
+
+/* Updates available */
+.can-update .update {
+ display: inline;
+}
+
+.can-update[data-details$="locally edited"] button.update:after {
+ content: "*";
+}
+
+.can-update .check-update {
+ display: none;
+}
+
+/* Updates not available */
+.no-update:not(.update-problem) .check-update {
+ display: none;
+}
+
+/* Updates done */
+.update-done .check-update {
+ display: none;
+}
+
+#apply-all-updates:after {
+ content: " (" attr(data-value) ")";
+}
+
+.update-in-progress #check-all-updates {
+ position: relative;
+}
+
+.update-in-progress #update-progress {
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ background-color: currentColor;
+ content: "";
+ opacity: .35;
+}
+
+#update-all-no-updates[data-skipped-edited="true"]:after {
+ content: " __MSG_updateAllCheckSucceededSomeEdited__";
+}
+
+#check-all-updates-force {
+ margin-top: 1ex;
+}
+
+/* highlight updated/added styles */
+.highlight {
+ animation: highlight 10s cubic-bezier(0,.82,.47,.98);
+}
+
+@keyframes highlight {
+ from {
+ background-color: rgba(128, 128, 128, .5);
+ }
+ to {
+ background-color: none;
+ }
+}
+
+.hidden {
+ display: none !important;
+}
+
+fieldset {
+ border-width: 1px;
+ border-radius: 6px;
+ margin: 1em 0;
+}
+
+fieldset > * {
+ display: flex;
+ align-items: center;
+}
+
+#search {
+ width: calc(100% - 4px);
+ margin: 0.25rem 4px 0;
+ border-radius: 0.25rem;
+ padding-left: 0.25rem;
+ border-width: 1px;
+}
+
+#import ul {
+ margin-left: 0;
+ padding-left: 0;
+ list-style: none;
+}
+
+#import li {
+ margin-bottom: .5em;
+}
+
+#import pre {
+ background: #eee;
+ overflow: auto;
+ margin: 0 0 .5em 0;
+}
+
+/* drag-n-drop on import button */
+.dropzone:after {
+ background-color: rgba(0, 0, 0, 0.7);
+ color: white;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ position: fixed;
+ padding: calc(50vh - 3em) calc(50vw - 5em);
+ content: attr(dragndrop-hint);
+ text-shadow: 1px 1px 10px black;
+ font-size: xx-large;
+ text-align: center;
+ animation: fadein 1s cubic-bezier(.03, .67, .08, .94);
+ animation-fill-mode: both;
+}
+
+.fadeout.dropzone:after {
+ animation: fadeout .25s ease-in-out;
+ animation-fill-mode: both;
+}
+
+/* post-import report */
+#message-box details:not([data-id="invalid"]) div:hover {
+ background-color: rgba(128, 128, 128, .3);
+}
+
+#message-box details:not(:last-child) {
+ margin-bottom: 1em;
+}
+
+#message-box details small div {
+ margin-left: 1.5em;
+}
+
+.update-history-log {
+ font-size: 11px;
+ white-space: pre;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
+}
+
+@keyframes fadein {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes fadeout {
+ from {
+ opacity: 1;
+ }
+ to {
+ opacity: 0;
+ }
+}
+
+@keyframes fadein-25pct {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: .25;
+ }
+}
+
+@media (max-width: 675px) {
+ #header {
+ height: auto;
+ position: static;
+ width: auto;
+ border-right: none;
+ border-bottom: 1px dashed #AAA;
+ }
+
+ #installed {
+ position: static;
+ padding-left: 0;
+ overflow: visible;
+ }
+
+ #header h1,
+ #header h2,
+ #header h3,
+ #backup-message {
+ display: none;
+ }
+
+ #header p,
+ #header fieldset div,
+ #backup {
+ display: inline-block;
+ }
+
+ #find-editor-styles {
+ display: inline-block;
+ }
+
+ #backup {
+ margin-right: 1em;
+ }
+
+ #backup p,
+ #header fieldset {
+ margin: 0;
+ }
+
+ .entry {
+ margin: 0;
+ }
+}
+
+@media (max-width: 800px) {
+ body {
+ flex-direction: column;
+ }
+
+ .newUI #header {
+ height: auto;
+ position: static;
+ width: auto;
+ border-right: none;
+ border-bottom: 1px dashed #AAA;
+ overflow: visible;
+ }
+
+ .newUI #installed {
+ padding-left: 0;
+ }
+
+ .newUI #header h1,
+ .newUI #header h2,
+ .newUI #header h3,
+ .newUI #header legend,
+ .newUI #backup-message {
+ display: none;
+ }
+
+ .newUI #header p,
+ .newUI #header fieldset div,
+ .newUI #options,
+ .newUI #backup,
+ .newUI #find-editor-styles,
+ .newUI #header fieldset label,
+ .newUI #header fieldset input,
+ .newUI #newUIoptions > * {
+ display: inline;
+ vertical-align: middle;
+ margin-top: 1ex;
+ margin-bottom: 1ex;
+ }
+
+ .newUI #header > * {
+ display: inline-block
+ }
+
+ .newUI #header button,
+ .newUI #header span,
+ .newUI #header div {
+ margin-right: 1ex;
+ }
+
+ .newUI #header label,
+ .newUI #header a {
+ white-space: nowrap
+ }
+
+ .newUI #backup p,
+ .newUI #header fieldset {
+ margin: 0;
+ padding: 0;
+ border: none;
+ }
+
+ .newUI #header fieldset input {
+ margin-left: 0;
+ }
+
+ .newUI #search {
+ width: auto;
+ }
+
+ .newUI .entry {
+ margin: 0;
+ }
+
+ .newUI .style-name {
+ width: 50%;
+ }
+
+ .newUI .target {
+ max-width: calc(50vw - 60px);
+ }
+}
+
+@media (max-width: 500px) {
+ .newUI #header > * {
+ display: inline;
+ }
+
+ .newUI .style-name {
+ word-break: break-all;
+ }
+}
diff --git a/manage.html b/manage.html
index 798a4eab..eb405e0a 100644
--- a/manage.html
+++ b/manage.html
@@ -1,300 +1,223 @@
-
+
-
-
-
+
+
+
+
-
-
-
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ,
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-