// ==UserScript==
// @name AO3 Qscore
// @description Autosorting 'Quality' Indicator trained on 11k+ works. Very generous with small fics, rewards engagement over popularity (bookmarks-collections-comments/kudos instead of hits) with a 0-100 score spread. Sort & position toggles included.
// @version 2.27
// @author C89sd
// @namespace https://greasyfork.org/users/1376767
// @match https://archiveofourown.org/*
// @grant GM_addStyle
// @noframes
// ==/UserScript==
'use strict';
// ======================== CONFIGURATION ========================
/*
# Scoring:
There are 3 metrics pairs: (bookmarks, collections, comments)/kudos.
Each pair has a trained regression model validated above a certain `min`.
Each metric is tested against min, and if it passes, it is scored by the model; otherwise it gets dimmed to 0.
The final score is derived by mixing all valid scores using a blend of MAX() and AVERAGE().
- MAX Keeps the best score, rewards works at the top of any single metric.
Gives 100 scores if any of the metrics are 100.
- AVERAGE Rewards works with the most high scores, and most valid metrics (3>2>1).
Missing scores bring down the final score by blending in a 0.
Only gives 100 scores if all three metrics are 100.
Lowering `min` to be more generous with small works and show more scores
would corrupt the top scores by making the models generate outside of their range.
To solve this, I added `bmin`. If all the metrics are invalid (<min), the score
is recomputed with the lower `>bmin` threshold. These scores are dimmed and placed at the bottom.
Scores that fail this test as well get a dimmed 0.
Since these scores are less accurate, they use a separate `BMIN_ALPHA` factor to smooth them more.
If you do not care about being generous with small works, you can set 'ENABLE_BMIN=false',
and raise the `dim_below` thresholds to fold more final scores in the dimmed section,
or raise `min` but this will use the worse `bmin` to score those in the dimmed section.
OR, if you want to adjust the separate BMIN_ALPHA for dimmed scores,
you can keep `min=dim_below` and raise `bmin` and `min` directly .
*/
// Add indicators showing the individual scores with z_scale applied (bookmarks, collections, comments).
// Border color: yellow=bmin_valid, red=invalid.
const DEBUG = false;
// Blending factor between MAX and AVERAGE `finalScore = A*MAX+(1-A)*AVG`. 1=MAX, 0=AVERAGE
const ALPHA = 0.8;
const BMIN_ALPHA = 0.7;
const ENABLE_BMIN = true;
// Weight of missing scores in the Average formula.
// Example: (ALPHA=0.7, zero_weight=0.3, avg_weight=1): 2 valid + 1 invalid (−3 pts); 1 valid + 2 invalid (−10 pts);
const AVG_ZERO_WEIGHT = 0.3;
// `min` Metrics below this value are invalid and will not contribute to the final ALPHA blended score.
// `bmin` Bottom min. If ENABLE_BMIN=true, invalid final scores are *rescored* with `bmin` and `BMIN_ALPHA`, dimmed and sorted at the end.
//
// `dim_below` Dim final scores if all metrics are below this second threshold.
// This is a convenience to hide smaller works without touching min.
// `dim_below=0` is disabled and default to `min`: the only dimmed scores are from the range [bmin,min[.
// `avg_weight` Weight of a score in the weighted AVERAGE score.
// `z_scale` Scales the positive z-scores, resulting in fewer 100s for that metric.
// To halve the number of 100 scores: const Halve100 = 0.9176338854906885;
// const Halve99 = 0.892167842806324;
const THRESHOLDS = {
'kudos': { bmin: 5, min: 8, /**/ dim_below: 0 }, // Gates all others.
//
'bookmarks': { bmin: 1, min: 3, /**/ dim_below: 0, avg_weight: 1.0, z_scale: 1.0 },
'collections': { bmin: 1, min: 2, /**/ dim_below: 0, avg_weight: 1.0, z_scale: 1.0 },
'comments': { bmin: 3, min: 3, /**/ dim_below: 0, avg_weight: 1.0, z_scale: 1.0 },
//
'chapters': { bmin: 1, min: 2, /**/ dim_below: 0 }
};
// Replace {.dim_below=0 by .min}
Object.values(THRESHOLDS).forEach(obj => obj.dim_below === 0 && (obj.dim_below = obj.min));
// Note on 'chapters':
// ('comments','kudos') is actually ('comment_per_chapter','kudos_per_chapter').
// which is why this metric is gated on 'chapters' as well.
// ======================== MODEL & CONSTANTS ========================
let MODEL = {"Z_95": 1.0364333894937898, "regressions": [{"x_metric": "bookmarks", "y_metric": "kudos", "x_grid": [0.0, 0.3323, 0.6646, 0.997, 1.3293, 1.6616, 1.9939, 2.3263, 2.6586, 2.9909, 3.3232, 3.6556, 3.9879, 4.3202, 4.6525, 4.9849, 5.3172, 5.6495, 5.9818, 6.3142, 6.6465, 6.9788, 7.3111, 7.6435, 7.9758, 8.3081, 8.6404, 8.9728, 9.3051, 9.6374, 9.9697, 10.3021, 10.6344, 10.9667, 11.299, 11.6314, 11.9637, 12.296, 12.6283, 12.9607], "q05_curve_y": [2.1722, 2.3297, 2.4859, 2.6704, 2.9142, 3.2016, 3.5162, 3.8411, 4.1655, 4.4864, 4.8014, 5.1093, 5.4097, 5.7043, 5.9953, 6.2845, 6.5734, 6.863, 7.1536, 7.4456, 7.7394, 8.036, 8.337, 8.6442, 8.9592, 9.2832, 9.6137, 9.9459, 10.2753, 10.5945, 10.894, 11.1935, 11.493, 11.7925, 12.092, 12.3915, 12.691, 12.9905, 13.29, 13.5895], "q50_curve_y": [2.6252, 2.8076, 2.99, 3.1987, 3.4591, 3.7542, 4.0665, 4.3786, 4.6822, 4.9785, 5.2694, 5.5568, 5.8423, 6.1263, 6.4088, 6.6897, 6.9693, 7.2481, 7.5271, 7.8075, 8.0904, 8.3768, 8.6675, 8.9631, 9.2643, 9.5715, 9.883, 10.1953, 10.5046, 10.8076, 11.1071, 11.4066, 11.7061, 12.0056, 12.3051, 12.6046, 12.9041, 13.2036, 13.5031, 13.8026], "q95_curve_y": [3.2949, 3.4494, 3.6051, 3.7878, 4.0228, 4.2933, 4.5821, 4.8722, 5.1554, 5.4336, 5.7092, 5.9846, 6.2616, 6.5397, 6.8183, 7.0964, 7.3736, 7.6496, 7.925, 8.2006, 8.4768, 8.7546, 9.0347, 9.3182, 9.606, 9.899, 10.1957, 10.493, 10.7875, 11.0783, 11.3778, 11.6773, 11.9768, 12.2763, 12.5758, 12.8753, 13.1748, 13.4743, 13.7738, 14.0733], "mu": 0.010967210394413519, "sigma": 1.0618674385105822}, {"x_metric": "collections", "y_metric": "kudos", "x_grid": [0.0, 0.1849, 0.3698, 0.5547, 0.7395, 0.9244, 1.1093, 1.2942, 1.4791, 1.664, 1.8489, 2.0338, 2.2186, 2.4035, 2.5884, 2.7733, 2.9582, 3.1431, 3.328, 3.5128, 3.6977, 3.8826, 4.0675, 4.2524, 4.4373, 4.6222, 4.8071, 4.9919, 5.1768, 5.3617, 5.5466, 5.7315, 5.9164, 6.1013, 6.2861, 6.471, 6.6559, 6.8408, 7.0257, 7.2106], "q05_curve_y": [1.414, 2.2679, 3.0662, 3.8165, 4.5249, 5.1912, 5.8014, 6.3417, 6.8012, 7.1803, 7.486, 7.7255, 7.9099, 8.0571, 8.1842, 8.3065, 8.4306, 8.5581, 8.6903, 8.8278, 8.9698, 9.1149, 9.2619, 9.4096, 9.5572, 9.7011, 9.8291, 9.957, 10.0849, 10.2129, 10.3408, 10.4688, 10.5967, 10.7246, 10.8526, 10.9805, 11.1085, 11.2364, 11.3644, 11.4923], "q50_curve_y": [4.6143, 5.0468, 5.4793, 5.9118, 6.344, 6.7656, 7.1615, 7.5184, 7.8241, 8.0778, 8.2846, 8.4498, 8.5805, 8.6882, 8.7847, 8.8811, 8.9822, 9.0886, 9.2013, 9.3202, 9.4438, 9.5706, 9.6989, 9.8278, 9.9566, 10.0849, 10.2128, 10.3408, 10.4687, 10.5966, 10.7246, 10.8525, 10.9805, 11.1084, 11.2363, 11.3643, 11.4922, 11.6202, 11.7481, 11.8761], "q95_curve_y": [7.5724, 7.5553, 7.6064, 7.7156, 7.8738, 8.0693, 8.2843, 8.4997, 8.6981, 8.872, 9.021, 9.1463, 9.2512, 9.3418, 9.4254, 9.5093, 9.596, 9.6856, 9.7781, 9.8735, 9.9715, 10.0714, 10.1726, 10.2747, 10.3775, 10.4844, 10.6124, 10.7403, 10.8683, 10.9962, 11.1241, 11.2521, 11.38, 11.508, 11.6359, 11.7638, 11.8918, 12.0197, 12.1477, 12.2756], "mu": -0.021871244232891534, "sigma": 1.0510446029054428}, {"x_metric": "comments", "y_metric": "kudos", "x_grid": [0.0, 0.3328, 0.6657, 0.9985, 1.3314, 1.6642, 1.9971, 2.3299, 2.6628, 2.9956, 3.3285, 3.6613, 3.9942, 4.327, 4.6599, 4.9927, 5.3256, 5.6584, 5.9913, 6.3241, 6.657, 6.9898, 7.3227, 7.6555, 7.9884, 8.3212, 8.6541, 8.9869, 9.3198, 9.6526, 9.9855, 10.3183, 10.6512, 10.984, 11.3169, 11.6497, 11.9826, 12.3154, 12.6483, 12.9811], "q05_curve_y": [2.0093, 2.3035, 2.5978, 2.8931, 3.1913, 3.4928, 3.7983, 4.1076, 4.4169, 4.7214, 5.0158, 5.2959, 5.5604, 5.8098, 6.0444, 6.2648, 6.4745, 6.6782, 6.8804, 7.0851, 7.289, 7.4859, 7.6695, 7.8344, 7.9817, 8.116, 8.2422, 8.3681, 8.495, 8.6219, 8.7488, 8.8757, 9.0026, 9.1294, 9.2563, 9.3832, 9.5101, 9.637, 9.7639, 9.8908], "q50_curve_y": [2.8284, 3.1258, 3.4232, 3.7217, 4.0229, 4.3275, 4.636, 4.9483, 5.2606, 5.5679, 5.8652, 6.1481, 6.4155, 6.6677, 6.9051, 7.1284, 7.3411, 7.5479, 7.7533, 7.9612, 8.1685, 8.369, 8.5563, 8.7249, 8.8761, 9.0144, 9.1446, 9.2716, 9.3984, 9.5253, 9.6522, 9.7791, 9.906, 10.0329, 10.1597, 10.2866, 10.4135, 10.5404, 10.6673, 10.7942], "q95_curve_y": [3.6809, 3.9759, 4.271, 4.5671, 4.866, 5.1684, 5.4748, 5.785, 6.0954, 6.4009, 6.6965, 6.9777, 7.2435, 7.4942, 7.7301, 7.9519, 8.1631, 8.3682, 8.5721, 8.7784, 8.9842, 9.183, 9.3686, 9.5355, 9.6847, 9.8211, 9.9493, 10.0758, 10.2027, 10.3296, 10.4564, 10.5833, 10.7102, 10.8371, 10.964, 11.0909, 11.2178, 11.3446, 11.4715, 11.5984], "mu": -0.02560361702737164, "sigma": 1.0479841045712814}]};
/* -- Halve100 --
from scipy.stats import norm
z = norm.ppf(98.5/100) # round([99.5,100]) = 100
p = 0.5 * (1 - norm.cdf(z)) # make 100s half as likely
print("sigma scale:", z / norm.ppf(1 - p))
*/
// --- HELPER FUNCTIONS ---
function interpolate(xs, ys, targetX) {
if (targetX <= xs[0]) return ys[0];
if (targetX >= xs[xs.length - 1]) return ys[ys.length - 1];
const i = xs.findIndex(x => x > targetX) - 1;
const fraction = (targetX - xs[i]) / (xs[i + 1] - xs[i]);
return ys[i] + fraction * (ys[i + 1] - ys[i]);
}
function sum(arr) { return arr.reduce((a, b) => a + b, 0); }
function ncdf(z) {
let t = 1 / (1 + 0.2315419 * Math.abs(z));
let d = 0.3989423 * Math.exp(-z * z / 2);
let prob = d * t * (0.3193815 + t * (-0.3565638 + t * (1.781478 + t * (-1.821256 + t * 1.330274))));
if (z > 0) prob = 1 - prob;
return prob;
}
// ======================== CORE SCORING FUNCTION ========================
function calculateScore(article, metrics) {
let valid_scores = []
let bmin_valid_scores = []
let all_scores = []
let all_dimmed = true;
for (const regression of MODEL.regressions) {
const x_metric = regression.x_metric; // bookmarks
const y_metric = regression.y_metric; // kudos
let is_valid =
metrics[x_metric] >= THRESHOLDS[x_metric].min &&
metrics[y_metric] >= THRESHOLDS[y_metric].min &&
(x_metric === 'comments' ? metrics['chapters'] >= THRESHOLDS['chapters'].min : true);
let is_bmin_valid =
is_valid ||
metrics[x_metric] >= THRESHOLDS[x_metric].bmin &&
metrics[y_metric] >= THRESHOLDS[y_metric].bmin &&
(x_metric === 'comments' ? metrics['chapters'] >= THRESHOLDS['chapters'].bmin : true);
let not_dimmed =
is_valid &&
metrics[x_metric] >= THRESHOLDS[x_metric].dim_below &&
metrics[y_metric] >= THRESHOLDS[y_metric].dim_below &&
(x_metric === 'comments' ? metrics['chapters'] >= THRESHOLDS['chapters'].dim_below : true);
if (not_dimmed) all_dimmed = false;
// Compute score
const x_point = Math.log1p(metrics[x_metric]);
const y_point = Math.log1p(metrics[y_metric]);
const y_center = interpolate(regression.x_grid, regression.q50_curve_y, x_point);
const y_lower = interpolate(regression.x_grid, regression.q05_curve_y, x_point);
const y_upper = interpolate(regression.x_grid, regression.q95_curve_y, x_point);
let ratio;
let z_scale = 1.0;
if (y_point >= y_center) {
const upper_diff = y_upper - y_center;
ratio = (upper_diff > 1e-6) ? (y_point - y_center) / upper_diff : 0.0;
} else {
const lower_diff = y_center - y_lower;
ratio = (lower_diff > 1e-6) ? (y_point - y_center) / lower_diff : 0.0;
z_scale = THRESHOLDS[x_metric].z_scale; // only scale positive zscores
}
const z_score = - MODEL.Z_95 * ratio; // flip the upside down graph
const standardized_z = (z_score - regression.mu) / regression.sigma;
const score = 100 * ncdf(standardized_z * z_scale);
let res = { x_metric, score, is_valid, is_bmin_valid, dimmed: !not_dimmed, weight: THRESHOLDS[x_metric].avg_weight };
if (is_valid) valid_scores.push(res);
if (is_bmin_valid) bmin_valid_scores.push(res);
all_scores.push(res);
}
const valid = (valid_scores.length > 0);
const bmin_valid = (bmin_valid_scores.length > 0);
if (valid) {
// AVERAGE
let premult_sum = 0.0,
weights_sum = 0.0;
for (let s of all_scores) {
premult_sum += s.is_valid ? s.score * s.weight : 0.0;
weights_sum += s.is_valid ? s.weight : s.weight * AVG_ZERO_WEIGHT;
}
let avg_score = premult_sum / weights_sum;
// MAX
let max_score = 0.0;
for (let s of valid_scores) max_score = Math.max(max_score, s.score);
const final_score = ALPHA * max_score + (1 - ALPHA) * avg_score;
return [final_score, valid, true, all_dimmed, all_scores];
}
if (ENABLE_BMIN && bmin_valid) {
// AVERAGE
let premult_sum = 0.0,
weights_sum = 0.0;
for (let s of all_scores) {
premult_sum += s.is_bmin_valid ? s.score * s.weight : 0.0;
weights_sum += s.is_bmin_valid ? s.weight : s.weight * AVG_ZERO_WEIGHT;
}
let avg_score = premult_sum / weights_sum;
// MAX
let max_score = 0.0; // include invalid
for (let s of bmin_valid_scores) max_score = Math.max(max_score, s.score);
const final_score = BMIN_ALPHA * max_score + (1 - BMIN_ALPHA) * avg_score;
return [final_score, false, true, true, all_scores];
}
return [0, false, false, true, all_scores];
}
// ---------------------------------- COLORS ----------------------------------
GM_addStyle(`
/* half width navbar toggle */
.halfWidth { width: 1.3ch !important; padding: 0.429em calc(0.75em/1) !important; }
/* Indicator styling */
.scoreA { display: inline-block; width: 28px; text-align: center; line-height: 18px; padding: 0; color: rgb(42,42,42); } /* em scales different on mobile */
/* Position inside Work pages (needs repeated selector to win) */
.inWork.inWork.inWork .scoreA { float: right; } /* .stats becomes float:left inside works */
.inWork.inWork.inWork.inCorner .scoreA { position: absolute; top: 10px; right: 10px; z-index: 1; }
/* Default corner position */
.inCorner .scoreA { position: absolute; top: -3px; right: -2px; }
.inCorner .isDate { top: 17px; }
/* Move extra below the corner Private Bookmark icon */
.inCorner.cornerFull:not(.khx-collapsed) .isDate { top: calc(17px + 28px); }
.inCorner.cornerFull:not(.khx-collapsed) .scoreA { top: 27px; }
/* ?? khx fix skipped before:: text
.skipped-work .isDate { top: -18px; } */
@-moz-document url-prefix() {
@media (max-width: 655px) {
.scoreA {
width: 26px; /* on desktop 26px doesn't cover the date, 27 does, but the 26px gap looks nicer on mobile. */
}
}
}
:root {
--boost: 85%;
--boostDM: 75%; /* 82%; */
--darken: 55%;
--darkenDM: 33.3%;
}
:root.dark-theme {
--darken: var(--darkenDM);
--boost: var(--boostDM);
}
.scoreA {
background-image: linear-gradient(hsl(0, 0%, var(--boost)), hsl(0, 0%, var(--boost)));
background-blend-mode: color-burn;
}
.scoreA.darkenA, .darkenA {
background-image: linear-gradient(hsl(0, 0%, var(--darken)), hsl(0, 0%, var(--darken)));
background-blend-mode: multiply;
}
.invalidA { border: solid 1px #d00 }
.validA { border: solid 1px #008d00 }
.bminValidA { border: solid 1px #e1a518 }
`);
const isDarkMode = window.getComputedStyle(document.body).color.match(/\d+/g)[0] > 128;
if (isDarkMode) document.documentElement.classList.add('dark-theme');
const HSL_STRINGS = [
'hsl(0.0, 90.7%, 92.3%)',
'hsl(47.8, 67.1%, 81.5%)',
'hsl(118.4, 51.2%, 85%)',
'hsl(122.9, 35.1%, 63.4%)',
];
const COLORS = HSL_STRINGS.map(str => (([h, s, l]) => ({ h, s, l }))(str.match(/[\d.]+/g).map(Number)));
function clamp(a, b, x) { return x < a ? a : (x > b ? b : x); }
function color(t, range=1.0, use3colors=false) {
let a, b;
t = t/range;
if (t < 0) { t = 0.0; }
if (use3colors && t > 1.0) { t = 1.0; }
else if (t > 1.5) { t = 1.5; }
if (t < 0.5) {
a = COLORS[0], b = COLORS[1];
t = t * 2.0;
} else if (t <= 1.0) {
a = COLORS[1], b = COLORS[2];
t = (t - 0.5) * 2.0;
} else {
a = COLORS[2], b = COLORS[3];
t = (t - 1.0) * 2.0;
}
const h = clamp(0, 360, a.h + (b.h - a.h) * t);
const s = clamp(0, 100, a.s + (b.s - a.s) * t);
const l = clamp(0, 100, a.l + (b.l - a.l) * t);
return `hsl(${h.toFixed(1)}, ${s.toFixed(1)}%, ${l.toFixed(1)}%)`;
}
// ---------------------------------- NAVBAR ----------------------------------
let navSortingString = null;
let sortingTxt = ['⇊', '⇅'];
let navCornerString = null;
let cornerTxt = ['⇱', '⇲'];
{
let navbar = document.querySelector('ul.primary');
if (navbar) {
let searchBox = navbar.querySelector('.search');
if (searchBox) {
{
let li = document.createElement('li');
li.classList.add('dropdown');
navSortingString = localStorage.getItem('C89AO3_sorting') || sortingTxt[0];
navSortingString = sortingTxt.includes(navSortingString) ? navSortingString : sortingTxt[0];
let a = document.createElement('a');
a.className = 'halfWidth';
a.textContent = navSortingString;
a.href = '#';
a.addEventListener('click', (e) => {
navSortingString = sortingTxt[(sortingTxt.indexOf(a.textContent) + 1) % sortingTxt.length];
a.textContent = navSortingString;
localStorage.setItem('C89AO3_sorting', navSortingString);
a.blur();
toggleSorting();
});
li.appendChild(a);
navbar.insertBefore(li, searchBox);
}
{
let li = document.createElement('li');
li.classList.add('dropdown');
navCornerString = localStorage.getItem('C89AO3_corner') || cornerTxt[0];
navCornerString = cornerTxt.includes(navCornerString) ? navCornerString : cornerTxt[0];
let a = document.createElement('a');
a.className = 'halfWidth';
a.textContent = navCornerString;
a.href = '#';
a.addEventListener('click', (e) => {
navCornerString = cornerTxt[(cornerTxt.indexOf(a.textContent) + 1) % cornerTxt.length];
a.textContent = navCornerString;
localStorage.setItem('C89AO3_corner', navCornerString);
a.blur();
toggleCorner();
});
li.appendChild(a);
navbar.insertBefore(li, searchBox);
}
}
}
}
// ---------------------------------- MAIN ----------------------------------
const commaRegex = /,/g
function parse(str) {
return str ? parseInt(str.replace(commaRegex, ''), 10) : null;
}
let sortingData = [];
const articles = document.querySelectorAll('li.work[role="article"], li.bookmark[role="article"], dl.work.meta.group');
function removeOldScores() {
document.querySelectorAll('.scoreA').forEach(el => el.remove());
}
function isVisible(el) { return window.getComputedStyle(el).display !== "none"; }
function processArticles() {
removeOldScores();
sortingData = [];
let i = 0;
for (let article of articles) {
let bookmarkCornerOccupied = !!article.querySelector('.status'); // isVisible(article.querySelector('.status'));
let insideAWork = article?.tagName === 'DL';
let stats = article.querySelector('dl.stats');
if (!stats) continue;
const metrics = {
bookmarks: parse(stats.querySelector('dd.bookmarks')?.textContent) || 0,
collections: parse(stats.querySelector('dd.collections')?.textContent) || 0,
comments: parse(stats.querySelector('dd.comments')?.textContent) || 0,
kudos: parse(stats.querySelector('dd.kudos')?.textContent) || 0,
chapters: parse(stats.querySelector('dd.chapters')?.textContent.split('/')[0]) || 1,
};
if (bookmarkCornerOccupied) article.classList.add('cornerFull');
if (insideAWork) article.classList.add('inWork');
let [finalScore, valid, bmin_valid, dimmed, all_scores] = calculateScore(article, metrics);
function makeIndicator(score) {
const el = document.createElement('span');
el.classList.add('scoreA');
el.textContent = Math.round(score);
el.style.backgroundColor = Number.isNaN(score) ? '#b8b8b8' : color(score / 100, 1, true);
if (dimmed) el.classList.add('darkenA');
return el;
}
stats.appendChild(document.createTextNode(' '));
const indicator = makeIndicator(finalScore);
stats.appendChild(indicator);
if (DEBUG) {
const span = document.createElement("span");
span.textContent = indicator.textContent;
span.style.minWidth = '28px';
span.style.zIndex = '999';
span.style.boxShadow = 'rgba(0, 0, 0, 0.38) 0px 0px 10px';
indicator.textContent = '';
indicator.prepend(span);
indicator.style.display = 'flex';
indicator.style.alignItems = 'center';
indicator.style.right = '85px';
if (!valid) {
if (bmin_valid) indicator.classList.add('bminValidA');
else indicator.classList.add('invalidA');
}
stats.appendChild(indicator);
all_scores.forEach(({ score, dimmed, is_valid, is_bmin_valid }) => {
const dbg = makeIndicator(score);
if (dimmed) dbg.classList.add('darkenA');
if (is_valid) {} // dbg.classList.add('validA');
else if (is_bmin_valid) dbg.classList.add('bminValidA');
else dbg.classList.add('invalidA');
dbg.style.position = 'static';
dbg.style.display = 'inline-block';
dbg.style.minWidth = '28px';
indicator.appendChild(dbg);
});
}
let sortKey = dimmed ? finalScore : finalScore + 100;
sortingData.push({ indicator, article, score: finalScore, index: i++, bookmarkCornerOccupied, insideAWork, sortKey });
}
}
function updateAllScores() {
processArticles();
toggleSorting();
toggleCorner();
}
// ---------------------------------- SORTING ----------------------------------
let isSorted = false;
function toggleSorting() {
let parent = articles[0]?.parentNode;
if (parent) {
let run = false;
if (navSortingString === sortingTxt[0]) {
sortingData.sort((a, b) => b.sortKey - a.sortKey); isSorted = true; run = true;
} else if (isSorted) {
sortingData.sort((a, b) => a.index - b.index); isSorted = false; run = true;
}
if (run) sortingData.forEach(({ article }) => {
parent.appendChild(article);
});
}
}
// ---------------------------------- CORNER TOGGLE ----------------------------------
let isInCorner = false;
function toggleCorner() {
let run = false;
if (navCornerString === cornerTxt[0]) {
isInCorner = true; run = true;
} else if (isInCorner) {
isInCorner = false; run = true;
}
if (run) {
sortingData.forEach(({ article, indicator, insideAWork }) => {
if (indicator) {
article.querySelector('.datetime')?.classList.add('isDate'); // can be null if inside a work
if (isInCorner) {
let cornerParent = insideAWork ? article : article.querySelector('div.header.module');
if (cornerParent) {
article.classList.add('inCorner');
cornerParent.appendChild(indicator);
}
}
else {
let stats = article.querySelector('dl.stats');
if (stats) {
article.classList.remove('inCorner');
stats.appendChild(indicator);
}
}
}
});
}
}
updateAllScores()