add multi-column mode option
This commit is contained in:
		
							parent
							
								
									6979958908
								
							
						
					
					
						commit
						ef998e423e
					
				|  | @ -762,6 +762,9 @@ | |||
|     "message": "Number of applies-to items", | ||||
|     "description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page" | ||||
|   }, | ||||
|   "manageMinColumnWidth": { | ||||
|     "message": "Minimum column width (in pixels; 9999 disables multi-column mode)" | ||||
|   }, | ||||
|   "manageNewStyleAsUsercss": { | ||||
|     "message": "as Usercss", | ||||
|     "description": "VERY SHORT label for the checkbox next to the 'Write new style' button in the style manager" | ||||
|  |  | |||
|  | @ -37,7 +37,7 @@ async function InjectionOrder(show, el, selector) { | |||
|     parts.name.href = '/edit.html?id=' + style.id; | ||||
|     parts.name.textContent = style.name; | ||||
|     return Object.assign(entry.cloneNode(true), { | ||||
|       styleNameLowerCase: style.name.toLocaleLowerCase(), | ||||
|       styleNameLC: style.name.toLocaleLowerCase(), | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|  |  | |||
|  | @ -314,9 +314,7 @@ function setupLivePrefs(ids) { | |||
|   function getValue(el) { | ||||
|     const type = el.dataset.valueType || el.type; | ||||
|     return type === 'checkbox' ? el.checked : | ||||
|       // https://stackoverflow.com/questions/18062069/why-does-valueasnumber-return-nan-as-a-value
 | ||||
|       // valueAsNumber is not applicable for input[text/radio] or select
 | ||||
|       type === 'number' ? Number(el.value) : | ||||
|       type === 'number' ? parseFloat(el.value) : | ||||
|       el.value; | ||||
|   } | ||||
|   function isSame(el, oldValue, value) { | ||||
|  | @ -465,6 +463,7 @@ prefs.ready.then(() => { | |||
|         const max = (innerWidth < 850 ? screen.availWidth : innerWidth) / 3; | ||||
|         width = Math.round(Math.max(200, Math.min(max, Number(width) || 0))); | ||||
|         $.root.style.setProperty('--header-width', width + 'px'); | ||||
|         dom.HWval = width; | ||||
|         return width; | ||||
|       }, | ||||
|     }); | ||||
|  |  | |||
|  | @ -55,6 +55,7 @@ | |||
|     'manage.actions.expanded': true, | ||||
|     'manage.backup.expanded': true, | ||||
|     'manage.filters.expanded': true, | ||||
|     'manage.minColumnWidth': 750, | ||||
|     // the new compact layout doesn't look good on Android yet
 | ||||
|     'manage.newUI': true, | ||||
|     'manage.newUI.favicons': false, // show favicons for the sites in applies-to
 | ||||
|  |  | |||
|  | @ -18,6 +18,8 @@ | |||
|         <a target="_blank" class="homepage"></a> | ||||
|       </h2> | ||||
|       <p class="applies-to"> | ||||
|         <span class="style-info" data-type="age"></span>. | ||||
|         <label i18n="genericSize">:</label><span class="style-info" data-type="size"></span>. | ||||
|         <label i18n="appliesDisplay"></label> | ||||
|         <span class="targets"></span> | ||||
|       </p> | ||||
|  | @ -29,10 +31,8 @@ | |||
|         <button class="disable" i18n="disableStyleLabel"></button> | ||||
|         <button class="delete" i18n="deleteStyleLabel"></button> | ||||
|         <button class="check-update" i18n="checkForUpdate"></button> | ||||
|         <button class="update" i18n="installUpdate"></button> | ||||
|         <button class="update" i18n="installUpdate" hidden></button> | ||||
|         <button class="configure-usercss" i18n="configureStyle"></button> | ||||
|         <span class="style-info" data-type="size"></span> | ||||
|         <span class="style-info" data-type="age"></span> | ||||
|         <span class="update-note"></span> | ||||
|       </p> | ||||
|     </div> | ||||
|  | @ -49,9 +49,9 @@ | |||
|             | ||||
|           <span class="style-info" data-type="version"></span> | ||||
|         </a> | ||||
|         <a target="_blank" class="homepage" tabindex="0"></a> | ||||
|       </h2> | ||||
|       <p class="actions"> | ||||
|         <a target="_blank" class="homepage" tabindex="0"></a> | ||||
|         <a class="delete" i18n="title:deleteStyleLabel" tabindex="0"> | ||||
|           <svg class="svg-icon" viewBox="0 0 20 20"> | ||||
|             <polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5 | ||||
|  | @ -391,6 +391,7 @@ | |||
| </template> | ||||
| 
 | ||||
| <link rel="stylesheet" href="manage/manage.css"> | ||||
| <link rel="stylesheet" href="manage/manage-newui.css" id="newUI"> | ||||
| <script src="js/dark-themer.js"></script> <!-- must be last in HEAD to avoid FOUC --> | ||||
| </head> | ||||
| 
 | ||||
|  |  | |||
|  | @ -130,7 +130,9 @@ const Events = { | |||
|   }, | ||||
| 
 | ||||
|   name(event, entry) { | ||||
|     if (newUI.enabled) Events.edit(event, entry); | ||||
|     if (newUI.enabled && !event.target.closest('.homepage')) { | ||||
|       Events.edit(event, entry); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|   toggle(event, entry) { | ||||
|  | @ -193,7 +195,7 @@ function handleUpdate(style, {reason, method} = {}) { | |||
|   } | ||||
|   entry = entry || createStyleElement(styleToDummyEntry(style)); | ||||
|   if (oldEntry) { | ||||
|     if (oldEntry.styleNameLowerCase === entry.styleNameLowerCase) { | ||||
|     if (oldEntry.styleNameLC === entry.styleNameLC) { | ||||
|       installed.replaceChild(entry, oldEntry); | ||||
|     } else { | ||||
|       oldEntry.remove(); | ||||
|  |  | |||
|  | @ -57,7 +57,7 @@ | |||
|     let found; | ||||
|     for (const entry of rotated || entries) { | ||||
|       if (entry.classList.contains('hidden')) continue; | ||||
|       const name = entry.styleNameLowerCase; | ||||
|       const name = entry.styleNameLC; | ||||
|       const pos = name.indexOf(text); | ||||
|       if (pos === 0) { | ||||
|         found = entry; | ||||
|  | @ -73,7 +73,7 @@ | |||
|     if (found && found !== focusedEntry) { | ||||
|       focusedEntry = found; | ||||
|       focusedLink = $('a', found); | ||||
|       focusedName = found.styleNameLowerCase; | ||||
|       focusedName = found.styleNameLC; | ||||
|       scrollElementIntoView(found, {invalidMarginRatio: .25}); | ||||
|       animateElement(found, 'highlight-quick'); | ||||
|       replaceInlineStyle({ | ||||
|  |  | |||
							
								
								
									
										278
									
								
								manage/manage-newui.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								manage/manage-newui.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,278 @@ | |||
| .disabled.entry .svg-icon { | ||||
|   color: var(--c50); | ||||
|   fill: var(--c80); | ||||
|   font-weight: normal; | ||||
|   transition: color .5s .1s, fill .5s .1s; | ||||
| } | ||||
| #installed { | ||||
|   margin-top: .75rem; | ||||
|   margin-bottom: .75rem; | ||||
| } | ||||
| .entry { | ||||
|   padding: 0 .5em; | ||||
|   display: flex; | ||||
|   border: none; | ||||
| } | ||||
| .entry.odd { | ||||
|   background-color: rgba(128, 128, 128, 0.05); | ||||
| } | ||||
| .entry > * { | ||||
|   padding: .5rem 0; | ||||
|   margin: 0; | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
| .entry .actions { | ||||
|   position: relative; | ||||
| } | ||||
| .style-info[data-type=size], | ||||
| .style-info[data-type=age] { | ||||
|   color: var(--c50); | ||||
|   justify-content: end; | ||||
| } | ||||
| .style-info[data-type=age] { | ||||
|   flex: 0 0 4ch; | ||||
| } | ||||
| .style-info[data-type=size] { | ||||
|   flex: 0 0 var(--size-width); | ||||
| } | ||||
| .style-info[data-type=version] { | ||||
|   color: var(--c40); | ||||
|   padding-left: .5em; | ||||
|   font-weight: normal; | ||||
| } | ||||
| .style-info[data-type=version][data-is-date], | ||||
| .style-info[data-type=version][data-value=""], | ||||
| .style-info[data-type=version][data-value="1.0.0"] { | ||||
|   display: none; | ||||
| } | ||||
| .entry input[type="checkbox"]:not(.slider) { | ||||
|   pointer-events: all; | ||||
| } | ||||
| .style-name { | ||||
|   font-size: 14px; | ||||
|   padding-left: var(--name-padding-left); | ||||
|   position: relative; | ||||
|   cursor: pointer; | ||||
|   justify-content: space-between; | ||||
|   flex: 0 0 var(--name-width); | ||||
|   min-width: 25%; | ||||
|   max-width: 50%; | ||||
| } | ||||
| #installed[style*="--num-targets:0"] .style-name { | ||||
|   max-width: none; | ||||
|   flex-grow: 1; | ||||
| } | ||||
| .checkmate { | ||||
|   flex-shrink: 0; | ||||
| } | ||||
| .style-name-link { | ||||
|   width: 100%; | ||||
| } | ||||
| .entry .style-name:hover::before { | ||||
|   content: ""; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
|   background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent); | ||||
|   pointer-events: none; | ||||
| } | ||||
| .entry.enabled .style-name:hover .style-name-link { | ||||
|   color: var(--accent-1); | ||||
| } | ||||
| .homepage { | ||||
|   margin-top: -3px; | ||||
| } | ||||
| .actions { | ||||
|   flex: 0 0 calc(3 * (var(--action-size) + var(--action-margin))); | ||||
|   flex-wrap: nowrap; | ||||
|   z-index: 100; | ||||
| } | ||||
| .actions > * { | ||||
|   width: var(--action-size); | ||||
|   height: var(--action-size); | ||||
|   display: flex; | ||||
|   align-items: center; | ||||
| } | ||||
| .updater-icons > * { | ||||
|   transition: opacity 1s; | ||||
|   display: none; | ||||
| } | ||||
| .entry .svg-icon { | ||||
|   fill: var(--c60); | ||||
| } | ||||
| .entry:hover .svg-icon { | ||||
|   fill: var(--c40); | ||||
| } | ||||
| .entry .svg-icon.checked, | ||||
| .entry:hover .svg-icon.checked, | ||||
| .entry:hover .svg-icon:hover { | ||||
|   fill: var(--fg); | ||||
| } | ||||
| .checking-update .check-update { | ||||
|   opacity: 0; | ||||
|   display: inline-block; | ||||
|   pointer-events: none; | ||||
| } | ||||
| .can-update .update, | ||||
| .no-update:not(.update-problem):not(.update-done) .up-to-date, | ||||
| .no-update.update-problem .check-update, | ||||
| .update-done .updated { | ||||
|   display: inline-block; | ||||
| } | ||||
| .up-to-date svg, | ||||
| .updated svg { | ||||
|   cursor: auto; | ||||
| } | ||||
| .update-done .updated svg { | ||||
|   top: -4px; | ||||
|   position: relative; | ||||
|   filter: drop-shadow(0 5px 0 currentColor); | ||||
| } | ||||
| .can-update .update, | ||||
| .no-update.update-problem .check-update { | ||||
|   cursor: pointer; | ||||
| } | ||||
| .can-update[data-details$="locally edited"] .update svg, | ||||
| .update-problem .check-update svg { | ||||
|   fill: #ef6969; | ||||
| } | ||||
| .can-update[data-details$="locally edited"]:hover .update svg, | ||||
| .entry.update-problem:hover .check-update svg { | ||||
|   fill: #fd4040; | ||||
| } | ||||
| .can-update[data-details$="locally edited"]:hover .update svg:hover, | ||||
| .entry.update-problem:hover .check-update svg:hover { | ||||
|   fill: red; | ||||
| } | ||||
| .updater-icons > :not(.check-update):after { | ||||
|   content: attr(title); | ||||
|   position: absolute; | ||||
|   display: block; | ||||
|   width: max-content; | ||||
|   max-width: 25vw; | ||||
|   padding: 1ex 1.5ex; | ||||
|   border: 1px solid #ded597; | ||||
|   background-color: #fffbd6; | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 2px 3px 10px rgba(0,0,0,.25); | ||||
|   font-size: 90%; | ||||
|   animation: fadeout 10s; | ||||
|   animation-fill-mode: both; | ||||
| } | ||||
| .update-problem .check-update:after { | ||||
|   background-color: red; | ||||
|   border: 1px solid #d40000; | ||||
|   color: white; | ||||
|   animation: none; | ||||
| } | ||||
| .can-update .update:after { | ||||
|   animation: none; | ||||
| } | ||||
| .can-update:not([data-details$="locally edited"]) .update:after { | ||||
|   background-color: #c0fff0; | ||||
|   border: 1px solid #89cac9; | ||||
| } | ||||
| .applies-to { | ||||
|   padding: .25em 0 .25em 1em; | ||||
|   flex-grow: 999; | ||||
| } | ||||
| #installed[style*="--num-targets:0"] .applies-to { | ||||
|   display: none; | ||||
| } | ||||
| .targets { | ||||
|   overflow: hidden; | ||||
|   max-height: calc(var(--num-targets) * 18px); | ||||
|   width: 100%; | ||||
| } | ||||
| .applies-to.expanded .targets { | ||||
|   max-height: none; | ||||
| } | ||||
| .target { | ||||
|   display: block; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   padding-right: 1em; | ||||
|   line-height: 18px; | ||||
|   width: 0; | ||||
|   min-width: 100%; | ||||
|   box-sizing: border-box; | ||||
| } | ||||
| .applies-to:not(.has-more) .expander { | ||||
|   display: none; | ||||
| } | ||||
| .target:hover { | ||||
|   background-color: inherit; | ||||
| } | ||||
| .target img { | ||||
|   width: 16px; | ||||
|   height: 16px; | ||||
|   vertical-align: middle; | ||||
|   margin: -1px 4px 0 -20px; | ||||
|   transition: opacity .5s, filter .5s; | ||||
|   /* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */ | ||||
|   backface-visibility: hidden; | ||||
|   visibility: hidden; | ||||
| } | ||||
| .favicons-grayed .target img { | ||||
|   filter: grayscale(1); | ||||
|   opacity: .25; | ||||
| } | ||||
| .has-favicons .target { | ||||
|   padding-left: 20px; | ||||
| } | ||||
| .has-favicons .target img[src] { | ||||
|   visibility: visible; | ||||
| } | ||||
| .entry:hover .target img { | ||||
|   opacity: 1; | ||||
|   filter: none; | ||||
| } | ||||
| .target b::after { | ||||
|   content: '?'; | ||||
|   margin: -2px 4px 0 -20px; | ||||
|   display: inline-block; | ||||
|   vertical-align: baseline; | ||||
|   background: var(--c85); | ||||
|   width: 16px; | ||||
|   line-height: 16px; | ||||
|   text-align: center; | ||||
|   border-radius: 50%; | ||||
|   color: var(--bg); | ||||
| } | ||||
| @media (max-width: 850px) { | ||||
|   .entry { | ||||
|     padding: 0; | ||||
|   } | ||||
|   .entry .checkmate { | ||||
|     position: absolute; | ||||
|     left: 14px; | ||||
|     top: 0; | ||||
|     bottom: 0; | ||||
|     margin: auto; | ||||
|   } | ||||
|   .entry .style-name { | ||||
|     text-indent: unset; | ||||
|   } | ||||
|   .entry .actions { | ||||
|     width: 104px; | ||||
|     padding: .5rem 0 .5rem 6px; | ||||
|   } | ||||
|   .entry .applies-to { | ||||
|     padding: .25rem .5rem .25rem 0; | ||||
|   } | ||||
|   .entry .target { | ||||
|     max-width: 100%; | ||||
|     padding-right: 0; | ||||
|   } | ||||
|   .style-name-link::after { | ||||
|     text-indent: 0; | ||||
|     display: inline-block; | ||||
|   } | ||||
|   .entry > .style-info { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
|  | @ -1,7 +1,9 @@ | |||
| :root { | ||||
|   --name-padding-left: 20px; | ||||
|   --name-padding-right: 40px; | ||||
|   --actions-width: 75px; | ||||
|   --name-width: 30ch; | ||||
|   --size-width: 4ch; | ||||
|   --action-size: 20px; | ||||
|   --action-margin: 6px; | ||||
| } | ||||
| body { | ||||
|   /* Fill the entire viewport to enable json import via drag'n'drop */ | ||||
|  | @ -124,17 +126,21 @@ a:hover { | |||
| } | ||||
| 
 | ||||
| #installed { | ||||
|   position: relative; | ||||
|   padding-left: var(--header-width); | ||||
|   box-sizing: border-box; | ||||
|   width: 100%; | ||||
|   align-self: start; | ||||
|   display: flex; | ||||
|   flex-wrap: wrap; | ||||
| } | ||||
| 
 | ||||
| .entry { | ||||
|   margin: 0; | ||||
|   padding: 1.25em 2em; | ||||
|   border-top: 1px solid var(--c85); | ||||
|   box-sizing: border-box; | ||||
|   position: relative; | ||||
|   width: calc(100% / var(--columns, 1)); | ||||
| } | ||||
| 
 | ||||
| .entry:first-child { | ||||
|  | @ -158,15 +164,13 @@ a:hover { | |||
|   margin-top: .25em; | ||||
|   overflow-wrap: break-word; | ||||
| } | ||||
| 
 | ||||
| .style-name a, .style-edit-link { | ||||
|   text-decoration: none; | ||||
| } | ||||
| 
 | ||||
| .style-name span, | ||||
| .applies-to { | ||||
|   overflow-wrap: break-word; | ||||
|   overflow-wrap: anywhere; | ||||
| } | ||||
| 
 | ||||
| .applies-to, | ||||
| .actions { | ||||
|   padding-left: 15px; | ||||
|  | @ -181,11 +185,10 @@ a:hover { | |||
| 
 | ||||
| .actions > * { | ||||
|   margin-bottom: .25rem; | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .actions > *:not(:last-child) { | ||||
|   margin-right: .25rem; | ||||
|   margin-right: var(--action-margin); | ||||
| } | ||||
| 
 | ||||
| .applies-to label { | ||||
|  | @ -240,8 +243,7 @@ a:hover { | |||
| } | ||||
| 
 | ||||
| .disabled h2 .style-name-link, | ||||
| .disabled .applies-to, | ||||
| .newUI .disabled.entry .svg-icon { | ||||
| .disabled .applies-to { | ||||
|   color: var(--c50); | ||||
|   fill: var(--c80); | ||||
|   font-weight: normal; | ||||
|  | @ -319,56 +321,8 @@ a:hover { | |||
| 
 | ||||
| /* compact layout */ | ||||
| 
 | ||||
| .newUI #installed { | ||||
|   display: table; | ||||
|   margin-top: .75rem; | ||||
|   margin-bottom: .75rem; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry { | ||||
|   display: table-row; | ||||
|   padding-top: 0; | ||||
|   padding-bottom: 0; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry.odd { | ||||
|   background-color: rgba(128, 128, 128, 0.05); | ||||
| } | ||||
| 
 | ||||
| .newUI .entry > * { | ||||
|   padding: .5rem 0; | ||||
|   margin: 0; | ||||
|   display: table-cell; | ||||
|   vertical-align: middle; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry .actions { | ||||
|   position: relative; | ||||
| } | ||||
| 
 | ||||
| .style-info[data-type=version] { | ||||
|   color: var(--c40); | ||||
|   padding-left: .5em; | ||||
|   font-weight: normal; | ||||
| } | ||||
| .newUI .style-info[data-type=version][data-is-date], | ||||
| .newUI .style-info[data-type=version][data-value=""], | ||||
| .newUI .style-info[data-type=version][data-value="1.0.0"] { | ||||
|   display: none; | ||||
| } | ||||
| .newUI .entry .style-info[data-type=size], | ||||
| .newUI .entry .style-info[data-type=age] { | ||||
|   color: var(--c50); | ||||
|   text-align: right; | ||||
|   padding-right: 1em; | ||||
| } | ||||
| 
 | ||||
| /************ checkbox & select************/ | ||||
| 
 | ||||
| #newUIoptions > div, #newUIoptions > label { | ||||
|   margin: 4px 0; | ||||
| } | ||||
| 
 | ||||
| .filter-selection { | ||||
|   position: relative; | ||||
|   left: -9px; | ||||
|  | @ -393,10 +347,6 @@ a:hover { | |||
|   margin-top: -2px; | ||||
| } | ||||
| 
 | ||||
| .newUI #newUIoptions > label { | ||||
|   padding-left: 0; | ||||
| } | ||||
| 
 | ||||
| .filter-selection select { | ||||
|   height: 18px; | ||||
|   border: none; | ||||
|  | @ -439,7 +389,7 @@ a:hover { | |||
| 
 | ||||
| .entry .checkmate { | ||||
|   vertical-align: middle; | ||||
|   margin: -2px 1ex 0 0; | ||||
|   margin-right: 1ch; | ||||
| } | ||||
| 
 | ||||
| #manage-text { | ||||
|  | @ -456,179 +406,10 @@ a:hover { | |||
|   margin: 0 .5em; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry input[type="checkbox"]:not(.slider) { | ||||
|   pointer-events: all; | ||||
| } | ||||
| 
 | ||||
| .newUI .style-name { | ||||
|   font-size: 14px; | ||||
|   padding-left: var(--name-padding-left); | ||||
|   padding-right: var(--name-padding-right); | ||||
|   position: relative; | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry .style-name:hover::before { | ||||
|   content: ""; | ||||
|   position: absolute; | ||||
|   top: 0; | ||||
|   left: 0; | ||||
|   right: 0; | ||||
|   bottom: 0; | ||||
|   background: linear-gradient(to right, hsla(180, 50%, 30%, 0.2), hsla(180, 20%, 10%, 0.05) 50%, transparent); | ||||
|   pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry.enabled .style-name:hover .style-name-link { | ||||
|   color: var(--accent-1); | ||||
| } | ||||
| 
 | ||||
| .newUI .style-name:after { | ||||
|   text-indent: 1.2rem; | ||||
| } | ||||
| 
 | ||||
| .newUI .actions:after { | ||||
|   text-indent: -25px; | ||||
| } | ||||
| 
 | ||||
| .newUI .actions .homepage[href=""] { | ||||
|   display: inline-block; | ||||
|   visibility: hidden; | ||||
|   height: 0; | ||||
| } | ||||
| 
 | ||||
| .newUI .actions { | ||||
|   width: var(--actions-width); | ||||
|   height: 20px; | ||||
|   white-space: nowrap; | ||||
| } | ||||
| 
 | ||||
| .newUI .actions > * { | ||||
|   margin: 0 6px 0 0; | ||||
|   width: 20px; | ||||
|   height: 20px; | ||||
| } | ||||
| 
 | ||||
| .newUI .updater-icons > * { | ||||
|   transition: opacity 1s; | ||||
|   display: none; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry .svg-icon { | ||||
|   fill: var(--c60); | ||||
| } | ||||
| 
 | ||||
| .newUI .entry:hover .svg-icon { | ||||
|   fill: var(--c40); | ||||
| } | ||||
| 
 | ||||
| button .svg-icon, | ||||
| .newUI .entry .svg-icon.checked, | ||||
| .newUI .entry:hover .svg-icon.checked, | ||||
| .newUI .entry:hover .svg-icon:hover { | ||||
| button .svg-icon { | ||||
|   fill: var(--fg); | ||||
| } | ||||
| 
 | ||||
| .newUI .checking-update .check-update { | ||||
|   opacity: 0; | ||||
|   display: inline-block; | ||||
|   pointer-events: none; | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update .update, | ||||
| .newUI .no-update:not(.update-problem):not(.update-done) .up-to-date, | ||||
| .newUI .no-update.update-problem .check-update, | ||||
| .newUI .update-done .updated { | ||||
|   display: inline-block; | ||||
| } | ||||
| 
 | ||||
| .newUI .up-to-date svg, | ||||
| .newUI .updated svg { | ||||
|   cursor: auto; | ||||
| } | ||||
| 
 | ||||
| .newUI .update-done .updated svg { | ||||
|   top: -4px; | ||||
|   position: relative; | ||||
|   filter: drop-shadow(0 5px 0 currentColor); | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update .update, | ||||
| .newUI .no-update.update-problem .check-update { | ||||
|   cursor: pointer; | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update[data-details$="locally edited"] .update svg, | ||||
| .newUI .update-problem .check-update svg { | ||||
|   fill: #ef6969; | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update[data-details$="locally edited"]:hover .update svg, | ||||
| .newUI .entry.update-problem:hover .check-update svg { | ||||
|   fill: #fd4040; | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update[data-details$="locally edited"]:hover .update svg:hover, | ||||
| .newUI .entry.update-problem:hover .check-update svg:hover { | ||||
|   fill: red; | ||||
| } | ||||
| 
 | ||||
| .newUI .actions { | ||||
|   z-index: 100; | ||||
| } | ||||
| 
 | ||||
| .newUI .updater-icons > :not(.check-update):after { | ||||
|   content: attr(title); | ||||
|   position: absolute; | ||||
|   margin-top: 18px; | ||||
|   margin-left: -36px; | ||||
|   padding: 1ex 1.5ex; | ||||
|   border: 1px solid #ded597; | ||||
|   background-color: #fffbd6; | ||||
|   border-radius: 4px; | ||||
|   box-shadow: 2px 3px 10px rgba(0,0,0,.25); | ||||
|   font-size: 90%; | ||||
|   animation: fadeout 10s; | ||||
|   animation-fill-mode: both; | ||||
| } | ||||
| 
 | ||||
| .newUI .update-problem .check-update:after { | ||||
|   background-color: red; | ||||
|   border: 1px solid #d40000; | ||||
|   color: white; | ||||
|   animation: none; | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update .update:after { | ||||
|   animation: none; | ||||
| } | ||||
| 
 | ||||
| .newUI .can-update:not([data-details$="locally edited"]) .update:after { | ||||
|   background-color: #c0fff0; | ||||
|   border: 1px solid #89cac9; | ||||
| } | ||||
| 
 | ||||
| .newUI .applies-to { | ||||
|   padding-top: .25rem; | ||||
|   padding-bottom: .25rem; | ||||
| } | ||||
| .newUI .targets { | ||||
|   overflow: hidden; | ||||
|   max-height: calc(var(--num-targets) * 18px); | ||||
| } | ||||
| .newUI .applies-to.expanded .targets { | ||||
|   max-height: none; | ||||
| } | ||||
| .newUI .target { | ||||
|   display: block; | ||||
|   overflow: hidden; | ||||
|   text-overflow: ellipsis; | ||||
|   white-space: nowrap; | ||||
|   max-width: calc(75vw - var(--header-width) - var(--actions-width) - var(--name-padding-left) - var(--name-padding-right) - 6rem); | ||||
|   box-sizing: border-box; | ||||
|   padding-right: 1rem; | ||||
|   line-height: 18px; | ||||
| } | ||||
| .expander { | ||||
|   cursor: pointer; | ||||
|   position: absolute; | ||||
|  | @ -643,54 +424,6 @@ button .svg-icon, | |||
|   transform: rotate(180deg); | ||||
|   transform-origin: 8px 8px; | ||||
| } | ||||
| .newUI .applies-to:not(.has-more) .expander { | ||||
|   display: none; | ||||
| } | ||||
| .newUI .target:hover { | ||||
|   background-color: inherit; | ||||
| } | ||||
| 
 | ||||
| .newUI .target img { | ||||
|   width: 16px; | ||||
|   height: 16px; | ||||
|   vertical-align: middle; | ||||
|   margin: -1px 4px 0 -20px; | ||||
|   transition: opacity .5s, filter .5s; | ||||
|   /* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */ | ||||
|   backface-visibility: hidden; | ||||
|   visibility: hidden; | ||||
| } | ||||
| 
 | ||||
| .newUI .favicons-grayed .target img { | ||||
|   filter: grayscale(1); | ||||
|   opacity: .25; | ||||
| } | ||||
| 
 | ||||
| .newUI .has-favicons .target { | ||||
|   padding-left: 20px; | ||||
| } | ||||
| 
 | ||||
| .newUI .has-favicons .target img[src] { | ||||
|   visibility: visible; | ||||
| } | ||||
| 
 | ||||
| .newUI .entry:hover .target img { | ||||
|   opacity: 1; | ||||
|   filter: none; | ||||
| } | ||||
| 
 | ||||
| .newUI .target b::after { | ||||
|   content: '?'; | ||||
|   margin: -2px 4px 0 -20px; | ||||
|   display: inline-block; | ||||
|   vertical-align: baseline; | ||||
|   background: var(--c85); | ||||
|   width: 16px; | ||||
|   line-height: 16px; | ||||
|   text-align: center; | ||||
|   border-radius: 50%; | ||||
|   color: var(--bg); | ||||
| } | ||||
| 
 | ||||
| /* Default, no update buttons */ | ||||
| .updater-icons .update, | ||||
|  | @ -1032,12 +765,6 @@ button .svg-icon, | |||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 1000px) { | ||||
|   .newUI .entry > .style-info { | ||||
|     display: none; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 850px) { | ||||
|   :root { | ||||
|     --name-padding-left: 34px; | ||||
|  | @ -1058,7 +785,7 @@ button .svg-icon, | |||
|     left: 3.75rem; | ||||
|   } | ||||
| 
 | ||||
|   html:not(.newUI) .applies-to { | ||||
|   .oldUI .applies-to { | ||||
|     word-break: break-all; | ||||
|   } | ||||
| 
 | ||||
|  | @ -1066,10 +793,6 @@ button .svg-icon, | |||
|     table-layout: fixed; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry .actions { | ||||
|     padding-right: 30px | ||||
|   } | ||||
| 
 | ||||
|   #search-wrapper, | ||||
|   #sort-wrapper, | ||||
|   #header summary { | ||||
|  | @ -1127,41 +850,6 @@ button .svg-icon, | |||
|     margin-top: 0; | ||||
|     padding-bottom: .25rem; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry { | ||||
|     padding: 0; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry .checkmate { | ||||
|     position: absolute; | ||||
|     left: 14px; | ||||
|     top: 0; | ||||
|     bottom: 0; | ||||
|     margin: auto; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry .style-name { | ||||
|     text-indent: unset; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry .actions { | ||||
|     width: 104px; | ||||
|     padding: .5rem 0 .5rem 6px; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry .applies-to { | ||||
|     padding: .25rem .5rem .25rem 0; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .entry .target { | ||||
|     max-width: 100%; | ||||
|     padding-right: 0; | ||||
|   } | ||||
| 
 | ||||
|   .newUI .style-name-link::after { | ||||
|     text-indent: 0; | ||||
|     display: inline-block; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| @supports (-moz-appearance: none) { | ||||
|  |  | |||
|  | @ -28,9 +28,16 @@ const newUI = { | |||
| Object.assign(newUI, { | ||||
|   ids: Object.keys(newUI), | ||||
|   prefKeyForId: id => `manage.newUI.${id}`.replace(/\.enabled$/, ''), | ||||
|   readPrefs(dest = newUI, cb) { | ||||
|     for (const id of newUI.ids) { | ||||
|       const val = dest[id] = prefs.get(newUI.prefKeyForId(id)); | ||||
|       if (cb) cb(id, val); | ||||
|     } | ||||
|   }, | ||||
|   renderClass: () => { | ||||
|     $.rootCL.toggle('newUI', newUI.enabled); | ||||
|     $.rootCL.toggle('oldUI', !newUI.enabled); | ||||
|     $('#newUI').media = newUI.enabled ? '' : '?'; | ||||
|   }, | ||||
|   hasFavs: () => newUI.enabled && newUI.favicons, | ||||
|   badFavsKey: 'badFavs', | ||||
|  | @ -41,9 +48,7 @@ Object.assign(newUI, { | |||
|   }, | ||||
| }); | ||||
| // ...read the actual values
 | ||||
| for (const id of newUI.ids) { | ||||
|   newUI[id] = prefs.get(newUI.prefKeyForId(id)); | ||||
| } | ||||
| newUI.readPrefs(); | ||||
| newUI.renderClass(); | ||||
| 
 | ||||
| (async function init() { | ||||
|  |  | |||
|  | @ -3,7 +3,6 @@ | |||
| /* global URLS debounce getOwnTab isEmptyObj sessionStore stringAsRegExp */// toolbox.js
 | ||||
| /* global filterAndAppend */// filters.js
 | ||||
| /* global installed newUI */// manage.js
 | ||||
| /* global prefs */ | ||||
| /* global sorter */ | ||||
| /* global t */// localization.js
 | ||||
| 'use strict'; | ||||
|  | @ -18,6 +17,7 @@ const AGES = [ | |||
|   [Infinity, 'y', t('dateAbbrYear', '\x01')], | ||||
| ]; | ||||
| const groupThousands = num => `${num}`.replace(/\d(?=(\d{3})+$)/g, '$&\xA0'); | ||||
| const renderSize = size => groupThousands(Math.round(size / 1024)) + 'k'; | ||||
| 
 | ||||
| (() => { | ||||
|   const proto = HTMLImageElement.prototype; | ||||
|  | @ -79,7 +79,7 @@ function calcObjSize(obj) { | |||
|         Object.entries(obj).reduce((sum, [k, v]) => sum + k.length + calcObjSize(v), 0); | ||||
| } | ||||
| 
 | ||||
| function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, styleSize: size}) { | ||||
| function createStyleElement({styleMeta: style, styleNameLC: nameLC, styleSize: size}) { | ||||
|   // query the sub-elements just once, then reuse the references
 | ||||
|   if ((elementParts || {}).newUI !== newUI.enabled) { | ||||
|     const entry = t.template[newUI.enabled ? 'styleNewUI' : 'style'].cloneNode(true); | ||||
|  | @ -105,7 +105,6 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style | |||
|       }, | ||||
|       oldConfigure: !newUI.enabled && $('.configure-usercss', entry), | ||||
|       oldCheckUpdate: !newUI.enabled && $('.check-update', entry), | ||||
|       oldUpdate: !newUI.enabled && $('.update', entry), | ||||
|     }; | ||||
|   } | ||||
|   const parts = elementParts; | ||||
|  | @ -126,12 +125,11 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style | |||
|   } | ||||
|   createAgeText(parts.infoAge, style); | ||||
|   parts.infoSize.dataset.value = Math.log10(size || 1) >> 0; // for CSS to target big/small styles
 | ||||
|   parts.infoSize.textContent = groupThousands(Math.round(size / 1024)) + 'k'; | ||||
|   parts.infoSize.textContent = renderSize(size); | ||||
|   parts.infoSize.title = `${t('genericSize')}: ${groupThousands(size)} B`; | ||||
|   if (!newUI.enabled) { | ||||
|     parts.oldConfigure.classList.toggle('hidden', !configurable); | ||||
|     parts.oldCheckUpdate.classList.toggle('hidden', !style.updateUrl); | ||||
|     parts.oldUpdate.classList.toggle('hidden', !style.updateUrl); | ||||
|   } | ||||
| 
 | ||||
|   // clear the code to free up some memory
 | ||||
|  | @ -142,7 +140,7 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style | |||
|   const entry = parts.entry.cloneNode(true); | ||||
|   entry.id = ENTRY_ID_PREFIX_RAW + style.id; | ||||
|   entry.styleId = style.id; | ||||
|   entry.styleNameLowerCase = nameLC; | ||||
|   entry.styleNameLC = nameLC; | ||||
|   entry.styleMeta = style; | ||||
|   entry.styleSize = size; | ||||
|   entry.className = parts.entryClassBase + ' ' + | ||||
|  | @ -166,6 +164,12 @@ function createStyleElement({styleMeta: style, styleNameLowerCase: nameLC, style | |||
| } | ||||
| 
 | ||||
| function createTargetsElement({entry, expanded, style = entry.styleMeta}) { | ||||
|   const maxTargets = expanded ? 1000 : newUI.enabled ? newUI.targets : 10; | ||||
|   if (!maxTargets) { | ||||
|     entry._numTargets = 0; | ||||
|     return; | ||||
|   } | ||||
|   const displayed = new Set(); | ||||
|   const entryTargets = $('.targets', entry); | ||||
|   const expanderCls = $('.applies-to', entry).classList; | ||||
|   const targets = elementParts.targets.cloneNode(true); | ||||
|  | @ -173,8 +177,6 @@ function createTargetsElement({entry, expanded, style = entry.styleMeta}) { | |||
|   let el = entryTargets.firstElementChild; | ||||
|   let numTargets = 0; | ||||
|   let allTargetsRendered = true; | ||||
|   const maxTargets = expanded ? 1000 : newUI.enabled ? newUI.targets : 10; | ||||
|   const displayed = new Set(); | ||||
|   for (const type of TARGET_TYPES) { | ||||
|     for (const section of style.sections) { | ||||
|       for (const targetValue of section[type] || []) { | ||||
|  | @ -352,26 +354,43 @@ function padLeft(val, width) { | |||
|   return ' '.repeat(Math.max(0, width - val.length)) + val; | ||||
| } | ||||
| 
 | ||||
| function fitNameColumn(styles) { | ||||
|   const align = 1e9; // required by sort()
 | ||||
|   const lengths = styles.map(s => align + | ||||
|     (s = s.displayName || s.name || '').length + | ||||
|     s.replace(/[^\u3000-\uFE00]+/g, '').length).sort(); // CJK glyphs are twice as wide
 | ||||
|   const len = styles.length; | ||||
|   const fringe = len * 5 / 100 | 0; // ignoring 5% of outliers at each end
 | ||||
|   let avgName = 0; | ||||
|   for (let i = fringe; i < len - fringe; i++) { | ||||
|     avgName = Math.max(avgName, lengths[i] - align); | ||||
|   } | ||||
|   $.root.style.setProperty('--name-width', avgName + 'ch'); | ||||
| } | ||||
| 
 | ||||
| function fitSizeColumn(entries) { | ||||
|   const max = entries.reduce((res, e) => Math.max(res, e.styleSize), 0); | ||||
|   $.root.style.setProperty('--size-width', renderSize(max).length + 'ch'); | ||||
| } | ||||
| 
 | ||||
| function showStyles(styles = [], matchUrlIds) { | ||||
|   const sorted = sorter.sort(styles.map(styleToDummyEntry)); | ||||
|   const dummies = styles.map(styleToDummyEntry); | ||||
|   const sorted = sorter.sort(dummies); | ||||
|   let index = 0; | ||||
|   let firstRun = true; | ||||
|   installed.dataset.total = styles.length; | ||||
|   const scrollY = (history.state || {}).scrollY; | ||||
|   const shouldRenderAll = scrollY > window.innerHeight || sessionStore.justEditedStyleId; | ||||
|   const renderBin = document.createDocumentFragment(); | ||||
|   if (scrollY) { | ||||
|   fitNameColumn(styles); | ||||
|   fitSizeColumn(dummies); | ||||
|   renderStyles(); | ||||
|   } else { | ||||
|     requestAnimationFrame(renderStyles); | ||||
|   } | ||||
| 
 | ||||
|   function renderStyles() { | ||||
|     const t0 = performance.now(); | ||||
|     while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 20)) { | ||||
|       const info = sorted[index++]; | ||||
|       const entry = createStyleElement(info); | ||||
|       if (matchUrlIds && !matchUrlIds.includes(info.style.id)) { | ||||
|     while (index < sorted.length && (shouldRenderAll || performance.now() - t0 < 50)) { | ||||
|       const entry = createStyleElement(sorted[index++]); | ||||
|       if (matchUrlIds && !matchUrlIds.includes(entry.styleMeta.id)) { | ||||
|         entry.classList.add('not-matching'); | ||||
|       } | ||||
|       renderBin.appendChild(entry); | ||||
|  | @ -398,7 +417,7 @@ function styleToDummyEntry(style) { | |||
|     styleMeta: style, | ||||
|     styleSize: calcObjSize(style), | ||||
|     // sort case-insensitively the whole list then sort dupes like `Foo` and `foo` case-sensitively
 | ||||
|     styleNameLowerCase: name.toLocaleLowerCase() + '\n' + name, | ||||
|     styleNameLC: name.toLocaleLowerCase() + '\n' + name, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
|  | @ -407,13 +426,11 @@ function switchUI({styleOnly} = {}) { | |||
|   const current = {}; | ||||
|   const changed = {}; | ||||
|   let someChanged = false; | ||||
|   for (const id of newUI.ids) { | ||||
|     const value = prefs.get(newUI.prefKeyForId(id)); | ||||
|   newUI.readPrefs(current, (id, value) => { | ||||
|     const valueChanged = value !== newUI[id] && (id === 'enabled' || current.enabled); | ||||
|     current[id] = value; | ||||
|     changed[id] = valueChanged; | ||||
|     someChanged |= valueChanged; | ||||
|   } | ||||
|   }); | ||||
| 
 | ||||
|   if (!styleOnly && !someChanged) { | ||||
|     return; | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| /* global $ $create messageBoxProxy */// dom.js
 | ||||
| /* global $ $create dom messageBoxProxy */// dom.js
 | ||||
| /* global installed */// manage.js
 | ||||
| /* global prefs */ | ||||
| /* global t */// localization.js
 | ||||
|  | @ -6,6 +6,10 @@ | |||
| 
 | ||||
| const sorter = (() => { | ||||
| 
 | ||||
|   const COL_MIN = 300; // same as options.html
 | ||||
|   const COL_MAX = 9999; // same as options.html
 | ||||
|   const COL_PROP = '--columns'; | ||||
| 
 | ||||
|   const sorterType = { | ||||
|     alpha: (a, b) => a < b ? -1 : a === b ? 0 : 1, | ||||
|     number: (a, b) => (a || 0) - (b || 0), | ||||
|  | @ -14,7 +18,7 @@ const sorter = (() => { | |||
|   const tagData = { | ||||
|     title: { | ||||
|       text: t('genericTitle'), | ||||
|       parse: v => v.styleNameLowerCase, | ||||
|       parse: v => v.styleNameLC, | ||||
|       sorter: sorterType.alpha, | ||||
|     }, | ||||
|     usercss: { | ||||
|  | @ -71,12 +75,13 @@ const sorter = (() => { | |||
|   const getPref = () => prefs.get(ID) || prefs.defaults[ID]; | ||||
| 
 | ||||
|   let columns = 1; | ||||
|   let minWidth; | ||||
| 
 | ||||
|   function init() { | ||||
|     prefs.subscribe(ID, sorter.update); | ||||
|     $('#sorter-help').onclick = showHelp; | ||||
|     addOptions(); | ||||
|     updateColumnCount(); | ||||
|     prefs.subscribe('manage.minColumnWidth', updateColumnWidth, {runNow: true}); | ||||
|   } | ||||
| 
 | ||||
|   function addOptions() { | ||||
|  | @ -172,21 +177,40 @@ const sorter = (() => { | |||
|   }; | ||||
| 
 | ||||
|   function updateColumnCount() { | ||||
|     let newValue = 1; | ||||
|     for (let el = $.root.lastElementChild; | ||||
|          el.localName === 'style'; | ||||
|          el = el.previousElementSibling) { | ||||
|       if (el.textContent.includes('--columns:')) { | ||||
|         newValue = Math.max(1, getComputedStyle($.root).getPropertyValue('--columns') | 0); | ||||
|         break; | ||||
|       } | ||||
|     } | ||||
|     if (columns !== newValue) { | ||||
|       columns = newValue; | ||||
|     const useStyle = [].some.call($.root.children, | ||||
|       el => el.tagName === 'STYLE' && el.textContent.includes(COL_PROP + ':')); | ||||
|     const v = useStyle ? Math.max(1, getComputedStyle($.root).getPropertyValue(COL_PROP) >> 0) | ||||
|       : minWidth ? onResize() | ||||
|         : columns; | ||||
|     if (columns !== v) { | ||||
|       columns = v; | ||||
|       return true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function updateColumnWidth(_, val) { | ||||
|     minWidth = Math.max(val, COL_MIN); | ||||
|     if (val < COL_MAX) { | ||||
|       window.on('resize', onResize); | ||||
|     } else { | ||||
|       window.off('resize', onResize); | ||||
|       $.root.style.removeProperty(COL_PROP); | ||||
|     } | ||||
|     sorter.updateStripes({onlyWhenColumnsChanged: true}); | ||||
|   } | ||||
| 
 | ||||
|   function onResize(evt) { | ||||
|     const c = Math.max(1, (window.innerWidth - dom.HWval) / minWidth >> 0); | ||||
|     if (columns !== c) { | ||||
|       $.root.style.setProperty(COL_PROP, c); | ||||
|       if (evt) { | ||||
|         columns = c; | ||||
|         sorter.updateStripes(); | ||||
|       } | ||||
|     } | ||||
|     return c; | ||||
|   } | ||||
| 
 | ||||
|   async function showHelp(event) { | ||||
|     event.preventDefault(); | ||||
|     messageBoxProxy.show({ | ||||
|  |  | |||
|  | @ -240,6 +240,10 @@ | |||
|       <div class="block"> | ||||
|         <h1 i18n="openManage"></h1> | ||||
|         <div class="items"> | ||||
|           <label> | ||||
|             <span i18n="manageMinColumnWidth"></span> | ||||
|             <input id="manage.minColumnWidth" type="number" min="300" max="9999"> | ||||
|           </label> | ||||
|           <label> | ||||
|             <span i18n="manageNewUI"></span> | ||||
|             <span class="onoffswitch"> | ||||
|  | @ -268,7 +272,7 @@ | |||
|           </label> | ||||
|           <label> | ||||
|             <span i18n="manageMaxTargets"></span> | ||||
|             <input id="manage.newUI.targets" type="number" min="1" max="99"> | ||||
|             <input id="manage.newUI.targets" type="number" min="0" max="99"> | ||||
|           </label> | ||||
|         </div> | ||||
|       </div> | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	Block a user