shuffle and tidy up options (#1406)
* move updates/sync to the top, theme to the bottom * remove font override * replace 'Back to manage' with 'Close' * add a note for the built-in shortcuts UI in FF - update button + confirm reset * one button to connect/disconnect * shorten ids * simplify/extract sync js * reuse :invalid style
This commit is contained in:
parent
c9b8593830
commit
8d3e01e05a
|
@ -1163,7 +1163,7 @@
|
||||||
"message": "Reset options"
|
"message": "Reset options"
|
||||||
},
|
},
|
||||||
"optionsStylusThemes": {
|
"optionsStylusThemes": {
|
||||||
"message": "Find a Stylus UI theme"
|
"message": "Click Stylus icon in the browser toolbar on any Stylus page including this one, then click 'Find styles'"
|
||||||
},
|
},
|
||||||
"optionsSubheading": {
|
"optionsSubheading": {
|
||||||
"message": "More Options",
|
"message": "More Options",
|
||||||
|
@ -1500,6 +1500,9 @@
|
||||||
"shortcutsNote": {
|
"shortcutsNote": {
|
||||||
"message": "Define keyboard shortcuts"
|
"message": "Define keyboard shortcuts"
|
||||||
},
|
},
|
||||||
|
"shortcutsNoteFF": {
|
||||||
|
"message": "In Firefox 66+ you can open the built-in shortcuts UI manually:\n1) right-click Stylus icon in the toolbar and choose 'Manage'\n(alternatively, open about:addons via the main menu or Ctrl-Shift-A),\n2) in the page that opens click the cog wheel icon in the top right corner,\n3) choose 'Manage extension shortcuts'.\n\nYou can also customize the shortcuts here."
|
||||||
|
},
|
||||||
"sortDateNewestFirst": {
|
"sortDateNewestFirst": {
|
||||||
"message": "newest first",
|
"message": "newest first",
|
||||||
"description": "Text added to indicate that sorting a date would add the newest entries at the top"
|
"description": "Text added to indicate that sorting a date would add the newest entries at the top"
|
||||||
|
|
|
@ -215,10 +215,6 @@ label {
|
||||||
#options span .svg-icon {
|
#options span .svg-icon {
|
||||||
margin-top: -3px; /* inline info and config icons */
|
margin-top: -3px; /* inline info and config icons */
|
||||||
}
|
}
|
||||||
input:invalid {
|
|
||||||
background-color: rgba(255, 0, 0, 0.1);
|
|
||||||
color: darkred;
|
|
||||||
}
|
|
||||||
#enabled {
|
#enabled {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,6 +116,11 @@ input[type=search] {
|
||||||
border: 1px solid var(--c65);
|
border: 1px solid var(--c65);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:invalid {
|
||||||
|
background-color: rgba(255, 0, 0, 0.1);
|
||||||
|
color: darkred;
|
||||||
|
}
|
||||||
|
|
||||||
.svg-icon {
|
.svg-icon {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
|
|
|
@ -43,6 +43,7 @@ Object.assign(t, {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (node.localName === 'template') {
|
if (node.localName === 'template') {
|
||||||
|
node.remove();
|
||||||
t.createTemplate(node);
|
t.createTemplate(node);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -87,17 +88,20 @@ Object.assign(t, {
|
||||||
text.replace(t.RX_WORD_BREAK, '$&\u00AD');
|
text.replace(t.RX_WORD_BREAK, '$&\u00AD');
|
||||||
},
|
},
|
||||||
|
|
||||||
createTemplate(node) {
|
createTemplate(el) {
|
||||||
const el = node.content.firstElementChild.cloneNode(true);
|
const {content} = el;
|
||||||
t.NodeList(el);
|
const toRemove = [];
|
||||||
t.template[node.dataset.id] = el;
|
// Compress inter-tag whitespace to reduce DOM tree and avoid space between elements without flex
|
||||||
// compress inter-tag whitespace to reduce number of DOM nodes by 25%
|
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT);
|
||||||
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
||||||
for (let n; (n = walker.nextNode());) {
|
for (let n; (n = walker.nextNode());) {
|
||||||
if (!/[\xA0\S]/.test(n.textContent)) { // allow \xA0 to keep
|
if (!/[\xA0\S]/.test(n.textContent) || // allowing \xA0 so as to preserve
|
||||||
n.remove();
|
n.nodeType === Node.COMMENT_NODE) {
|
||||||
|
toRemove.push(n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
toRemove.forEach(n => n.remove());
|
||||||
|
t.NodeList(content.querySelectorAll('*'));
|
||||||
|
t.template[el.dataset.id] = content.childNodes.length > 1 ? content : content.childNodes[0];
|
||||||
},
|
},
|
||||||
|
|
||||||
createText(str) {
|
createText(str) {
|
||||||
|
|
151
options.html
151
options.html
|
@ -5,7 +5,7 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title i18n-text-append="optionsHeading">Stylus </title>
|
<title i18n-text-append="optionsHeading">Stylus </title>
|
||||||
<link rel="stylesheet" href="global.css">
|
<link rel="stylesheet" href="global.css">
|
||||||
<link href="global-dark.css" rel="stylesheet">
|
<link href="global-dark.css?bugfix" rel="stylesheet"> <!-- https://crbug.com/1298600 -->
|
||||||
|
|
||||||
<script src="js/polyfill.js"></script>
|
<script src="js/polyfill.js"></script>
|
||||||
<script src="js/toolbox.js"></script>
|
<script src="js/toolbox.js"></script>
|
||||||
|
@ -19,6 +19,30 @@
|
||||||
|
|
||||||
<link rel="stylesheet" href="options/onoffswitch.css">
|
<link rel="stylesheet" href="options/onoffswitch.css">
|
||||||
<link rel="stylesheet" href="options/options.css">
|
<link rel="stylesheet" href="options/options.css">
|
||||||
|
|
||||||
|
<template data-id="shortcutsFF">
|
||||||
|
<p style="line-height: 1.5" i18n-text="shortcutsNoteFF"></p>
|
||||||
|
<table style="margin: 0 auto">
|
||||||
|
<tr>
|
||||||
|
<td i18n-text="optionsCustomizePopup"></td>
|
||||||
|
<td><input id="hotkey._execute_browser_action" type="search"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td i18n-text="openManage"></td>
|
||||||
|
<td><input id="hotkey.openManage" type="search"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td i18n-text="disableAllStyles"></td>
|
||||||
|
<td><input id="hotkey.styleDisableAll" type="search"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<p style="text-align: center">
|
||||||
|
<a href="https://developer.mozilla.org/Add-ons/WebExtensions/manifest.json/commands#Key_combinations"
|
||||||
|
target="_blank" i18n-text="helpAlt"></a>
|
||||||
|
</p>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="options/options-sync.js"></script>
|
||||||
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
|
<script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC -->
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -38,12 +62,55 @@
|
||||||
|
|
||||||
<div class="options-wrapper">
|
<div class="options-wrapper">
|
||||||
|
|
||||||
<div class="block">
|
<div class="block" id="updates">
|
||||||
<h1 i18n-text="cm_theme"></h1>
|
<h1 i18n-text="optionsCustomizeUpdate"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<div class="label">
|
<label>
|
||||||
<a i18n-text="optionsStylusThemes" target="_blank"
|
<span i18n-text="optionsUpdateInterval">
|
||||||
href="https://33kk.github.io/uso-archive/?category=chrome-extension&search=Stylus"></a>
|
<a i18n-title="optionsUpdateImportNote"
|
||||||
|
data-cmd="note" class="svg-inline-wrapper" tabindex="0">
|
||||||
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<input type="number" min="0" id="updateInterval">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="block sync-options">
|
||||||
|
<h1 i18n-text="optionsCustomizeSync"></h1>
|
||||||
|
<div class="items">
|
||||||
|
<label>
|
||||||
|
<span class="sync-status"></span>
|
||||||
|
<div class="select-resizer">
|
||||||
|
<select class="cloud-name">
|
||||||
|
<option value="none" i18n-text="optionsSyncNone"></option>
|
||||||
|
<option value="dropbox">Dropbox</option>
|
||||||
|
<option value="google">Google Drive</option>
|
||||||
|
<option value="onedrive">OneDrive</option>
|
||||||
|
<option value="webdav">WebDAV</option>
|
||||||
|
</select>
|
||||||
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
<table class="drive-options" data-drive="webdav">
|
||||||
|
<tr>
|
||||||
|
<td i18n-text="optionsSyncUrl"></td>
|
||||||
|
<td><input type="url" data-option="url"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td i18n-text="optionsSyncUsername"></td>
|
||||||
|
<td><input type="text" data-option="username"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td i18n-text="optionsSyncPassword"></td>
|
||||||
|
<td><input type="password" data-option="password"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="connect"></button>
|
||||||
|
<button class="sync-now" i18n-text="optionsSyncSyncNow"></button>
|
||||||
|
<button class="sync-login" i18n-text="optionsSyncLogin"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -207,62 +274,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block" id="updates">
|
|
||||||
<h1 i18n-text="optionsCustomizeUpdate"></h1>
|
|
||||||
<div class="items">
|
|
||||||
<label>
|
|
||||||
<span i18n-text="optionsUpdateInterval">
|
|
||||||
<a i18n-title="optionsUpdateImportNote"
|
|
||||||
data-cmd="note" class="svg-inline-wrapper" tabindex="0">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
<input type="number" min="0" id="updateInterval">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block sync-options">
|
|
||||||
<h1 i18n-text="optionsCustomizeSync"></h1>
|
|
||||||
<div class="items">
|
|
||||||
<div class="label">
|
|
||||||
<span class="sync-status"></span>
|
|
||||||
<div class="select-resizer">
|
|
||||||
<select class="cloud-name">
|
|
||||||
<option value="none" i18n-text="optionsSyncNone"></option>
|
|
||||||
<option value="dropbox">Dropbox</option>
|
|
||||||
<option value="google">Google Drive</option>
|
|
||||||
<option value="onedrive">OneDrive</option>
|
|
||||||
<option value="webdav">WebDAV</option>
|
|
||||||
</select>
|
|
||||||
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<fieldset class="drive-options">
|
|
||||||
<div class="webdav-options" data-drive="webdav">
|
|
||||||
<label class="url">
|
|
||||||
<span i18n-text="optionsSyncUrl"></span>
|
|
||||||
<input type="text" data-option="url">
|
|
||||||
</label>
|
|
||||||
<label class="username">
|
|
||||||
<span i18n-text="optionsSyncUsername"></span>
|
|
||||||
<input type="text" data-option="username">
|
|
||||||
</label>
|
|
||||||
<label class="password">
|
|
||||||
<span i18n-text="optionsSyncPassword"></span>
|
|
||||||
<input type="password" data-option="password">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<div class="actions">
|
|
||||||
<button type="button" class="connect" i18n-text="optionsSyncConnect"></button>
|
|
||||||
<button type="button" class="disconnect" i18n-text="optionsSyncDisconnect"></button>
|
|
||||||
<button type="button" class="sync-now" i18n-text="optionsSyncSyncNow"></button>
|
|
||||||
<button type="button" class="sync-login" i18n-text="optionsSyncLogin"></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="block" id="advanced">
|
<div class="block" id="advanced">
|
||||||
<h1 i18n-text="optionsAdvanced"></h1>
|
<h1 i18n-text="optionsAdvanced"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
@ -325,18 +336,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="block">
|
||||||
|
<h1 i18n-text="cm_theme"></h1>
|
||||||
|
<div class="items" i18n-text="optionsStylusThemes" style="width:0"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block" id="actions">
|
<div class="block" id="actions">
|
||||||
<button data-cmd="reset" i18n-text="optionsResetButton" i18n-title="optionsReset"></button>
|
<button id="reset" i18n-text="optionsResetButton" i18n-title="optionsReset"></button>
|
||||||
<button data-cmd="open-manage" i18n-text="styleCancelEditLabel"></button>
|
<button id="shortcuts" i18n-text="shortcuts" i18n-title="shortcutsNote"></button>
|
||||||
<div data-cmd="check-updates">
|
<button id="manage" i18n-text="confirmClose"></button>
|
||||||
<button i18n-text="optionsCheck" i18n-title="optionsCheckUpdate">
|
|
||||||
<span id="update-progress"></span>
|
|
||||||
</button>
|
|
||||||
<div id="updates-installed" i18n-text="updatesCurrentlyInstalled"></div>
|
|
||||||
</div>
|
|
||||||
<button data-cmd="open-keyboard" class="chromium-only" i18n-text="shortcuts" i18n-title="shortcutsNote"></button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
96
options/options-sync.js
Normal file
96
options/options-sync.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/* global API msg */// msg.js
|
||||||
|
/* global t */// localization.js
|
||||||
|
/* global $ $$ toggleDataset waitForSelector */// dom.js
|
||||||
|
/* global capitalize */// toolbox.js
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
API.sync.getStatus(),
|
||||||
|
waitForSelector('.sync-options'),
|
||||||
|
]).then(([status, elSync]) => {
|
||||||
|
const elCloud = $('.cloud-name', elSync);
|
||||||
|
const elToggle = $('.connect', elSync);
|
||||||
|
const elSyncNow = $('.sync-now', elSync);
|
||||||
|
const elStatus = $('.sync-status', elSync);
|
||||||
|
const elLogin = $('.sync-login', elSync);
|
||||||
|
const elDriveOptions = $$('.drive-options', elSync);
|
||||||
|
updateButtons();
|
||||||
|
msg.onExtension(e => {
|
||||||
|
if (e.method === 'syncStatusUpdate') {
|
||||||
|
setStatus(e.status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
elCloud.on('change', updateButtons);
|
||||||
|
elToggle.onclick = async () => {
|
||||||
|
if (elToggle.dataset.cmd === 'start') {
|
||||||
|
await API.sync.setDriveOptions(elCloud.value, getDriveOptions());
|
||||||
|
await API.sync.start(elCloud.value);
|
||||||
|
} else {
|
||||||
|
await API.sync.stop();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
elSyncNow.onclick = API.sync.syncNow;
|
||||||
|
elLogin.onclick = async () => {
|
||||||
|
await API.sync.login();
|
||||||
|
await API.sync.syncNow();
|
||||||
|
};
|
||||||
|
|
||||||
|
function getDriveOptions() {
|
||||||
|
const result = {};
|
||||||
|
for (const el of $$(`[data-drive=${elCloud.value}] [data-option]`)) {
|
||||||
|
result[el.dataset.option] = el.value;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setDriveOptions(options) {
|
||||||
|
for (const el of $$(`[data-drive=${elCloud.value}] [data-option]`)) {
|
||||||
|
el.value = options[el.dataset.option] || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStatus(newStatus) {
|
||||||
|
status = newStatus;
|
||||||
|
updateButtons();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateButtons() {
|
||||||
|
const {state, STATES} = status;
|
||||||
|
const isConnected = state === STATES.connected;
|
||||||
|
const off = state === STATES.disconnected;
|
||||||
|
if (status.currentDriveName) {
|
||||||
|
elCloud.value = status.currentDriveName;
|
||||||
|
}
|
||||||
|
elCloud.disabled = !off;
|
||||||
|
elToggle.disabled = status.syncing;
|
||||||
|
elToggle.textContent = t(`optionsSync${off ? 'Connect' : 'Disconnect'}`);
|
||||||
|
elToggle.dataset.cmd = off ? 'start' : 'stop';
|
||||||
|
elSyncNow.disabled = !isConnected || status.syncing || !status.login;
|
||||||
|
elStatus.textContent = getStatusText();
|
||||||
|
elLogin.hidden = !isConnected || status.login;
|
||||||
|
for (const el of elDriveOptions) {
|
||||||
|
el.hidden = el.dataset.drive !== elCloud.value;
|
||||||
|
el.disabled = !off;
|
||||||
|
}
|
||||||
|
toggleDataset(elSync, 'enabled', elCloud.value !== 'none');
|
||||||
|
setDriveOptions(await API.sync.getDriveOptions(elCloud.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusText() {
|
||||||
|
if (status.syncing) {
|
||||||
|
const {phase, loaded, total} = status.progress || {};
|
||||||
|
return phase
|
||||||
|
? t(`optionsSyncStatus${capitalize(phase)}`, [loaded + 1, total], false) ||
|
||||||
|
`${phase} ${loaded} / ${total}`
|
||||||
|
: t('optionsSyncStatusSyncing');
|
||||||
|
}
|
||||||
|
const {state, errorMessage, STATES} = status;
|
||||||
|
if (errorMessage && (state === STATES.connected || state === STATES.disconnected)) {
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
if (state === STATES.connected && !status.login) {
|
||||||
|
return t('optionsSyncStatusRelogin');
|
||||||
|
}
|
||||||
|
return t(`optionsSyncStatus${capitalize(state)}`, null, false) || state;
|
||||||
|
}
|
||||||
|
});
|
|
@ -8,8 +8,6 @@ html {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: none;
|
background: none;
|
||||||
font-family: "Helvetica Neue", Helvetica, sans-serif;
|
|
||||||
font-size: 12px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
@ -184,12 +182,6 @@ input[type=number] {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=number]:invalid,
|
|
||||||
input[type=text]:invalid {
|
|
||||||
background-color: rgba(255, 0, 0, 0.1);
|
|
||||||
color: darkred;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="color"] {
|
input[type="color"] {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
height: 2em;
|
height: 2em;
|
||||||
|
@ -201,25 +193,16 @@ input[type=time] {
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions {
|
#actions {
|
||||||
justify-content: space-around;
|
justify-content: center;
|
||||||
align-items: stretch;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: .5em 1em 1em;
|
padding: .5em 1em 1em;
|
||||||
white-space: nowrap;
|
|
||||||
background-color: rgba(0, 0, 0, .05);
|
background-color: rgba(0, 0, 0, .05);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border-top: 1px solid var(--c60);
|
border-top: 1px solid var(--c60);
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
min-height: min-content; /* workaround for old Chrome ~70 bug when the window height is small */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions button {
|
#actions button {
|
||||||
width: auto;
|
margin: .5em 1em 0 0;
|
||||||
margin-top: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#actions button:not(:last-child) {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-cmd="check-updates"] button {
|
[data-cmd="check-updates"] button {
|
||||||
|
@ -229,41 +212,6 @@ input[type=time] {
|
||||||
padding: .5em 0 .5em 0;
|
padding: .5em 0 .5em 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.update-in-progress [data-cmd="check-updates"] {
|
|
||||||
opacity: .5;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.update-in-progress #update-progress {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: currentColor;
|
|
||||||
content: "";
|
|
||||||
opacity: .35;
|
|
||||||
}
|
|
||||||
|
|
||||||
#updates-installed {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 85%;
|
|
||||||
margin-top: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#updates-installed::after {
|
|
||||||
content: attr(data-value);
|
|
||||||
margin-left: .5ex;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#updates-installed:not([data-value]),
|
|
||||||
#updates-installed[data-value=""] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html:not(.firefox):not(.opera) #updates {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.svg-inline-wrapper .svg-icon {
|
.svg-inline-wrapper .svg-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
|
@ -298,24 +246,27 @@ html:not(.firefox):not(.opera) #updates {
|
||||||
.sync-status::first-letter {
|
.sync-status::first-letter {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.sync-options .drive-options {
|
[data-drive="webdav"] {
|
||||||
margin: 0;
|
width: 100%;
|
||||||
padding: 0;
|
border-spacing: 0;
|
||||||
border: 0;
|
border-collapse: collapse;
|
||||||
}
|
}
|
||||||
.drive-options > :not([hidden]) {
|
[data-drive="webdav"] td:nth-child(1) {
|
||||||
display: table;
|
padding: 1px .5em 1px 0;
|
||||||
|
max-width: 10em;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
|
[data-drive="webdav"] td:nth-child(2) {
|
||||||
|
padding: 1px 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.drive-options > * > label {
|
[data-drive="webdav"] input {
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
.drive-options > * > label > * {
|
|
||||||
display: table-cell;
|
|
||||||
}
|
|
||||||
.drive-options > * input {
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
.sync-options:not([data-enabled]) .actions {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
.sync-options .actions button {
|
.sync-options .actions button {
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
/* global API msg */// msg.js
|
/* global API */// msg.js
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global t */// localization.js
|
/* global t */// localization.js
|
||||||
/* global
|
/* global $ $$ getEventKeyName messageBoxProxy setupLivePrefs */// dom.js
|
||||||
$
|
|
||||||
$$
|
|
||||||
$create
|
|
||||||
$createLink
|
|
||||||
getEventKeyName
|
|
||||||
messageBoxProxy
|
|
||||||
setupLivePrefs
|
|
||||||
*/// dom.js
|
|
||||||
/* global
|
/* global
|
||||||
CHROME_POPUP_BORDER_BUG
|
CHROME_POPUP_BORDER_BUG
|
||||||
FIREFOX
|
FIREFOX
|
||||||
URLS
|
URLS
|
||||||
capitalize
|
|
||||||
clamp
|
clamp
|
||||||
ignoreChromeError
|
ignoreChromeError
|
||||||
openURL
|
openURL
|
||||||
|
@ -23,242 +14,61 @@
|
||||||
|
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
$$('input[min], input[max]').forEach(enforceInputRange);
|
$$('input[min], input[max]').forEach(enforceInputRange);
|
||||||
|
|
||||||
if (CHROME_POPUP_BORDER_BUG) {
|
if (CHROME_POPUP_BORDER_BUG) {
|
||||||
$('.chrome-no-popup-border').classList.remove('chrome-no-popup-border');
|
$('.chrome-no-popup-border').classList.remove('chrome-no-popup-border');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FIREFOX && 'update' in (chrome.commands || {})) {
|
if (FIREFOX && 'update' in (chrome.commands || {})) {
|
||||||
$('[data-cmd="open-keyboard"]').classList.remove('chromium-only');
|
$('#shortcuts').classList.remove('chromium-only');
|
||||||
}
|
}
|
||||||
|
|
||||||
// actions
|
// actions
|
||||||
$('#options-close-icon').onclick = () => {
|
$('#options-close-icon').onclick = () => {
|
||||||
top.dispatchEvent(new CustomEvent('closeOptions'));
|
top.dispatchEvent(new CustomEvent('closeOptions'));
|
||||||
};
|
};
|
||||||
|
$('#manage').onclick = () => {
|
||||||
document.onclick = e => {
|
|
||||||
const target = e.target.closest('[data-cmd]');
|
|
||||||
if (!target) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// prevent double-triggering in case a sub-element was clicked
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
switch (target.dataset.cmd) {
|
|
||||||
case 'open-manage':
|
|
||||||
API.openManage();
|
API.openManage();
|
||||||
break;
|
};
|
||||||
|
$('#shortcuts').onclick = () => {
|
||||||
case 'check-updates':
|
|
||||||
checkUpdates();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'open-keyboard':
|
|
||||||
if (FIREFOX) {
|
if (FIREFOX) {
|
||||||
customizeHotkeys();
|
customizeHotkeys();
|
||||||
} else {
|
} else {
|
||||||
openURL({url: URLS.configureCommands});
|
openURL({url: URLS.configureCommands});
|
||||||
}
|
}
|
||||||
e.preventDefault();
|
};
|
||||||
break;
|
$('#reset').onclick = async () => {
|
||||||
|
if (await messageBoxProxy.confirm(t('confirmDiscardChanges'))) {
|
||||||
case 'reset':
|
for (const el of $$('input')) {
|
||||||
$$('input')
|
const id = el.id || el.name;
|
||||||
.filter(input => prefs.knownKeys.includes(input.id))
|
if (prefs.knownKeys.includes(id)) {
|
||||||
.forEach(input => prefs.reset(input.id));
|
prefs.reset(id);
|
||||||
break;
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// sync to cloud
|
|
||||||
(() => {
|
|
||||||
const elCloud = $('.sync-options .cloud-name');
|
|
||||||
const elStart = $('.sync-options .connect');
|
|
||||||
const elStop = $('.sync-options .disconnect');
|
|
||||||
const elSyncNow = $('.sync-options .sync-now');
|
|
||||||
const elStatus = $('.sync-options .sync-status');
|
|
||||||
const elLogin = $('.sync-options .sync-login');
|
|
||||||
const elDriveOptions = $('.sync-options .drive-options');
|
|
||||||
/** @type {Sync.Status} */
|
|
||||||
let status = {};
|
|
||||||
msg.onExtension(e => {
|
|
||||||
if (e.method === 'syncStatusUpdate') {
|
|
||||||
setStatus(e.status);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
API.sync.getStatus()
|
|
||||||
.then(setStatus);
|
|
||||||
|
|
||||||
elCloud.on('change', updateButtons);
|
|
||||||
for (const [btn, fn] of [
|
|
||||||
[elStart, async () => {
|
|
||||||
await API.sync.setDriveOptions(elCloud.value, getDriveOptions());
|
|
||||||
await API.sync.start(elCloud.value);
|
|
||||||
}],
|
|
||||||
[elStop, API.sync.stop],
|
|
||||||
[elSyncNow, API.sync.syncNow],
|
|
||||||
[elLogin, async () => {
|
|
||||||
await API.sync.login();
|
|
||||||
await API.sync.syncNow();
|
|
||||||
}],
|
|
||||||
]) {
|
|
||||||
btn.on('click', e => {
|
|
||||||
if (getEventKeyName(e) === 'MouseL') {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDriveOptions() {
|
|
||||||
const result = {};
|
|
||||||
for (const el of $$(`[data-drive=${elCloud.value}] [data-option]`)) {
|
|
||||||
result[el.dataset.option] = el.value;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDriveOptions(options) {
|
|
||||||
for (const el of $$(`[data-drive=${elCloud.value}] [data-option]`)) {
|
|
||||||
el.value = options[el.dataset.option] || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setStatus(newStatus) {
|
|
||||||
status = newStatus;
|
|
||||||
updateButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateButtons() {
|
|
||||||
const {state, STATES} = status;
|
|
||||||
const isConnected = state === STATES.connected;
|
|
||||||
const isDisconnected = state === STATES.disconnected;
|
|
||||||
if (status.currentDriveName) {
|
|
||||||
elCloud.value = status.currentDriveName;
|
|
||||||
}
|
|
||||||
for (const [el, enable] of [
|
|
||||||
[elCloud, isDisconnected],
|
|
||||||
[elDriveOptions, isDisconnected],
|
|
||||||
[elStart, isDisconnected && elCloud.value !== 'none'],
|
|
||||||
[elStop, isConnected && !status.syncing],
|
|
||||||
[elSyncNow, isConnected && !status.syncing && status.login],
|
|
||||||
]) {
|
|
||||||
el.disabled = !enable;
|
|
||||||
}
|
|
||||||
elStatus.textContent = getStatusText();
|
|
||||||
elLogin.hidden = !isConnected || status.login;
|
|
||||||
for (const el of elDriveOptions.children) {
|
|
||||||
el.hidden = el.dataset.drive !== elCloud.value;
|
|
||||||
}
|
|
||||||
setDriveOptions(await API.sync.getDriveOptions(elCloud.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStatusText() {
|
|
||||||
if (status.syncing) {
|
|
||||||
const {phase, loaded, total} = status.progress || {};
|
|
||||||
return phase
|
|
||||||
? t(`optionsSyncStatus${capitalize(phase)}`, [loaded + 1, total], false) ||
|
|
||||||
`${phase} ${loaded} / ${total}`
|
|
||||||
: t('optionsSyncStatusSyncing');
|
|
||||||
}
|
|
||||||
|
|
||||||
const {state, errorMessage, STATES} = status;
|
|
||||||
if (errorMessage && (state === STATES.connected || state === STATES.disconnected)) {
|
|
||||||
return errorMessage;
|
|
||||||
}
|
|
||||||
if (state === STATES.connected && !status.login) {
|
|
||||||
return t('optionsSyncStatusRelogin');
|
|
||||||
}
|
|
||||||
return t(`optionsSyncStatus${capitalize(state)}`, null, false) || state;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
function checkUpdates() {
|
|
||||||
let total = 0;
|
|
||||||
let checked = 0;
|
|
||||||
let updated = 0;
|
|
||||||
const maxWidth = $('#update-progress').parentElement.clientWidth;
|
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(function onConnect(port) {
|
|
||||||
if (port.name !== 'updater') return;
|
|
||||||
port.onMessage.addListener(observer);
|
|
||||||
chrome.runtime.onConnect.removeListener(onConnect);
|
|
||||||
});
|
|
||||||
|
|
||||||
API.updater.checkAllStyles({observe: true});
|
|
||||||
|
|
||||||
function observer(info) {
|
|
||||||
if ('count' in info) {
|
|
||||||
total = info.count;
|
|
||||||
document.body.classList.add('update-in-progress');
|
|
||||||
} else if (info.updated) {
|
|
||||||
updated++;
|
|
||||||
checked++;
|
|
||||||
} else if (info.error) {
|
|
||||||
checked++;
|
|
||||||
} else if (info.done) {
|
|
||||||
document.body.classList.remove('update-in-progress');
|
|
||||||
}
|
|
||||||
$('#update-progress').style.width = Math.round(checked / total * maxWidth) + 'px';
|
|
||||||
$('#updates-installed').dataset.value = updated || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function customizeHotkeys() {
|
function customizeHotkeys() {
|
||||||
// command name -> i18n id
|
|
||||||
const hotkeys = new Map([
|
|
||||||
['_execute_browser_action', 'optionsCustomizePopup'],
|
|
||||||
['openManage', 'openManage'],
|
|
||||||
['styleDisableAll', 'disableAllStyles'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
messageBoxProxy.show({
|
messageBoxProxy.show({
|
||||||
title: t('shortcutsNote'),
|
title: t('shortcutsNote'),
|
||||||
contents: [
|
contents: t.template.shortcutsFF.cloneNode(true),
|
||||||
$create('table',
|
className: 'center-dialog pre-line',
|
||||||
[...hotkeys.entries()].map(([cmd, i18n]) =>
|
|
||||||
$create('tr', [
|
|
||||||
$create('td', t(i18n)),
|
|
||||||
$create('td',
|
|
||||||
$create('input', {
|
|
||||||
id: 'hotkey.' + cmd,
|
|
||||||
type: 'search',
|
|
||||||
//placeholder: t('helpKeyMapHotkey'),
|
|
||||||
})),
|
|
||||||
]))),
|
|
||||||
],
|
|
||||||
className: 'center',
|
|
||||||
buttons: [t('confirmClose')],
|
buttons: [t('confirmClose')],
|
||||||
onshow(box) {
|
onshow(box) {
|
||||||
const ids = [];
|
box.oninput = onInput;
|
||||||
for (const cmd of hotkeys.keys()) {
|
setupLivePrefs($$('input', box).map(el => el.id));
|
||||||
const id = 'hotkey.' + cmd;
|
|
||||||
ids.push(id);
|
|
||||||
$('#' + id).oninput = onInput;
|
|
||||||
}
|
|
||||||
setupLivePrefs(ids);
|
|
||||||
$('button', box).insertAdjacentElement('beforebegin',
|
|
||||||
$createLink(
|
|
||||||
'https://developer.mozilla.org/Add-ons/WebExtensions/manifest.json/commands#Key_combinations',
|
|
||||||
t('helpAlt')));
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
async function onInput({target: el}) {
|
||||||
function onInput() {
|
const name = el.id.split('.')[1];
|
||||||
const name = this.id.split('.')[1];
|
const shortcut = el.value.trim();
|
||||||
const shortcut = this.value.trim();
|
|
||||||
if (!shortcut) {
|
if (!shortcut) {
|
||||||
browser.commands.reset(name).catch(ignoreChromeError);
|
browser.commands.reset(name).catch(ignoreChromeError);
|
||||||
this.setCustomValidity('');
|
el.setCustomValidity('');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
browser.commands.update({name, shortcut}).then(
|
await browser.commands.update({name, shortcut});
|
||||||
() => this.setCustomValidity(''),
|
el.setCustomValidity('');
|
||||||
err => this.setCustomValidity(err)
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setCustomValidity(err);
|
el.setCustomValidity(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user