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
|
||||
importStyles: true
|
||||
getActiveTabRealURL: true
|
||||
openURL: true
|
||||
onDOMready: true
|
||||
getDomains: true
|
||||
webSqlStorage: 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
|
||||
// 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;
|
||||
getCodeMirrorThemes(function(themes) {
|
||||
codeMirrorThemes = themes;
|
||||
|
|
|
@ -64,6 +64,7 @@ function importFromString(jsonString) {
|
|||
});
|
||||
} else {
|
||||
refreshAllTabs().then(() => {
|
||||
scrollTo(0, 0);
|
||||
setTimeout(alert, 100, numStyles + ' styles installed/updated');
|
||||
resolve(numStyles);
|
||||
});
|
||||
|
|
|
@ -45,14 +45,15 @@
|
|||
}
|
||||
/************ header ************/
|
||||
#header {
|
||||
height: calc(100vh - 30px);
|
||||
width: 280px;
|
||||
height: 100vh;
|
||||
overflow: auto;
|
||||
width: 250px;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
padding: 15px;
|
||||
border-right: 1px dashed #AAA;
|
||||
-webkit-box-shadow: 0 0 3rem -1.2rem black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
#header h1 {
|
||||
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;
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
healthCheck();
|
||||
setTimeout(healthCheck, 0);
|
||||
|
||||
function healthCheck() {
|
||||
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>
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title i18n-text="manageTitle"></title>
|
||||
<style>
|
||||
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 */
|
||||
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
|
||||
<title i18n-text="manageTitle"></title>
|
||||
<link href="manage.css" rel="stylesheet">
|
||||
|
||||
.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">
|
||||
<div>
|
||||
<h2 class="style-name"></h2>
|
||||
<p class="applies-to"></p>
|
||||
<p class="actions">
|
||||
<a class="style-edit-link" href="edit.html?id="><button i18n-text="editStyleLabel"></button></a>
|
||||
<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>
|
||||
|
||||
<template data-id="styleHomepage">
|
||||
<a target="_blank" class="homepage">
|
||||
<svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg>
|
||||
<template data-id="style">
|
||||
<div class="entry">
|
||||
<h2 class="style-name"><a href="edit.html?id="></a></h2>
|
||||
<p class="applies-to"><span></span></p>
|
||||
<p class="actions">
|
||||
<a class="style-edit-link" href="edit.html?id=">
|
||||
<button i18n-text="editStyleLabel"></button>
|
||||
</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>
|
||||
<script src="health.js"></script>
|
||||
<script src="storage.js"></script>
|
||||
<script src="messaging.js"></script>
|
||||
<script src="apply.js"></script>
|
||||
<script src="manage.js"></script>
|
||||
<template data-id="styleHomepage">
|
||||
<a target="_blank" class="homepage">
|
||||
<svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<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>
|
||||
|
||||
<body id="stylus-manage" i18n-dragndrop-hint="dragDropMessage">
|
||||
<div id="header">
|
||||
<h1 id="manage-heading" i18n-text="manageHeading"></h1>
|
||||
<fieldset>
|
||||
<legend id="filters" i18n-text="manageFilters"></legend>
|
||||
<div>
|
||||
<input id="manage.onlyEnabled" type="checkbox">
|
||||
<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 id="header">
|
||||
<h1 id="manage-heading" i18n-text="manageHeading"></h1>
|
||||
<fieldset>
|
||||
<legend id="filters" i18n-text="manageFilters"></legend>
|
||||
<div>
|
||||
<input id="manage.onlyEnabled" type="checkbox">
|
||||
<label id="manage.onlyEnabled-label" for="manage.onlyEnabled" i18n-text="manageOnlyEnabled"></label>
|
||||
</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">
|
||||
<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>
|
||||
</symbol>
|
||||
</svg>
|
||||
<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">
|
||||
<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>
|
||||
</svg>
|
||||
|
||||
<script src="openOptions.js"></script>
|
||||
<script src="backup/fileSaveLoad.js"></script>
|
||||
<script src="manage.js"></script>
|
||||
<script src="openOptions.js"></script>
|
||||
<script src="backup/fileSaveLoad.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
819
manage.js
819
manage.js
|
@ -1,434 +1,471 @@
|
|||
/* globals styleSectionsEqual */
|
||||
var lastUpdatedStyleId = null;
|
||||
var installed;
|
||||
|
||||
var appliesToExtraTemplate = document.createElement("span");
|
||||
appliesToExtraTemplate.className = "applies-to-extra";
|
||||
appliesToExtraTemplate.innerHTML = " " + t('appliesDisplayTruncatedSuffix');
|
||||
const installed = $('#installed');
|
||||
const TARGET_LABEL = t('appliesDisplay', '').trim();
|
||||
const TARGET_TYPES = ['domains', 'urls', 'urlPrefixes', 'regexps'];
|
||||
const TARGET_LIMIT = 10;
|
||||
|
||||
getStylesSafe({code: false}).then(showStyles);
|
||||
|
||||
function showStyles(styles) {
|
||||
if (!installed) {
|
||||
// "getStyles" message callback is invoked before document is loaded,
|
||||
// postpone the action until DOMContentLoaded is fired
|
||||
document.stylishStyles = styles;
|
||||
return;
|
||||
}
|
||||
styles.sort(function(a, b) { return a.name.localeCompare(b.name)});
|
||||
styles.forEach(handleUpdate);
|
||||
if (history.state) {
|
||||
window.scrollTo(0, history.state.scrollY);
|
||||
}
|
||||
getStylesSafe({code: false})
|
||||
.then(showStyles)
|
||||
.then(initGlobalEvents);
|
||||
|
||||
|
||||
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||
switch (msg.method) {
|
||||
case 'styleUpdated':
|
||||
case 'styleAdded':
|
||||
handleUpdate(msg.style, msg);
|
||||
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) {
|
||||
var e = template.style.cloneNode(true);
|
||||
e.setAttribute("class", style.enabled ? "enabled" : "disabled");
|
||||
e.setAttribute("style-id", style.id);
|
||||
if (style.updateUrl) {
|
||||
e.setAttribute("style-update-url", style.updateUrl);
|
||||
}
|
||||
if (style.md5Url) {
|
||||
e.setAttribute("style-md5-url", style.md5Url);
|
||||
}
|
||||
if (style.originalMd5) {
|
||||
e.setAttribute("style-original-md5", style.originalMd5);
|
||||
}
|
||||
const entry = template.style.cloneNode(true);
|
||||
entry.classList.add(style.enabled ? 'enabled' : 'disabled');
|
||||
entry.setAttribute('style-id', style.id);
|
||||
entry.styleId = style.id;
|
||||
if (style.updateUrl) {
|
||||
entry.setAttribute('style-update-url', style.updateUrl);
|
||||
}
|
||||
if (style.md5Url) {
|
||||
entry.setAttribute('style-md5-url', style.md5Url);
|
||||
}
|
||||
if (style.originalMd5) {
|
||||
entry.setAttribute('style-original-md5', style.originalMd5);
|
||||
}
|
||||
|
||||
var styleName = e.querySelector(".style-name");
|
||||
styleName.appendChild(document.createTextNode(style.name));
|
||||
if (style.url) {
|
||||
var homepage = template.styleHomepage.cloneNode(true)
|
||||
homepage.setAttribute("href", style.url);
|
||||
styleName.appendChild(document.createTextNode(" " ));
|
||||
styleName.appendChild(homepage);
|
||||
}
|
||||
var domains = [];
|
||||
var urls = [];
|
||||
var urlPrefixes = [];
|
||||
var regexps = [];
|
||||
function add(array, property) {
|
||||
style.sections.forEach(function(section) {
|
||||
if (section[property]) {
|
||||
section[property].filter(function(value) {
|
||||
return array.indexOf(value) == -1;
|
||||
}).forEach(function(value) {
|
||||
array.push(value);
|
||||
});;
|
||||
}
|
||||
});
|
||||
}
|
||||
add(domains, 'domains');
|
||||
add(urls, 'urls');
|
||||
add(urlPrefixes, 'urlPrefixes');
|
||||
add(regexps, 'regexps');
|
||||
var appliesToToShow = [];
|
||||
if (domains)
|
||||
appliesToToShow = appliesToToShow.concat(domains);
|
||||
if (urls)
|
||||
appliesToToShow = appliesToToShow.concat(urls);
|
||||
if (urlPrefixes)
|
||||
appliesToToShow = appliesToToShow.concat(urlPrefixes.map(function(u) { return u + "*"; }));
|
||||
if (regexps)
|
||||
appliesToToShow = appliesToToShow.concat(regexps.map(function(u) { return "/" + u + "/"; }));
|
||||
var appliesToString = "";
|
||||
var showAppliesToExtra = false;
|
||||
if (appliesToToShow.length == "")
|
||||
appliesToString = t('appliesToEverything');
|
||||
else if (appliesToToShow.length <= 10)
|
||||
appliesToString = appliesToToShow.join(", ");
|
||||
else {
|
||||
appliesToString = appliesToToShow.slice(0, 10).join(", ");
|
||||
showAppliesToExtra = true;
|
||||
}
|
||||
e.querySelector(".applies-to").appendChild(document.createTextNode(t('appliesDisplay', [appliesToString])));
|
||||
if (showAppliesToExtra) {
|
||||
e.querySelector(".applies-to").appendChild(appliesToExtraTemplate.cloneNode(true));
|
||||
}
|
||||
var editLink = e.querySelector(".style-edit-link");
|
||||
editLink.setAttribute("href", editLink.getAttribute("href") + style.id);
|
||||
editLink.addEventListener("click", function(event) {
|
||||
if (!event.altKey) {
|
||||
var left = event.button == 0, middle = event.button == 1,
|
||||
shift = event.shiftKey, ctrl = event.ctrlKey;
|
||||
var openWindow = left && shift && !ctrl;
|
||||
var openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
|
||||
var openForegroundTab = (middle && shift) || (left && ctrl && shift);
|
||||
var url = event.target.href || event.target.parentNode.href;
|
||||
event.preventDefault();
|
||||
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;
|
||||
const styleName = $('.style-name', entry);
|
||||
const styleNameEditLink = $('a', styleName);
|
||||
styleNameEditLink.appendChild(document.createTextNode(style.name));
|
||||
styleNameEditLink.href = styleNameEditLink.getAttribute('href') + style.id;
|
||||
styleNameEditLink.onclick = EntryOnClick.edit;
|
||||
if (style.url) {
|
||||
const homepage = template.styleHomepage.cloneNode(true);
|
||||
homepage.href = style.url;
|
||||
styleName.appendChild(document.createTextNode(' '));
|
||||
styleName.appendChild(homepage);
|
||||
}
|
||||
|
||||
const targets = new Map(TARGET_TYPES.map(t => [t, new Set()]));
|
||||
const decorations = {
|
||||
urlPrefixesAfter: '*',
|
||||
regexpsBefore: '/',
|
||||
regexpsAfter: '/',
|
||||
};
|
||||
for (let [name, target] of targets.entries()) {
|
||||
for (let section of style.sections) {
|
||||
for (let targetValue of section[name] || []) {
|
||||
target.add(
|
||||
(decorations[name + 'Before'] || '') +
|
||||
targetValue.trim() +
|
||||
(decorations[name + 'After'] || ''));
|
||||
}
|
||||
}
|
||||
}
|
||||
const appliesTo = $('.applies-to', entry);
|
||||
appliesTo.firstElementChild.textContent = TARGET_LABEL;
|
||||
const targetsList = Array.prototype.concat.apply([],
|
||||
[...targets.values()].map(set => [...set.values()]));
|
||||
if (!targetsList.length) {
|
||||
appliesTo.appendChild(template.appliesToEverything.cloneNode(true));
|
||||
entry.classList.add('global');
|
||||
} else {
|
||||
let index = 0;
|
||||
let container = appliesTo;
|
||||
for (let target of targetsList) {
|
||||
if (index > 0) {
|
||||
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
||||
}
|
||||
if (++index == TARGET_LIMIT) {
|
||||
container = appliesTo.appendChild(template.extraAppliesTo.cloneNode(true));
|
||||
}
|
||||
const item = template.appliesToTarget.cloneNode(true);
|
||||
item.textContent = target;
|
||||
container.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
const editLink = $('.style-edit-link', entry);
|
||||
editLink.href = editLink.getAttribute('href') + style.id;
|
||||
editLink.onclick = EntryOnClick.edit;
|
||||
|
||||
$('.enable', entry).onclick = EntryOnClick.toggle;
|
||||
$('.disable', entry).onclick = EntryOnClick.toggle;
|
||||
$('.check-update', entry).onclick = EntryOnClick.check;
|
||||
$('.update', entry).onclick = EntryOnClick.update;
|
||||
$('.delete', entry).onclick = EntryOnClick.delete;
|
||||
return entry;
|
||||
}
|
||||
|
||||
function enable(event, enabled) {
|
||||
var id = getId(event);
|
||||
enableStyle(id, enabled);
|
||||
class EntryOnClick {
|
||||
|
||||
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'))) {
|
||||
return;
|
||||
}
|
||||
var id = getId(event);
|
||||
deleteStyle(id);
|
||||
|
||||
function handleUpdate(style, {reason} = {}) {
|
||||
const element = createStyleElement(style);
|
||||
const oldElement = $(`[style-id="${style.id}"]`, installed);
|
||||
if (!oldElement) {
|
||||
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) {
|
||||
var node = installed.querySelector("[style-id='" + id + "']");
|
||||
if (node) {
|
||||
installed.removeChild(node);
|
||||
}
|
||||
const node = $(`[style-id="${id}"]`, installed);
|
||||
if (node) {
|
||||
node.remove();
|
||||
}
|
||||
}
|
||||
|
||||
function doCheckUpdate(event) {
|
||||
checkUpdate(getStyleElement(event));
|
||||
}
|
||||
|
||||
function applyUpdateAll() {
|
||||
var btnApply = document.getElementById("apply-all-updates");
|
||||
btnApply.disabled = true;
|
||||
setTimeout(function() {
|
||||
btnApply.style.display = "none";
|
||||
btnApply.disabled = false;
|
||||
}, 1000);
|
||||
const btnApply = $('#apply-all-updates');
|
||||
btnApply.disabled = true;
|
||||
setTimeout(() => {
|
||||
btnApply.style.display = 'none';
|
||||
btnApply.disabled = false;
|
||||
}, 1000);
|
||||
|
||||
Array.prototype.forEach.call(document.querySelectorAll(".can-update .update"), function(button) {
|
||||
button.click();
|
||||
});
|
||||
[...document.querySelectorAll('.can-update .update')]
|
||||
.forEach(button => {
|
||||
// align to the bottom of the visible area if wasn't visible
|
||||
button.scrollIntoView(false);
|
||||
button.click();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function checkUpdateAll() {
|
||||
var btnCheck = document.getElementById("check-all-updates");
|
||||
var btnApply = document.getElementById("apply-all-updates");
|
||||
var noUpdates = document.getElementById("update-all-no-updates");
|
||||
const btnCheck = $('#check-all-updates');
|
||||
const btnApply = $('#apply-all-updates');
|
||||
const noUpdates = $('#update-all-no-updates');
|
||||
|
||||
btnCheck.disabled = true;
|
||||
btnApply.classList.add("hidden");
|
||||
noUpdates.classList.add("hidden");
|
||||
btnCheck.disabled = true;
|
||||
btnApply.classList.add('hidden');
|
||||
noUpdates.classList.add('hidden');
|
||||
|
||||
var elements = document.querySelectorAll("[style-update-url]");
|
||||
var toCheckCount = elements.length;
|
||||
var updatableCount = 0;
|
||||
Array.prototype.forEach.call(elements, function(element) {
|
||||
checkUpdate(element, function(success) {
|
||||
if (success) {
|
||||
++updatableCount;
|
||||
}
|
||||
if (--toCheckCount == 0) {
|
||||
btnCheck.disabled = false;
|
||||
if (updatableCount) {
|
||||
btnApply.classList.remove("hidden");
|
||||
} else {
|
||||
noUpdates.classList.remove("hidden");
|
||||
setTimeout(function() {
|
||||
noUpdates.classList.add("hidden");
|
||||
}, 10000);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// notify the automatic updater to reset the next automatic update accordingly
|
||||
chrome.runtime.sendMessage({
|
||||
method: 'resetInterval'
|
||||
});
|
||||
const elements = document.querySelectorAll('[style-update-url]');
|
||||
Promise.all([...elements].map(checkUpdate))
|
||||
.then(updatables => {
|
||||
btnCheck.disabled = false;
|
||||
if (updatables.includes(true)) {
|
||||
btnApply.classList.remove('hidden');
|
||||
} else {
|
||||
noUpdates.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
noUpdates.classList.add('hidden');
|
||||
}, 10e3);
|
||||
}
|
||||
});
|
||||
|
||||
// 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) {
|
||||
chrome.runtime.sendMessage({method: "getStyles", id: id}, function(styles) {
|
||||
var style = styles[0];
|
||||
var needsUpdate = false;
|
||||
if (!forceUpdate && styleSectionsEqual(style, serverJson)) {
|
||||
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 checkUpdate(element) {
|
||||
$('.update-note', element).innerHTML = t('checkingForUpdate');
|
||||
element.classList.remove('checking-update', 'no-update', 'can-update');
|
||||
element.classList.add('checking-update');
|
||||
return new Updater(element).run();
|
||||
}
|
||||
|
||||
function checkUpdateFullCode(url, forceUpdate, successCallback, failureCallback) {
|
||||
download(url, function(responseText) {
|
||||
successCallback(forceUpdate, JSON.parse(responseText));
|
||||
}, failureCallback);
|
||||
|
||||
class Updater {
|
||||
constructor(element) {
|
||||
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) {
|
||||
if (responseText.length != 32) {
|
||||
failureCallback(-1);
|
||||
return;
|
||||
}
|
||||
successCallback(responseText != originalMd5);
|
||||
}, failureCallback);
|
||||
|
||||
function searchStyles(immediately, bin) {
|
||||
const query = $('#search').value.toLocaleLowerCase();
|
||||
if (query == (searchStyles.lastQuery || '') && !bin) {
|
||||
return;
|
||||
}
|
||||
searchStyles.lastQuery = query;
|
||||
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();
|
||||
xhr.onreadystatechange = function (aEvt) {
|
||||
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 getClickedStyleId(event) {
|
||||
return (getClickedStyleElement(event) || {}).styleId;
|
||||
}
|
||||
|
||||
function handleNeedsUpdate(needsUpdate, id, serverJson) {
|
||||
var e = document.querySelector("[style-id='" + id + "']");
|
||||
e.className = e.className.replace("checking-update", "");
|
||||
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 getClickedStyleElement(event) {
|
||||
return event.target.closest('.entry');
|
||||
}
|
||||
|
||||
function doUpdate(event) {
|
||||
var element = getStyleElement(event);
|
||||
|
||||
var updatedCode = element.updatedCode;
|
||||
// update everything but name
|
||||
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 rememberScrollPosition() {
|
||||
history.replaceState({scrollY}, document.title);
|
||||
}
|
||||
|
||||
function searchStyles(immediately) {
|
||||
var query = document.getElementById("search").value.toLocaleLowerCase();
|
||||
if (query == (searchStyles.lastQuery || "")) {
|
||||
return;
|
||||
}
|
||||
searchStyles.lastQuery = query;
|
||||
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 $(selector, base = document) {
|
||||
if (selector.startsWith('#') && /^#[^,\s]+$/.test(selector)) {
|
||||
return document.getElementById(selector.slice(1));
|
||||
} else {
|
||||
return base.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
||||
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 OWN_ORIGIN = chrome.runtime.getURL('');
|
||||
|
||||
|
||||
function notifyAllTabs(request) {
|
||||
// list all tabs including chrome-extension:// which can be ours
|
||||
if (request.codeIsUpdated === false && request.style) {
|
||||
|
@ -24,6 +25,7 @@ function notifyAllTabs(request) {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
function refreshAllTabs() {
|
||||
return new Promise(resolve => {
|
||||
// list all tabs including chrome-extension:// which can be ours
|
||||
|
@ -47,6 +49,7 @@ function refreshAllTabs() {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
function updateIcon(tab, styles) {
|
||||
// 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)
|
||||
|
@ -62,7 +65,7 @@ function updateIcon(tab, styles) {
|
|||
});
|
||||
return;
|
||||
}
|
||||
getTabRealURL(tab, url => {
|
||||
getTabRealURL(tab).then(url => {
|
||||
// if we have access to this, call directly
|
||||
// (Chrome no longer sends messages to the page itself)
|
||||
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) {
|
||||
callback(tabs[0]);
|
||||
|
||||
function getActiveTab() {
|
||||
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) {
|
||||
if (tab.url != "chrome://newtab/") {
|
||||
callback(tab.url);
|
||||
} else {
|
||||
chrome.webNavigation.getFrame({tabId: tab.id, frameId: 0, processId: -1}, function(frame) {
|
||||
frame && callback(frame.url);
|
||||
function openURL({url}) {
|
||||
url = !url.includes('://') ? chrome.runtime.getURL(url) : url;
|
||||
return new Promise(resolve => {
|
||||
chrome.tabs.query({currentWindow: true, url}, tabs => {
|
||||
// switch to an existing tab with the requested 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) {
|
||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, "\\$&"), flags);
|
||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=*!|]/g, '\\$&'), flags);
|
||||
}
|
||||
|
||||
|
||||
// expands * as .*?
|
||||
function wildcardAsRegExp(s, flags) {
|
||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=!|]/g, "\\$&").replace(/\*/g, '.*?'), flags);
|
||||
return new RegExp(s.replace(/[{}()\[\]\/\\.+?^$:=!|]/g, '\\$&').replace(/\*/g, '.*?'), flags);
|
||||
}
|
||||
|
||||
|
||||
var configureCommands = {
|
||||
get url () {
|
||||
return navigator.userAgent.indexOf('OPR') > -1 ?
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<td i18n-text="optionsUpdateInterval"><sup>1</sup></td>
|
||||
|
|
|
@ -7,6 +7,7 @@ function restore () {
|
|||
document.getElementById('badgeNormal').value = bg.prefs.get('badgeNormal');
|
||||
document.getElementById('popupWidth').value = localStorage.getItem('popupWidth') || '246';
|
||||
document.getElementById('updateInterval').value = bg.prefs.get('updateInterval');
|
||||
enforceValueRange('popupWidth');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -14,7 +15,7 @@ function save () {
|
|||
chrome.runtime.getBackgroundPage(bg => {
|
||||
bg.prefs.set('badgeDisabled', document.getElementById('badgeDisabled').value);
|
||||
bg.prefs.set('badgeNormal', document.getElementById('badgeNormal').value);
|
||||
localStorage.setItem('popupWidth', document.getElementById('popupWidth').value);
|
||||
localStorage.setItem('popupWidth', enforceValueRange('popupWidth'));
|
||||
bg.prefs.set(
|
||||
'updateInterval',
|
||||
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.getElementById('save').addEventListener('click', save);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ input[type=checkbox] {
|
|||
}
|
||||
a, a:visited {
|
||||
color: black;
|
||||
text-decoration-skip: ink;
|
||||
}
|
||||
|
||||
.left-gutter {
|
||||
|
@ -112,6 +113,10 @@ body:not(.blocked) #unavailable {
|
|||
display: none;
|
||||
}
|
||||
|
||||
body.blocked #unavailable {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Never shown, but can be enabled with a style */
|
||||
.enable, .disable {
|
||||
display: none;
|
||||
|
@ -120,7 +125,7 @@ body:not(.blocked) #unavailable {
|
|||
/* 'New style' links */
|
||||
#write-style-for {margin-right: .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 */
|
||||
#match {overflow-wrap: break-word;}
|
||||
|
||||
|
@ -154,6 +159,7 @@ body:not(.blocked) #unavailable {
|
|||
.breadcrumbs > .write-style-link:focus ~ .write-style-link[subdomain] {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
text-decoration-skip: ink;
|
||||
}
|
||||
|
||||
/* action buttons */
|
||||
|
|
|
@ -29,11 +29,16 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template data-id="writeStyle">
|
||||
<a class="write-style-link"></a>
|
||||
</template>
|
||||
|
||||
<script src="localization.js"></script>
|
||||
<script src="health.js"></script>
|
||||
<script src="storage.js"></script>
|
||||
<script src="messaging.js"></script>
|
||||
<script src="apply.js"></script>
|
||||
<script src="popup.js"></script>
|
||||
</head>
|
||||
|
||||
<body id="stylus-popup">
|
||||
|
@ -75,11 +80,10 @@
|
|||
<!-- Actions -->
|
||||
<div id="popup-options">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
493
popup.js
493
popup.js
|
@ -1,275 +1,298 @@
|
|||
/* globals configureCommands */
|
||||
/* globals configureCommands, openURL */
|
||||
|
||||
var writeStyleTemplate = document.createElement("a");
|
||||
writeStyleTemplate.className = "write-style-link";
|
||||
const RX_SUPPORTED_URLS = new RegExp(
|
||||
`^(file|https?|ftps?):|^${OWN_ORIGIN}`);
|
||||
let installed;
|
||||
|
||||
var installed = document.getElementById("installed");
|
||||
|
||||
if (!prefs.get("popup.stylesFirst")) {
|
||||
document.body.insertBefore(document.querySelector("body > .actions"), installed);
|
||||
getActiveTabRealURL().then(url => {
|
||||
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) {
|
||||
var enabledFirst = prefs.get("popup.enabledFirst");
|
||||
styles.sort(function(a, b) {
|
||||
if (enabledFirst && a.enabled !== b.enabled) return !(a.enabled < b.enabled) ? -1 : 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
if (styles.length == 0) {
|
||||
installed.innerHTML = "<div class='entry' id='no-styles'>" + t('noStylesForSite') + "</div>";
|
||||
}
|
||||
styles.map(createStyleElement).forEach(function(e) {
|
||||
installed.appendChild(e);
|
||||
});
|
||||
// force Chrome to resize the popup
|
||||
document.body.style.height = '10px';
|
||||
document.documentElement.style.height = '10px';
|
||||
if (!styles.length) {
|
||||
installed.innerHTML =
|
||||
`<div class="entry" id="no-styles">${t('noStylesForSite')}</div>`;
|
||||
} else {
|
||||
const enabledFirst = prefs.get('popup.enabledFirst');
|
||||
styles.sort((a, b) =>
|
||||
enabledFirst && a.enabled !== b.enabled
|
||||
? !(a.enabled < b.enabled) ? -1 : 1
|
||||
: a.name.localeCompare(b.name));
|
||||
const fragment = document.createDocumentFragment();
|
||||
for (let style of styles) {
|
||||
fragment.appendChild(createStyleElement(style));
|
||||
}
|
||||
installed.appendChild(fragment);
|
||||
}
|
||||
// force Chrome to resize the popup
|
||||
document.body.style.height = '10px';
|
||||
document.documentElement.style.height = '10px';
|
||||
}
|
||||
|
||||
|
||||
function createStyleElement(style) {
|
||||
// reuse event function references
|
||||
createStyleElement.events = createStyleElement.events || {
|
||||
checkboxClick() {
|
||||
enableStyle(getClickedStyleId(event), this.checked);
|
||||
},
|
||||
styleNameClick(event) {
|
||||
this.checkbox.click();
|
||||
event.preventDefault();
|
||||
},
|
||||
toggleClick(event) {
|
||||
enableStyle(getClickedStyleId(event), this.matches('.enable'));
|
||||
},
|
||||
deleteClick() {
|
||||
doDelete(event);
|
||||
// reuse event listener function references
|
||||
const listeners = createStyleElement.listeners = createStyleElement.listeners || {
|
||||
checkboxClick() {
|
||||
enableStyle(getClickedStyleId(event), this.checked)
|
||||
.then(handleUpdate);
|
||||
},
|
||||
styleNameClick(event) {
|
||||
this.checkbox.click();
|
||||
event.preventDefault();
|
||||
},
|
||||
toggleClick(event) {
|
||||
enableStyle(getClickedStyleId(event), this.matches('.enable'))
|
||||
.then(handleUpdate);
|
||||
},
|
||||
deleteClick(event) {
|
||||
doDelete(event);
|
||||
}
|
||||
};
|
||||
const entry = template.style.cloneNode(true);
|
||||
entry.setAttribute('style-id', style.id);
|
||||
Object.assign(entry, {
|
||||
styleId: style.id,
|
||||
className: ['entry', style.enabled ? 'enabled' : 'disabled'].join(' '),
|
||||
onmousedown: openEditorOnMiddleclick,
|
||||
onauxclick: openEditorOnMiddleclick,
|
||||
});
|
||||
};
|
||||
const entry = template.style.cloneNode(true);
|
||||
entry.setAttribute('style-id', style.id);
|
||||
Object.assign(entry, {
|
||||
styleId: style.id,
|
||||
className: ['entry', style.enabled ? 'enabled' : 'disabled'].join(' '),
|
||||
onmousedown: openEditorOnMiddleclick,
|
||||
onauxclick: openEditorOnMiddleclick,
|
||||
});
|
||||
|
||||
const checkbox = entry.querySelector('.checker');
|
||||
Object.assign(checkbox, {
|
||||
id: 'style-' + style.id,
|
||||
checked: style.enabled,
|
||||
onclick: createStyleElement.events.checkboxClick,
|
||||
});
|
||||
const checkbox = $('.checker', entry);
|
||||
Object.assign(checkbox, {
|
||||
id: 'style-' + style.id,
|
||||
checked: style.enabled,
|
||||
onclick: listeners.checkboxClick,
|
||||
});
|
||||
|
||||
const editLink = entry.querySelector('.style-edit-link');
|
||||
Object.assign(editLink, {
|
||||
href: editLink.getAttribute('href') + style.id,
|
||||
onclick: openLinkInTabOrWindow,
|
||||
});
|
||||
const editLink = $('.style-edit-link', entry);
|
||||
Object.assign(editLink, {
|
||||
href: editLink.getAttribute('href') + style.id,
|
||||
onclick: openLinkInTabOrWindow,
|
||||
});
|
||||
|
||||
const styleName = entry.querySelector('.style-name');
|
||||
Object.assign(styleName, {
|
||||
htmlFor: 'style-' + style.id,
|
||||
onclick: createStyleElement.events.styleNameClick,
|
||||
});
|
||||
styleName.checkbox = checkbox;
|
||||
styleName.appendChild(document.createTextNode(style.name));
|
||||
const styleName = $('.style-name', entry);
|
||||
Object.assign(styleName, {
|
||||
htmlFor: 'style-' + style.id,
|
||||
onclick: listeners.styleNameClick,
|
||||
});
|
||||
styleName.checkbox = checkbox;
|
||||
styleName.appendChild(document.createTextNode(style.name));
|
||||
|
||||
entry.querySelector('.enable').onclick = createStyleElement.events.toggleClick;
|
||||
entry.querySelector('.disable').onclick = createStyleElement.events.toggleClick;
|
||||
entry.querySelector('.delete').onclick = createStyleElement.events.deleteClick;
|
||||
$('.enable', entry).onclick = listeners.toggleClick;
|
||||
$('.disable', entry).onclick = listeners.toggleClick;
|
||||
$('.delete', entry).onclick = listeners.deleteClick;
|
||||
|
||||
return entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
function doDelete(event) {
|
||||
document.getElementById('confirm').dataset.display = true;
|
||||
const id = getClickedStyleId(event);
|
||||
document.querySelector('#confirm b').textContent =
|
||||
document.querySelector(`[style-id="${id}"] label`).textContent;
|
||||
document.getElementById('confirm').dataset.id = id;
|
||||
$('#confirm').dataset.display = true;
|
||||
const id = getClickedStyleId(event);
|
||||
$('#confirm b').textContent =
|
||||
$(`[style-id="${id}"] label`).textContent;
|
||||
$('#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) {
|
||||
const entry = event.target.closest('.entry');
|
||||
return entry ? entry.styleId : null;
|
||||
const entry = event.target.closest('.entry');
|
||||
return entry ? entry.styleId : null;
|
||||
}
|
||||
|
||||
|
||||
function openLinkInTabOrWindow(event) {
|
||||
event.preventDefault();
|
||||
if (prefs.get("openEditInWindow", false)) {
|
||||
var options = {url: event.target.href}
|
||||
var wp = prefs.get("windowPosition", {});
|
||||
for (var k in wp) options[k] = wp[k];
|
||||
chrome.windows.create(options);
|
||||
} else {
|
||||
openLink(event);
|
||||
}
|
||||
close();
|
||||
if (!prefs.get('openEditInWindow', false)) {
|
||||
openURLandHide(event);
|
||||
return;
|
||||
}
|
||||
event.preventDefault();
|
||||
chrome.windows.create(
|
||||
Object.assign({
|
||||
url: event.target.href
|
||||
}, prefs.get('windowPosition', {}))
|
||||
);
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
function openEditorOnMiddleclick(event) {
|
||||
if (event.button != 1) {
|
||||
return;
|
||||
}
|
||||
// open an editor on middleclick
|
||||
if (event.target.matches('.entry, .style-name, .style-edit-link')) {
|
||||
this.querySelector('.style-edit-link').click();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
// prevent the popup being opened in a background tab
|
||||
// when an irrelevant link was accidentally clicked
|
||||
if (event.target.closest('a')) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (event.button != 1) {
|
||||
return;
|
||||
}
|
||||
// open an editor on middleclick
|
||||
if (event.target.matches('.entry, .style-name, .style-edit-link')) {
|
||||
$('.style-edit-link', this).click();
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
// prevent the popup being opened in a background tab
|
||||
// when an irrelevant link was accidentally clicked
|
||||
if (event.target.closest('a')) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function openLink(event) {
|
||||
event.preventDefault();
|
||||
chrome.runtime.sendMessage({method: "openURL", url: event.target.href});
|
||||
close();
|
||||
|
||||
function openURLandHide(event) {
|
||||
event.preventDefault();
|
||||
openURL({url: event.target.href})
|
||||
.then(close);
|
||||
}
|
||||
|
||||
|
||||
function handleUpdate(style) {
|
||||
var styleElement = installed.querySelector("[style-id='" + style.id + "']");
|
||||
if (styleElement) {
|
||||
installed.replaceChild(createStyleElement(style), styleElement);
|
||||
} else {
|
||||
getActiveTabRealURL(function(url) {
|
||||
if (chrome.extension.getBackgroundPage().getApplicableSections(style, url).length) {
|
||||
// a new style for the current url is installed
|
||||
document.getElementById("unavailable").style.display = "none";
|
||||
installed.appendChild(createStyleElement(style));
|
||||
}
|
||||
});
|
||||
}
|
||||
const styleElement = $(`[style-id="${style.id}"]`, installed);
|
||||
if (styleElement) {
|
||||
installed.replaceChild(createStyleElement(style), styleElement);
|
||||
} else {
|
||||
getActiveTabRealURL().then(url => {
|
||||
if (getApplicableSections(style, url).length) {
|
||||
// a new style for the current url is installed
|
||||
$('#unavailable').style.display = 'none';
|
||||
installed.appendChild(createStyleElement(style));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handleDelete(id) {
|
||||
var styleElement = installed.querySelector("[style-id='" + id + "']");
|
||||
if (styleElement) {
|
||||
installed.removeChild(styleElement);
|
||||
}
|
||||
var styleElement = $(`[style-id="${id}"]`, installed);
|
||||
if (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) {
|
||||
document.getElementById(id).addEventListener("click", openLink, false);
|
||||
});
|
||||
|
||||
document.getElementById("disableAll").addEventListener("change", function(event) {
|
||||
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';
|
||||
function $(selector, base = document) {
|
||||
if (selector.startsWith('#') && /^#[^,\s]+$/.test(selector)) {
|
||||
return document.getElementById(selector.slice(1));
|
||||
} else {
|
||||
return base.querySelector(selector);
|
||||
}
|
||||
}
|
||||
|
|
26
storage.js
26
storage.js
|
@ -277,23 +277,21 @@ function addMissingStyleTargets(style) {
|
|||
|
||||
|
||||
function enableStyle(id, enabled) {
|
||||
saveStyle({id, enabled})
|
||||
.then(handleUpdate);
|
||||
return saveStyle({id, enabled});
|
||||
}
|
||||
|
||||
|
||||
function deleteStyle(id, callback = function (){}) {
|
||||
getDatabase(function(db) {
|
||||
var tx = db.transaction(["styles"], "readwrite");
|
||||
var os = tx.objectStore("styles");
|
||||
var request = os.delete(Number(id));
|
||||
request.onsuccess = function(event) {
|
||||
handleDelete(id);
|
||||
invalidateCache(true, {deletedId: id});
|
||||
notifyAllTabs({method: "styleDeleted", id});
|
||||
callback();
|
||||
};
|
||||
});
|
||||
function deleteStyle(id) {
|
||||
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});
|
||||
notifyAllTabs({method: 'styleDeleted', id});
|
||||
resolve(id);
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user