Add manager sort block
This commit is contained in:
parent
8c96165fb4
commit
eaa5725c8f
|
@ -49,6 +49,7 @@ globals:
|
||||||
tNodeList: false
|
tNodeList: false
|
||||||
tDocLoader: false
|
tDocLoader: false
|
||||||
tWordBreak: false
|
tWordBreak: false
|
||||||
|
formatDate: false
|
||||||
# dom.js
|
# dom.js
|
||||||
onDOMready: false
|
onDOMready: false
|
||||||
onDOMscriptReady: false
|
onDOMscriptReady: false
|
||||||
|
|
|
@ -214,6 +214,10 @@
|
||||||
"message": "Disabled",
|
"message": "Disabled",
|
||||||
"description": "Used in various lists/options to indicate that something is disabled"
|
"description": "Used in various lists/options to indicate that something is disabled"
|
||||||
},
|
},
|
||||||
|
"genericEnabledLabel": {
|
||||||
|
"message": "Enabled",
|
||||||
|
"description": "Used in various lists/options to indicate that something is enabled"
|
||||||
|
},
|
||||||
"genericHistoryLabel": {
|
"genericHistoryLabel": {
|
||||||
"message": "History",
|
"message": "History",
|
||||||
"description": "Used in various places to show a history log of something"
|
"description": "Used in various places to show a history log of something"
|
||||||
|
@ -234,6 +238,14 @@
|
||||||
"message": "Saved",
|
"message": "Saved",
|
||||||
"description": "Used in various parts of the UI to indicate that something was saved"
|
"description": "Used in various parts of the UI to indicate that something was saved"
|
||||||
},
|
},
|
||||||
|
"genericTitle": {
|
||||||
|
"message": "Title",
|
||||||
|
"description": "Used in various parts of the UI to indicate the title of something"
|
||||||
|
},
|
||||||
|
"genericUnknown": {
|
||||||
|
"message": "Unknown",
|
||||||
|
"description": "Used in various parts of the UI to indicate if something is unknown (e.g. an unknown date)"
|
||||||
|
},
|
||||||
"confirmNo": {
|
"confirmNo": {
|
||||||
"message": "No",
|
"message": "No",
|
||||||
"description": "'No' button in a confirm dialog"
|
"description": "'No' button in a confirm dialog"
|
||||||
|
@ -262,6 +274,14 @@
|
||||||
"message": "Close",
|
"message": "Close",
|
||||||
"description": "'Close' button in a confirm dialog"
|
"description": "'Close' button in a confirm dialog"
|
||||||
},
|
},
|
||||||
|
"dateInstalled": {
|
||||||
|
"message": "Date installed",
|
||||||
|
"description": "Option text for the user to sort the style by install date"
|
||||||
|
},
|
||||||
|
"dateUpdated": {
|
||||||
|
"message": "Date updated",
|
||||||
|
"description": "Option text for the user to sort the style by last update date"
|
||||||
|
},
|
||||||
"dbError": {
|
"dbError": {
|
||||||
"message": "An error has occurred using the Stylus database. Would you like to visit a web page with possible solutions?",
|
"message": "An error has occurred using the Stylus database. Would you like to visit a web page with possible solutions?",
|
||||||
"description": "Prompt when a DB error is encountered"
|
"description": "Prompt when a DB error is encountered"
|
||||||
|
@ -816,6 +836,38 @@
|
||||||
"shortcutsNote": {
|
"shortcutsNote": {
|
||||||
"message": "Define keyboard shortcuts"
|
"message": "Define keyboard shortcuts"
|
||||||
},
|
},
|
||||||
|
"sortLabel": {
|
||||||
|
"message": "Select a sort to apply to the installed styles",
|
||||||
|
"description": "Title on the sort select to indicate it is used for sorting entries"
|
||||||
|
},
|
||||||
|
"sortDescending": {
|
||||||
|
"message": "descending",
|
||||||
|
"description": "Text added to indicate that a sort is in the descending (Z to A) direction"
|
||||||
|
},
|
||||||
|
"sortDateNewestFirst": {
|
||||||
|
"message": "newest first",
|
||||||
|
"description": "Text added to indicate that sorting a date would add the newest entries at the top"
|
||||||
|
},
|
||||||
|
"sortDateOldestFirst": {
|
||||||
|
"message": "oldest first",
|
||||||
|
"description": "Text added to indicate that sorting a date would add the oldest entries at the top"
|
||||||
|
},
|
||||||
|
"sortLabelFirst": {
|
||||||
|
"message": "first",
|
||||||
|
"description": "Text added to indicate that entry with a disabled or usercss label will be sorted first (at the top)"
|
||||||
|
},
|
||||||
|
"sortLabelLast": {
|
||||||
|
"message": "last",
|
||||||
|
"description": "Text added to indicate that entry with a disabled or usercss label will be sorted last (at the bottom)"
|
||||||
|
},
|
||||||
|
"sortStylesHelpTitle": {
|
||||||
|
"message": "Sort contents",
|
||||||
|
"description": "Label for the sort info popup on the Manage styles page"
|
||||||
|
},
|
||||||
|
"sortStylesHelp": {
|
||||||
|
"message": "Choose the type of sort to apply to the installed entries from within the sort dropdown. The default setting applies an ascending sort (A to Z) to the entry titles. Sorts that include a \"(descending)\" label will apply a descending sort (Z to A) to the title.\nThere are other presets that will allow sorting the entries by multiple criteria. Think of this like sorting a table with multiple columns and each category in a select (between the plus signs) represents a column, or group.\nFor example, if the setting is \"Disabled (at bottom) + Title\", the entries would sort so that all the disabled entries are sorted to the bottom of the list and the entry titles are then applied to both disabled tags and the non-disabled tags separately.",
|
||||||
|
"description": "Text in the minihelp displayed when clicking (i) icon to the right of the sort input field on the Manage styles page"
|
||||||
|
},
|
||||||
"styleBadRegexp": {
|
"styleBadRegexp": {
|
||||||
"message": "Regexp is invalid.",
|
"message": "Regexp is invalid.",
|
||||||
"description": "Validation message for a bad regexp in a style"
|
"description": "Validation message for a bad regexp in a style"
|
||||||
|
|
|
@ -115,7 +115,7 @@ select {
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-resizer {
|
.select-resizer {
|
||||||
display: inline-flex!important;
|
display: inline-flex !important;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
@ -197,3 +197,14 @@ function tWordBreak(text) {
|
||||||
return text.length <= 10 ? text :
|
return text.length <= 10 ? text :
|
||||||
text.replace(/([\d\w\u007B-\uFFFF]{10}|[\d\w\u007B-\uFFFF]{5,10}[!-/]|((?!\s)\W){10})(?!\b|\s)/g, '$&\u00AD');
|
text.replace(/([\d\w\u007B-\uFFFF]{10}|[\d\w\u007B-\uFFFF]{5,10}[!-/]|((?!\s)\W){10})(?!\b|\s)/g, '$&\u00AD');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatDate(date) {
|
||||||
|
return tryCatch(lang => {
|
||||||
|
const newDate = new Date(parseInt(date));
|
||||||
|
return newDate.toLocaleDateString(lang, {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: newDate.getYear() === new Date().getYear() ? undefined : '2-digit',
|
||||||
|
});
|
||||||
|
}, [chrome.i18n.getUILanguage(), 'en']) || '';
|
||||||
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ var prefs = new function Prefs() {
|
||||||
'manage.newUI.favicons': false, // show favicons for the sites in applies-to
|
'manage.newUI.favicons': false, // show favicons for the sites in applies-to
|
||||||
'manage.newUI.faviconsGray': true, // gray out favicons
|
'manage.newUI.faviconsGray': true, // gray out favicons
|
||||||
'manage.newUI.targets': 3, // max number of applies-to targets visible: 0 = none
|
'manage.newUI.targets': 3, // max number of applies-to targets visible: 0 = none
|
||||||
|
'manage.newUI.sort': 'title,asc', // current applied sort
|
||||||
|
|
||||||
'editor.options': {}, // CodeMirror.defaults.*
|
'editor.options': {}, // CodeMirror.defaults.*
|
||||||
'editor.options.expanded': true, // UI element state: expanded/collapsed
|
'editor.options.expanded': true, // UI element state: expanded/collapsed
|
||||||
|
|
13
manage.html
13
manage.html
|
@ -155,6 +155,7 @@
|
||||||
<script src="manage/object-diff.js"></script>
|
<script src="manage/object-diff.js"></script>
|
||||||
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
|
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
|
||||||
<script src="manage/config-dialog.js"></script>
|
<script src="manage/config-dialog.js"></script>
|
||||||
|
<script src="manage/sort.js"></script>
|
||||||
<script src="manage/manage.js"></script>
|
<script src="manage/manage.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
|
@ -245,7 +246,6 @@
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<p class="nowrap">
|
<p class="nowrap">
|
||||||
|
@ -285,6 +285,16 @@
|
||||||
|
|
||||||
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
|
<summary><h2 id="options-heading" i18n-text="optionsHeading"></h2></summary>
|
||||||
|
|
||||||
|
<div id="sort-wrapper" i18n-title="sortLabel">
|
||||||
|
<div class="sorter-selection">
|
||||||
|
<select id="sort-select"></select>
|
||||||
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
|
</div>
|
||||||
|
<a href="#" id="sorter-help">
|
||||||
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<input id="manage.newUI" type="checkbox">
|
<input id="manage.newUI" type="checkbox">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
|
@ -344,7 +354,6 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="installed"></div>
|
<div id="installed"></div>
|
||||||
|
|
||||||
<script src="manage/import-export.js"></script>
|
<script src="manage/import-export.js"></script>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* global installed messageBox */
|
/* global installed messageBox */
|
||||||
|
/* global updateSort */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const filtersSelector = {
|
const filtersSelector = {
|
||||||
|
@ -155,6 +156,7 @@ function filterOnChange({target: el, forceRefilter}) {
|
||||||
if (installed) {
|
if (installed) {
|
||||||
reapplyFilter();
|
reapplyFilter();
|
||||||
}
|
}
|
||||||
|
updateSort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -190,17 +192,12 @@ function reapplyFilter(container = installed) {
|
||||||
filterContainer({hide: false});
|
filterContainer({hide: false});
|
||||||
}
|
}
|
||||||
// filtering needed or a single-element job from handleUpdate()
|
// filtering needed or a single-element job from handleUpdate()
|
||||||
const entries = installed.children;
|
|
||||||
const numEntries = entries.length;
|
|
||||||
let numVisible = numEntries - $$('.entry.hidden').length;
|
|
||||||
for (const entry of toUnhide.children || toUnhide) {
|
for (const entry of toUnhide.children || toUnhide) {
|
||||||
const next = findInsertionPoint(entry);
|
if (!entry.parentNode) {
|
||||||
if (entry.nextElementSibling !== next) {
|
installed.appendChild(entry);
|
||||||
installed.insertBefore(entry, next);
|
|
||||||
}
|
}
|
||||||
if (entry.classList.contains('hidden')) {
|
if (entry.classList.contains('hidden')) {
|
||||||
entry.classList.remove('hidden');
|
entry.classList.remove('hidden');
|
||||||
numVisible++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// B: hide
|
// B: hide
|
||||||
|
@ -218,18 +215,10 @@ function reapplyFilter(container = installed) {
|
||||||
// 1. add all hidden entries to the end
|
// 1. add all hidden entries to the end
|
||||||
// 2. add the visible entries before the first hidden entry
|
// 2. add the visible entries before the first hidden entry
|
||||||
if (container instanceof DocumentFragment) {
|
if (container instanceof DocumentFragment) {
|
||||||
for (const entry of toHide) {
|
installed.appendChild(container);
|
||||||
installed.appendChild(entry);
|
|
||||||
}
|
|
||||||
installed.insertBefore(container, $('.entry.hidden'));
|
|
||||||
showFiltersStats();
|
showFiltersStats();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// normal filtering of the page or a single-element job from handleUpdate()
|
|
||||||
// we need to keep the visible entries together at the start
|
|
||||||
// first pass only moves one hidden entry in hidden groups with odd number of items
|
|
||||||
shuffle(false);
|
|
||||||
setTimeout(shuffle, 0, true);
|
|
||||||
// single-element job from handleEvent(): add the last wraith
|
// single-element job from handleEvent(): add the last wraith
|
||||||
if (toHide.length === 1 && toHide[0].parentElement !== installed) {
|
if (toHide.length === 1 && toHide[0].parentElement !== installed) {
|
||||||
installed.appendChild(toHide[0]);
|
installed.appendChild(toHide[0]);
|
||||||
|
@ -256,95 +245,6 @@ function reapplyFilter(container = installed) {
|
||||||
toUnhide = $$(selector, container);
|
toUnhide = $$(selector, container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shuffle(fullPass) {
|
|
||||||
if (fullPass && !document.body.classList.contains('update-in-progress')) {
|
|
||||||
$('#check-all-updates').disabled = !$('.updatable:not(.can-update)');
|
|
||||||
}
|
|
||||||
// 1. skip the visible group on top
|
|
||||||
let firstHidden = $('#installed > .hidden');
|
|
||||||
let entry = firstHidden;
|
|
||||||
let i = [...entries].indexOf(entry);
|
|
||||||
let horizon = entries[numVisible];
|
|
||||||
const skipGroup = state => {
|
|
||||||
const start = i;
|
|
||||||
const first = entry;
|
|
||||||
while (entry && entry.classList.contains('hidden') === state) {
|
|
||||||
entry = entry.nextElementSibling;
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return {first, start, len: i - start};
|
|
||||||
};
|
|
||||||
let prevGroup = i ? {first: entries[0], start: 0, len: i} : skipGroup(true);
|
|
||||||
// eslint-disable-next-line no-unmodified-loop-condition
|
|
||||||
while (entry) {
|
|
||||||
// 2a. find the next hidden group's start and end
|
|
||||||
// 2b. find the next visible group's start and end
|
|
||||||
const isHidden = entry.classList.contains('hidden');
|
|
||||||
const group = skipGroup(isHidden);
|
|
||||||
const hidden = isHidden ? group : prevGroup;
|
|
||||||
const visible = isHidden ? prevGroup : group;
|
|
||||||
// 3. move the shortest group; repeat 2-3
|
|
||||||
if (hidden.len < visible.len && (fullPass || hidden.len % 2)) {
|
|
||||||
// 3a. move hidden under the horizon
|
|
||||||
for (let j = 0; j < (fullPass ? hidden.len : 1); j++) {
|
|
||||||
const entry = entries[hidden.start];
|
|
||||||
installed.insertBefore(entry, horizon);
|
|
||||||
horizon = entry;
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
prevGroup = isHidden ? skipGroup(false) : group;
|
|
||||||
firstHidden = entry;
|
|
||||||
} else if (isHidden || !fullPass) {
|
|
||||||
prevGroup = group;
|
|
||||||
} else {
|
|
||||||
// 3b. move visible above the horizon
|
|
||||||
for (let j = 0; j < visible.len; j++) {
|
|
||||||
const entry = entries[visible.start + j];
|
|
||||||
installed.insertBefore(entry, firstHidden);
|
|
||||||
}
|
|
||||||
prevGroup = {
|
|
||||||
first: firstHidden,
|
|
||||||
start: hidden.start + visible.len,
|
|
||||||
len: hidden.len + skipGroup(true).len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (fullPass) {
|
|
||||||
showFiltersStats({immediately: true});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function findInsertionPoint(entry) {
|
|
||||||
const nameLLC = entry.styleNameLowerCase;
|
|
||||||
let a = 0;
|
|
||||||
let b = Math.min(numEntries, numVisible) - 1;
|
|
||||||
if (b < 0) {
|
|
||||||
return entries[numVisible];
|
|
||||||
}
|
|
||||||
if (entries[0].styleNameLowerCase > nameLLC) {
|
|
||||||
return entries[0];
|
|
||||||
}
|
|
||||||
if (entries[b].styleNameLowerCase <= nameLLC) {
|
|
||||||
return entries[numVisible];
|
|
||||||
}
|
|
||||||
// bisect
|
|
||||||
while (a < b - 1) {
|
|
||||||
const c = (a + b) / 2 | 0;
|
|
||||||
if (nameLLC < entries[c].styleNameLowerCase) {
|
|
||||||
b = c;
|
|
||||||
} else {
|
|
||||||
a = c;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (entries[a].styleNameLowerCase > nameLLC) {
|
|
||||||
return entries[a];
|
|
||||||
}
|
|
||||||
while (a <= b && entries[a].styleNameLowerCase < nameLLC) {
|
|
||||||
a++;
|
|
||||||
}
|
|
||||||
return entries[entries[a].styleNameLowerCase <= nameLLC ? a + 1 : a];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -311,7 +311,7 @@ select {
|
||||||
display: table-row;
|
display: table-row;
|
||||||
}
|
}
|
||||||
|
|
||||||
.newUI .entry:nth-child(2n) {
|
.newUI .entry.odd {
|
||||||
background-color: rgba(128, 128, 128, 0.05);
|
background-color: rgba(128, 128, 128, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +331,7 @@ select {
|
||||||
margin: 4px 0;
|
margin: 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-selection {
|
.filter-selection, .sorter-selection {
|
||||||
position: relative;
|
position: relative;
|
||||||
left: -9px;
|
left: -9px;
|
||||||
}
|
}
|
||||||
|
@ -358,7 +358,7 @@ select {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-selection select {
|
.filter-selection select, .sorter-selection select {
|
||||||
height: 18px;
|
height: 18px;
|
||||||
border: none;
|
border: none;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -367,7 +367,8 @@ select {
|
||||||
margin-left: 4px;
|
margin-left: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-selection .select-arrow {
|
.filter-selection .select-arrow,
|
||||||
|
.sorter-selection .select-arrow {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -388,7 +389,8 @@ select {
|
||||||
}
|
}
|
||||||
|
|
||||||
#filters label:hover,
|
#filters label:hover,
|
||||||
#filters .filter-selection:hover {
|
#filters .filter-selection:hover,
|
||||||
|
#options .sorter-selection:hover {
|
||||||
background-color: hsla(0, 0%, 50%, .2);
|
background-color: hsla(0, 0%, 50%, .2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -420,6 +422,24 @@ select {
|
||||||
margin: 0 .5em;
|
margin: 0 .5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#options .sorter-selection {
|
||||||
|
display: block;
|
||||||
|
left: 0;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
max-width: calc(100% - 2em);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-selection select {
|
||||||
|
margin-left: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sorter-selection .select-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.newUI .entry .svg-icon.checked,
|
.newUI .entry .svg-icon.checked,
|
||||||
.newUI .entry:hover .svg-icon.checked {
|
.newUI .entry:hover .svg-icon.checked {
|
||||||
fill: #000;
|
fill: #000;
|
||||||
|
@ -833,7 +853,7 @@ input[id^="manage.newUI"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search-wrapper {
|
#search-wrapper, #sort-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -1026,6 +1046,7 @@ input[id^="manage.newUI"] {
|
||||||
}
|
}
|
||||||
|
|
||||||
#installed {
|
#installed {
|
||||||
|
margin-top: 0;
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1080,7 +1101,7 @@ input[id^="manage.newUI"] {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-selection select {
|
.filter-selection select, .sorter-selection select {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
/* global checkUpdate, handleUpdateInstalled */
|
/* global checkUpdate, handleUpdateInstalled */
|
||||||
/* global objectDiff */
|
/* global objectDiff */
|
||||||
/* global configDialog */
|
/* global configDialog */
|
||||||
|
/* global sortInit, sortStyles, updateSort */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let installed;
|
let installed;
|
||||||
|
@ -58,6 +59,9 @@ function initGlobalEvents() {
|
||||||
$('#manage-options-button').onclick = () => chrome.runtime.openOptionsPage();
|
$('#manage-options-button').onclick = () => chrome.runtime.openOptionsPage();
|
||||||
$('#manage-shortcuts-button').onclick = () => openURL({url: URLS.configureCommands});
|
$('#manage-shortcuts-button').onclick = () => openURL({url: URLS.configureCommands});
|
||||||
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
||||||
|
// show date installed & last update on hover
|
||||||
|
installed.addEventListener('mouseover', debounceEntryTitle);
|
||||||
|
installed.addEventListener('mouseout', debounceEntryTitle);
|
||||||
|
|
||||||
// remember scroll position on normal history navigation
|
// remember scroll position on normal history navigation
|
||||||
window.onbeforeunload = rememberScrollPosition;
|
window.onbeforeunload = rememberScrollPosition;
|
||||||
|
@ -81,6 +85,7 @@ function initGlobalEvents() {
|
||||||
|
|
||||||
// N.B. triggers existing onchange listeners
|
// N.B. triggers existing onchange listeners
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
|
sortInit();
|
||||||
|
|
||||||
$$('[id^="manage.newUI"]')
|
$$('[id^="manage.newUI"]')
|
||||||
.forEach(el => (el.oninput = (el.onchange = switchUI)));
|
.forEach(el => (el.oninput = (el.onchange = switchUI)));
|
||||||
|
@ -103,10 +108,10 @@ function initGlobalEvents() {
|
||||||
|
|
||||||
|
|
||||||
function showStyles(styles = []) {
|
function showStyles(styles = []) {
|
||||||
const sorted = styles
|
const sorted = sortStyles({styles, parser: 'style'})
|
||||||
.map(style => ({name: style.name.toLocaleLowerCase(), style}))
|
.map(style => ({name: style.name.toLocaleLowerCase(), style}));
|
||||||
.sort((a, b) => (a.name < b.name ? -1 : a.name === b.name ? 0 : 1));
|
|
||||||
let index = 0;
|
let index = 0;
|
||||||
|
installed.dataset.total = styles.length;
|
||||||
const scrollY = (history.state || {}).scrollY;
|
const scrollY = (history.state || {}).scrollY;
|
||||||
const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId;
|
const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId;
|
||||||
const renderBin = document.createDocumentFragment();
|
const renderBin = document.createDocumentFragment();
|
||||||
|
@ -188,6 +193,8 @@ function createStyleElement({style, name}) {
|
||||||
(style.enabled ? 'enabled' : 'disabled') +
|
(style.enabled ? 'enabled' : 'disabled') +
|
||||||
(style.updateUrl ? ' updatable' : '') +
|
(style.updateUrl ? ' updatable' : '') +
|
||||||
(style.usercssData ? ' usercss' : '');
|
(style.usercssData ? ' usercss' : '');
|
||||||
|
entry.dataset.installdate = style.installDate || t('genericUnknown');
|
||||||
|
entry.dataset.updatedate = style.updateDate || style.installDate || t('genericUnknown');
|
||||||
|
|
||||||
if (style.url) {
|
if (style.url) {
|
||||||
$('.homepage', entry).appendChild(parts.homepageIcon.cloneNode(true));
|
$('.homepage', entry).appendChild(parts.homepageIcon.cloneNode(true));
|
||||||
|
@ -207,6 +214,32 @@ function createStyleElement({style, name}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function debounceEntryTitle(event) {
|
||||||
|
if (event.target.nodeName === 'A' && event.target.classList.contains('style-name-link')) {
|
||||||
|
if (event.type === 'mouseover' && !event.target.title) {
|
||||||
|
debounce(addEntryTitle, 50, event.target);
|
||||||
|
} else if (debounce.timers.size) {
|
||||||
|
debounce.unregister(addEntryTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add entry install & updated date to process locales
|
||||||
|
function addEntryTitle(link) {
|
||||||
|
const unknown = t('genericUnknown');
|
||||||
|
const entry = link.closest('.entry');
|
||||||
|
// eslint-disable-next-line no-inner-declarations
|
||||||
|
function checkValidDate(date) {
|
||||||
|
const check = formatDate(date);
|
||||||
|
return (date === unknown || check === 'Invalid Date') ? unknown : check;
|
||||||
|
}
|
||||||
|
if (entry) {
|
||||||
|
link.title = `${t('dateInstalled')}: ${checkValidDate(entry.dataset.installdate)}\n` +
|
||||||
|
`${t('dateUpdated')}: ${checkValidDate(entry.dataset.updatedate)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function createStyleTargetsElement({entry, style, postponeFavicons}) {
|
function createStyleTargetsElement({entry, style, postponeFavicons}) {
|
||||||
const parts = createStyleElement.parts;
|
const parts = createStyleElement.parts;
|
||||||
const targets = parts.targets.cloneNode(true);
|
const targets = parts.targets.cloneNode(true);
|
||||||
|
@ -416,6 +449,7 @@ function handleUpdate(style, {reason, method} = {}) {
|
||||||
handleUpdateInstalled(entry, reason);
|
handleUpdateInstalled(entry, reason);
|
||||||
}
|
}
|
||||||
filterAndAppend({entry});
|
filterAndAppend({entry});
|
||||||
|
updateSort();
|
||||||
if (!entry.matches('.hidden') && reason !== 'import') {
|
if (!entry.matches('.hidden') && reason !== 'import') {
|
||||||
animateElement(entry);
|
animateElement(entry);
|
||||||
scrollElementIntoView(entry);
|
scrollElementIntoView(entry);
|
||||||
|
@ -524,6 +558,18 @@ function switchUI({styleOnly} = {}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function updateStripes() {
|
||||||
|
let index = 0;
|
||||||
|
[...installed.children].forEach(entry => {
|
||||||
|
const list = entry.classList;
|
||||||
|
if (!list.contains('hidden')) {
|
||||||
|
list.add(index % 2 ? 'odd' : 'even');
|
||||||
|
list.remove(index++ % 2 ? 'even' : 'odd');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function rememberScrollPosition() {
|
function rememberScrollPosition() {
|
||||||
history.replaceState({scrollY: window.scrollY}, document.title);
|
history.replaceState({scrollY: window.scrollY}, document.title);
|
||||||
}
|
}
|
||||||
|
|
169
manage/sort.js
Normal file
169
manage/sort.js
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
/* global installed updateStripes */
|
||||||
|
/* global messageBox */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const sorterType = {
|
||||||
|
alpha: (a, b) => (a < b ? -1 : a === b ? 0 : 1),
|
||||||
|
number: (a, b) => a - b
|
||||||
|
};
|
||||||
|
|
||||||
|
const tagData = {
|
||||||
|
title: {
|
||||||
|
text: t('genericTitle'),
|
||||||
|
parse: {
|
||||||
|
style: style => style.name.toLowerCase(),
|
||||||
|
entry: entry => {
|
||||||
|
const el = $('.style-name-link', entry);
|
||||||
|
return el ? el.textContent.trim().toLowerCase() : '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sorter: sorterType.alpha
|
||||||
|
},
|
||||||
|
usercss: {
|
||||||
|
text: 'Usercss',
|
||||||
|
parse: {
|
||||||
|
style: style => (style.usercssData ? 0 : 1),
|
||||||
|
entry: entry => (entry.classList.contains('usercss') ? 0 : 1)
|
||||||
|
},
|
||||||
|
sorter: sorterType.number
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
text: '', // added as either "enabled" or "disabled" by the addSortOptions function
|
||||||
|
parse: {
|
||||||
|
style: style => (style.enabled ? 1 : 0),
|
||||||
|
entry: entry => (entry.classList.contains('enabled') ? 1 : 0)
|
||||||
|
},
|
||||||
|
sorter: sorterType.number
|
||||||
|
},
|
||||||
|
dateInstalled: {
|
||||||
|
text: t('dateInstalled'),
|
||||||
|
parse: {
|
||||||
|
style: style => style.installDate,
|
||||||
|
entry: entry => entry.dataset.installdate
|
||||||
|
},
|
||||||
|
sorter: sorterType.number
|
||||||
|
},
|
||||||
|
dateUpdated: {
|
||||||
|
text: t('dateUpdated'),
|
||||||
|
parse: {
|
||||||
|
style: style => style.updateDate,
|
||||||
|
entry: entry => entry.dataset.updatedate
|
||||||
|
},
|
||||||
|
sorter: sorterType.number
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Adding (assumed) most commonly used ('title,asc' should always be first)
|
||||||
|
// whitespace before & after the comma is ignored
|
||||||
|
const sortSelectOptions = [
|
||||||
|
'title,asc',
|
||||||
|
'title,desc',
|
||||||
|
'dateInstalled,desc, title,asc',
|
||||||
|
'dateInstalled,asc, title,asc',
|
||||||
|
'dateUpdated,desc, title,asc',
|
||||||
|
'dateUpdated,asc, title,asc',
|
||||||
|
'usercss,asc, title,asc',
|
||||||
|
'usercss,desc, title,asc',
|
||||||
|
'disabled,asc, title,asc',
|
||||||
|
'disabled,desc, title,asc',
|
||||||
|
'disabled,desc, usercss,asc, title,asc',
|
||||||
|
'usercss,asc, title,desc',
|
||||||
|
'usercss,desc, title,desc',
|
||||||
|
'disabled,desc, title,desc',
|
||||||
|
'disabled,desc, usercss,asc, title,desc'
|
||||||
|
];
|
||||||
|
|
||||||
|
const sortByRegex = /\s*,\s*/;
|
||||||
|
|
||||||
|
function addSortOptions() {
|
||||||
|
const select = $('#options .sorter-selection select');
|
||||||
|
const renderBin = document.createDocumentFragment();
|
||||||
|
const option = $create('option');
|
||||||
|
const meta = {
|
||||||
|
enabled: t('genericEnabledLabel'),
|
||||||
|
disabled: t('genericDisabledLabel'),
|
||||||
|
desc: ` (${t('sortDescending')})`,
|
||||||
|
dateNew: ` (${t('sortDateNewestFirst')})`,
|
||||||
|
dateOld: ` (${t('sortDateOldestFirst')})`,
|
||||||
|
labelFirst: ` (${t('sortLabelFirst')})`,
|
||||||
|
labelLast: ` (${t('sortLabelLast')})`
|
||||||
|
};
|
||||||
|
sortSelectOptions.forEach(sort => {
|
||||||
|
const opt = option.cloneNode();
|
||||||
|
let lastTag = '';
|
||||||
|
opt.textContent = sort.split(sortByRegex).reduce((acc, val) => {
|
||||||
|
if (tagData[val]) {
|
||||||
|
lastTag = val;
|
||||||
|
return acc + (acc !== '' ? ' + ' : '') + tagData[val].text;
|
||||||
|
}
|
||||||
|
if (lastTag.indexOf('date') > -1) return acc + meta[val === 'desc' ? 'dateNew' : 'dateOld'];
|
||||||
|
if (lastTag === 'disabled') return acc + meta[val === 'desc' ? 'enabled' : 'disabled'] + meta['labelFirst'];
|
||||||
|
if (lastTag !== 'title') return acc + meta[val === 'desc' ? 'labelLast' : 'labelFirst'];
|
||||||
|
return acc + (meta[val] || '');
|
||||||
|
}, '');
|
||||||
|
opt.value = sort;
|
||||||
|
renderBin.appendChild(opt);
|
||||||
|
});
|
||||||
|
select.appendChild(renderBin);
|
||||||
|
select.value = prefs.get('manage.newUI.sort');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortStyles({styles, parser}) {
|
||||||
|
if (!styles) {
|
||||||
|
styles = [...installed.children];
|
||||||
|
parser = 'entry';
|
||||||
|
} else {
|
||||||
|
parser = 'style';
|
||||||
|
}
|
||||||
|
const sortBy = prefs.get('manage.newUI.sort').split(sortByRegex); // 'title,asc'
|
||||||
|
const len = sortBy.length;
|
||||||
|
return styles.sort((a, b) => {
|
||||||
|
let types, direction;
|
||||||
|
let result = 0;
|
||||||
|
let indx = 0;
|
||||||
|
// multi-sort
|
||||||
|
while (result === 0 && indx < len) {
|
||||||
|
types = tagData[sortBy[indx++]];
|
||||||
|
direction = sortBy[indx++] === 'asc' ? 1 : -1;
|
||||||
|
result = types.sorter(types.parse[parser](a), types.parse[parser](b)) * direction;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function manageSort(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
prefs.set('manage.newUI.sort', this.value);
|
||||||
|
updateSort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSort() {
|
||||||
|
debounce(() => {
|
||||||
|
const renderBin = document.createDocumentFragment();
|
||||||
|
const entries = sortStyles({parser: 'entry'});
|
||||||
|
for (const entry of entries) {
|
||||||
|
renderBin.appendChild(entry);
|
||||||
|
}
|
||||||
|
installed.appendChild(renderBin);
|
||||||
|
updateStripes();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSortHelp(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
messageBox({
|
||||||
|
className: 'help-text',
|
||||||
|
title: t('sortStylesHelpTitle'),
|
||||||
|
contents:
|
||||||
|
$create('div',
|
||||||
|
t('sortStylesHelp').split('\n').map(line =>
|
||||||
|
$create('p', line))),
|
||||||
|
buttons: [t('confirmOK')],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function sortInit() {
|
||||||
|
$('#options select').addEventListener('change', manageSort);
|
||||||
|
$('#sorter-help').onclick = showSortHelp;
|
||||||
|
addSortOptions();
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
/* global messageBox */
|
/* global messageBox */
|
||||||
/* global ENTRY_ID_PREFIX, newUI */
|
/* global ENTRY_ID_PREFIX, newUI */
|
||||||
/* global filtersSelector, filterAndAppend */
|
/* global filtersSelector, filterAndAppend, updateSort */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
|
@ -144,6 +144,7 @@ function reportUpdateState(state, style, details) {
|
||||||
}
|
}
|
||||||
if (filtersSelector.hide) {
|
if (filtersSelector.hide) {
|
||||||
filterAndAppend({entry});
|
filterAndAppend({entry});
|
||||||
|
updateSort();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,14 @@
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#message-box-contents p:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#message-box-contents p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#message-box-buttons {
|
#message-box-buttons {
|
||||||
padding: .75rem .375rem;
|
padding: .75rem .375rem;
|
||||||
background-color: #f0f0f0;
|
background-color: #f0f0f0;
|
||||||
|
|
|
@ -16,8 +16,6 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
const BASE_URL = 'https://userstyles.org';
|
const BASE_URL = 'https://userstyles.org';
|
||||||
const UPDATE_URL = 'https://update.userstyles.org/%.md5';
|
const UPDATE_URL = 'https://update.userstyles.org/%.md5';
|
||||||
|
|
||||||
const UI_LANG = chrome.i18n.getUILanguage();
|
|
||||||
|
|
||||||
// normal category is just one word like 'github' or 'google'
|
// normal category is just one word like 'github' or 'google'
|
||||||
// but for some sites we need a fallback
|
// but for some sites we need a fallback
|
||||||
// key: category.tld
|
// key: category.tld
|
||||||
|
@ -459,17 +457,9 @@ window.addEventListener('showStyles:done', function _() {
|
||||||
|
|
||||||
Object.assign($('[data-type="updated"] time', entry), {
|
Object.assign($('[data-type="updated"] time', entry), {
|
||||||
dateTime: result.updated,
|
dateTime: result.updated,
|
||||||
textContent: tryCatch(lang => {
|
textContent: formatDate(result.updated)
|
||||||
const date = new Date(result.updated);
|
|
||||||
return date.toLocaleDateString(lang, {
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'short',
|
|
||||||
year: date.getYear() === new Date().getYear() ? undefined : '2-digit',
|
|
||||||
});
|
|
||||||
}, [UI_LANG, 'en']) || '',
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
$('[data-type="weekly"] dd', entry).textContent = formatNumber(result.weekly_install_count);
|
$('[data-type="weekly"] dd', entry).textContent = formatNumber(result.weekly_install_count);
|
||||||
$('[data-type="total"] dd', entry).textContent = formatNumber(result.total_install_count);
|
$('[data-type="total"] dd', entry).textContent = formatNumber(result.total_install_count);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user