fix/deduplicate/simplify installer html/css/js (#1383)

This commit is contained in:
tophf 2022-01-18 16:39:33 +03:00 committed by GitHub
parent 936f5b40d2
commit 0705392fb2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 248 additions and 294 deletions

View File

@ -230,44 +230,6 @@
</table>
</template>
<template data-id="styleSettings">
<div>
<fieldset class="style-settings can-close-on-esc">
<label i18n-text="styleUpdateUrlLabel">
<input id="ss-update-url" type="text">
</label>
<div>
<div i18n-text="installPreferSchemeLabel"></div>
<label i18n-text-append="installPreferSchemeNone">
<input name="ss-scheme" type="radio" value="none">
</label>
<label i18n-text-append="installPreferSchemeDark">
<input name="ss-scheme" type="radio" value="dark">
</label>
<label i18n-text-append="installPreferSchemeLight">
<input name="ss-scheme" type="radio" value="light">
</label>
</div>
<label i18n-text="styleIncludeLabel">
<textarea id="ss-inclusions" spellcheck="false"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label i18n-text="styleExcludeLabel">
<textarea id="ss-exclusions" spellcheck="false"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
</fieldset>
<div class="buttons">
<button id="ss-save" i18n-text="confirmSave" disabled></button>
<label i18n-title="configOnChangeTooltip" i18n-text-append="configOnChange">
<input id="config.autosave" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<button id="ss-close" i18n-text="confirmClose"></button>
</div>
</div>
</template>
<link href="vendor/codemirror/lib/codemirror.css" rel="stylesheet">
<link href="vendor/codemirror/addon/dialog/dialog.css" rel="stylesheet">
<link href="vendor/codemirror/addon/fold/foldgutter.css" rel="stylesheet">

35
edit/settings.html Normal file
View File

@ -0,0 +1,35 @@
<div>
<fieldset class="style-settings can-close-on-esc">
<label i18n-text="styleUpdateUrlLabel">
<input id="ss-update-url" type="text">
</label>
<div id="ss-scheme">
<div i18n-text="installPreferSchemeLabel"></div>
<label i18n-text-append="installPreferSchemeNone">
<input name="ss-scheme" type="radio" value="none">
</label>
<label i18n-text-append="installPreferSchemeDark">
<input name="ss-scheme" type="radio" value="dark">
</label>
<label i18n-text-append="installPreferSchemeLight">
<input name="ss-scheme" type="radio" value="light">
</label>
</div>
<label i18n-text="styleIncludeLabel">
<textarea id="ss-inclusions" spellcheck="false"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
<label i18n-text="styleExcludeLabel">
<textarea id="ss-exclusions" spellcheck="false"
placeholder="*://site1.com/*&#10;*://site2.com/*"></textarea>
</label>
</fieldset>
<div class="buttons">
<button id="ss-save" i18n-text="confirmSave" disabled></button>
<label i18n-title="configOnChangeTooltip" i18n-text-append="configOnChange">
<input id="config.autosave" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
<button id="ss-close" i18n-text="confirmClose"></button>
</div>
</div>

View File

@ -7,9 +7,10 @@
/* exported StyleSettings */
'use strict';
function StyleSettings() {
async function StyleSettings() {
const AUTOSAVE_DELAY = 500; // same as config-dialog.js
const SS_ID = 'styleSettings';
await t.fetchTemplate('/edit/settings.html', SS_ID);
const {style} = editor;
const ui = t.template[SS_ID].cloneNode(true);
const elAuto = $('[id="config.autosave"]', ui);

View File

@ -11,13 +11,12 @@
<script src="js/polyfill.js"></script>
<script src="js/msg.js"></script>
<script src="js/toolbox.js"></script>
<script src="install-usercss/preinit.js"></script>
<script src="js/prefs.js"></script>
<script src="js/dom.js"></script>
<script src="js/localization.js"></script>
<script src="install-usercss/preinit.js"></script>
<script src="content/style-injector.js"></script>
<script src="content/apply.js"></script>
@ -25,46 +24,40 @@
<link href="install-usercss/install-usercss.css" rel="stylesheet">
</head>
<body id="stylus-install-usercss">
<div class="container">
<div id="header">
<div id="header-content-wrapper">
<h1>
<div id="header-contents">
<h1 class="w100">
<span class="meta-name"></span>
<small class="meta-version"></small>
</h1>
<div class="actions">
<h2 hidden class="installed" i18n-text="installButtonInstalled"></h2>
<button class="install" i18n-text="installButton"></button>
<div id="install-wrapper">
<h2 class="install-show" i18n-text="installButtonInstalled"></h2>
<button class="install install-hide" i18n-text="installButton"></button>
<a class="configure-usercss" i18n-title="configureStyle" tabindex="0">
<svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
</a>
<p id="live-reload-install-hint" hidden></p>
<label class="set-update-url">
<div class="install-show w100-full">
<a href="manage.html"><button i18n-text="openManage"></button></a>
<a id="edit" href="edit.html?id="><button i18n-text="editStyleLabel"></button></a>
<a id="delete" tabindex="0"><button i18n-text="deleteStyleLabel"></button></a>
</div>
</div>
<div id="ss-scheme" class="install-dim"></div>
<div class="set-update-url w100 checkbox-wrapper install-disable">
<label>
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="installUpdateFromLabel"></span>
<p></p>
</label>
<label class="live-reload">
<p></p>
</div>
<label class="live-reload checkbox-wrapper">
<input type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="liveReloadLabel"></span>
</label>
<label class="set-prefer-scheme">
<span i18n-text="installPreferSchemeLabel"></span>
<select>
<option value="none" i18n-text="installPreferSchemeNone"></option>
<option value="dark" i18n-text="installPreferSchemeDark"></option>
<option value="light" i18n-text="installPreferSchemeLight"></option>
</select>
</label>
<p hidden class="installed-actions">
<a href="manage.html" tabindex="0"><button i18n-text="openManage"></button></a>
<a href="edit.html?id=" tabindex="0"><button i18n-text="editStyleLabel"></button></a>
<a id="delete" tabindex="0"><button i18n-text="deleteStyleLabel"></button></a>
</p>
</div>
<p class="meta-description"></p>
<div id="live-reload-install-hint" class="w100" hidden></div>
<div class="meta-description w100 hide-empty"></div>
<div>
<h3 i18n-text="author"></h3>
<span class="meta-author"></span>
@ -73,8 +66,8 @@
<h3 i18n-text="license"></h3>
<span class="meta-license"></span>
</div>
<div class="external-link"></div>
<div id="applies-to-wrapper">
<div class="external-link hide-empty"></div>
<div class="w100">
<h3 i18n-text="appliesLabel"></h3>
<ul class="applies-to">
</ul>
@ -85,7 +78,6 @@
<div class="main">
<div class="warnings"></div>
</div>
</div>
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000">

View File

@ -2,6 +2,8 @@ body {
overflow: hidden;
margin: 0;
background: white;
display: flex;
height: 100vh;
}
a {
@ -22,25 +24,29 @@ input:disabled + span {
color: rgb(128, 128, 128);
}
.container {
display: flex;
height: 100vh;
}
#header,
.warnings {
flex: 0 0 var(--header-width);
box-sizing: border-box;
padding: 1rem;
box-shadow: 0 0 50px -18px black;
word-break: break-all;
overflow-wrap: break-word;
overflow-y: auto;
z-index: 100;
}
#header {
--child-gap: 1rem;
}
#header-contents > :nth-last-child(n + 2) {
margin-bottom: var(--child-gap);
}
#header.meta-init-error {
display: none;
}
#header-contents ul {
margin: 0;
}
.warnings {
display: none;
@ -72,26 +78,44 @@ input:disabled + span {
h1 {
margin-top: 0;
display: flex;
align-items: baseline;
flex-wrap: wrap;
}
h1 small {
.meta-name {
margin-right: .5em;
}
.meta-version {
font-size: 0.6em;
white-space: nowrap;
}
.meta-version::before {
content: "v";
}
.actions {
margin-bottom: 1em;
.checkbox-wrapper {
padding-left: 16px;
position: relative;
box-sizing: border-box;
display: block;
}
.actions label {
max-width: -moz-fit-content;
max-width: fit-content;
.set-update-url p {
word-break: break-all;
opacity: .5;
margin: .25em 0 0;
}
#install-wrapper {
display: flex;
align-items: center;
margin: 0.5em 0;
flex-wrap: wrap;
}
#install-wrapper > :nth-last-child(n + 2) {
margin-right: .5rem;
}
#live-reload-install-hint {
color: darkcyan;
}
.w100 {
width: 100%;
}
.install {
@ -107,7 +131,6 @@ h1 small {
-webkit-appearance: none;
-moz-appearance: none;
border-style: none;
margin-bottom: 1ex;
cursor: pointer;
box-shadow: inset 0 -1px 0 0 hsl(0, 0%, 24%), inset 0 1px 0 0 hsl(0, 0%, 30%), inset 1px 0 0 0 hsl(0, 0%, 24%);
transition: color .25s, background-color .25s;
@ -212,44 +235,25 @@ h1 small {
filter: hue-rotate(-18deg) brightness(.7) contrast(2);
}
.install.installed {
display: none;
}
h2.installed.active {
display: inline-block;
h2 {
font-weight: bold;
margin-top: 0;
margin: 0;
color: darkcyan;
}
h2.installed.active ~ .configure-usercss svg {
.installed .configure-usercss svg {
fill: hsl(180, 100%, 20%);
}
h2.installed.active ~ .configure-usercss:hover svg {
.installed .configure-usercss:hover svg {
fill: hsl(180, 100%, 30%);
}
.actions label input {
margin: 0 0.5em 0 0;
flex: 0 0 auto;
#header-contents > .hide-empty:empty,
body:not(.installed) .install-show,
.installed .install-hide {
display: none !important;
}
.actions label span {
min-width: 0;
}
.set-update-url {
flex-wrap: wrap;
}
.set-update-url p {
word-break: break-all;
.installed .install-dim {
opacity: .5;
width: 100%;
margin: .25em 0 .25em;
}
label.set-prefer-scheme:not(.unavailable) {
padding-left: 0;
}
.external {
@ -270,8 +274,8 @@ li {
.main,
.main .CodeMirror {
height: 100% !important;
width: 100% !important;
height: 100%;
width: 100%;
border: none;
}
@ -290,22 +294,13 @@ li {
}
#header.meta-init > * {
opacity: 1;
transition: opacity .5s;
-moz-user-select: auto;
user-select: auto;
}
#header.meta-init[data-arrived-fast="true"] > * {
.meta-init[data-arrived-fast="true"] > * {
transition-duration: .1s;
}
label {
/* FIXME: why do we want to give all labels a padding? */
padding-left: 16px;
position: relative;
}
.lds-spinner {
top: 50px;
opacity: .2;
@ -316,7 +311,6 @@ label {
.configure-usercss .svg-icon.config {
width: 20px;
height: 20px;
margin-top: -3px;
}
#message-box.config-dialog {
width: 0;
@ -331,118 +325,74 @@ label {
@media (max-width: 850px) {
body {
overflow: hidden;
}
.container {
flex-direction: column;
}
#header {
flex: 0 1 auto;
border-right: none;
border-bottom: 1px dashed #AAA;
overflow-x: auto;
overflow-y: hidden;
padding: 0;
min-height: 6rem;
max-height: 40vh;
resize: vertical;
flex: 0 1 auto;
--child-gap: .75rem;
}
#header:not(.meta-init) {
min-height: 300px;
}
.main {
flex: 1;
}
#header-content-wrapper {
display: flex;
flex-wrap: wrap;
padding: .5rem 0 0 1rem;
box-sizing: border-box;
height: min-content;
}
#header-content-wrapper > * {
flex-grow: 1;
margin: 0;
padding: 0 1rem .5rem 0;
min-width: 0;
}
#header-content-wrapper > .meta-description + .flex-wrapper {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding: 0;
}
#header-content-wrapper > .meta-description + .flex-wrapper > * {
display: flex;
flex-direction: column;
flex: 1;
flex-wrap: wrap;
white-space: nowrap;
padding: 0 1rem .5rem 0;
box-sizing: border-box;
}
.flex-wrapper ul {
margin: 0;
}
#header-content-wrapper > .meta-description {
flex-basis: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.actions {
#header-contents {
display: flex;
flex-wrap: wrap;
align-content: flex-start;
align-items: flex-start;
}
.set-update-url p {
#header-contents > :not(.w100) {
margin-right: 1rem;
}
.set-update-url {
display: flex;
white-space: nowrap;
box-sizing: border-box;
}
.set-update-url p {
margin: 0 0 0 1rem;
overflow: hidden;
text-overflow: ellipsis;
}
.actions label {
min-width: 100px;
flex: 1;
}
.actions label span {
white-space: nowrap;
}
.has-warnings #header {
min-height: 4em;
max-height: 20%;
}
.warnings {
max-height: 20%;
}
.warning:not(:last-child) {
border-bottom: 1px dashed #b57c7c;
padding-bottom: 1em;
}
ul.applies-to,
.actions label {
margin: 0;
}
#header-content-wrapper > h1 {
font-size: 1.75em;
display: flex;
align-items: baseline;
}
#header-content-wrapper > h1 > .meta-version {
padding-left: 3px;
}
#header-content-wrapper > h1 > .meta-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
#header-content-wrapper > * h3 {
#header-contents h3 {
margin: 0 0 .5rem;
}
.install {
flex-shrink: 0;
margin-right: 1em;
}
#message-box.config-dialog > div {
top: auto;
bottom: 3rem;
}
h1 {
flex-wrap: nowrap;
}
.meta-name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.main {
height: auto;
flex: 1;
}
}
@media (min-width: 850px) {
#header {
height: 100% !important; /* overrides user resize */
}
#ss-scheme > label {
display: block;
}
.w100-full {
width: 100%;
margin-top: var(--child-gap);
}
}
/* Retina-specific stuff here */

