Import styles: show report with Undo button
This commit is contained in:
parent
4bcfcb1503
commit
b1c19bdf3d
|
@ -1,9 +1,8 @@
|
|||
/* globals getStyles, saveStyle, invalidateCache, refreshAllTabs, handleUpdate */
|
||||
'use strict';
|
||||
|
||||
var STYLISH_DUMP_FILE_EXT = '.txt';
|
||||
var STYLISH_DUMPFILE_EXTENSION = '.json';
|
||||
var STYLISH_DEFAULT_SAVE_NAME = 'stylus-mm-dd-yyyy' + STYLISH_DUMP_FILE_EXT;
|
||||
const STYLISH_DUMP_FILE_EXT = '.txt';
|
||||
const STYLUS_BACKUP_FILE_EXT = '.json';
|
||||
|
||||
function importFromFile({fileTypeFilter, file} = {}) {
|
||||
return new Promise(resolve => {
|
||||
|
@ -47,52 +46,184 @@ function importFromFile({fileTypeFilter, file} = {}) {
|
|||
|
||||
function importFromString(jsonString) {
|
||||
const json = runTryCatch(() => Array.from(JSON.parse(jsonString))) || [];
|
||||
const numStyles = json.length;
|
||||
const oldStyles = json.length && deepCopyStyles();
|
||||
const oldStylesByName = json.length && new Map(
|
||||
oldStyles.map(style => [style.name.trim(), style]));
|
||||
const stats = {
|
||||
added: {names: [], ids: [], legend: 'added'},
|
||||
unchanged: {names: [], ids: [], legend: 'identical skipped'},
|
||||
metaAndCode: {names: [], ids: [], legend: 'updated both meta info and code'},
|
||||
metaOnly: {names: [], ids: [], legend: 'updated meta info'},
|
||||
codeOnly: {names: [], ids: [], legend: 'updated code'},
|
||||
invalid: {names: [], legend: 'invalid skipped'},
|
||||
};
|
||||
let index = 0;
|
||||
return new Promise(proceed);
|
||||
|
||||
if (numStyles) {
|
||||
invalidateCache(true);
|
||||
function proceed(resolve) {
|
||||
while (index < json.length) {
|
||||
const item = json[index++];
|
||||
if (!item || !item.name || !item.name.trim() || typeof item != 'object'
|
||||
|| (item.sections && !(item.sections instanceof Array))) {
|
||||
stats.invalid.names.push(`#${index}: ${limitString(item && item.name || '')}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
return new Promise(resolve => {
|
||||
proceed();
|
||||
function proceed() {
|
||||
const nextStyle = json.shift();
|
||||
if (nextStyle) {
|
||||
saveStyle(nextStyle, {notify: false}).then(style => {
|
||||
item.name = item.name.trim();
|
||||
const byId = (cachedStyles.byId.get(item.id) || {}).style;
|
||||
const byName = oldStylesByName.get(item.name);
|
||||
const oldStyle = byId && byId.name.trim() == item.name || !byName ? byId : byName;
|
||||
if (oldStyle == byName && byName) {
|
||||
item.id = byName.id;
|
||||
}
|
||||
const oldStyleKeys = oldStyle && Object.keys(oldStyle);
|
||||
const metaEqual = oldStyleKeys &&
|
||||
oldStyleKeys.length == Object.keys(item).length &&
|
||||
oldStyleKeys.every(k => k == 'sections' || oldStyle[k] === item[k]);
|
||||
const codeEqual = oldStyle && styleSectionsEqual(oldStyle, item);
|
||||
if (metaEqual && codeEqual) {
|
||||
stats.unchanged.names.push(oldStyle.name);
|
||||
stats.unchanged.ids.push(oldStyle.id);
|
||||
continue;
|
||||
}
|
||||
saveStyle(item, {notify: false}).then(style => {
|
||||
handleUpdate(style, {reason: 'import'});
|
||||
setTimeout(proceed, 0);
|
||||
setTimeout(proceed, 0, resolve);
|
||||
if (!oldStyle) {
|
||||
stats.added.names.push(style.name);
|
||||
stats.added.ids.push(style.id);
|
||||
}
|
||||
else if (!metaEqual && !codeEqual) {
|
||||
stats.metaAndCode.names.push(reportNameChange(oldStyle, style));
|
||||
stats.metaAndCode.ids.push(style.id);
|
||||
}
|
||||
else if (!codeEqual) {
|
||||
stats.codeOnly.names.push(style.name);
|
||||
stats.codeOnly.ids.push(style.id);
|
||||
}
|
||||
else {
|
||||
stats.metaOnly.names.push(reportNameChange(oldStyle, style));
|
||||
stats.metaOnly.ids.push(style.id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
refreshAllTabs().then(() => {
|
||||
return;
|
||||
}
|
||||
done(resolve);
|
||||
}
|
||||
|
||||
function done(resolve) {
|
||||
const numChanged = stats.metaAndCode.names.length +
|
||||
stats.metaOnly.names.length +
|
||||
stats.codeOnly.names.length +
|
||||
stats.added.names.length;
|
||||
Promise.resolve(numChanged && refreshAllTabs()).then(() => {
|
||||
scrollTo(0, 0);
|
||||
setTimeout(alert, 100, numStyles + ' styles installed/updated');
|
||||
resolve(numStyles);
|
||||
const report = Object.keys(stats)
|
||||
.filter(kind => stats[kind].names.length)
|
||||
.map(kind => `<details data-id="${kind}">
|
||||
<summary><b>${stats[kind].names.length} ${stats[kind].legend}</b></summary>
|
||||
<small>` + stats[kind].names.map((name, i) =>
|
||||
`<div data-id="${stats[kind].ids[i]}">${name}</div>`).join('') + `
|
||||
</small>
|
||||
</details>`)
|
||||
.join('');
|
||||
const box = messageBox({
|
||||
title: 'Finished importing styles',
|
||||
contents: report || 'Nothing was changed.',
|
||||
buttons: [t('confirmOK'), numChanged && t('undo')],
|
||||
onclick: btnIndex => btnIndex == 1 && undo(),
|
||||
});
|
||||
}
|
||||
}
|
||||
bindClick(box);
|
||||
resolve(numChanged);
|
||||
});
|
||||
}
|
||||
|
||||
function generateFileName() {
|
||||
var today = new Date();
|
||||
var dd = '0' + today.getDate();
|
||||
var mm = '0' + (today.getMonth() + 1);
|
||||
var yyyy = today.getFullYear();
|
||||
function undo() {
|
||||
const oldStylesById = new Map(oldStyles.map(style => [style.id, style]));
|
||||
const newIds = [
|
||||
...stats.metaAndCode.ids,
|
||||
...stats.metaOnly.ids,
|
||||
...stats.codeOnly.ids,
|
||||
...stats.added.ids,
|
||||
];
|
||||
index = 0;
|
||||
return new Promise(undoNextId)
|
||||
.then(refreshAllTabs)
|
||||
.then(() => messageBox({
|
||||
title: 'Import has been undone',
|
||||
contents: newIds.length + ' styles were reverted.',
|
||||
buttons: [t('confirmOK')],
|
||||
}));
|
||||
function undoNextId(resolve) {
|
||||
if (index == newIds.length) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
const id = newIds[index++];
|
||||
deleteStyle(id, {notify: false}).then(id => {
|
||||
handleDelete(id);
|
||||
const oldStyle = oldStylesById.get(id);
|
||||
if (oldStyle) {
|
||||
saveStyle(Object.assign(oldStyle, {reason: 'undoImport'}), {notify: false})
|
||||
.then(handleUpdate)
|
||||
.then(() => setTimeout(undoNextId, 0, resolve));
|
||||
} else {
|
||||
setTimeout(undoNextId, 0, resolve);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
dd = dd.substr(-2);
|
||||
mm = mm.substr(-2);
|
||||
function bindClick(box) {
|
||||
for (let block of [...box.querySelectorAll('details')]) {
|
||||
if (block.dataset.id != 'invalid') {
|
||||
block.style.cursor = 'pointer';
|
||||
block.onclick = event => {
|
||||
const styleElement = $(`[style-id="${event.target.dataset.id}"]`);
|
||||
if (styleElement) {
|
||||
scrollElementIntoView(styleElement);
|
||||
highlightElement(styleElement);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
today = mm + '-' + dd + '-' + yyyy;
|
||||
function deepCopyStyles() {
|
||||
const clonedStyles = [];
|
||||
for (let style of cachedStyles.list || []) {
|
||||
style = Object.assign({}, style);
|
||||
style.sections = style.sections.slice();
|
||||
for (let i = 0, section; (section = style.sections[i]); i++) {
|
||||
const copy = style.sections[i] = Object.assign({}, section);
|
||||
for (let propName in copy) {
|
||||
const prop = copy[propName];
|
||||
if (prop instanceof Array) {
|
||||
copy[propName] = prop.slice();
|
||||
}
|
||||
}
|
||||
}
|
||||
clonedStyles.push(style);
|
||||
}
|
||||
return clonedStyles;
|
||||
}
|
||||
|
||||
return 'stylus-' + today + STYLISH_DUMPFILE_EXTENSION;
|
||||
function limitString(s, limit = 100) {
|
||||
return s.length <= limit ? s : s.substr(0, limit) + '...';
|
||||
}
|
||||
|
||||
function reportNameChange(oldStyle, newStyle) {
|
||||
return newStyle.name != oldStyle.name
|
||||
? oldStyle.name + ' —> ' + newStyle.name
|
||||
: oldStyle.name;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('file-all-styles').onclick = () => {
|
||||
getStyles({}, function (styles) {
|
||||
let text = JSON.stringify(styles, null, '\t');
|
||||
let fileName = generateFileName() || STYLISH_DEFAULT_SAVE_NAME;
|
||||
const text = JSON.stringify(styles, null, '\t');
|
||||
const fileName = generateFileName();
|
||||
|
||||
let url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
|
||||
const url = 'data:text/plain;charset=utf-8,' + encodeURIComponent(text);
|
||||
// for long URLs; https://github.com/schomery/stylish-chrome/issues/13#issuecomment-284582600
|
||||
fetch(url)
|
||||
.then(res => res.blob())
|
||||
|
@ -103,46 +234,51 @@ document.getElementById('file-all-styles').onclick = () => {
|
|||
a.dispatchEvent(new MouseEvent('click'));
|
||||
});
|
||||
});
|
||||
|
||||
function generateFileName() {
|
||||
const today = new Date();
|
||||
const dd = ('0' + today.getDate()).substr(-2);
|
||||
const mm = ('0' + (today.getMonth() + 1)).substr(-2);
|
||||
const yyyy = today.getFullYear();
|
||||
return `stylus-${mm}-${dd}-${yyyy}${STYLUS_BACKUP_FILE_EXT}`;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
document.getElementById('unfile-all-styles').onclick = () => {
|
||||
importFromFile({fileTypeFilter: STYLISH_DUMPFILE_EXTENSION});
|
||||
importFromFile({fileTypeFilter: STYLUS_BACKUP_FILE_EXT});
|
||||
};
|
||||
|
||||
const dropTarget = Object.assign(document.body, {
|
||||
ondragover: event => {
|
||||
Object.assign(document.body, {
|
||||
ondragover(event) {
|
||||
const hasFiles = event.dataTransfer.types.includes('Files');
|
||||
event.dataTransfer.dropEffect = hasFiles || event.target.type == 'search' ? 'copy' : 'none';
|
||||
dropTarget.classList.toggle('dropzone', hasFiles);
|
||||
this.classList.toggle('dropzone', hasFiles);
|
||||
if (hasFiles) {
|
||||
event.preventDefault();
|
||||
clearTimeout(dropTarget.fadeoutTimer);
|
||||
dropTarget.classList.remove('fadeout');
|
||||
clearTimeout(this.fadeoutTimer);
|
||||
this.classList.remove('fadeout');
|
||||
}
|
||||
},
|
||||
ondragend: event => {
|
||||
dropTarget.classList.add('fadeout');
|
||||
// transitionend event may not fire if the user switched to another tab so we'll use a timer
|
||||
clearTimeout(dropTarget.fadeoutTimer);
|
||||
dropTarget.fadeoutTimer = setTimeout(() => {
|
||||
dropTarget.classList.remove('dropzone', 'fadeout');
|
||||
}, 250);
|
||||
ondragend(event) {
|
||||
this.classList.add('fadeout');
|
||||
this.addEventListener('animationend', function _() {
|
||||
this.removeEventListener('animationend', _);
|
||||
this.style.animationDuration = '';
|
||||
this.classList.remove('dropzone', 'fadeout');
|
||||
});
|
||||
},
|
||||
ondragleave: event => {
|
||||
ondragleave(event) {
|
||||
// Chrome sets screen coords to 0 on Escape key pressed or mouse out of document bounds
|
||||
if (!event.screenX && !event.screenX) {
|
||||
dropTarget.ondragend();
|
||||
this.ondragend();
|
||||
}
|
||||
},
|
||||
ondrop: event => {
|
||||
ondrop(event) {
|
||||
this.ondragend();
|
||||
if (event.dataTransfer.files.length) {
|
||||
event.preventDefault();
|
||||
importFromFile({file: event.dataTransfer.files[0]}).then(() => {
|
||||
dropTarget.classList.remove('dropzone');
|
||||
});
|
||||
} else {
|
||||
dropTarget.ondragend();
|
||||
importFromFile({file: event.dataTransfer.files[0]});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
18
manage.css
18
manage.css
|
@ -111,9 +111,12 @@ a.homepage {
|
|||
display: inline;
|
||||
}
|
||||
|
||||
.applies-to-extra summary {
|
||||
summary {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.applies-to-extra summary {
|
||||
list-style-type: none; /* for FF, allegedly */
|
||||
}
|
||||
|
||||
|
@ -255,6 +258,19 @@ fieldset {
|
|||
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;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {
|
||||
opacity: 0;
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title i18n-text="manageTitle"></title>
|
||||
<link href="manage.css" rel="stylesheet">
|
||||
<link href="msgbox/msgbox.css" rel="stylesheet">
|
||||
|
||||
<template data-id="style">
|
||||
<div class="entry">
|
||||
|
@ -121,8 +122,8 @@
|
|||
</svg>
|
||||
|
||||
<script src="manage.js"></script>
|
||||
<script src="openOptions.js"></script>
|
||||
<script src="backup/fileSaveLoad.js"></script>
|
||||
</body>
|
||||
<script src="msgbox/msgbox.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
|
35
manage.js
35
manage.js
|
@ -28,6 +28,11 @@ function initGlobalEvents() {
|
|||
$('#check-all-updates').onclick = checkUpdateAll;
|
||||
$('#apply-all-updates').onclick = applyUpdateAll;
|
||||
$('#search').oninput = searchStyles;
|
||||
$('#manage-options-button').onclick = () => chrome.runtime.openOptionsPage();
|
||||
$('#manage-shortcuts-button').onclick = configureCommands.open;
|
||||
$('#editor-styles-button').onclick = () => openURL({
|
||||
url: 'https://userstyles.org/styles/browse/chrome-extension',
|
||||
});
|
||||
|
||||
// focus search field on / key
|
||||
document.onkeypress = event => {
|
||||
|
@ -233,11 +238,7 @@ class EntryOnClick {
|
|||
function handleUpdate(style, {reason} = {}) {
|
||||
const element = createStyleElement(style);
|
||||
const oldElement = $(`[style-id="${style.id}"]`, installed);
|
||||
element.addEventListener('animationend', function _() {
|
||||
element.removeEventListener('animationend', _);
|
||||
element.classList.remove('highlight');
|
||||
});
|
||||
element.classList.add('highlight');
|
||||
highlightElement(element);
|
||||
if (!oldElement) {
|
||||
installed.appendChild(element);
|
||||
} else {
|
||||
|
@ -247,11 +248,7 @@ function handleUpdate(style, {reason} = {}) {
|
|||
$('.update-note', element).innerHTML = t('updateCompleted');
|
||||
}
|
||||
}
|
||||
// align to the top/bottom of the visible area if wasn't visible
|
||||
const bounds = element.getBoundingClientRect();
|
||||
if (bounds.top < 0 || bounds.top > innerHeight - bounds.height) {
|
||||
element.scrollIntoView(bounds.top < 0 );
|
||||
}
|
||||
scrollElementIntoView(element);
|
||||
}
|
||||
|
||||
|
||||
|
@ -466,6 +463,24 @@ function getClickedStyleElement(event) {
|
|||
}
|
||||
|
||||
|
||||
function scrollElementIntoView(element) {
|
||||
// align to the top/bottom of the visible area if wasn't visible
|
||||
const bounds = element.getBoundingClientRect();
|
||||
if (bounds.top < 0 || bounds.top > innerHeight - bounds.height) {
|
||||
element.scrollIntoView(bounds.top < 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function highlightElement(element) {
|
||||
element.addEventListener('animationend', function _() {
|
||||
element.removeEventListener('animationend', _);
|
||||
element.classList.remove('highlight');
|
||||
});
|
||||
element.classList.add('highlight');
|
||||
}
|
||||
|
||||
|
||||
function rememberScrollPosition() {
|
||||
history.replaceState({scrollY}, document.title);
|
||||
}
|
||||
|
|
102
msgbox/msgbox.css
Normal file
102
msgbox/msgbox.css
Normal file
|
@ -0,0 +1,102 @@
|
|||
#message-box {
|
||||
top: 3rem;
|
||||
right: 3rem;
|
||||
min-width: 10rem;
|
||||
max-width: 50vw;
|
||||
min-height: 5rem;
|
||||
max-height: 90vh;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
box-shadow: 5px 5px 50px rgba(0, 0, 0, 0.35);
|
||||
animation: fadein .25s ease-in-out;
|
||||
flex-direction: column;
|
||||
z-index: 9999990;
|
||||
}
|
||||
|
||||
#message-box > * {
|
||||
z-index: 9999991;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#message-box.fadeout {
|
||||
animation: fadeout .5s ease-in-out;
|
||||
}
|
||||
|
||||
#message-box::before {
|
||||
position: fixed;
|
||||
content: "";
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, .25);
|
||||
}
|
||||
|
||||
#message-box.big {
|
||||
max-width: none;
|
||||
left: 3rem;
|
||||
}
|
||||
|
||||
#message-box-title {
|
||||
font-weight: bold;
|
||||
background-color: rgb(145, 208, 198);
|
||||
padding: .75rem 50px .75rem .75rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
#message-box-title::before {
|
||||
content: "";
|
||||
width: 0;
|
||||
height: 0;
|
||||
padding: 0 32px 32px 0;
|
||||
background: url(/32.png);
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: .5rem;
|
||||
}
|
||||
|
||||
#message-box-close-icon {
|
||||
cursor: pointer;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 8px solid transparent;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background: linear-gradient(-45deg, transparent 5px, black 5px, black 6px, transparent 6.5px) no-repeat, linear-gradient(45deg, transparent 5px, black 5px, black 6px, transparent 6.5px) no-repeat;
|
||||
}
|
||||
|
||||
#message-box-contents {
|
||||
overflow: auto;
|
||||
padding: .75rem;
|
||||
position: relative;
|
||||
flex-grow: 9;
|
||||
}
|
||||
|
||||
#message-box-buttons {
|
||||
padding: .75rem;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
#message-box-buttons button:not(:last-child) {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
64
msgbox/msgbox.js
Normal file
64
msgbox/msgbox.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
'use strict';
|
||||
|
||||
function messageBox({title, contents, buttons, onclick}) {
|
||||
// keep the same reference to be able to remove the listener later
|
||||
messageBox.close = messageBox.close || close;
|
||||
if (messageBox.element) {
|
||||
messageBox.element.remove();
|
||||
}
|
||||
const id = 'message-box';
|
||||
const putAs = typeof contents == 'string' ? 'innerHTML' : 'appendChild';
|
||||
messageBox.element = $element({id, appendChild: [
|
||||
$element({id: `${id}-title`, innerHTML: title}),
|
||||
$element({id: `${id}-close-icon`, onclick: messageBox.close}),
|
||||
$element({id: `${id}-contents`, [putAs]: contents}),
|
||||
$element({id: `${id}-buttons`,
|
||||
onclick: relayButtonClick,
|
||||
appendChild: (buttons || []).map(textContent =>
|
||||
textContent && $element({tag: 'button', textContent}))
|
||||
}),
|
||||
]});
|
||||
show();
|
||||
return messageBox.element;
|
||||
|
||||
function show() {
|
||||
document.body.appendChild(messageBox.element);
|
||||
document.addEventListener('keydown', messageBox.close);
|
||||
}
|
||||
|
||||
function close(event) {
|
||||
if ((!event
|
||||
|| event.type == 'click'
|
||||
|| event.keyCode == 27 && !event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey)
|
||||
&& messageBox.element) {
|
||||
const box = messageBox.element;
|
||||
box.classList.add('fadeout');
|
||||
box.addEventListener('animationend', function _() {
|
||||
box.removeEventListener('animationend', _);
|
||||
box.remove();
|
||||
});
|
||||
document.removeEventListener('keydown', messageBox.close);
|
||||
$(`#${id}-buttons`).onclick = null;
|
||||
messageBox.element = null;
|
||||
}
|
||||
}
|
||||
|
||||
function relayButtonClick(event) {
|
||||
const button = event.target.closest('button');
|
||||
if (button) {
|
||||
close();
|
||||
if (onclick) {
|
||||
onclick([...this.children].indexOf(button));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function $element(opt) {
|
||||
const element = document.createElement(opt.tag || 'div');
|
||||
(opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild])
|
||||
.forEach(child => child && element.appendChild(child));
|
||||
delete opt.appendChild;
|
||||
delete opt.tag;
|
||||
return Object.assign(element, opt);
|
||||
}
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
/* globals configureCommands */
|
||||
'use strict';
|
||||
|
||||
document.querySelector('#manage-options-button').addEventListener("click", function() {
|
||||
if (chrome.runtime.openOptionsPage) {
|
||||
// Supported (Chrome 42+)
|
||||
chrome.runtime.openOptionsPage();
|
||||
} else {
|
||||
// Fallback
|
||||
window.open(chrome.runtime.getURL('options/index.html'));
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelector('#manage-shortcuts-button').addEventListener("click", configureCommands.open);
|
||||
|
||||
document.querySelector('#editor-styles-button').addEventListener("click", function() {
|
||||
chrome.tabs.create({
|
||||
'url': 'https://userstyles.org/styles/browse/chrome-extension'
|
||||
});
|
||||
});
|
25
storage.js
25
storage.js
|
@ -256,23 +256,28 @@ function saveStyle(style, {notify = true} = {}) {
|
|||
const tx = db.transaction(['styles'], 'readwrite');
|
||||
const os = tx.objectStore('styles');
|
||||
|
||||
const id = style.id !== undefined && style.id !== null ? Number(style.id) : null;
|
||||
const reason = style.reason;
|
||||
delete style.method;
|
||||
delete style.reason;
|
||||
|
||||
// Update
|
||||
if (style.id) {
|
||||
style.id = Number(style.id);
|
||||
os.get(style.id).onsuccess = eventGet => {
|
||||
if (id != null) {
|
||||
style.id = id;
|
||||
os.get(id).onsuccess = eventGet => {
|
||||
const existed = !!eventGet.target.result;
|
||||
const oldStyle = Object.assign({}, eventGet.target.result);
|
||||
const codeIsUpdated = 'sections' in style && !styleSectionsEqual(style, oldStyle);
|
||||
style = Object.assign(oldStyle, style);
|
||||
addMissingStyleTargets(style);
|
||||
os.put(style).onsuccess = eventPut => {
|
||||
style.id = style.id || eventPut.target.result;
|
||||
invalidateCache(notify, {updated: style});
|
||||
invalidateCache(notify, existed ? {updated: style} : {added: style});
|
||||
if (notify) {
|
||||
notifyAllTabs({method: 'styleUpdated', style, codeIsUpdated, reason});
|
||||
notifyAllTabs({
|
||||
method: existed ? 'styleUpdated' : 'styleAdded',
|
||||
style, codeIsUpdated, reason,
|
||||
});
|
||||
}
|
||||
resolve(style);
|
||||
};
|
||||
|
@ -294,8 +299,10 @@ function saveStyle(style, {notify = true} = {}) {
|
|||
os.add(style).onsuccess = event => {
|
||||
// Give it the ID that was generated
|
||||
style.id = event.target.result;
|
||||
invalidateCache(true, {added: style});
|
||||
invalidateCache(notify, {added: style});
|
||||
if (notify) {
|
||||
notifyAllTabs({method: 'styleAdded', style, reason});
|
||||
}
|
||||
resolve(style);
|
||||
};
|
||||
});
|
||||
|
@ -320,14 +327,16 @@ function enableStyle(id, enabled) {
|
|||
}
|
||||
|
||||
|
||||
function deleteStyle(id) {
|
||||
function deleteStyle(id, {notify = true} = {}) {
|
||||
return new Promise(resolve =>
|
||||
getDatabase(db => {
|
||||
const tx = db.transaction(['styles'], 'readwrite');
|
||||
const os = tx.objectStore('styles');
|
||||
os.delete(Number(id)).onsuccess = event => {
|
||||
invalidateCache(true, {deletedId: id});
|
||||
invalidateCache(notify, {deletedId: id});
|
||||
if (notify) {
|
||||
notifyAllTabs({method: 'styleDeleted', id});
|
||||
}
|
||||
resolve(id);
|
||||
};
|
||||
}));
|
||||
|
|
Loading…
Reference in New Issue
Block a user