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 */ | ||||
|          | ||||
|         .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> | ||||
|   <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"> | ||||
|   <title i18n-text="manageTitle"></title> | ||||
|   <link href="manage.css" rel="stylesheet"> | ||||
| 
 | ||||
|     <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); | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										10
									
								
								popup.css
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								popup.css
									
									
									
									
									
								
							|  | @ -25,6 +25,7 @@ input[type=checkbox] { | |||
| } | ||||
| a, a:visited { | ||||
|   color: black; | ||||
|   text-decoration-skip: ink; | ||||
| } | ||||
| 
 | ||||
| .left-gutter { | ||||
|  | @ -59,7 +60,7 @@ body.blocked > DIV { | |||
| } | ||||
| #installed { | ||||
|   padding-top: 2px; | ||||
|   max-height: 434px;  | ||||
|   max-height: 434px; | ||||
|   overflow-y: auto; | ||||
| } | ||||
| #installed.disabled .style-name { | ||||
|  | @ -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