Refactor and speed up popup & manager
Popup: * Enforce 200-800px range for the popup width option Manage: * faster search via cachedStyles.byId * faster restoration of search results on history nav * style name is clickable and opens the editor * animated highlight of style element on update/add/save * expandable extra applies-to targets * remember scroll position on normal history navigation * boz-sizing in #header, also in editor * applies-to targets use structured markup * get*Tab*, enableStyle and deleteStyle are promisified
This commit is contained in:
parent
9fd067c6e3
commit
2f4da37fdb
|
@ -34,6 +34,8 @@ globals:
|
||||||
getType: true
|
getType: true
|
||||||
importStyles: true
|
importStyles: true
|
||||||
getActiveTabRealURL: true
|
getActiveTabRealURL: true
|
||||||
|
openURL: true
|
||||||
|
onDOMready: true
|
||||||
getDomains: true
|
getDomains: true
|
||||||
webSqlStorage: true
|
webSqlStorage: true
|
||||||
notifyAllTabs: true
|
notifyAllTabs: true
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* globals wildcardAsRegExp, KEEP_CHANNEL_OPEN */
|
/* globals openURL, wildcardAsRegExp, KEEP_CHANNEL_OPEN */
|
||||||
|
|
||||||
// This happens right away, sometimes so fast that the content script isn't even ready. That's
|
// This happens right away, sometimes so fast that the content script isn't even ready. That's
|
||||||
// why the content script also asks for this stuff.
|
// why the content script also asks for this stuff.
|
||||||
|
@ -149,21 +149,6 @@ chrome.tabs.onAttached.addListener(function(tabId, data) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function openURL(options) {
|
|
||||||
chrome.tabs.query({currentWindow: true, url: options.url}, function(tabs) {
|
|
||||||
// switch to an existing tab with the requested url
|
|
||||||
if (tabs.length) {
|
|
||||||
chrome.tabs.highlight({windowId: tabs[0].windowId, tabs: tabs[0].index}, function (window) {});
|
|
||||||
} else {
|
|
||||||
delete options.method;
|
|
||||||
getActiveTab(function(tab) {
|
|
||||||
// re-use an active new tab page
|
|
||||||
chrome.tabs[tab.url == "chrome://newtab/" ? "update" : "create"](options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var codeMirrorThemes;
|
var codeMirrorThemes;
|
||||||
getCodeMirrorThemes(function(themes) {
|
getCodeMirrorThemes(function(themes) {
|
||||||
codeMirrorThemes = themes;
|
codeMirrorThemes = themes;
|
||||||
|
|
|
@ -64,6 +64,7 @@ function importFromString(jsonString) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
refreshAllTabs().then(() => {
|
refreshAllTabs().then(() => {
|
||||||
|
scrollTo(0, 0);
|
||||||
setTimeout(alert, 100, numStyles + ' styles installed/updated');
|
setTimeout(alert, 100, numStyles + ' styles installed/updated');
|
||||||
resolve(numStyles);
|
resolve(numStyles);
|
||||||
});
|
});
|
||||||
|
|
|
@ -45,14 +45,15 @@
|
||||||
}
|
}
|
||||||
/************ header ************/
|
/************ header ************/
|
||||||
#header {
|
#header {
|
||||||
height: calc(100vh - 30px);
|
width: 280px;
|
||||||
|
height: 100vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
width: 250px;
|
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
border-right: 1px dashed #AAA;
|
border-right: 1px dashed #AAA;
|
||||||
-webkit-box-shadow: 0 0 3rem -1.2rem black;
|
-webkit-box-shadow: 0 0 3rem -1.2rem black;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#header h1 {
|
#header h1 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|
2
edit.js
2
edit.js
|
@ -415,7 +415,7 @@ chrome.tabs.query({currentWindow: true}, function(tabs) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
getActiveTab(function(tab) {
|
getActiveTab().then(tab => {
|
||||||
useHistoryBack = sessionStorageHash("manageStylesHistory").value[tab.id] == location.href;
|
useHistoryBack = sessionStorageHash("manageStylesHistory").value[tab.id] == location.href;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
healthCheck();
|
setTimeout(healthCheck, 0);
|
||||||
|
|
||||||
function healthCheck() {
|
function healthCheck() {
|
||||||
chrome.runtime.sendMessage({method: "healthCheck"}, function(ok) {
|
chrome.runtime.sendMessage({method: "healthCheck"}, function(ok) {
|
||||||
|
|
284
manage.css
Normal file
284
manage.css
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font: 12px arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:visited {
|
||||||
|
color: inherit;
|
||||||
|
opacity: .75;
|
||||||
|
-webkit-transition: opacity 0.5s;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover,
|
||||||
|
a.homepage:hover {
|
||||||
|
opacity: .6;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.homepage {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header h1 {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#installed {
|
||||||
|
position: relative;
|
||||||
|
margin-left: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
margin: 0;
|
||||||
|
padding: 1.25em 2em 1.5em;
|
||||||
|
border-top: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry:first-child {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
cursor: pointer;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-left: 0.3rem;
|
||||||
|
margin-right: 0.3rem;
|
||||||
|
margin-top: -4px;
|
||||||
|
transition: opacity .5s;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-name {
|
||||||
|
margin-top: .25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.style-name a, .style-edit-link {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to,
|
||||||
|
.actions {
|
||||||
|
padding-left: 15px;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to > :first-child {
|
||||||
|
margin-right: .5ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to .target:hover {
|
||||||
|
background-color: rgba(128, 128, 128, .15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to-extra {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to-extra summary {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style-type: none; /* for FF, allegedly */
|
||||||
|
}
|
||||||
|
|
||||||
|
.applies-to-extra summary::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled h2::after {
|
||||||
|
content: " (Disabled)";
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.disabled .disable {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enabled .enable {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default, no update buttons */
|
||||||
|
|
||||||
|
.update,
|
||||||
|
.check-update {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check update button for things that can*/
|
||||||
|
|
||||||
|
*[style-update-url] .check-update {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update check in progress */
|
||||||
|
|
||||||
|
.checking-update .check-update {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Updates available */
|
||||||
|
|
||||||
|
.can-update .update {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.can-update .check-update {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Updates not available */
|
||||||
|
|
||||||
|
.no-update .check-update {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Updates done */
|
||||||
|
|
||||||
|
.update-done .check-update {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border-width: 1px;
|
||||||
|
border-radius: 6px;
|
||||||
|
margin: 1em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.enabled-only > .disabled,
|
||||||
|
.edited-only > [style-update-url] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeout {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 675px) {
|
||||||
|
#header {
|
||||||
|
height: auto;
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
border-right: none;
|
||||||
|
border-bottom: 1px dashed #AAA;
|
||||||
|
}
|
||||||
|
|
||||||
|
#installed {
|
||||||
|
position: static;
|
||||||
|
margin-left: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header h1,
|
||||||
|
#header h2,
|
||||||
|
#header h3,
|
||||||
|
#backup-message {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header p,
|
||||||
|
#header fieldset div,
|
||||||
|
#backup {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
#backup {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#backup p,
|
||||||
|
#header fieldset {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.entry {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
412
manage.html
412
manage.html
|
@ -1,312 +1,128 @@
|
||||||
<html>
|
<html id="stylus">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||||
<title i18n-text="manageTitle"></title>
|
<title i18n-text="manageTitle"></title>
|
||||||
<style>
|
<link href="manage.css" rel="stylesheet">
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font: 12px arial, sans-serif;
|
|
||||||
}
|
|
||||||
a,
|
|
||||||
a:visited {
|
|
||||||
color: inherit;
|
|
||||||
opacity: .75;
|
|
||||||
-webkit-transition: opacity 0.5s;
|
|
||||||
}
|
|
||||||
a:hover,
|
|
||||||
a.homepage:hover {
|
|
||||||
opacity: .6;
|
|
||||||
}
|
|
||||||
a.homepage {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
#header {
|
|
||||||
height: 100%;
|
|
||||||
width: 250px;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
padding: 15px;
|
|
||||||
border-right: 1px dashed #AAA;
|
|
||||||
-webkit-box-shadow: 0 0 50px -18px black;
|
|
||||||
}
|
|
||||||
#header h1 {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
#installed {
|
|
||||||
position: relative;
|
|
||||||
margin-left: 280px;
|
|
||||||
}
|
|
||||||
[style-id] {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
||||||
[style-id] {
|
|
||||||
border-top: 2px solid gray;
|
|
||||||
}
|
|
||||||
#installed::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 2px;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
.svg-icon {
|
|
||||||
cursor: pointer;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-left: 0.3rem;
|
|
||||||
margin-right: 0.3rem;
|
|
||||||
margin-top: -4px;
|
|
||||||
transition: opacity .5s;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
fill: currentColor;
|
|
||||||
}
|
|
||||||
.style-name {
|
|
||||||
margin-top: .25em;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.applies-to {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
.applies-to,
|
|
||||||
.actions {
|
|
||||||
padding-left: 15px;
|
|
||||||
}
|
|
||||||
.applies-to-extra {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
.disabled h2::after {
|
|
||||||
content: " (Disabled)";
|
|
||||||
}
|
|
||||||
.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
.disabled .disable {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.enabled .enable {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.style-name a[target="_blank"] {
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
/* Default, no update buttons */
|
|
||||||
|
|
||||||
.update,
|
|
||||||
.check-update {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* Check update button for things that can*/
|
|
||||||
|
|
||||||
*[style-update-url] .check-update {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
/* Update check in progress */
|
|
||||||
|
|
||||||
.checking-update .check-update {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* Updates available */
|
|
||||||
|
|
||||||
.can-update .update {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
.can-update .check-update {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* Updates not available */
|
|
||||||
|
|
||||||
.no-update .check-update {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* Updates done */
|
|
||||||
|
|
||||||
.update-done .check-update {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.hidden {
|
|
||||||
display: none
|
|
||||||
}
|
|
||||||
@media(max-width:675px) {
|
|
||||||
#header {
|
|
||||||
height: auto;
|
|
||||||
position: inherit;
|
|
||||||
width: auto;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
#installed {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
[style-id] {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#header {
|
|
||||||
overflow: auto;
|
|
||||||
height: calc(100vh - 30px)
|
|
||||||
}
|
|
||||||
fieldset {
|
|
||||||
border-width: 1px;
|
|
||||||
border-radius: 6px;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
.enabled-only > .disabled,
|
|
||||||
.edited-only > [style-update-url] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
#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;
|
|
||||||
}
|
|
||||||
@keyframes fadein {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
@keyframes fadeout {
|
|
||||||
from { opacity: 1; }
|
|
||||||
to { opacity: 0; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
<template data-id="style">
|
<template data-id="style">
|
||||||
<div>
|
<div class="entry">
|
||||||
<h2 class="style-name"></h2>
|
<h2 class="style-name"><a href="edit.html?id="></a></h2>
|
||||||
<p class="applies-to"></p>
|
<p class="applies-to"><span></span></p>
|
||||||
<p class="actions">
|
<p class="actions">
|
||||||
<a class="style-edit-link" href="edit.html?id="><button i18n-text="editStyleLabel"></button></a>
|
<a class="style-edit-link" href="edit.html?id=">
|
||||||
<button class="enable" i18n-text="enableStyleLabel"></button>
|
<button i18n-text="editStyleLabel"></button>
|
||||||
<button class="disable" i18n-text="disableStyleLabel"></button>
|
|
||||||
<button class="delete" i18n-text="deleteStyleLabel"></button>
|
|
||||||
<button class="check-update" i18n-text="checkForUpdate"></button>
|
|
||||||
<button class="update" i18n-text="installUpdate"></button>
|
|
||||||
<span class="update-note"></span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template data-id="styleHomepage">
|
|
||||||
<a target="_blank" class="homepage">
|
|
||||||
<svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg>
|
|
||||||
</a>
|
</a>
|
||||||
</template>
|
<button class="enable" i18n-text="enableStyleLabel"></button>
|
||||||
|
<button class="disable" i18n-text="disableStyleLabel"></button>
|
||||||
|
<button class="delete" i18n-text="deleteStyleLabel"></button>
|
||||||
|
<button class="check-update" i18n-text="checkForUpdate"></button>
|
||||||
|
<button class="update" i18n-text="installUpdate"></button>
|
||||||
|
<span class="update-note"></span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script src="localization.js"></script>
|
<template data-id="styleHomepage">
|
||||||
<script src="health.js"></script>
|
<a target="_blank" class="homepage">
|
||||||
<script src="storage.js"></script>
|
<svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg>
|
||||||
<script src="messaging.js"></script>
|
</a>
|
||||||
<script src="apply.js"></script>
|
</template>
|
||||||
<script src="manage.js"></script>
|
|
||||||
|
<template data-id="appliesToTarget">
|
||||||
|
<span class="target"></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template data-id="appliesToSeparator">
|
||||||
|
<span class="sep">, </span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template data-id="appliesToEverything">
|
||||||
|
<span class="target" i18n-text="appliesToEverything"></span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template data-id="extraAppliesTo">
|
||||||
|
<details class="applies-to-extra">
|
||||||
|
<summary i18n-html="appliesDisplayTruncatedSuffix"></summary>
|
||||||
|
</details>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script src="health.js"></script>
|
||||||
|
<script src="storage.js"></script>
|
||||||
|
<script src="messaging.js"></script>
|
||||||
|
<script src="apply.js"></script>
|
||||||
|
<script src="localization.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
|
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h1 id="manage-heading" i18n-text="manageHeading"></h1>
|
<h1 id="manage-heading" i18n-text="manageHeading"></h1>
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend id="filters" i18n-text="manageFilters"></legend>
|
<legend id="filters" i18n-text="manageFilters"></legend>
|
||||||
<div>
|
<div>
|
||||||
<input id="manage.onlyEnabled" type="checkbox">
|
<input id="manage.onlyEnabled" type="checkbox">
|
||||||
<label id="manage.onlyEnabled-label" for="manage.onlyEnabled" i18n-text="manageOnlyEnabled"></label>
|
<label id="manage.onlyEnabled-label" for="manage.onlyEnabled" i18n-text="manageOnlyEnabled"></label>
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input id="manage.onlyEdited" type="checkbox">
|
|
||||||
<label id="manage.onlyEdited-label" for="manage.onlyEdited" i18n-text="manageOnlyEdited"></label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input id="search" type="search" i18n-placeholder="searchStyles">
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
<p>
|
|
||||||
<button id="check-all-updates" i18n-text="checkAllUpdates"></button>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button>
|
|
||||||
<span id="update-all-no-updates" class="hidden" i18n-text="updateAllCheckSucceededNoUpdate"></span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a href="edit.html">
|
|
||||||
<button id="add-style-label" i18n-text="addStyleLabel"></button>
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
<div id="options">
|
|
||||||
<h2 id="options-heading" i18n-text="optionsHeading"></h2>
|
|
||||||
<div>
|
|
||||||
<input id="show-badge" type="checkbox">
|
|
||||||
<label id="show-badge-label" for="show-badge" i18n-text="prefShowBadge"></label>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<input id="popup.stylesFirst" type="checkbox">
|
|
||||||
<label id="stylesFirst-label" for="popup.stylesFirst" i18n-text="popupStylesFirst"></label>
|
|
||||||
</div>
|
|
||||||
<div id="more-options">
|
|
||||||
<h3 id="options-subheading" i18n-text="optionsSubheading"></h3>
|
|
||||||
<button id="manage-options-button" i18n-text="openOptionsManage"></button>
|
|
||||||
<button id="manage-shortcuts-button" i18n-text="openOptionsShortcuts"></button>
|
|
||||||
<p>
|
|
||||||
<button id="editor-styles-button" i18n-text="editorStylesButton"></button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="backup">
|
|
||||||
<h2 id="backup-title" i18n-text="backupButtons"></h2>
|
|
||||||
<span id="backup-message" i18n-text="backupMessage"></span>
|
|
||||||
<p>
|
|
||||||
<button id="file-all-styles" i18n-text="bckpInstStyles"></button>
|
|
||||||
<button id="unfile-all-styles" i18n-text="retrieveBckp"></button>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p id="manage-text" i18n-html="manageText"></p>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="installed"></div>
|
<div>
|
||||||
|
<input id="manage.onlyEdited" type="checkbox">
|
||||||
|
<label id="manage.onlyEdited-label" for="manage.onlyEdited" i18n-text="manageOnlyEdited"></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="search" type="search" i18n-placeholder="searchStyles">
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
<p>
|
||||||
|
<button id="check-all-updates" i18n-text="checkAllUpdates"></button>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<button id="apply-all-updates" class="hidden" i18n-text="applyAllUpdates"></button>
|
||||||
|
<span id="update-all-no-updates" class="hidden" i18n-text="updateAllCheckSucceededNoUpdate"></span>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="edit.html">
|
||||||
|
<button id="add-style-label" i18n-text="addStyleLabel"></button>
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<div id="options">
|
||||||
|
<h2 id="options-heading" i18n-text="optionsHeading"></h2>
|
||||||
|
<div>
|
||||||
|
<input id="show-badge" type="checkbox">
|
||||||
|
<label id="show-badge-label" for="show-badge" i18n-text="prefShowBadge"></label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input id="popup.stylesFirst" type="checkbox">
|
||||||
|
<label id="stylesFirst-label" for="popup.stylesFirst" i18n-text="popupStylesFirst"></label>
|
||||||
|
</div>
|
||||||
|
<div id="more-options">
|
||||||
|
<h3 id="options-subheading" i18n-text="optionsSubheading"></h3>
|
||||||
|
<button id="manage-options-button" i18n-text="openOptionsManage"></button>
|
||||||
|
<button id="manage-shortcuts-button" i18n-text="openOptionsShortcuts"></button>
|
||||||
|
<p>
|
||||||
|
<button id="editor-styles-button" i18n-text="editorStylesButton"></button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="backup">
|
||||||
|
<h2 id="backup-title" i18n-text="backupButtons"></h2>
|
||||||
|
<span id="backup-message" i18n-text="backupMessage"></span>
|
||||||
|
<p>
|
||||||
|
<button id="file-all-styles" i18n-text="bckpInstStyles"></button>
|
||||||
|
<button id="unfile-all-styles" i18n-text="retrieveBckp"></button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p id="manage-text" i18n-html="manageText"></p>
|
||||||
|
</div>
|
||||||
|
<div id="installed"></div>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
|
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
|
||||||
<symbol id="svg-icon-external-link" height="16" width="16" viewBox="0 0 8 8">
|
<symbol id="svg-icon-external-link" height="16" width="16" viewBox="0 0 8 8">
|
||||||
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
|
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
|
||||||
</symbol>
|
</symbol>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
<script src="openOptions.js"></script>
|
<script src="manage.js"></script>
|
||||||
<script src="backup/fileSaveLoad.js"></script>
|
<script src="openOptions.js"></script>
|
||||||
|
<script src="backup/fileSaveLoad.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
819
manage.js
819
manage.js
|
@ -1,434 +1,471 @@
|
||||||
/* globals styleSectionsEqual */
|
/* globals styleSectionsEqual */
|
||||||
var lastUpdatedStyleId = null;
|
|
||||||
var installed;
|
|
||||||
|
|
||||||
var appliesToExtraTemplate = document.createElement("span");
|
const installed = $('#installed');
|
||||||
appliesToExtraTemplate.className = "applies-to-extra";
|
const TARGET_LABEL = t('appliesDisplay', '').trim();
|
||||||
appliesToExtraTemplate.innerHTML = " " + t('appliesDisplayTruncatedSuffix');
|
const TARGET_TYPES = ['domains', 'urls', 'urlPrefixes', 'regexps'];
|
||||||
|
const TARGET_LIMIT = 10;
|
||||||
|
|
||||||
getStylesSafe({code: false}).then(showStyles);
|
|
||||||
|
|
||||||
function showStyles(styles) {
|
getStylesSafe({code: false})
|
||||||
if (!installed) {
|
.then(showStyles)
|
||||||
// "getStyles" message callback is invoked before document is loaded,
|
.then(initGlobalEvents);
|
||||||
// postpone the action until DOMContentLoaded is fired
|
|
||||||
document.stylishStyles = styles;
|
|
||||||
return;
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
}
|
switch (msg.method) {
|
||||||
styles.sort(function(a, b) { return a.name.localeCompare(b.name)});
|
case 'styleUpdated':
|
||||||
styles.forEach(handleUpdate);
|
case 'styleAdded':
|
||||||
if (history.state) {
|
handleUpdate(msg.style, msg);
|
||||||
window.scrollTo(0, history.state.scrollY);
|
break;
|
||||||
}
|
case 'styleDeleted':
|
||||||
|
handleDelete(msg.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function initGlobalEvents() {
|
||||||
|
$('#check-all-updates').onclick = checkUpdateAll;
|
||||||
|
$('#apply-all-updates').onclick = applyUpdateAll;
|
||||||
|
$('#search').oninput = searchStyles;
|
||||||
|
|
||||||
|
// focus search field on / key
|
||||||
|
document.onkeypress = event => {
|
||||||
|
if (event.keyCode == 47
|
||||||
|
&& !event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey
|
||||||
|
&& !event.target.matches('[type="text"], [type="search"]')) {
|
||||||
|
event.preventDefault();
|
||||||
|
$('#search').focus();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// remember scroll position on normal history navigation
|
||||||
|
document.addEventListener('visibilitychange', event => {
|
||||||
|
if (document.visibilityState != 'visible') {
|
||||||
|
rememberScrollPosition();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setupLivePrefs([
|
||||||
|
'manage.onlyEnabled',
|
||||||
|
'manage.onlyEdited',
|
||||||
|
'show-badge',
|
||||||
|
'popup.stylesFirst'
|
||||||
|
]);
|
||||||
|
|
||||||
|
[
|
||||||
|
['enabled-only', $('#manage.onlyEnabled')],
|
||||||
|
['edited-only', $('#manage.onlyEdited')],
|
||||||
|
]
|
||||||
|
.forEach(([className, checkbox]) => {
|
||||||
|
checkbox.onchange = () => installed.classList.toggle(className, checkbox.checked);
|
||||||
|
checkbox.onchange();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showStyles(styles = []) {
|
||||||
|
const sorted = styles
|
||||||
|
.map(style => ({name: style.name.toLocaleLowerCase(), style}))
|
||||||
|
.sort((a, b) => a.name < b.name ? -1 : a.name == b.name ? 0 : 1);
|
||||||
|
const shouldRenderAll = history.state && history.state.scrollY > innerHeight;
|
||||||
|
const renderBin = document.createDocumentFragment();
|
||||||
|
renderStyles(0);
|
||||||
|
// TODO: remember how many styles fit one page to display just that portion first next time
|
||||||
|
function renderStyles(index) {
|
||||||
|
const t0 = performance.now();
|
||||||
|
while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 10)) {
|
||||||
|
renderBin.appendChild(createStyleElement(sorted[index++].style));
|
||||||
|
}
|
||||||
|
if ($('#search').value) {
|
||||||
|
// re-apply filtering on history Back
|
||||||
|
searchStyles(true, renderBin);
|
||||||
|
}
|
||||||
|
installed.appendChild(renderBin);
|
||||||
|
if (index < sorted.length) {
|
||||||
|
setTimeout(renderStyles, 0, index);
|
||||||
|
}
|
||||||
|
else if (shouldRenderAll && history.state && 'scrollY' in history.state) {
|
||||||
|
setTimeout(() => scrollTo(0, history.state.scrollY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function createStyleElement(style) {
|
function createStyleElement(style) {
|
||||||
var e = template.style.cloneNode(true);
|
const entry = template.style.cloneNode(true);
|
||||||
e.setAttribute("class", style.enabled ? "enabled" : "disabled");
|
entry.classList.add(style.enabled ? 'enabled' : 'disabled');
|
||||||
e.setAttribute("style-id", style.id);
|
entry.setAttribute('style-id', style.id);
|
||||||
if (style.updateUrl) {
|
entry.styleId = style.id;
|
||||||
e.setAttribute("style-update-url", style.updateUrl);
|
if (style.updateUrl) {
|
||||||
}
|
entry.setAttribute('style-update-url', style.updateUrl);
|
||||||
if (style.md5Url) {
|
}
|
||||||
e.setAttribute("style-md5-url", style.md5Url);
|
if (style.md5Url) {
|
||||||
}
|
entry.setAttribute('style-md5-url', style.md5Url);
|
||||||
if (style.originalMd5) {
|
}
|
||||||
e.setAttribute("style-original-md5", style.originalMd5);
|
if (style.originalMd5) {
|
||||||
}
|
entry.setAttribute('style-original-md5', style.originalMd5);
|
||||||
|
}
|
||||||
|
|
||||||
var styleName = e.querySelector(".style-name");
|
const styleName = $('.style-name', entry);
|
||||||
styleName.appendChild(document.createTextNode(style.name));
|
const styleNameEditLink = $('a', styleName);
|
||||||
if (style.url) {
|
styleNameEditLink.appendChild(document.createTextNode(style.name));
|
||||||
var homepage = template.styleHomepage.cloneNode(true)
|
styleNameEditLink.href = styleNameEditLink.getAttribute('href') + style.id;
|
||||||
homepage.setAttribute("href", style.url);
|
styleNameEditLink.onclick = EntryOnClick.edit;
|
||||||
styleName.appendChild(document.createTextNode(" " ));
|
if (style.url) {
|
||||||
styleName.appendChild(homepage);
|
const homepage = template.styleHomepage.cloneNode(true);
|
||||||
}
|
homepage.href = style.url;
|
||||||
var domains = [];
|
styleName.appendChild(document.createTextNode(' '));
|
||||||
var urls = [];
|
styleName.appendChild(homepage);
|
||||||
var urlPrefixes = [];
|
}
|
||||||
var regexps = [];
|
|
||||||
function add(array, property) {
|
const targets = new Map(TARGET_TYPES.map(t => [t, new Set()]));
|
||||||
style.sections.forEach(function(section) {
|
const decorations = {
|
||||||
if (section[property]) {
|
urlPrefixesAfter: '*',
|
||||||
section[property].filter(function(value) {
|
regexpsBefore: '/',
|
||||||
return array.indexOf(value) == -1;
|
regexpsAfter: '/',
|
||||||
}).forEach(function(value) {
|
};
|
||||||
array.push(value);
|
for (let [name, target] of targets.entries()) {
|
||||||
});;
|
for (let section of style.sections) {
|
||||||
}
|
for (let targetValue of section[name] || []) {
|
||||||
});
|
target.add(
|
||||||
}
|
(decorations[name + 'Before'] || '') +
|
||||||
add(domains, 'domains');
|
targetValue.trim() +
|
||||||
add(urls, 'urls');
|
(decorations[name + 'After'] || ''));
|
||||||
add(urlPrefixes, 'urlPrefixes');
|
}
|
||||||
add(regexps, 'regexps');
|
}
|
||||||
var appliesToToShow = [];
|
}
|
||||||
if (domains)
|
const appliesTo = $('.applies-to', entry);
|
||||||
appliesToToShow = appliesToToShow.concat(domains);
|
appliesTo.firstElementChild.textContent = TARGET_LABEL;
|
||||||
if (urls)
|
const targetsList = Array.prototype.concat.apply([],
|
||||||
appliesToToShow = appliesToToShow.concat(urls);
|
[...targets.values()].map(set => [...set.values()]));
|
||||||
if (urlPrefixes)
|
if (!targetsList.length) {
|
||||||
appliesToToShow = appliesToToShow.concat(urlPrefixes.map(function(u) { return u + "*"; }));
|
appliesTo.appendChild(template.appliesToEverything.cloneNode(true));
|
||||||
if (regexps)
|
entry.classList.add('global');
|
||||||
appliesToToShow = appliesToToShow.concat(regexps.map(function(u) { return "/" + u + "/"; }));
|
} else {
|
||||||
var appliesToString = "";
|
let index = 0;
|
||||||
var showAppliesToExtra = false;
|
let container = appliesTo;
|
||||||
if (appliesToToShow.length == "")
|
for (let target of targetsList) {
|
||||||
appliesToString = t('appliesToEverything');
|
if (index > 0) {
|
||||||
else if (appliesToToShow.length <= 10)
|
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
||||||
appliesToString = appliesToToShow.join(", ");
|
}
|
||||||
else {
|
if (++index == TARGET_LIMIT) {
|
||||||
appliesToString = appliesToToShow.slice(0, 10).join(", ");
|
container = appliesTo.appendChild(template.extraAppliesTo.cloneNode(true));
|
||||||
showAppliesToExtra = true;
|
}
|
||||||
}
|
const item = template.appliesToTarget.cloneNode(true);
|
||||||
e.querySelector(".applies-to").appendChild(document.createTextNode(t('appliesDisplay', [appliesToString])));
|
item.textContent = target;
|
||||||
if (showAppliesToExtra) {
|
container.appendChild(item);
|
||||||
e.querySelector(".applies-to").appendChild(appliesToExtraTemplate.cloneNode(true));
|
}
|
||||||
}
|
}
|
||||||
var editLink = e.querySelector(".style-edit-link");
|
|
||||||
editLink.setAttribute("href", editLink.getAttribute("href") + style.id);
|
const editLink = $('.style-edit-link', entry);
|
||||||
editLink.addEventListener("click", function(event) {
|
editLink.href = editLink.getAttribute('href') + style.id;
|
||||||
if (!event.altKey) {
|
editLink.onclick = EntryOnClick.edit;
|
||||||
var left = event.button == 0, middle = event.button == 1,
|
|
||||||
shift = event.shiftKey, ctrl = event.ctrlKey;
|
$('.enable', entry).onclick = EntryOnClick.toggle;
|
||||||
var openWindow = left && shift && !ctrl;
|
$('.disable', entry).onclick = EntryOnClick.toggle;
|
||||||
var openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
|
$('.check-update', entry).onclick = EntryOnClick.check;
|
||||||
var openForegroundTab = (middle && shift) || (left && ctrl && shift);
|
$('.update', entry).onclick = EntryOnClick.update;
|
||||||
var url = event.target.href || event.target.parentNode.href;
|
$('.delete', entry).onclick = EntryOnClick.delete;
|
||||||
event.preventDefault();
|
return entry;
|
||||||
event.stopPropagation();
|
|
||||||
if (openWindow || openBackgroundTab || openForegroundTab) {
|
|
||||||
if (openWindow) {
|
|
||||||
var options = prefs.get("windowPosition");
|
|
||||||
options.url = url;
|
|
||||||
chrome.windows.create(options);
|
|
||||||
} else {
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
method: "openURL",
|
|
||||||
url: url,
|
|
||||||
active: openForegroundTab
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
history.replaceState({scrollY: window.scrollY}, document.title);
|
|
||||||
getActiveTab(function(tab) {
|
|
||||||
sessionStorageHash("manageStylesHistory").set(tab.id, url);
|
|
||||||
location.href = url;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
e.querySelector(".enable").addEventListener("click", function(event) { enable(event, true); }, false);
|
|
||||||
e.querySelector(".disable").addEventListener("click", function(event) { enable(event, false); }, false);
|
|
||||||
e.querySelector(".check-update").addEventListener("click", doCheckUpdate, false);
|
|
||||||
e.querySelector(".update").addEventListener("click", doUpdate, false);
|
|
||||||
e.querySelector(".delete").addEventListener("click", doDelete, false);
|
|
||||||
return e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function enable(event, enabled) {
|
class EntryOnClick {
|
||||||
var id = getId(event);
|
|
||||||
enableStyle(id, enabled);
|
static edit(event) {
|
||||||
|
if (event.altKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const left = event.button == 0, middle = event.button == 1,
|
||||||
|
shift = event.shiftKey, ctrl = event.ctrlKey;
|
||||||
|
const openWindow = left && shift && !ctrl;
|
||||||
|
const openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
|
||||||
|
const openForegroundTab = (middle && shift) || (left && ctrl && shift);
|
||||||
|
const url = event.target.closest('[href]').href;
|
||||||
|
if (openWindow || openBackgroundTab || openForegroundTab) {
|
||||||
|
if (openWindow) {
|
||||||
|
chrome.windows.create(Object.assign(prefs.get('windowPosition'), {url}));
|
||||||
|
} else {
|
||||||
|
openURL({url, active: openForegroundTab});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rememberScrollPosition();
|
||||||
|
getActiveTab().then(tab => {
|
||||||
|
sessionStorageHash('manageStylesHistory').set(tab.id, url);
|
||||||
|
location.href = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static toggle(event) {
|
||||||
|
enableStyle(getClickedStyleId(event), this.matches('.enable'))
|
||||||
|
.then(handleUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static check(event) {
|
||||||
|
checkUpdate(getClickedStyleElement(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
static update(event) {
|
||||||
|
const element = getClickedStyleElement(event);
|
||||||
|
const updatedCode = element.updatedCode;
|
||||||
|
// update everything but name
|
||||||
|
delete updatedCode.name;
|
||||||
|
updatedCode.id = element.styleId;
|
||||||
|
updatedCode.reason = 'update';
|
||||||
|
saveStyle(updatedCode)
|
||||||
|
.then(style => handleUpdate(style, {reason: 'update'}));
|
||||||
|
}
|
||||||
|
|
||||||
|
static delete(event) {
|
||||||
|
if (confirm(t('deleteStyleConfirm'))) {
|
||||||
|
deleteStyle(getClickedStyleId(event))
|
||||||
|
.then(handleDelete);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function doDelete(event) {
|
|
||||||
if (!confirm(t('deleteStyleConfirm'))) {
|
function handleUpdate(style, {reason} = {}) {
|
||||||
return;
|
const element = createStyleElement(style);
|
||||||
}
|
const oldElement = $(`[style-id="${style.id}"]`, installed);
|
||||||
var id = getId(event);
|
if (!oldElement) {
|
||||||
deleteStyle(id);
|
installed.appendChild(element);
|
||||||
|
} else {
|
||||||
|
installed.replaceChild(element, oldElement);
|
||||||
|
if (reason == 'update') {
|
||||||
|
element.classList.add('update-done');
|
||||||
|
$('.update-note', element).innerHTML = t('updateCompleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// align to the bottom of the visible area if wasn't visible
|
||||||
|
element.scrollIntoView(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getId(event) {
|
|
||||||
return getStyleElement(event).getAttribute("style-id");
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStyleElement(event) {
|
|
||||||
var e = event.target;
|
|
||||||
while (e) {
|
|
||||||
if (e.hasAttribute("style-id")) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
e = e.parentNode;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
|
||||||
switch (request.method) {
|
|
||||||
case "styleUpdated":
|
|
||||||
case "styleAdded":
|
|
||||||
handleUpdate(request.style);
|
|
||||||
break;
|
|
||||||
case "styleDeleted":
|
|
||||||
handleDelete(request.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleUpdate(style) {
|
|
||||||
var element = createStyleElement(style);
|
|
||||||
var oldElement = installed.querySelector(`[style-id="${style.id}"]`);
|
|
||||||
if (!oldElement) {
|
|
||||||
installed.appendChild(element);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
installed.replaceChild(element, oldElement);
|
|
||||||
if (style.id == lastUpdatedStyleId) {
|
|
||||||
lastUpdatedStyleId = null;
|
|
||||||
element.className = element.className += ' update-done';
|
|
||||||
element.querySelector('.update-note').innerHTML = t('updateCompleted');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleDelete(id) {
|
function handleDelete(id) {
|
||||||
var node = installed.querySelector("[style-id='" + id + "']");
|
const node = $(`[style-id="${id}"]`, installed);
|
||||||
if (node) {
|
if (node) {
|
||||||
installed.removeChild(node);
|
node.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function doCheckUpdate(event) {
|
|
||||||
checkUpdate(getStyleElement(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyUpdateAll() {
|
function applyUpdateAll() {
|
||||||
var btnApply = document.getElementById("apply-all-updates");
|
const btnApply = $('#apply-all-updates');
|
||||||
btnApply.disabled = true;
|
btnApply.disabled = true;
|
||||||
setTimeout(function() {
|
setTimeout(() => {
|
||||||
btnApply.style.display = "none";
|
btnApply.style.display = 'none';
|
||||||
btnApply.disabled = false;
|
btnApply.disabled = false;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
Array.prototype.forEach.call(document.querySelectorAll(".can-update .update"), function(button) {
|
[...document.querySelectorAll('.can-update .update')]
|
||||||
button.click();
|
.forEach(button => {
|
||||||
});
|
// align to the bottom of the visible area if wasn't visible
|
||||||
|
button.scrollIntoView(false);
|
||||||
|
button.click();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function checkUpdateAll() {
|
function checkUpdateAll() {
|
||||||
var btnCheck = document.getElementById("check-all-updates");
|
const btnCheck = $('#check-all-updates');
|
||||||
var btnApply = document.getElementById("apply-all-updates");
|
const btnApply = $('#apply-all-updates');
|
||||||
var noUpdates = document.getElementById("update-all-no-updates");
|
const noUpdates = $('#update-all-no-updates');
|
||||||
|
|
||||||
btnCheck.disabled = true;
|
btnCheck.disabled = true;
|
||||||
btnApply.classList.add("hidden");
|
btnApply.classList.add('hidden');
|
||||||
noUpdates.classList.add("hidden");
|
noUpdates.classList.add('hidden');
|
||||||
|
|
||||||
var elements = document.querySelectorAll("[style-update-url]");
|
const elements = document.querySelectorAll('[style-update-url]');
|
||||||
var toCheckCount = elements.length;
|
Promise.all([...elements].map(checkUpdate))
|
||||||
var updatableCount = 0;
|
.then(updatables => {
|
||||||
Array.prototype.forEach.call(elements, function(element) {
|
btnCheck.disabled = false;
|
||||||
checkUpdate(element, function(success) {
|
if (updatables.includes(true)) {
|
||||||
if (success) {
|
btnApply.classList.remove('hidden');
|
||||||
++updatableCount;
|
} else {
|
||||||
}
|
noUpdates.classList.remove('hidden');
|
||||||
if (--toCheckCount == 0) {
|
setTimeout(() => {
|
||||||
btnCheck.disabled = false;
|
noUpdates.classList.add('hidden');
|
||||||
if (updatableCount) {
|
}, 10e3);
|
||||||
btnApply.classList.remove("hidden");
|
}
|
||||||
} else {
|
});
|
||||||
noUpdates.classList.remove("hidden");
|
|
||||||
setTimeout(function() {
|
// notify the automatic updater to reset the next automatic update accordingly
|
||||||
noUpdates.classList.add("hidden");
|
chrome.runtime.sendMessage({
|
||||||
}, 10000);
|
method: 'resetInterval'
|
||||||
}
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// notify the automatic updater to reset the next automatic update accordingly
|
|
||||||
chrome.runtime.sendMessage({
|
|
||||||
method: 'resetInterval'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUpdate(element, callback) {
|
|
||||||
element.querySelector(".update-note").innerHTML = t('checkingForUpdate');
|
|
||||||
element.className = element.className.replace("checking-update", "").replace("no-update", "").replace("can-update", "") + " checking-update";
|
|
||||||
var id = element.getAttribute("style-id");
|
|
||||||
var url = element.getAttribute("style-update-url");
|
|
||||||
var md5Url = element.getAttribute("style-md5-url");
|
|
||||||
var originalMd5 = element.getAttribute("style-original-md5");
|
|
||||||
|
|
||||||
function handleSuccess(forceUpdate, serverJson) {
|
function checkUpdate(element) {
|
||||||
chrome.runtime.sendMessage({method: "getStyles", id: id}, function(styles) {
|
$('.update-note', element).innerHTML = t('checkingForUpdate');
|
||||||
var style = styles[0];
|
element.classList.remove('checking-update', 'no-update', 'can-update');
|
||||||
var needsUpdate = false;
|
element.classList.add('checking-update');
|
||||||
if (!forceUpdate && styleSectionsEqual(style, serverJson)) {
|
return new Updater(element).run();
|
||||||
handleNeedsUpdate("no", id, serverJson);
|
|
||||||
} else {
|
|
||||||
handleNeedsUpdate("yes", id, serverJson);
|
|
||||||
needsUpdate = true;
|
|
||||||
}
|
|
||||||
if (callback) {
|
|
||||||
callback(needsUpdate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleFailure(status) {
|
|
||||||
if (status == 0) {
|
|
||||||
handleNeedsUpdate(t('updateCheckFailServerUnreachable'), id, null);
|
|
||||||
} else {
|
|
||||||
handleNeedsUpdate(t('updateCheckFailBadResponseCode', [status]), id, null);
|
|
||||||
}
|
|
||||||
if (callback) {
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!md5Url || !originalMd5) {
|
|
||||||
checkUpdateFullCode(url, false, handleSuccess, handleFailure)
|
|
||||||
} else {
|
|
||||||
checkUpdateMd5(originalMd5, md5Url, function(needsUpdate) {
|
|
||||||
if (needsUpdate) {
|
|
||||||
// If the md5 shows a change we will update regardless of whether the code looks different
|
|
||||||
checkUpdateFullCode(url, true, handleSuccess, handleFailure);
|
|
||||||
} else {
|
|
||||||
handleNeedsUpdate("no", id, null);
|
|
||||||
if (callback) {
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, handleFailure);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUpdateFullCode(url, forceUpdate, successCallback, failureCallback) {
|
|
||||||
download(url, function(responseText) {
|
class Updater {
|
||||||
successCallback(forceUpdate, JSON.parse(responseText));
|
constructor(element) {
|
||||||
}, failureCallback);
|
Object.assign(this, {
|
||||||
|
element,
|
||||||
|
id: element.getAttribute('style-id'),
|
||||||
|
url: element.getAttribute('style-update-url'),
|
||||||
|
md5Url: element.getAttribute('style-md5-url'),
|
||||||
|
md5: element.getAttribute('style-original-md5'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run() {
|
||||||
|
return this.md5Url && this.md5
|
||||||
|
? this.checkMd5()
|
||||||
|
: this.checkFullCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkMd5() {
|
||||||
|
return this.download(this.md5Url).then(
|
||||||
|
md5 => md5.length == 32
|
||||||
|
? this.decideOnMd5(md5 != this.md5)
|
||||||
|
: this.onFailure(-1),
|
||||||
|
this.onFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
decideOnMd5(md5changed) {
|
||||||
|
if (md5changed) {
|
||||||
|
return this.checkFullCode({forceUpdate: true});
|
||||||
|
}
|
||||||
|
this.display();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkFullCode({forceUpdate = false} = {}) {
|
||||||
|
return this.download(this.url).then(
|
||||||
|
text => this.handleJson(forceUpdate, JSON.parse(text)),
|
||||||
|
this.onFailure);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleJson(forceUpdate, json) {
|
||||||
|
return getStylesSafe({id: this.id}).then(([style]) => {
|
||||||
|
const needsUpdate = forceUpdate || !styleSectionsEqual(style, json);
|
||||||
|
this.display({json: needsUpdate && json});
|
||||||
|
return needsUpdate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFailure(status) {
|
||||||
|
this.display({
|
||||||
|
message: status == 0
|
||||||
|
? t('updateCheckFailServerUnreachable')
|
||||||
|
: t('updateCheckFailBadResponseCode', [status]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
display({json, message} = {}) {
|
||||||
|
// json on success
|
||||||
|
// message on failure
|
||||||
|
// none on update not needed
|
||||||
|
this.element.classList.remove('checking-update');
|
||||||
|
if (json) {
|
||||||
|
this.element.classList.add('can-update');
|
||||||
|
this.element.updatedCode = json;
|
||||||
|
$('.update-note', this.element).innerHTML = '';
|
||||||
|
} else {
|
||||||
|
this.element.classList.add('no-update');
|
||||||
|
$('.update-note', this.element).innerHTML = message || t('updateCheckSucceededNoUpdate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
download(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest();
|
||||||
|
xhr.onloadend = () => xhr.status == 200
|
||||||
|
? resolve(xhr.responseText)
|
||||||
|
: reject(xhr.status);
|
||||||
|
if (url.length > 2000) {
|
||||||
|
const [mainUrl, query] = url.split('?');
|
||||||
|
xhr.open('POST', mainUrl, true);
|
||||||
|
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
|
||||||
|
xhr.send(query);
|
||||||
|
} else {
|
||||||
|
xhr.open('GET', url);
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkUpdateMd5(originalMd5, md5Url, successCallback, failureCallback) {
|
|
||||||
download(md5Url, function(responseText) {
|
function searchStyles(immediately, bin) {
|
||||||
if (responseText.length != 32) {
|
const query = $('#search').value.toLocaleLowerCase();
|
||||||
failureCallback(-1);
|
if (query == (searchStyles.lastQuery || '') && !bin) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
successCallback(responseText != originalMd5);
|
searchStyles.lastQuery = query;
|
||||||
}, failureCallback);
|
if (!immediately) {
|
||||||
|
clearTimeout(searchStyles.timeout);
|
||||||
|
searchStyles.timeout = setTimeout(doSearch, 200, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let element of (bin || installed).children) {
|
||||||
|
const {style} = cachedStyles.byId.get(element.styleId) || {};
|
||||||
|
if (style) {
|
||||||
|
const isMatching = !query || isMatchingText(style.name) || isMatchingStyle(style);
|
||||||
|
element.style.display = isMatching ? '' : 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMatchingStyle(style) {
|
||||||
|
for (let section of style.sections) {
|
||||||
|
for (let prop in section) {
|
||||||
|
const value = section[prop];
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'string':
|
||||||
|
if (isMatchingText(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'object':
|
||||||
|
for (let str of value) {
|
||||||
|
if (isMatchingText(str)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isMatchingText(text) {
|
||||||
|
return text.toLocaleLowerCase().indexOf(query) >= 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function download(url, successCallback, failureCallback) {
|
|
||||||
var xhr = new XMLHttpRequest();
|
function getClickedStyleId(event) {
|
||||||
xhr.onreadystatechange = function (aEvt) {
|
return (getClickedStyleElement(event) || {}).styleId;
|
||||||
if (xhr.readyState == 4) {
|
|
||||||
if (xhr.status == 200) {
|
|
||||||
successCallback(xhr.responseText)
|
|
||||||
} else {
|
|
||||||
failureCallback(xhr.status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 handleNeedsUpdate(needsUpdate, id, serverJson) {
|
|
||||||
var e = document.querySelector("[style-id='" + id + "']");
|
function getClickedStyleElement(event) {
|
||||||
e.className = e.className.replace("checking-update", "");
|
return event.target.closest('.entry');
|
||||||
switch (needsUpdate) {
|
|
||||||
case "yes":
|
|
||||||
e.className += " can-update";
|
|
||||||
e.updatedCode = serverJson;
|
|
||||||
e.querySelector(".update-note").innerHTML = '';
|
|
||||||
break;
|
|
||||||
case "no":
|
|
||||||
e.className += " no-update";
|
|
||||||
e.querySelector(".update-note").innerHTML = t('updateCheckSucceededNoUpdate');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
e.className += " no-update";
|
|
||||||
e.querySelector(".update-note").innerHTML = needsUpdate;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function doUpdate(event) {
|
|
||||||
var element = getStyleElement(event);
|
|
||||||
|
|
||||||
var updatedCode = element.updatedCode;
|
function rememberScrollPosition() {
|
||||||
// update everything but name
|
history.replaceState({scrollY}, document.title);
|
||||||
delete updatedCode.name;
|
|
||||||
updatedCode.id = element.getAttribute('style-id');
|
|
||||||
updatedCode.method = "saveStyle";
|
|
||||||
|
|
||||||
// updating the UI will be handled by the general update listener
|
|
||||||
lastUpdatedStyleId = updatedCode.id;
|
|
||||||
chrome.runtime.sendMessage(updatedCode, function () {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchStyles(immediately) {
|
|
||||||
var query = document.getElementById("search").value.toLocaleLowerCase();
|
function $(selector, base = document) {
|
||||||
if (query == (searchStyles.lastQuery || "")) {
|
if (selector.startsWith('#') && /^#[^,\s]+$/.test(selector)) {
|
||||||
return;
|
return document.getElementById(selector.slice(1));
|
||||||
}
|
} else {
|
||||||
searchStyles.lastQuery = query;
|
return base.querySelector(selector);
|
||||||
if (immediately) {
|
}
|
||||||
doSearch();
|
|
||||||
} else {
|
|
||||||
clearTimeout(searchStyles.timeout);
|
|
||||||
searchStyles.timeout = setTimeout(doSearch, 100);
|
|
||||||
}
|
|
||||||
function doSearch() {
|
|
||||||
chrome.runtime.sendMessage({method: "getStyles"}, function(styles) {
|
|
||||||
styles.forEach(function(style) {
|
|
||||||
var el = document.querySelector("[style-id='" + style.id + "']");
|
|
||||||
if (el) {
|
|
||||||
el.style.display = !query || isMatchingText(style.name) || isMatchingStyle(style) ? "" : "none";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function isMatchingStyle(style) {
|
|
||||||
return style.sections.some(function(section) {
|
|
||||||
return Object.keys(section).some(function(key) {
|
|
||||||
var value = section[key];
|
|
||||||
switch (typeof value) {
|
|
||||||
case "string": return isMatchingText(value);
|
|
||||||
case "object": return value.some(isMatchingText);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function isMatchingText(text) {
|
|
||||||
return text.toLocaleLowerCase().indexOf(query) >= 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onFilterChange (className, event) {
|
|
||||||
installed.classList.toggle(className, event.target.checked);
|
|
||||||
}
|
|
||||||
function initFilter(className, node) {
|
|
||||||
node.addEventListener("change", onFilterChange.bind(undefined, className), false);
|
|
||||||
onFilterChange(className, {target: node});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
installed = document.getElementById("installed");
|
|
||||||
if (document.stylishStyles) {
|
|
||||||
showStyles(document.stylishStyles);
|
|
||||||
delete document.stylishStyles;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("check-all-updates").addEventListener("click", checkUpdateAll);
|
|
||||||
document.getElementById("apply-all-updates").addEventListener("click", applyUpdateAll);
|
|
||||||
document.getElementById("search").addEventListener("input", searchStyles);
|
|
||||||
searchStyles(true); // re-apply filtering on history Back
|
|
||||||
|
|
||||||
setupLivePrefs([
|
|
||||||
"manage.onlyEnabled",
|
|
||||||
"manage.onlyEdited",
|
|
||||||
"show-badge",
|
|
||||||
"popup.stylesFirst"
|
|
||||||
]);
|
|
||||||
initFilter("enabled-only", document.getElementById("manage.onlyEnabled"));
|
|
||||||
initFilter("edited-only", document.getElementById("manage.onlyEdited"));
|
|
||||||
});
|
|
||||||
|
|
82
messaging.js
82
messaging.js
|
@ -2,6 +2,7 @@
|
||||||
const KEEP_CHANNEL_OPEN = true;
|
const KEEP_CHANNEL_OPEN = true;
|
||||||
const OWN_ORIGIN = chrome.runtime.getURL('');
|
const OWN_ORIGIN = chrome.runtime.getURL('');
|
||||||
|
|
||||||
|
|
||||||
function notifyAllTabs(request) {
|
function notifyAllTabs(request) {
|
||||||
// list all tabs including chrome-extension:// which can be ours
|
// list all tabs including chrome-extension:// which can be ours
|
||||||
if (request.codeIsUpdated === false && request.style) {
|
if (request.codeIsUpdated === false && request.style) {
|
||||||
|
@ -24,6 +25,7 @@ function notifyAllTabs(request) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function refreshAllTabs() {
|
function refreshAllTabs() {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// list all tabs including chrome-extension:// which can be ours
|
// list all tabs including chrome-extension:// which can be ours
|
||||||
|
@ -47,6 +49,7 @@ function refreshAllTabs() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function updateIcon(tab, styles) {
|
function updateIcon(tab, styles) {
|
||||||
// while NTP is still loading only process the request for its main frame with a real url
|
// while NTP is still loading only process the request for its main frame with a real url
|
||||||
// (but when it's loaded we should process style toggle requests from popups, for example)
|
// (but when it's loaded we should process style toggle requests from popups, for example)
|
||||||
|
@ -62,7 +65,7 @@ function updateIcon(tab, styles) {
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
getTabRealURL(tab, url => {
|
getTabRealURL(tab).then(url => {
|
||||||
// if we have access to this, call directly
|
// if we have access to this, call directly
|
||||||
// (Chrome no longer sends messages to the page itself)
|
// (Chrome no longer sends messages to the page itself)
|
||||||
const options = {method: 'getStyles', matchUrl: url, enabled: true, asHash: true};
|
const options = {method: 'getStyles', matchUrl: url, enabled: true, asHash: true};
|
||||||
|
@ -106,37 +109,80 @@ function updateIcon(tab, styles) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveTab(callback) {
|
|
||||||
chrome.tabs.query({currentWindow: true, active: true}, function(tabs) {
|
function getActiveTab() {
|
||||||
callback(tabs[0]);
|
return new Promise(resolve =>
|
||||||
|
chrome.tabs.query({currentWindow: true, active: true}, tabs =>
|
||||||
|
resolve(tabs[0])));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getActiveTabRealURL() {
|
||||||
|
return getActiveTab()
|
||||||
|
.then(getTabRealURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function getTabRealURL(tab) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
if (tab.url != 'chrome://newtab/') {
|
||||||
|
resolve(tab.url);
|
||||||
|
} else {
|
||||||
|
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, frame => {
|
||||||
|
frame && resolve(frame.url);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getActiveTabRealURL(callback) {
|
|
||||||
getActiveTab(function(tab) {
|
|
||||||
getTabRealURL(tab, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTabRealURL(tab, callback) {
|
function openURL({url}) {
|
||||||
if (tab.url != "chrome://newtab/") {
|
url = !url.includes('://') ? chrome.runtime.getURL(url) : url;
|
||||||
callback(tab.url);
|
return new Promise(resolve => {
|
||||||
} else {
|
chrome.tabs.query({currentWindow: true, url}, tabs => {
|
||||||
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, function(frame) {
|
// switch to an existing tab with the requested url
|
||||||
frame && callback(frame.url);
|
if (tabs.length) {
|
||||||
|
chrome.tabs.highlight({
|
||||||
|
windowId: tabs[0].windowId,
|
||||||
|
tabs: tabs[0].index,
|
||||||
|
}, resolve);
|
||||||
|
} else {
|
||||||
|
// re-use an active new tab page
|
||||||
|
getActiveTab().then(tab =>
|
||||||
|
tab && tab.url == 'chrome://newtab/'
|
||||||
|
? chrome.tabs.update({url}, resolve)
|
||||||
|
: chrome.tabs.create({url}, resolve)
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function onDOMready() {
|
||||||
|
if (document.readyState != 'loading') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise(resolve => {
|
||||||
|
document.addEventListener('DOMContentLoaded', function _() {
|
||||||
|
document.removeEventListener('DOMContentLoaded', _);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function stringAsRegExp(s, flags) {
|
function stringAsRegExp(s, flags) {
|
||||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, "\\$&"), flags);
|
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, '\\$&'), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// expands * as .*?
|
// expands * as .*?
|
||||||
function wildcardAsRegExp(s, flags) {
|
function wildcardAsRegExp(s, flags) {
|
||||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=!|]/g, "\\$&").replace(/\*/g, '.*?'), flags);
|
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=!|]/g, '\\$&').replace(/\*/g, '.*?'), flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
var configureCommands = {
|
var configureCommands = {
|
||||||
get url () {
|
get url () {
|
||||||
return navigator.userAgent.indexOf('OPR') > -1 ?
|
return navigator.userAgent.indexOf('OPR') > -1 ?
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n-text="optionsPopupWidth"></td>
|
<td i18n-text="optionsPopupWidth"></td>
|
||||||
<td><input type="number" id="popupWidth" min="200"></td>
|
<td><input type="number" id="popupWidth" min="200" max="800"></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n-text="optionsUpdateInterval"><sup>1</sup></td>
|
<td i18n-text="optionsUpdateInterval"><sup>1</sup></td>
|
||||||
|
|
|
@ -7,6 +7,7 @@ function restore () {
|
||||||
document.getElementById('badgeNormal').value = bg.prefs.get('badgeNormal');
|
document.getElementById('badgeNormal').value = bg.prefs.get('badgeNormal');
|
||||||
document.getElementById('popupWidth').value = localStorage.getItem('popupWidth') || '246';
|
document.getElementById('popupWidth').value = localStorage.getItem('popupWidth') || '246';
|
||||||
document.getElementById('updateInterval').value = bg.prefs.get('updateInterval');
|
document.getElementById('updateInterval').value = bg.prefs.get('updateInterval');
|
||||||
|
enforceValueRange('popupWidth');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +15,7 @@ function save () {
|
||||||
chrome.runtime.getBackgroundPage(bg => {
|
chrome.runtime.getBackgroundPage(bg => {
|
||||||
bg.prefs.set('badgeDisabled', document.getElementById('badgeDisabled').value);
|
bg.prefs.set('badgeDisabled', document.getElementById('badgeDisabled').value);
|
||||||
bg.prefs.set('badgeNormal', document.getElementById('badgeNormal').value);
|
bg.prefs.set('badgeNormal', document.getElementById('badgeNormal').value);
|
||||||
localStorage.setItem('popupWidth', document.getElementById('popupWidth').value);
|
localStorage.setItem('popupWidth', enforceValueRange('popupWidth'));
|
||||||
bg.prefs.set(
|
bg.prefs.set(
|
||||||
'updateInterval',
|
'updateInterval',
|
||||||
Math.max(0, +document.getElementById('updateInterval').value)
|
Math.max(0, +document.getElementById('updateInterval').value)
|
||||||
|
@ -26,6 +27,19 @@ function save () {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function enforceValueRange(id) {
|
||||||
|
let element = document.getElementById(id);
|
||||||
|
let value = Number(element.value);
|
||||||
|
const min = Number(element.min);
|
||||||
|
const max = Number(element.max);
|
||||||
|
if (value < min || value > max) {
|
||||||
|
value = Math.max(min, Math.min(max, value));
|
||||||
|
element.value = value;
|
||||||
|
}
|
||||||
|
element.onchange = element.onchange || (() => enforceValueRange(id));
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', restore);
|
document.addEventListener('DOMContentLoaded', restore);
|
||||||
document.getElementById('save').addEventListener('click', save);
|
document.getElementById('save').addEventListener('click', save);
|
||||||
|
|
||||||
|
|
10
popup.css
10
popup.css
|
@ -25,6 +25,7 @@ input[type=checkbox] {
|
||||||
}
|
}
|
||||||
a, a:visited {
|
a, a:visited {
|
||||||
color: black;
|
color: black;
|
||||||
|
text-decoration-skip: ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-gutter {
|
.left-gutter {
|
||||||
|
@ -59,7 +60,7 @@ body.blocked > DIV {
|
||||||
}
|
}
|
||||||
#installed {
|
#installed {
|
||||||
padding-top: 2px;
|
padding-top: 2px;
|
||||||
max-height: 434px;
|
max-height: 434px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
#installed.disabled .style-name {
|
#installed.disabled .style-name {
|
||||||
|
@ -112,6 +113,10 @@ body:not(.blocked) #unavailable {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body.blocked #unavailable {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
/* Never shown, but can be enabled with a style */
|
/* Never shown, but can be enabled with a style */
|
||||||
.enable, .disable {
|
.enable, .disable {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -120,7 +125,7 @@ body:not(.blocked) #unavailable {
|
||||||
/* 'New style' links */
|
/* 'New style' links */
|
||||||
#write-style-for {margin-right: .6ex}
|
#write-style-for {margin-right: .6ex}
|
||||||
.write-style-link {margin-left: .6ex}
|
.write-style-link {margin-left: .6ex}
|
||||||
.write-style-link::before, .write-style-link::after {font-size: x-small}
|
.write-style-link::before, .write-style-link::after {font-size: 12px}
|
||||||
.write-style-link::before {content: "\00ad"} /* "soft" hyphen */
|
.write-style-link::before {content: "\00ad"} /* "soft" hyphen */
|
||||||
#match {overflow-wrap: break-word;}
|
#match {overflow-wrap: break-word;}
|
||||||
|
|
||||||
|
@ -154,6 +159,7 @@ body:not(.blocked) #unavailable {
|
||||||
.breadcrumbs > .write-style-link:focus ~ .write-style-link[subdomain] {
|
.breadcrumbs > .write-style-link:focus ~ .write-style-link[subdomain] {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
|
text-decoration-skip: ink;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* action buttons */
|
/* action buttons */
|
||||||
|
|
|
@ -29,11 +29,16 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template data-id="writeStyle">
|
||||||
|
<a class="write-style-link"></a>
|
||||||
|
</template>
|
||||||
|
|
||||||
<script src="localization.js"></script>
|
<script src="localization.js"></script>
|
||||||
<script src="health.js"></script>
|
<script src="health.js"></script>
|
||||||
<script src="storage.js"></script>
|
<script src="storage.js"></script>
|
||||||
<script src="messaging.js"></script>
|
<script src="messaging.js"></script>
|
||||||
<script src="apply.js"></script>
|
<script src="apply.js"></script>
|
||||||
|
<script src="popup.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body id="stylus-popup">
|
<body id="stylus-popup">
|
||||||
|
@ -75,11 +80,10 @@
|
||||||
<!-- Actions -->
|
<!-- Actions -->
|
||||||
<div id="popup-options">
|
<div id="popup-options">
|
||||||
<button id="popup-manage-button" i18n-text="openManage"></button>
|
<button id="popup-manage-button" i18n-text="openManage"></button>
|
||||||
<button id="popup-options-button" i18n-text="openOptionsPopup">
|
<button id="popup-options-button" i18n-text="openOptionsPopup"></button>
|
||||||
<button id="popup-shortcuts-button" i18n-text="openShortcutsPopup"></button>
|
<button id="popup-shortcuts-button" i18n-text="openShortcutsPopup"></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="popup.js"></script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
493
popup.js
493
popup.js
|
@ -1,275 +1,298 @@
|
||||||
/* globals configureCommands */
|
/* globals configureCommands, openURL */
|
||||||
|
|
||||||
var writeStyleTemplate = document.createElement("a");
|
const RX_SUPPORTED_URLS = new RegExp(
|
||||||
writeStyleTemplate.className = "write-style-link";
|
`^(file|https?|ftps?):|^${OWN_ORIGIN}`);
|
||||||
|
let installed;
|
||||||
|
|
||||||
var installed = document.getElementById("installed");
|
|
||||||
|
|
||||||
if (!prefs.get("popup.stylesFirst")) {
|
getActiveTabRealURL().then(url => {
|
||||||
document.body.insertBefore(document.querySelector("body > .actions"), installed);
|
const isUrlSupported = RX_SUPPORTED_URLS.test(url);
|
||||||
|
Promise.all([
|
||||||
|
isUrlSupported ? getStylesSafe({matchUrl: url}) : null,
|
||||||
|
onDOMready().then(() => initPopup(isUrlSupported ? url : '')),
|
||||||
|
])
|
||||||
|
.then(([styles]) => styles && showStyles(styles));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
|
if (msg.method == 'updatePopup') {
|
||||||
|
switch (msg.reason) {
|
||||||
|
case 'styleAdded':
|
||||||
|
case 'styleUpdated':
|
||||||
|
handleUpdate(msg.style);
|
||||||
|
break;
|
||||||
|
case 'styleDeleted':
|
||||||
|
handleDelete(msg.id);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function initPopup(url) {
|
||||||
|
installed = $('#installed');
|
||||||
|
|
||||||
|
// popup width
|
||||||
|
document.body.style.width =
|
||||||
|
Math.max(200, Math.min(800, Number(localStorage.popupWidth) || 246)) + 'px';
|
||||||
|
|
||||||
|
// confirm dialog
|
||||||
|
$('#confirm').onclick = e => {
|
||||||
|
const cmd = e.target.dataset.cmd;
|
||||||
|
if (cmd === 'ok') {
|
||||||
|
deleteStyle($('#confirm').dataset.id).then(() => {
|
||||||
|
// update view with 'No styles installed for this site' message
|
||||||
|
if ($('#installed').children.length === 0) {
|
||||||
|
showStyles([]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//
|
||||||
|
if (cmd) {
|
||||||
|
$('#confirm').dataset.display = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// action buttons
|
||||||
|
$('#disableAll').onchange = () =>
|
||||||
|
installed.classList.toggle('disabled', prefs.get('disableAll'));
|
||||||
|
setupLivePrefs(['disableAll']);
|
||||||
|
$('#find-styles-link').onclick = openURLandHide;
|
||||||
|
$('#popup-manage-button').href = 'manage.html';
|
||||||
|
$('#popup-manage-button').onclick = openURLandHide;
|
||||||
|
$('#popup-options-button').onclick = () => chrome.runtime.openOptionsPage();
|
||||||
|
$('#popup-shortcuts-button').onclick = configureCommands.open;
|
||||||
|
|
||||||
|
// styles first?
|
||||||
|
if (!prefs.get('popup.stylesFirst')) {
|
||||||
|
document.body.insertBefore(
|
||||||
|
$('body > .actions'),
|
||||||
|
installed);
|
||||||
|
}
|
||||||
|
|
||||||
|
// find styles link
|
||||||
|
$('#find-styles a').href =
|
||||||
|
'https://userstyles.org/styles/browse/all/' +
|
||||||
|
encodeURIComponent(url.startsWith('file:') ? 'file:' : url);
|
||||||
|
|
||||||
|
if (!url) {
|
||||||
|
document.body.classList.add('blocked');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write new style links
|
||||||
|
const writeStyle = $('#write-style');
|
||||||
|
const matchTargets = document.createElement('span');
|
||||||
|
matchTargets.id = 'match';
|
||||||
|
|
||||||
|
// For this URL
|
||||||
|
const urlLink = template.writeStyle.cloneNode(true);
|
||||||
|
Object.assign(urlLink, {
|
||||||
|
href: 'edit.html?url-prefix=' + encodeURIComponent(url),
|
||||||
|
title: `url-prefix("${url}")`,
|
||||||
|
textContent: prefs.get('popup.breadcrumbs.usePath')
|
||||||
|
? new URL(url).pathname.slice(1)
|
||||||
|
: t('writeStyleForURL').replace(/ /g, '\u00a0'), // this URL
|
||||||
|
onclick: openLinkInTabOrWindow,
|
||||||
|
});
|
||||||
|
if (prefs.get('popup.breadcrumbs')) {
|
||||||
|
urlLink.onmouseenter =
|
||||||
|
urlLink.onfocus = () => urlLink.parentNode.classList.add('url()');
|
||||||
|
urlLink.onmouseleave =
|
||||||
|
urlLink.onblur = () => urlLink.parentNode.classList.remove('url()');
|
||||||
|
}
|
||||||
|
matchTargets.appendChild(urlLink);
|
||||||
|
|
||||||
|
// For domain
|
||||||
|
const domains = getDomains(url);
|
||||||
|
for (let domain of domains) {
|
||||||
|
// Don't include TLD
|
||||||
|
if (domains.length > 1 && !domain.includes('.')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const domainLink = template.writeStyle.cloneNode(true);
|
||||||
|
Object.assign(domainLink, {
|
||||||
|
href: 'edit.html?domain=' + encodeURIComponent(domain),
|
||||||
|
textContent: domain,
|
||||||
|
title: `domain("${domain}")`,
|
||||||
|
onclick: openLinkInTabOrWindow,
|
||||||
|
});
|
||||||
|
domainLink.setAttribute('subdomain', domain.substring(0, domain.indexOf('.')));
|
||||||
|
matchTargets.appendChild(domainLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefs.get('popup.breadcrumbs')) {
|
||||||
|
matchTargets.classList.add('breadcrumbs');
|
||||||
|
matchTargets.appendChild(matchTargets.removeChild(matchTargets.firstElementChild));
|
||||||
|
}
|
||||||
|
writeStyle.appendChild(matchTargets);
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveTabRealURL(updatePopUp);
|
|
||||||
|
|
||||||
function updatePopUp(url) {
|
|
||||||
var urlWillWork = /^(file|http|https|ftps?|chrome\-extension):/.exec(url);
|
|
||||||
if (!urlWillWork) {
|
|
||||||
document.body.classList.add("blocked");
|
|
||||||
document.getElementById("unavailable").style.display = "flex";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
getStylesSafe({matchUrl: url}).then(showStyles);
|
|
||||||
|
|
||||||
document.querySelector("#find-styles a").href = "https://userstyles.org/styles/browse/all/" + encodeURIComponent("file" === urlWillWork[1] ? "file:" : url);
|
|
||||||
|
|
||||||
// Write new style links
|
|
||||||
var writeStyleLinks = [],
|
|
||||||
container = document.createElement('span');
|
|
||||||
container.id = "match";
|
|
||||||
|
|
||||||
// For this URL
|
|
||||||
var urlLink = writeStyleTemplate.cloneNode(true);
|
|
||||||
urlLink.href = "edit.html?url-prefix=" + encodeURIComponent(url);
|
|
||||||
urlLink.appendChild(document.createTextNode( // switchable; default="this URL"
|
|
||||||
!prefs.get("popup.breadcrumbs.usePath")
|
|
||||||
? t("writeStyleForURL").replace(/ /g, "\u00a0")
|
|
||||||
: /\/\/[^/]+\/(.*)/.exec(url)[1]
|
|
||||||
));
|
|
||||||
urlLink.title = "url-prefix(\"$\")".replace("$", url);
|
|
||||||
writeStyleLinks.push(urlLink);
|
|
||||||
document.querySelector("#write-style").appendChild(urlLink)
|
|
||||||
if (prefs.get("popup.breadcrumbs")) { // switchable; default=enabled
|
|
||||||
urlLink.addEventListener("mouseenter", function(event) { this.parentNode.classList.add("url()") }, false);
|
|
||||||
urlLink.addEventListener("focus", function(event) { this.parentNode.classList.add("url()") }, false);
|
|
||||||
urlLink.addEventListener("mouseleave", function(event) { this.parentNode.classList.remove("url()") }, false);
|
|
||||||
urlLink.addEventListener("blur", function(event) { this.parentNode.classList.remove("url()") }, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
// For domain
|
|
||||||
var domains = getDomains(url)
|
|
||||||
domains.forEach(function(domain) {
|
|
||||||
// Don't include TLD
|
|
||||||
if (domains.length > 1 && domain.indexOf(".") == -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var domainLink = writeStyleTemplate.cloneNode(true);
|
|
||||||
domainLink.href = "edit.html?domain=" + encodeURIComponent(domain);
|
|
||||||
domainLink.appendChild(document.createTextNode(domain));
|
|
||||||
domainLink.title = "domain(\"$\")".replace("$", domain);
|
|
||||||
domainLink.setAttribute("subdomain", domain.substring(0, domain.indexOf(".")));
|
|
||||||
writeStyleLinks.push(domainLink);
|
|
||||||
});
|
|
||||||
|
|
||||||
var writeStyle = document.querySelector("#write-style");
|
|
||||||
writeStyleLinks.forEach(function(link, index) {
|
|
||||||
link.addEventListener("click", openLinkInTabOrWindow, false);
|
|
||||||
container.appendChild(link);
|
|
||||||
});
|
|
||||||
if (prefs.get("popup.breadcrumbs")) {
|
|
||||||
container.classList.add("breadcrumbs");
|
|
||||||
container.appendChild(container.removeChild(container.firstChild));
|
|
||||||
}
|
|
||||||
writeStyle.appendChild(container);
|
|
||||||
}
|
|
||||||
|
|
||||||
function showStyles(styles) {
|
function showStyles(styles) {
|
||||||
var enabledFirst = prefs.get("popup.enabledFirst");
|
if (!styles.length) {
|
||||||
styles.sort(function(a, b) {
|
installed.innerHTML =
|
||||||
if (enabledFirst && a.enabled !== b.enabled) return !(a.enabled < b.enabled) ? -1 : 1;
|
`<div class="entry" id="no-styles">${t('noStylesForSite')}</div>`;
|
||||||
return a.name.localeCompare(b.name);
|
} else {
|
||||||
});
|
const enabledFirst = prefs.get('popup.enabledFirst');
|
||||||
if (styles.length == 0) {
|
styles.sort((a, b) =>
|
||||||
installed.innerHTML = "<div class='entry' id='no-styles'>" + t('noStylesForSite') + "</div>";
|
enabledFirst && a.enabled !== b.enabled
|
||||||
}
|
? !(a.enabled < b.enabled) ? -1 : 1
|
||||||
styles.map(createStyleElement).forEach(function(e) {
|
: a.name.localeCompare(b.name));
|
||||||
installed.appendChild(e);
|
const fragment = document.createDocumentFragment();
|
||||||
});
|
for (let style of styles) {
|
||||||
// force Chrome to resize the popup
|
fragment.appendChild(createStyleElement(style));
|
||||||
document.body.style.height = '10px';
|
}
|
||||||
document.documentElement.style.height = '10px';
|
installed.appendChild(fragment);
|
||||||
|
}
|
||||||
|
// force Chrome to resize the popup
|
||||||
|
document.body.style.height = '10px';
|
||||||
|
document.documentElement.style.height = '10px';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createStyleElement(style) {
|
function createStyleElement(style) {
|
||||||
// reuse event function references
|
// reuse event listener function references
|
||||||
createStyleElement.events = createStyleElement.events || {
|
const listeners = createStyleElement.listeners = createStyleElement.listeners || {
|
||||||
checkboxClick() {
|
checkboxClick() {
|
||||||
enableStyle(getClickedStyleId(event), this.checked);
|
enableStyle(getClickedStyleId(event), this.checked)
|
||||||
},
|
.then(handleUpdate);
|
||||||
styleNameClick(event) {
|
},
|
||||||
this.checkbox.click();
|
styleNameClick(event) {
|
||||||
event.preventDefault();
|
this.checkbox.click();
|
||||||
},
|
event.preventDefault();
|
||||||
toggleClick(event) {
|
},
|
||||||
enableStyle(getClickedStyleId(event), this.matches('.enable'));
|
toggleClick(event) {
|
||||||
},
|
enableStyle(getClickedStyleId(event), this.matches('.enable'))
|
||||||
deleteClick() {
|
.then(handleUpdate);
|
||||||
doDelete(event);
|
},
|
||||||
|
deleteClick(event) {
|
||||||
|
doDelete(event);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const entry = template.style.cloneNode(true);
|
const entry = template.style.cloneNode(true);
|
||||||
entry.setAttribute('style-id', style.id);
|
entry.setAttribute('style-id', style.id);
|
||||||
Object.assign(entry, {
|
Object.assign(entry, {
|
||||||
styleId: style.id,
|
styleId: style.id,
|
||||||
className: ['entry', style.enabled ? 'enabled' : 'disabled'].join(' '),
|
className: ['entry', style.enabled ? 'enabled' : 'disabled'].join(' '),
|
||||||
onmousedown: openEditorOnMiddleclick,
|
onmousedown: openEditorOnMiddleclick,
|
||||||
onauxclick: openEditorOnMiddleclick,
|
onauxclick: openEditorOnMiddleclick,
|
||||||
});
|
});
|
||||||
|
|
||||||
const checkbox = entry.querySelector('.checker');
|
const checkbox = $('.checker', entry);
|
||||||
Object.assign(checkbox, {
|
Object.assign(checkbox, {
|
||||||
id: 'style-' + style.id,
|
id: 'style-' + style.id,
|
||||||
checked: style.enabled,
|
checked: style.enabled,
|
||||||
onclick: createStyleElement.events.checkboxClick,
|
onclick: listeners.checkboxClick,
|
||||||
});
|
});
|
||||||
|
|
||||||
const editLink = entry.querySelector('.style-edit-link');
|
const editLink = $('.style-edit-link', entry);
|
||||||
Object.assign(editLink, {
|
Object.assign(editLink, {
|
||||||
href: editLink.getAttribute('href') + style.id,
|
href: editLink.getAttribute('href') + style.id,
|
||||||
onclick: openLinkInTabOrWindow,
|
onclick: openLinkInTabOrWindow,
|
||||||
});
|
});
|
||||||
|
|
||||||
const styleName = entry.querySelector('.style-name');
|
const styleName = $('.style-name', entry);
|
||||||
Object.assign(styleName, {
|
Object.assign(styleName, {
|
||||||
htmlFor: 'style-' + style.id,
|
htmlFor: 'style-' + style.id,
|
||||||
onclick: createStyleElement.events.styleNameClick,
|
onclick: listeners.styleNameClick,
|
||||||
});
|
});
|
||||||
styleName.checkbox = checkbox;
|
styleName.checkbox = checkbox;
|
||||||
styleName.appendChild(document.createTextNode(style.name));
|
styleName.appendChild(document.createTextNode(style.name));
|
||||||
|
|
||||||
entry.querySelector('.enable').onclick = createStyleElement.events.toggleClick;
|
$('.enable', entry).onclick = listeners.toggleClick;
|
||||||
entry.querySelector('.disable').onclick = createStyleElement.events.toggleClick;
|
$('.disable', entry).onclick = listeners.toggleClick;
|
||||||
entry.querySelector('.delete').onclick = createStyleElement.events.deleteClick;
|
$('.delete', entry).onclick = listeners.deleteClick;
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function doDelete(event) {
|
function doDelete(event) {
|
||||||
document.getElementById('confirm').dataset.display = true;
|
$('#confirm').dataset.display = true;
|
||||||
const id = getClickedStyleId(event);
|
const id = getClickedStyleId(event);
|
||||||
document.querySelector('#confirm b').textContent =
|
$('#confirm b').textContent =
|
||||||
document.querySelector(`[style-id="${id}"] label`).textContent;
|
$(`[style-id="${id}"] label`).textContent;
|
||||||
document.getElementById('confirm').dataset.id = id;
|
$('#confirm').dataset.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById('confirm').addEventListener('click', e => {
|
|
||||||
let cmd = e.target.dataset.cmd;
|
|
||||||
if (cmd === 'ok') {
|
|
||||||
deleteStyle(document.getElementById('confirm').dataset.id, () => {
|
|
||||||
// update view with 'No styles installed for this site' message
|
|
||||||
if (document.getElementById('installed').children.length === 0) {
|
|
||||||
showStyles([]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
//
|
|
||||||
if (cmd) {
|
|
||||||
document.getElementById('confirm').dataset.display = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function getClickedStyleId(event) {
|
function getClickedStyleId(event) {
|
||||||
const entry = event.target.closest('.entry');
|
const entry = event.target.closest('.entry');
|
||||||
return entry ? entry.styleId : null;
|
return entry ? entry.styleId : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function openLinkInTabOrWindow(event) {
|
function openLinkInTabOrWindow(event) {
|
||||||
event.preventDefault();
|
if (!prefs.get('openEditInWindow', false)) {
|
||||||
if (prefs.get("openEditInWindow", false)) {
|
openURLandHide(event);
|
||||||
var options = {url: event.target.href}
|
return;
|
||||||
var wp = prefs.get("windowPosition", {});
|
}
|
||||||
for (var k in wp) options[k] = wp[k];
|
event.preventDefault();
|
||||||
chrome.windows.create(options);
|
chrome.windows.create(
|
||||||
} else {
|
Object.assign({
|
||||||
openLink(event);
|
url: event.target.href
|
||||||
}
|
}, prefs.get('windowPosition', {}))
|
||||||
close();
|
);
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function openEditorOnMiddleclick(event) {
|
function openEditorOnMiddleclick(event) {
|
||||||
if (event.button != 1) {
|
if (event.button != 1) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// open an editor on middleclick
|
// open an editor on middleclick
|
||||||
if (event.target.matches('.entry, .style-name, .style-edit-link')) {
|
if (event.target.matches('.entry, .style-name, .style-edit-link')) {
|
||||||
this.querySelector('.style-edit-link').click();
|
$('.style-edit-link', this).click();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// prevent the popup being opened in a background tab
|
// prevent the popup being opened in a background tab
|
||||||
// when an irrelevant link was accidentally clicked
|
// when an irrelevant link was accidentally clicked
|
||||||
if (event.target.closest('a')) {
|
if (event.target.closest('a')) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openLink(event) {
|
|
||||||
event.preventDefault();
|
function openURLandHide(event) {
|
||||||
chrome.runtime.sendMessage({method: "openURL", url: event.target.href});
|
event.preventDefault();
|
||||||
close();
|
openURL({url: event.target.href})
|
||||||
|
.then(close);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function handleUpdate(style) {
|
function handleUpdate(style) {
|
||||||
var styleElement = installed.querySelector("[style-id='" + style.id + "']");
|
const styleElement = $(`[style-id="${style.id}"]`, installed);
|
||||||
if (styleElement) {
|
if (styleElement) {
|
||||||
installed.replaceChild(createStyleElement(style), styleElement);
|
installed.replaceChild(createStyleElement(style), styleElement);
|
||||||
} else {
|
} else {
|
||||||
getActiveTabRealURL(function(url) {
|
getActiveTabRealURL().then(url => {
|
||||||
if (chrome.extension.getBackgroundPage().getApplicableSections(style, url).length) {
|
if (getApplicableSections(style, url).length) {
|
||||||
// a new style for the current url is installed
|
// a new style for the current url is installed
|
||||||
document.getElementById("unavailable").style.display = "none";
|
$('#unavailable').style.display = 'none';
|
||||||
installed.appendChild(createStyleElement(style));
|
installed.appendChild(createStyleElement(style));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function handleDelete(id) {
|
function handleDelete(id) {
|
||||||
var styleElement = installed.querySelector("[style-id='" + id + "']");
|
var styleElement = $(`[style-id="${id}"]`, installed);
|
||||||
if (styleElement) {
|
if (styleElement) {
|
||||||
installed.removeChild(styleElement);
|
installed.removeChild(styleElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
|
|
||||||
if (request.method == "updatePopup") {
|
|
||||||
switch (request.reason) {
|
|
||||||
case "styleAdded":
|
|
||||||
case "styleUpdated":
|
|
||||||
handleUpdate(request.style);
|
|
||||||
break;
|
|
||||||
case "styleDeleted":
|
|
||||||
handleDelete(request.id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
["find-styles-link"].forEach(function(id) {
|
function $(selector, base = document) {
|
||||||
document.getElementById(id).addEventListener("click", openLink, false);
|
if (selector.startsWith('#') && /^#[^,\s]+$/.test(selector)) {
|
||||||
});
|
return document.getElementById(selector.slice(1));
|
||||||
|
} else {
|
||||||
document.getElementById("disableAll").addEventListener("change", function(event) {
|
return base.querySelector(selector);
|
||||||
installed.classList.toggle("disabled", prefs.get("disableAll"));
|
}
|
||||||
});
|
}
|
||||||
setupLivePrefs(["disableAll"]);
|
|
||||||
|
|
||||||
document.querySelector('#popup-manage-button').addEventListener("click", function() {
|
|
||||||
window.open(chrome.runtime.getURL('manage.html'));
|
|
||||||
});
|
|
||||||
|
|
||||||
document.querySelector('#popup-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('#popup-shortcuts-button').addEventListener("click", configureCommands.open);
|
|
||||||
|
|
||||||
// popup width
|
|
||||||
document.body.style.width = (localStorage.getItem('popupWidth') || '246') + 'px';
|
|
||||||
|
|
26
storage.js
26
storage.js
|
@ -277,23 +277,21 @@ function addMissingStyleTargets(style) {
|
||||||
|
|
||||||
|
|
||||||
function enableStyle(id, enabled) {
|
function enableStyle(id, enabled) {
|
||||||
saveStyle({id, enabled})
|
return saveStyle({id, enabled});
|
||||||
.then(handleUpdate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function deleteStyle(id, callback = function (){}) {
|
function deleteStyle(id) {
|
||||||
getDatabase(function(db) {
|
return new Promise(resolve =>
|
||||||
var tx = db.transaction(["styles"], "readwrite");
|
getDatabase(db => {
|
||||||
var os = tx.objectStore("styles");
|
const tx = db.transaction(['styles'], 'readwrite');
|
||||||
var request = os.delete(Number(id));
|
const os = tx.objectStore('styles');
|
||||||
request.onsuccess = function(event) {
|
os.delete(Number(id)).onsuccess = event => {
|
||||||
handleDelete(id);
|
invalidateCache(true, {deletedId: id});
|
||||||
invalidateCache(true, {deletedId: id});
|
notifyAllTabs({method: 'styleDeleted', id});
|
||||||
notifyAllTabs({method: "styleDeleted", id});
|
resolve(id);
|
||||||
callback();
|
};
|
||||||
};
|
}));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user