View File

@ -1,4 +1,4 @@
/* global $ $create $createLink $$remove showSpinner */// dom.js
/* global $$ $ $create $createLink $$remove showSpinner */// dom.js
/* global API */// msg.js
/* global URLS closeCurrentTab deepEqual */// toolbox.js
/* global messageBox */
@ -7,6 +7,9 @@
/* global t */// localization.js
'use strict';
const CFG_SEL = '#message-box.config-dialog';
let cfgShown = true;
let cm;
let initialUrl;
let installed;
@ -44,6 +47,7 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
'/vendor/codemirror/keymap/emacs',
'/vendor/codemirror/keymap/vim', // TODO: load conditionally
'/vendor/codemirror/mode/css/css',
'/vendor/codemirror/mode/stylus/stylus',
'/vendor/codemirror/addon/search/searchcursor',
'/vendor/codemirror/addon/fold/foldcode',
'/vendor/codemirror/addon/fold/foldgutter',
@ -63,6 +67,7 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
({tabId, initialUrl} = preinit);
liveReload = initLiveReload();
preinit.tpl.then(el => $('#ss-scheme').append(...$('[id=ss-scheme]', el).children));
const [
{dup, style, error, sourceCode},
@ -97,12 +102,10 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
// update UI
if (versionTest < 0) {
$('.actions').parentNode.insertBefore(
$create('.warning', t('versionInvalidOlder')),
$('.actions')
);
$('h1').after($create('.warning', t('versionInvalidOlder')));
}
$('button.install').onclick = () => {
shouldShowConfig();
(!dup ?
Promise.resolve(true) :
messageBox.confirm($create('span', t('styleInstallOverwrite', [
@ -136,11 +139,9 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
updateUrl.href.slice(0, 300) + '...';
// set prefer scheme
const preferScheme = $('.set-prefer-scheme select');
preferScheme.onchange = () => {
style.preferScheme = preferScheme.value;
$('#ss-scheme').onchange = e => {
style.preferScheme = e.target.value;
};
preferScheme.onchange();
if (URLS.isLocalhost(initialUrl)) {
$('.live-reload input').onchange = liveReload.onToggled;
@ -170,41 +171,28 @@ function updateMeta(style, dup = installedDup) {
!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) || '').replace(/\S+$/, '\n$&');
$('.meta-name').textContent = data.name;
$('.meta-version').textContent = data.version;
$('.meta-description').textContent = data.description;
$('.set-prefer-scheme select').value =
style.preferScheme === 'dark' ? 'dark' :
style.preferScheme === 'light' ? 'light' : 'none';
$$('#ss-scheme input').forEach(el => {
el.checked = el.value === (style.preferScheme || 'none');
});
if (data.author) {
$('.meta-author').parentNode.style.display = '';
$('.meta-author').textContent = '';
$('.meta-author').appendChild(makeAuthor(data.author));
} else {
$('.meta-author').parentNode.style.display = 'none';
}
$('.meta-license').parentNode.style.display = data.license ? '' : 'none';
$('.meta-license').textContent = data.license;
$('.applies-to').textContent = '';
replaceChildren($('.meta-author'), makeAuthor(data.author), true);
replaceChildren($('.meta-license'), data.license, true);
replaceChildren($('.external-link'), makeExternalLink());
getAppliesTo(style).then(list =>
$('.applies-to').append(...list.map(s => $create('li', s))));
$('.external-link').textContent = '';
const externalLink = makeExternalLink();
if (externalLink) {
$('.external-link').appendChild(externalLink);
}
replaceChildren($('.applies-to'), list.map(s => $create('li', s))));
Object.assign($('.configure-usercss'), {
hidden: !data.vars,
onclick: openConfigDialog,
});
if (!data.vars) {
$$remove('#message-box.config-dialog');
cfgShown = false;
$$remove(CFG_SEL);
} else if (!deepEqual(data.vars, vars)) {
vars = data.vars;
// Use the user-customized vars from the installed style
@ -214,6 +202,8 @@ function updateMeta(style, dup = installedDup) {
v.value = dv.value;
}
}
}
if (shouldShowConfig()) {
openConfigDialog();
}
@ -227,26 +217,26 @@ function updateMeta(style, dup = installedDup) {
if (dup) enablePostActions();
function makeAuthor(text) {
const match = text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))?$/);
const match = text && text.match(/^(.+?)(?:\s+<(.+?)>)?(?:\s+\((.+?)\))?$/);
if (!match) {
return document.createTextNode(text);
return text;
}
const [, name, email, url] = match;
const frag = document.createDocumentFragment();
const elems = [];
if (email) {
frag.appendChild($createLink(`mailto:${email}`, name));
elems.push($createLink(`mailto:${email}`, name));
} else {
frag.appendChild($create('span', name));
elems.push($create('span', name));
}
if (url) {
frag.appendChild($createLink(url,
elems.push($createLink(url,
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$create('SVG:path', {
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z',
}))
));
}
return frag;
return elems;
}
function makeExternalLink() {
@ -274,7 +264,7 @@ function updateMeta(style, dup = installedDup) {
function showError(err) {
$('.warnings').textContent = '';
$('.warnings').classList.toggle('visible', Boolean(err));
$('.container').classList.toggle('has-warnings', Boolean(err));
document.body.classList.toggle('has-warnings', Boolean(err));
err = Array.isArray(err) ? err : [err];
if (err[0]) {
let i;
@ -310,12 +300,11 @@ function install(style) {
$$remove('.warning');
$('button.install').disabled = true;
$('button.install').classList.add('installed');
$('#live-reload-install-hint').classList.toggle('hidden', !liveReload.enabled);
$('h2.installed').classList.add('active');
$('.set-update-url input[type=checkbox]').disabled = true;
$('#live-reload-install-hint').hidden = !liveReload.enabled;
$('.set-update-url').title = style.updateUrl ?
t('installUpdateFrom', style.updateUrl) : '';
$('.set-prefer-scheme select').disabled = true;
$$('.install-disable input').forEach(el => (el.disabled = true));
document.body.classList.add('installed');
enablePostActions();
updateMeta(style);
}
@ -323,9 +312,7 @@ function install(style) {
function enablePostActions() {
const {id} = installed || installedDup;
sessionStorage.justEditedStyleId = id;
$('h2.installed').hidden = !installed;
$('.installed-actions').hidden = false;
$('.installed-actions a[href*="edit.html"]').search = `?id=${id}`;
$('#edit').search = `?id=${id}`;
$('#delete').onclick = async () => {
if (await messageBox.confirm(t('deleteStyleConfirm'), 'danger center', t('confirmDelete'))) {
await API.styles.delete(id);
@ -434,3 +421,16 @@ function initLiveReload() {
});
}
}
function shouldShowConfig() {
// TODO: rewrite message-box to support multiple instances or find an existing tiny library
const prev = cfgShown;
cfgShown = $(CFG_SEL) != null;
return prev && !cfgShown;
}
function replaceChildren(el, children, toggleParent) {
if (el.firstChild) el.textContent = '';
if (children) el.append(...Array.isArray(children) ? children : [children]);
if (toggleParent) el.parentNode.hidden = !el.firstChild;
}

View File

@ -1,5 +1,6 @@
/* global API */// msg.js
/* global closeCurrentTab download */// toolbox.js
/* global t */// localization.js
'use strict';
/* exported preinit */
@ -89,5 +90,7 @@ const preinit = (() => {
return {error, sourceCode};
}
})(),
tpl: t.fetchTemplate('/edit/settings.html', 'styleSettings'),
};
})();

View File

@ -1,3 +1,4 @@
/* global download */// toolbox.js
'use strict';
//#region Exports
@ -118,6 +119,16 @@ Object.assign(t, {
return bin;
},
async fetchTemplate(url, name) {
let el = t.template[name];
if (!el) {
el = (await download(url, {responseType: 'document'})).body.firstElementChild;
t.NodeList(el.getElementsByTagName('*'));
t.template[name] = el;
}
return el;
},
sanitizeHtml(root) {
const toRemove = [];
const walker = document.createTreeWalker(root);

View File

@ -438,7 +438,7 @@ function download(url, {
const usoVars = [];
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const u = new URL(collapseUsoVars(url));
const u = new URL(collapseUsoVars(url), location);
const onTimeout = () => {
xhr.abort();
reject(new Error('Timeout fetching ' + u.href));