Update F95_BRATR_Management_Ratings_Helper.js
This commit is contained in:
parent
8821dad70d
commit
bc4e35e28e
@ -29,7 +29,7 @@
|
|||||||
// Cached DOM utilities
|
// Cached DOM utilities
|
||||||
const $ = (sel, root = document) => root.querySelector(sel);
|
const $ = (sel, root = document) => root.querySelector(sel);
|
||||||
const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel));
|
||||||
|
|
||||||
// Optimized text extraction with caching
|
// Optimized text extraction with caching
|
||||||
const textCache = new WeakMap();
|
const textCache = new WeakMap();
|
||||||
const text = el => {
|
const text = el => {
|
||||||
@ -54,17 +54,17 @@
|
|||||||
const csvEscape = s => `"${(s || '').replace(/"/g, '""')}"`;
|
const csvEscape = s => `"${(s || '').replace(/"/g, '""')}"`;
|
||||||
const avg = arr => arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
|
const avg = arr => arr.length ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
|
||||||
const truncate = (s, n = 80) => (s && s.length > n ? s.slice(0, n - 1) + '…' : (s || ''));
|
const truncate = (s, n = 80) => (s && s.length > n ? s.slice(0, n - 1) + '…' : (s || ''));
|
||||||
|
|
||||||
// Optimized Unicode-aware letter counters with caching
|
// Optimized Unicode-aware letter counters with caching
|
||||||
const letterCountCache = new Map();
|
const letterCountCache = new Map();
|
||||||
function countLetters(str) {
|
function countLetters(str) {
|
||||||
if (!str) return { all: 0, ascii: 0, nonAscii: 0 };
|
if (!str) return { all: 0, ascii: 0, nonAscii: 0 };
|
||||||
if (letterCountCache.has(str)) return letterCountCache.get(str);
|
if (letterCountCache.has(str)) return letterCountCache.get(str);
|
||||||
|
|
||||||
const all = (str.match(/\p{L}/gu) || []).length;
|
const all = (str.match(/\p{L}/gu) || []).length;
|
||||||
const ascii = (str.match(/[A-Za-z]/g) || []).length;
|
const ascii = (str.match(/[A-Za-z]/g) || []).length;
|
||||||
const result = { all, ascii, nonAscii: Math.max(0, all - ascii) };
|
const result = { all, ascii, nonAscii: Math.max(0, all - ascii) };
|
||||||
|
|
||||||
// Cache only reasonable length strings to avoid memory bloat
|
// Cache only reasonable length strings to avoid memory bloat
|
||||||
if (str.length < 1000) letterCountCache.set(str, result);
|
if (str.length < 1000) letterCountCache.set(str, result);
|
||||||
return result;
|
return result;
|
||||||
@ -84,7 +84,7 @@
|
|||||||
const enders = (str.match(/[.!?…]/g) || []).length;
|
const enders = (str.match(/[.!?…]/g) || []).length;
|
||||||
return enders < 1;
|
return enders < 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized review scraping with better error handling
|
// Optimized review scraping with better error handling
|
||||||
const reviews = $$('.message--review').map((msg, i) => {
|
const reviews = $$('.message--review').map((msg, i) => {
|
||||||
try {
|
try {
|
||||||
@ -117,7 +117,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleted = msg.classList.contains('message--deleted');
|
const deleted = msg.classList.contains('message--deleted');
|
||||||
|
|
||||||
// Batch action link queries
|
// Batch action link queries
|
||||||
const actionLinks = main.querySelectorAll('a[class*="actionBar-action"]');
|
const actionLinks = main.querySelectorAll('a[class*="actionBar-action"]');
|
||||||
const links = {
|
const links = {
|
||||||
@ -148,7 +148,7 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}).filter(Boolean); // Remove null entries
|
}).filter(Boolean); // Remove null entries
|
||||||
|
|
||||||
// Cliché patterns
|
// Cliché patterns
|
||||||
const cliché = [
|
const cliché = [
|
||||||
{ key: 'good game', label: '“good game”', rx: /\bgood game\b/i },
|
{ key: 'good game', label: '“good game”', rx: /\bgood game\b/i },
|
||||||
@ -159,16 +159,16 @@
|
|||||||
{ key: 'downloads flex', label: '“downloaded hundreds of games”', rx: /\bdownload(ed)? hundreds of games\b/i },
|
{ key: 'downloads flex', label: '“downloaded hundreds of games”', rx: /\bdownload(ed)? hundreds of games\b/i },
|
||||||
{ key: 'mentions 200', label: 'mentions “200” (char/limit/min)', rx: /\b200(?:\s*[- ]?(?:char(?:s|acters?)?|word(?:s)?|limit|minimum|min))?\b/i }
|
{ key: 'mentions 200', label: 'mentions “200” (char/limit/min)', rx: /\b200(?:\s*[- ]?(?:char(?:s|acters?)?|word(?:s)?|limit|minimum|min))?\b/i }
|
||||||
];
|
];
|
||||||
|
|
||||||
// Optimized spam analysis with caching and early returns
|
// Optimized spam analysis with caching and early returns
|
||||||
const spamAnalysisCache = new Map();
|
const spamAnalysisCache = new Map();
|
||||||
function analyzeSpam(body) {
|
function analyzeSpam(body) {
|
||||||
const s = body || '';
|
const s = body || '';
|
||||||
if (!s) return [];
|
if (!s) return [];
|
||||||
|
|
||||||
// Cache results for identical text
|
// Cache results for identical text
|
||||||
if (spamAnalysisCache.has(s)) return spamAnalysisCache.get(s);
|
if (spamAnalysisCache.has(s)) return spamAnalysisCache.get(s);
|
||||||
|
|
||||||
const reasons = [];
|
const reasons = [];
|
||||||
const len = s.length;
|
const len = s.length;
|
||||||
|
|
||||||
@ -182,10 +182,10 @@
|
|||||||
const punctRuns = s.match(/([.!?…,_\-])\1{5,}/g);
|
const punctRuns = s.match(/([.!?…,_\-])\1{5,}/g);
|
||||||
if (punctRuns?.length) {
|
if (punctRuns?.length) {
|
||||||
const totalRunChars = punctRuns.reduce((a, b) => a + b.length, 0);
|
const totalRunChars = punctRuns.reduce((a, b) => a + b.length, 0);
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'SPAM',
|
code: 'SPAM',
|
||||||
text: `DOTCARPET ×${punctRuns.length}`,
|
text: `DOTCARPET ×${punctRuns.length}`,
|
||||||
tip: `Punctuation runs total ${totalRunChars} chars`
|
tip: `Punctuation runs total ${totalRunChars} chars`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,10 +193,10 @@
|
|||||||
const punctChars = (s.match(/[^\w\s]/g) || []).length;
|
const punctChars = (s.match(/[^\w\s]/g) || []).length;
|
||||||
const pRatio = punctChars / len;
|
const pRatio = punctChars / len;
|
||||||
if (pRatio > CONFIG.PUNCTUATION_RATIO_THRESHOLD && len >= 120) {
|
if (pRatio > CONFIG.PUNCTUATION_RATIO_THRESHOLD && len >= 120) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'SPAM',
|
code: 'SPAM',
|
||||||
text: `PUNC ${Math.round(pRatio * 100)}%`,
|
text: `PUNC ${Math.round(pRatio * 100)}%`,
|
||||||
tip: `Non-alphanumeric ratio ${Math.round(pRatio * 100)}%`
|
tip: `Non-alphanumeric ratio ${Math.round(pRatio * 100)}%`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,20 +209,20 @@
|
|||||||
|
|
||||||
const totalTok = tokens.length;
|
const totalTok = tokens.length;
|
||||||
const freq = new Map();
|
const freq = new Map();
|
||||||
|
|
||||||
// Optimized frequency counting
|
// Optimized frequency counting
|
||||||
for (const token of tokens) {
|
for (const token of tokens) {
|
||||||
freq.set(token, (freq.get(token) || 0) + 1);
|
freq.set(token, (freq.get(token) || 0) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniq = freq.size;
|
const uniq = freq.size;
|
||||||
const uniqRatio = uniq / totalTok;
|
const uniqRatio = uniq / totalTok;
|
||||||
|
|
||||||
if (uniqRatio < CONFIG.UNIQUE_RATIO_THRESHOLD) {
|
if (uniqRatio < CONFIG.UNIQUE_RATIO_THRESHOLD) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'SPAM',
|
code: 'SPAM',
|
||||||
text: `LOW_UNIQUE ${uniq}/${totalTok}`,
|
text: `LOW_UNIQUE ${uniq}/${totalTok}`,
|
||||||
tip: `Unique/token ratio ${uniqRatio.toFixed(2)}`
|
tip: `Unique/token ratio ${uniqRatio.toFixed(2)}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,12 +234,12 @@
|
|||||||
maxWord = word;
|
maxWord = word;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxCount / totalTok > CONFIG.MAX_WORD_FREQUENCY_THRESHOLD && totalTok >= 8) {
|
if (maxCount / totalTok > CONFIG.MAX_WORD_FREQUENCY_THRESHOLD && totalTok >= 8) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'SPAM',
|
code: 'SPAM',
|
||||||
text: `REP "${truncate(maxWord, 12)}" ×${maxCount}`,
|
text: `REP "${truncate(maxWord, 12)}" ×${maxCount}`,
|
||||||
tip: `Word appears ${(maxCount / totalTok * 100).toFixed(0)}% of tokens`
|
tip: `Word appears ${(maxCount / totalTok * 100).toFixed(0)}% of tokens`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,18 +247,18 @@
|
|||||||
function analyzeNgrams(n) {
|
function analyzeNgrams(n) {
|
||||||
const ngramMap = new Map();
|
const ngramMap = new Map();
|
||||||
const limit = tokens.length - n;
|
const limit = tokens.length - n;
|
||||||
|
|
||||||
for (let i = 0; i <= limit; i++) {
|
for (let i = 0; i <= limit; i++) {
|
||||||
const ngram = tokens.slice(i, i + n).join(' ');
|
const ngram = tokens.slice(i, i + n).join(' ');
|
||||||
ngramMap.set(ngram, (ngramMap.get(ngram) || 0) + 1);
|
ngramMap.set(ngram, (ngramMap.get(ngram) || 0) + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const [ngram, count] of ngramMap) {
|
for (const [ngram, count] of ngramMap) {
|
||||||
if (count >= CONFIG.NGRAM_REPEAT_THRESHOLD) {
|
if (count >= CONFIG.NGRAM_REPEAT_THRESHOLD) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'SPAM',
|
code: 'SPAM',
|
||||||
text: `REP${n} "${truncate(ngram, 20)}" ×${count}`,
|
text: `REP${n} "${truncate(ngram, 20)}" ×${count}`,
|
||||||
tip: `Repeated ${n}-gram ${count} times`
|
tip: `Repeated ${n}-gram ${count} times`
|
||||||
});
|
});
|
||||||
return true; // Found one, stop looking
|
return true; // Found one, stop looking
|
||||||
}
|
}
|
||||||
@ -274,19 +274,19 @@
|
|||||||
// Optimized short sentence analysis
|
// Optimized short sentence analysis
|
||||||
const sentences = s.split(/(?<=[.!?])\s+/);
|
const sentences = s.split(/(?<=[.!?])\s+/);
|
||||||
const shortFreq = new Map();
|
const shortFreq = new Map();
|
||||||
|
|
||||||
for (const sentence of sentences) {
|
for (const sentence of sentences) {
|
||||||
const toks = sentence.toLowerCase().match(/[a-z0-9']+/g);
|
const toks = sentence.toLowerCase().match(/[a-z0-9']+/g);
|
||||||
if (toks && toks.length <= CONFIG.SHORT_SENTENCE_MAX_LENGTH) {
|
if (toks && toks.length <= CONFIG.SHORT_SENTENCE_MAX_LENGTH) {
|
||||||
const key = toks.join(' ');
|
const key = toks.join(' ');
|
||||||
const count = (shortFreq.get(key) || 0) + 1;
|
const count = (shortFreq.get(key) || 0) + 1;
|
||||||
shortFreq.set(key, count);
|
shortFreq.set(key, count);
|
||||||
|
|
||||||
if (count >= CONFIG.NGRAM_REPEAT_THRESHOLD) {
|
if (count >= CONFIG.NGRAM_REPEAT_THRESHOLD) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'SPAM',
|
code: 'SPAM',
|
||||||
text: `SHORT_SENT "${truncate(key, 18)}" ×${count}`,
|
text: `SHORT_SENT "${truncate(key, 18)}" ×${count}`,
|
||||||
tip: `Short sentence repeated ${count} times`
|
tip: `Short sentence repeated ${count} times`
|
||||||
});
|
});
|
||||||
break; // Found one, stop looking
|
break; // Found one, stop looking
|
||||||
}
|
}
|
||||||
@ -297,10 +297,10 @@
|
|||||||
if (spamAnalysisCache.size < 1000) {
|
if (spamAnalysisCache.size < 1000) {
|
||||||
spamAnalysisCache.set(s, reasons);
|
spamAnalysisCache.set(s, reasons);
|
||||||
}
|
}
|
||||||
|
|
||||||
return reasons;
|
return reasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized low-effort diagnosis with caching
|
// Optimized low-effort diagnosis with caching
|
||||||
const diagnosisCache = new Map();
|
const diagnosisCache = new Map();
|
||||||
function diagnoseLowEffort(r, cutoff = CONFIG.DEFAULT_CUTOFF) {
|
function diagnoseLowEffort(r, cutoff = CONFIG.DEFAULT_CUTOFF) {
|
||||||
@ -314,10 +314,10 @@
|
|||||||
|
|
||||||
// Length check
|
// Length check
|
||||||
if (!r.deleted && (r.effLen || 0) < cutoff) {
|
if (!r.deleted && (r.effLen || 0) < cutoff) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'LEN',
|
code: 'LEN',
|
||||||
text: `LEN ${r.effLen}<${cutoff}`,
|
text: `LEN ${r.effLen}<${cutoff}`,
|
||||||
tip: `Effective length ${r.effLen} is below cutoff ${cutoff}`
|
tip: `Effective length ${r.effLen} is below cutoff ${cutoff}`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -326,11 +326,11 @@
|
|||||||
for (const c of cliché) {
|
for (const c of cliché) {
|
||||||
const match = body.match(c.rx);
|
const match = body.match(c.rx);
|
||||||
if (match) {
|
if (match) {
|
||||||
reasons.push({
|
reasons.push({
|
||||||
code: 'CLICHÉ',
|
code: 'CLICHÉ',
|
||||||
text: `CLICHÉ: "${truncate(match[0], 28)}"`,
|
text: `CLICHÉ: "${truncate(match[0], 28)}"`,
|
||||||
tip: `Matched: ${match[0]}`,
|
tip: `Matched: ${match[0]}`,
|
||||||
key: c.key
|
key: c.key
|
||||||
});
|
});
|
||||||
break; // Found one cliché, no need to check others
|
break; // Found one cliché, no need to check others
|
||||||
}
|
}
|
||||||
@ -342,7 +342,7 @@
|
|||||||
const tip = `letters: ascii=${ascii}, nonAscii=${nonAscii}, share=${(nonAscii / Math.max(1, all)).toFixed(2)}`;
|
const tip = `letters: ascii=${ascii}, nonAscii=${nonAscii}, share=${(nonAscii / Math.max(1, all)).toFixed(2)}`;
|
||||||
reasons.push({ code: 'LANG', text: 'NON-EN', tip });
|
reasons.push({ code: 'LANG', text: 'NON-EN', tip });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Style check
|
// Style check
|
||||||
if (isRunOnWall(body)) {
|
if (isRunOnWall(body)) {
|
||||||
reasons.push({ code: 'STYLE', text: 'RUN-ON', tip: 'Long block with no sentence enders' });
|
reasons.push({ code: 'STYLE', text: 'RUN-ON', tip: 'Long block with no sentence enders' });
|
||||||
@ -363,15 +363,15 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
const result = { low, reasons };
|
const result = { low, reasons };
|
||||||
|
|
||||||
// Cache result (limit cache size)
|
// Cache result (limit cache size)
|
||||||
if (diagnosisCache.size < 500) {
|
if (diagnosisCache.size < 500) {
|
||||||
diagnosisCache.set(cacheKey, result);
|
diagnosisCache.set(cacheKey, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Panel
|
// Panel
|
||||||
const panel = document.createElement('div');
|
const panel = document.createElement('div');
|
||||||
panel.id = 'bratr-helper';
|
panel.id = 'bratr-helper';
|
||||||
@ -393,12 +393,14 @@
|
|||||||
<label><input type="checkbox" class="bh-cliche-only"> Only reviews matching selected cliché</label>
|
<label><input type="checkbox" class="bh-cliche-only"> Only reviews matching selected cliché</label>
|
||||||
<button class="bh-copy">Copy CSV</button>
|
<button class="bh-copy">Copy CSV</button>
|
||||||
<button class="bh-reset">Reset</button>
|
<button class="bh-reset">Reset</button>
|
||||||
|
<button class="bh-prev-page">← Prev</button>
|
||||||
|
<button class="bh-next-page">Next →</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="bh-table"></div>
|
<div class="bh-table"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
document.body.appendChild(panel);
|
document.body.appendChild(panel);
|
||||||
|
|
||||||
// Styles
|
// Styles
|
||||||
const css = document.createElement('style');
|
const css = document.createElement('style');
|
||||||
css.textContent = `
|
css.textContent = `
|
||||||
@ -412,7 +414,9 @@
|
|||||||
#bratr-helper .bh-controls{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}
|
#bratr-helper .bh-controls{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}
|
||||||
#bratr-helper .bh-controls label{display:flex;gap:6px;align-items:center;background:#161616;border:1px solid #2a2a2a;border-radius:8px;padding:4px 8px}
|
#bratr-helper .bh-controls label{display:flex;gap:6px;align-items:center;background:#161616;border:1px solid #2a2a2a;border-radius:8px;padding:4px 8px}
|
||||||
#bratr-helper input, #bratr-helper select{background:#0f0f0f;border:1px solid #333;color:#ddd;border-radius:6px;padding:3px 6px}
|
#bratr-helper input, #bratr-helper select{background:#0f0f0f;border:1px solid #333;color:#ddd;border-radius:6px;padding:3px 6px}
|
||||||
#bratr-helper button.bh-copy,#bratr-helper button.bh-reset{background:#1e1e1e;border:1px solid #444;color:#ddd;border-radius:8px;padding:4px 10px;cursor:pointer}
|
#bratr-helper button.bh-copy,#bratr-helper button.bh-reset,#bratr-helper button.bh-prev-page,#bratr-helper button.bh-next-page{background:#1e1e1e;border:1px solid #444;color:#ddd;border-radius:8px;padding:4px 10px;cursor:pointer}
|
||||||
|
#bratr-helper button.bh-prev-page,#bratr-helper button.bh-next-page{background:#2a4a2a;border-color:#4a6a4a;font-size:11px;padding:3px 8px}
|
||||||
|
#bratr-helper button.bh-prev-page:hover,#bratr-helper button.bh-next-page:hover{background:#3a5a3a}
|
||||||
#bratr-helper .bh-table{overflow:auto;max-height:48vh;border:1px solid #2a2a2a;border-radius:8px}
|
#bratr-helper .bh-table{overflow:auto;max-height:48vh;border:1px solid #2a2a2a;border-radius:8px}
|
||||||
#bratr-helper table{width:100%;border-collapse:collapse}
|
#bratr-helper table{width:100%;border-collapse:collapse}
|
||||||
#bratr-helper th,#bratr-helper td{padding:6px 8px;border-bottom:1px solid #232323;vertical-align:top}
|
#bratr-helper th,#bratr-helper td{padding:6px 8px;border-bottom:1px solid #232323;vertical-align:top}
|
||||||
@ -433,7 +437,7 @@
|
|||||||
@media (max-width: 880px){#bratr-helper{left:8px;right:8px;width:auto}}
|
@media (max-width: 880px){#bratr-helper{left:8px;right:8px;width:auto}}
|
||||||
`;
|
`;
|
||||||
document.head.appendChild(css);
|
document.head.appendChild(css);
|
||||||
|
|
||||||
const ui = {
|
const ui = {
|
||||||
min: panel.querySelector('.bh-minrating'),
|
min: panel.querySelector('.bh-minrating'),
|
||||||
maxlen: panel.querySelector('.bh-maxlen'),
|
maxlen: panel.querySelector('.bh-maxlen'),
|
||||||
@ -446,7 +450,7 @@
|
|||||||
table: panel.querySelector('.bh-table'),
|
table: panel.querySelector('.bh-table'),
|
||||||
metrics: panel.querySelector('.bh-metrics')
|
metrics: panel.querySelector('.bh-metrics')
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populate cliché dropdown
|
// Populate cliché dropdown
|
||||||
const frag = document.createDocumentFragment();
|
const frag = document.createDocumentFragment();
|
||||||
cliché.forEach(c => {
|
cliché.forEach(c => {
|
||||||
@ -456,7 +460,7 @@
|
|||||||
frag.appendChild(opt);
|
frag.appendChild(opt);
|
||||||
});
|
});
|
||||||
ui.clicheSel.appendChild(frag);
|
ui.clicheSel.appendChild(frag);
|
||||||
|
|
||||||
// collapse default minimized with persistence
|
// collapse default minimized with persistence
|
||||||
const COLLAPSE_KEY = 'bh-collapsed';
|
const COLLAPSE_KEY = 'bh-collapsed';
|
||||||
const bodyEl = panel.querySelector('.bh-body');
|
const bodyEl = panel.querySelector('.bh-body');
|
||||||
@ -469,7 +473,7 @@
|
|||||||
collapseBtn.addEventListener('click', () => setCollapsed(bodyEl.style.display !== 'none'));
|
collapseBtn.addEventListener('click', () => setCollapsed(bodyEl.style.display !== 'none'));
|
||||||
const stored = (typeof localStorage !== 'undefined') ? localStorage.getItem(COLLAPSE_KEY) : null;
|
const stored = (typeof localStorage !== 'undefined') ? localStorage.getItem(COLLAPSE_KEY) : null;
|
||||||
setCollapsed(stored === null ? true : stored === '1');
|
setCollapsed(stored === null ? true : stored === '1');
|
||||||
|
|
||||||
// Optimized event handling with debouncing
|
// Optimized event handling with debouncing
|
||||||
let renderTimeout;
|
let renderTimeout;
|
||||||
const debouncedRender = () => {
|
const debouncedRender = () => {
|
||||||
@ -494,6 +498,75 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Navigation helper function
|
||||||
|
function navigateToPage(pageNumber) {
|
||||||
|
try {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
const urlMatch = currentUrl.match(/\/bratr-ratings\/(?:page-(\d+)\/)?management/);
|
||||||
|
|
||||||
|
if (urlMatch) {
|
||||||
|
let targetUrl;
|
||||||
|
if (pageNumber === 1) {
|
||||||
|
// For page 1, remove the page number from URL
|
||||||
|
targetUrl = currentUrl.replace(
|
||||||
|
/\/bratr-ratings\/page-\d+\/management/,
|
||||||
|
'/bratr-ratings/management'
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// For other pages, add or update the page number
|
||||||
|
targetUrl = currentUrl.replace(
|
||||||
|
/\/bratr-ratings\/(?:page-\d+\/)?management/,
|
||||||
|
`/bratr-ratings/page-${pageNumber}/management`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Navigating to page ${pageNumber}: ${targetUrl}`);
|
||||||
|
window.location.href = targetUrl;
|
||||||
|
} else {
|
||||||
|
console.warn('Could not determine current page number from URL:', currentUrl);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error navigating to page:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previous page handler
|
||||||
|
panel.querySelector('.bh-prev-page').addEventListener('click', () => {
|
||||||
|
try {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
const urlMatch = currentUrl.match(/\/bratr-ratings\/(?:page-(\d+)\/)?management/);
|
||||||
|
|
||||||
|
if (urlMatch) {
|
||||||
|
const currentPage = urlMatch[1] ? parseInt(urlMatch[1], 10) : 1;
|
||||||
|
const prevPage = currentPage - 1;
|
||||||
|
|
||||||
|
if (prevPage >= 1) {
|
||||||
|
navigateToPage(prevPage);
|
||||||
|
} else {
|
||||||
|
console.log('Already on first page');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error navigating to previous page:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Next page handler
|
||||||
|
panel.querySelector('.bh-next-page').addEventListener('click', () => {
|
||||||
|
try {
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
const urlMatch = currentUrl.match(/\/bratr-ratings\/(?:page-(\d+)\/)?management/);
|
||||||
|
|
||||||
|
if (urlMatch) {
|
||||||
|
const currentPage = urlMatch[1] ? parseInt(urlMatch[1], 10) : 1;
|
||||||
|
const nextPage = currentPage + 1;
|
||||||
|
navigateToPage(nextPage);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error navigating to next page:', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Optimized event delegation
|
// Optimized event delegation
|
||||||
const controlElements = [ui.min, ui.maxlen, ui.user, ui.lowonly, ui.from, ui.to, ui.clicheSel, ui.clicheOnly];
|
const controlElements = [ui.min, ui.maxlen, ui.user, ui.lowonly, ui.from, ui.to, ui.clicheSel, ui.clicheOnly];
|
||||||
['input', 'change'].forEach(eventType => {
|
['input', 'change'].forEach(eventType => {
|
||||||
@ -503,7 +576,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function withinDates(iso, from, to) {
|
function withinDates(iso, from, to) {
|
||||||
if (!iso) return true;
|
if (!iso) return true;
|
||||||
const t = new Date(iso).getTime();
|
const t = new Date(iso).getTime();
|
||||||
@ -511,7 +584,7 @@
|
|||||||
if (to && t > new Date(to).getTime()) return false;
|
if (to && t > new Date(to).getTime()) return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reviewMatchesSelectedCliche(r) {
|
function reviewMatchesSelectedCliche(r) {
|
||||||
if (!ui.clicheOnly.checked) return true;
|
if (!ui.clicheOnly.checked) return true;
|
||||||
const selected = ui.clicheSel.value;
|
const selected = ui.clicheSel.value;
|
||||||
@ -520,7 +593,7 @@
|
|||||||
if (!def) return true;
|
if (!def) return true;
|
||||||
return def.rx.test(r.bodyTxt || '');
|
return def.rx.test(r.bodyTxt || '');
|
||||||
}
|
}
|
||||||
|
|
||||||
function currentRows() {
|
function currentRows() {
|
||||||
const min = parseFloat(ui.min.value || '0');
|
const min = parseFloat(ui.min.value || '0');
|
||||||
const maxlen = parseInt(ui.maxlen.value || '0', 10);
|
const maxlen = parseInt(ui.maxlen.value || '0', 10);
|
||||||
@ -528,25 +601,25 @@
|
|||||||
const lowOnly = ui.lowonly.checked;
|
const lowOnly = ui.lowonly.checked;
|
||||||
const from = ui.from.value;
|
const from = ui.from.value;
|
||||||
const to = ui.to.value;
|
const to = ui.to.value;
|
||||||
|
|
||||||
return reviews.filter(r => {
|
return reviews.filter(r => {
|
||||||
if (Number.isFinite(min) && r.rating != null && r.rating < min) return false;
|
if (Number.isFinite(min) && r.rating != null && r.rating < min) return false;
|
||||||
if (user && !r.author.toLowerCase().includes(user)) return false;
|
if (user && !r.author.toLowerCase().includes(user)) return false;
|
||||||
if (from || to) { if (!withinDates(r.timeIso, from, to)) return false; }
|
if (from || to) { if (!withinDates(r.timeIso, from, to)) return false; }
|
||||||
if (!reviewMatchesSelectedCliche(r)) return false;
|
if (!reviewMatchesSelectedCliche(r)) return false;
|
||||||
|
|
||||||
const diag = diagnoseLowEffort(r, maxlen);
|
const diag = diagnoseLowEffort(r, maxlen);
|
||||||
if (lowOnly && !diag.low) return false;
|
if (lowOnly && !diag.low) return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized render function with better error handling
|
// Optimized render function with better error handling
|
||||||
function render() {
|
function render() {
|
||||||
try {
|
try {
|
||||||
const maxlen = parseInt(ui.maxlen?.value || CONFIG.DEFAULT_CUTOFF.toString(), 10);
|
const maxlen = parseInt(ui.maxlen?.value || CONFIG.DEFAULT_CUTOFF.toString(), 10);
|
||||||
const live = reviews.filter(r => !r.deleted);
|
const live = reviews.filter(r => !r.deleted);
|
||||||
|
|
||||||
// Optimized metrics calculation
|
// Optimized metrics calculation
|
||||||
const lowCount = live.filter(r => {
|
const lowCount = live.filter(r => {
|
||||||
try {
|
try {
|
||||||
@ -556,12 +629,12 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}).length;
|
}).length;
|
||||||
|
|
||||||
const avgRating = avg(live.map(r => r.rating || 0)).toFixed(2);
|
const avgRating = avg(live.map(r => r.rating || 0)).toFixed(2);
|
||||||
const clicheInfo = ui.clicheOnly?.checked
|
const clicheInfo = ui.clicheOnly?.checked
|
||||||
? ` • cliché: ${ui.clicheSel?.options[ui.clicheSel?.selectedIndex]?.text || 'All'}`
|
? ` • cliché: ${ui.clicheSel?.options[ui.clicheSel?.selectedIndex]?.text || 'All'}`
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
if (ui.metrics) {
|
if (ui.metrics) {
|
||||||
ui.metrics.textContent = `on page: ${reviews.length} • avg ★ ${avgRating} • low-effort flagged: ${lowCount}${clicheInfo}`;
|
ui.metrics.textContent = `on page: ${reviews.length} • avg ★ ${avgRating} • low-effort flagged: ${lowCount}${clicheInfo}`;
|
||||||
}
|
}
|
||||||
@ -583,7 +656,7 @@
|
|||||||
'LEN': 'low', 'CLICHÉ': 'low', 'SPAM': 'spam',
|
'LEN': 'low', 'CLICHÉ': 'low', 'SPAM': 'spam',
|
||||||
'DELETED': 'delet', 'LANG': 'lang', 'STYLE': 'style'
|
'DELETED': 'delet', 'LANG': 'lang', 'STYLE': 'style'
|
||||||
};
|
};
|
||||||
|
|
||||||
const flagHtml = diag.reasons.map(reason => {
|
const flagHtml = diag.reasons.map(reason => {
|
||||||
let pill = 'pill';
|
let pill = 'pill';
|
||||||
const extra = pillClassByCode[reason.code];
|
const extra = pillClassByCode[reason.code];
|
||||||
@ -612,7 +685,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
html.push(`</tbody></table>`);
|
html.push(`</tbody></table>`);
|
||||||
|
|
||||||
if (ui.table) {
|
if (ui.table) {
|
||||||
ui.table.innerHTML = html.join('');
|
ui.table.innerHTML = html.join('');
|
||||||
|
|
||||||
@ -627,7 +700,7 @@
|
|||||||
console.error('Error in render function:', error);
|
console.error('Error in render function:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onRowClick(e) {
|
function onRowClick(e) {
|
||||||
if (e.target.closest('a, button')) return;
|
if (e.target.closest('a, button')) return;
|
||||||
const tr = e.target.closest('tr[data-i]');
|
const tr = e.target.closest('tr[data-i]');
|
||||||
@ -635,14 +708,14 @@
|
|||||||
const idx = parseInt(tr.getAttribute('data-i'), 10);
|
const idx = parseInt(tr.getAttribute('data-i'), 10);
|
||||||
const r = reviews[idx];
|
const r = reviews[idx];
|
||||||
if (!r || !r.node) return;
|
if (!r || !r.node) return;
|
||||||
|
|
||||||
r.node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
r.node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
r.node.classList.remove('bh-target-pulse');
|
r.node.classList.remove('bh-target-pulse');
|
||||||
void r.node.offsetWidth;
|
void r.node.offsetWidth;
|
||||||
r.node.classList.add('bh-target-pulse');
|
r.node.classList.add('bh-target-pulse');
|
||||||
setTimeout(() => r.node.classList.remove('bh-target-pulse'), 1800);
|
setTimeout(() => r.node.classList.remove('bh-target-pulse'), 1800);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tint live page with reasons as tooltip
|
// Tint live page with reasons as tooltip
|
||||||
function retint() {
|
function retint() {
|
||||||
const maxlen = parseInt(ui.maxlen?.value || '220', 10);
|
const maxlen = parseInt(ui.maxlen?.value || '220', 10);
|
||||||
@ -664,9 +737,9 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render();
|
render();
|
||||||
|
|
||||||
// Optimized CSV export with better error handling
|
// Optimized CSV export with better error handling
|
||||||
panel.querySelector('.bh-copy').addEventListener('click', async () => {
|
panel.querySelector('.bh-copy').addEventListener('click', async () => {
|
||||||
try {
|
try {
|
||||||
@ -674,7 +747,7 @@
|
|||||||
const maxlen = parseInt(ui.maxlen?.value || CONFIG.DEFAULT_CUTOFF.toString(), 10);
|
const maxlen = parseInt(ui.maxlen?.value || CONFIG.DEFAULT_CUTOFF.toString(), 10);
|
||||||
const header = ['author', 'rating', 'thread', 'threadUrl', 'timeIso', 'effLen', 'rawLen', 'deleted', 'low', 'reasons', 'body'];
|
const header = ['author', 'rating', 'thread', 'threadUrl', 'timeIso', 'effLen', 'rawLen', 'deleted', 'low', 'reasons', 'body'];
|
||||||
const lines = [header.join(',')];
|
const lines = [header.join(',')];
|
||||||
|
|
||||||
// Optimized CSV generation
|
// Optimized CSV generation
|
||||||
for (const r of rows) {
|
for (const r of rows) {
|
||||||
try {
|
try {
|
||||||
@ -697,9 +770,9 @@
|
|||||||
console.warn('Error processing row for CSV:', error);
|
console.warn('Error processing row for CSV:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const csv = lines.join('\n');
|
const csv = lines.join('\n');
|
||||||
|
|
||||||
// Try multiple clipboard methods
|
// Try multiple clipboard methods
|
||||||
try {
|
try {
|
||||||
if (typeof GM_setClipboard !== 'undefined') {
|
if (typeof GM_setClipboard !== 'undefined') {
|
||||||
@ -725,4 +798,4 @@
|
|||||||
console.error('Error exporting CSV:', error);
|
console.error('Error exporting CSV:', error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
Loading…
x
Reference in New Issue
Block a user