// ==UserScript==
// @name lbaqr-anti-flood
// @namespace http://tampermonkey.net/
// @license MIT
// @version 0.7.2
// @description Sur lbarq.fr (La Boîte à QR) : replie automatiquement les longues séries de questions posées les unes à la suite des autres par un même auteur.
// @description:en On lbarq.fr (French speaking website) : automatically folds long series of questions from the same author.
// @author Ed38
// @match https://*.lbaqr.fr/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=lbaqr.fr
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @run-at document-body
// ==/UserScript==
(function () {
'use strict';
let defMax = 4; // Nombre maximal de questions par default avant repliage automatique.
// Default maximum number of questions until start folding.
let defTolerance = 0;
const maxTolerance = 3;
let debug = true;
const msg_inputMax = "Nombre maximum de questions\n\nVeuillez entrer le nombre de questions au delà duquel le repliage automatique doit s'effectuer.\n\nLa page courante sera rechargée après validation.";
const msgErr_inputMax = "La valeur attendue est un nombre entier supérieur à 0.";
const msg_inputTolerance = "Tolérance (0-{maxTolerance})\n" +
"Valeur recommandée : 0 ou 1.\n\n" +
"Le repliage peut s'effectuer même si quelques questions d'autres auteurs se trouvent à l'intérieur de la série ; " +
"la valeur de tolérance indique le nombre de questions consécutives pouvant s'intercaler (elles ne seront pas masquées).\n\n" +
"La page courante sera rechargée après validation.";
const msgErr_inputTolerance = "Tolérance\n\nLa valeur attendue est un nombre entier compris entre 0 et {maxTolerance}.";
const msgErr_consistency = "Le nombre maximum de questions avant repliage doit être supérieur à la valeur de tolérance.";
const msgResetDefaults = "Réinitialiser les paramètres à leur valeur par défaut.\n\nLa page sera rechargée après validation.";
const msg_hide = "Masquer les {nbOfExtraQ} questions suivantes de {author}";
const msg_show = "Afficher les {nbOfExtraQ} autres questions de {author}";
const msg_menuMax = "Nb max ({qMax})";
const msg_menuTolerance = "Tolérance ({tolerance})";
const msg_menuResetDefaults = "Réinitialiser les paramètres";
const validPage = /^https:\/\/(?:.*\.)?lbaqr\.fr\/(?:category\/.*|top\/|login\/)?$/;
let menuMaxId;
let menuToleranceId;
let menuResetDefaultsId;
let qMax = GM_getValue("qMax", null);
if (!qMax){
qMax = defMax;
}
let tolerance = GM_getValue("tolerance", null);
if (!tolerance){
tolerance = defTolerance;
}
setMenu();
let unfoldedList = document.createElement("div");
unfoldedList.setAttribute("id","unfoldedList");
document.body.appendChild(unfoldedList);
let css = `
li.folder {
font-size:0.9em;
padding:6px;
list-style-type:'▶ ';
list-style-position:inside;
list-style-image:url('data:image/svg+xml,%3Csvg fill="%23000000" height="12px" width="12px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 330 330" %3E%3Cpath id="XMLID_222_" d="M250.606,154.389l-150-149.996c-5.857-5.858-15.355-5.858-21.213,0.001 c-5.857,5.858-5.857,15.355,0.001,21.213l139.393,139.39L79.393,304.394c-5.857,5.858-5.857,15.355,0.001,21.213 C82.322,328.536,86.161,330,90,330s7.678-1.464,10.607-4.394l149.999-150.004c2.814-2.813,4.394-6.628,4.394-10.606 C255,161.018,253.42,157.202,250.606,154.389z"/%3E%3C/svg%3E');
background: linear-gradient(0deg, rgba(0,213,255,0.5) 0%, rgba(135,239,255,.5) 16%, rgba(255,255,255,0.5) 100%);
}
li.folderOpen{
list-style-type:'▼ ';
list-style-image:url('data:image/svg+xml,%3Csvg fill="%23000000" height="12px" width="12px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 330 330" %3E%3Cpath id="XMLID_225_" d="M325.607,79.393c-5.857-5.857-15.355-5.858-21.213,0.001l-139.39,139.393L25.607,79.393 c-5.857-5.857-15.355-5.858-21.213,0.001c-5.858,5.858-5.858,15.355,0,21.213l150.004,150c2.813,2.813,6.628,4.393,10.606,4.393 s7.794-1.581,10.606-4.394l149.996-150C331.465,94.749,331.465,85.251,325.607,79.393z"/%3E%3C/svg%3E');
background: linear-gradient(0deg, rgba(255,255,255,0.5) 00%, rgba(0,213,255,0.25) 50%, rgba(255,255,255,0.5) 100%);
}
li.hide{
max-height:0px !important;
overflow:hidden;
padding-top: 0px !important;
padding-bottom: 0px !important;
border-bottom: 0px !important;
opacity:0;
}
li.transition{
transition-property: padding-top, padding-bottom, max-height, opacity;
transition-timing-function: cubic-bezier(.05,.23,0,1);
transition-duration: 800ms;
}
li.show{
border-left:4px solid rgba(0,213,255,0.25);
padding-left:16px !important;
}
ul.categoryStreamList li.folder:first-child, li.folder + li.folder {
display: none;
}
`;
GM_addStyle(css);
// Select the node that will be observed for mutations
let targetNode = document.body;
// Options for the observer (which mutations to observe)
let config = { childList: true , subtree: true};
// Callback function to execute when mutations are observed
let callback = function (mutationRecords) {
if ( !validPage.test(document.location) || !mutationRecords[0].addedNodes[0] || (mutationRecords[0].addedNodes[0].nodeName != "APP-CATEGORY-STREAM-ITEM") ){
return;
}
//Remove old folders.
let waste;
while ( ((waste=document.querySelector("ul.categoryStreamList"))) && waste.firstElementChild.classList.contains("folder") ){
waste.firstElementChild.remove();
}
let questions = document.evaluate('//li[@data-author]', document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null );
let c=0;
let author="";
let question;
let question1;
let i=0;
while ( ((question1 = questions.snapshotItem(i)))){
c = 0;
author = question1.dataset.author;
i++;
counter:{
while ( ((question = questions.snapshotItem(i))) ) {
if (question.dataset.author == author ){
i++;
c++;
}
else{
let j=1;
toleranceL: {
while( j <= tolerance ){
if ( questions.snapshotItem(i+j) && questions.snapshotItem(i+j).dataset.author == author ){
i = i + j + 1;
c++;
break toleranceL;
}
else {
j++;
}
}
break counter;
}
}
}
}
if ( c + 1 > qMax ){
if ( question1.dataset.fnumber ) {
// prevously processed
if ( question1.dataset.fnumber == c ){
//nothing to do
}
else if ( question1.dataset.folded == 1 ) {
//new question(s) to fold";
question1.dataset.folded = 0;
question1.dataset.fnumber = c;
toggleQ(question1.parentNode.nextSibling.getElementsByTagName("a")[0]);
}
}
else {
// Create new folder
let newFolder = document.createElement("li");
newFolder.classList.add("folder");
let button=document.createElement("a");
button.addEventListener ("click", toggleQ, false)
newFolder.appendChild(button);
question1.parentNode.parentNode.insertBefore(newFolder,question1.parentNode.nextSibling);
if ( wasUnfolded(question1) ){
question1.dataset.folded = 1;
}
else {
question1.dataset.folded = 0;
}
question1.dataset.fnumber = c;
toggleQ(button);
}
}
}
};
// Create an observer instance linked to the callback function
let observer = new MutationObserver(callback);
observer.observe(targetNode, config);
// END
// Function
function log(m){
if ( debug ){
console.log(m);
}
}
// Fold / Unfold
function toggleQ(link){
if ( link.target ){
link = this;
}
let folder = link.parentNode;
let firstQuestion = folder.previousSibling.querySelector("[data-author]");
let nbOfExtraQ = firstQuestion.dataset.fnumber;
let author = firstQuestion.dataset.author;
let show;
firstQuestion.style.borderBottom = "none";
if (firstQuestion.dataset.folded == 1) {
firstQuestion.dataset.folded = 0;
link.innerHTML = varsInMsg( { "{nbOfExtraQ}": nbOfExtraQ, "{author}": author } , msg_hide) ;
show = true;
folder.classList.add("folderOpen");
saveUnfolded(firstQuestion);
}
else{
firstQuestion.dataset.folded = 1;
link.innerHTML = varsInMsg( { "{nbOfExtraQ}": nbOfExtraQ, "{author}": author } , msg_show) ;
folder.classList.remove("folderOpen")
show = false;
removeUnfolded(firstQuestion);
}
let i = 1
let row = folder;
let li;
while ( i <= nbOfExtraQ ){
row = row.nextSibling;
if ( row.classList.contains("folder") ){
// remove old folders (scrolling backwards)
row = row.nextSibling;
row.previousSibling.remove();
}
if( li=row.querySelector('[data-author="'+author+'"]') ){
if( li.dataset.author && li.dataset.author==author ){
if (show === true){
li.classList.add("transition");
li.classList.add("show");
li.classList.remove("hide");
}
else{
li.classList.add("hide");
li.classList.remove("show");
}
i++;
}
}
}
}
// Save unfolded status
function saveUnfolded(q){
let title = q.querySelector('[class="questionCardTitle"]');
let storage = document.querySelector('[id="unfoldedList"');
storage.setAttribute("data-" + hashKey(title.innerHTML),"1");
}
function removeUnfolded(q){
let title = q.querySelector('[class="questionCardTitle"]');
let storage = document.querySelector('[id="unfoldedList"');
storage.removeAttribute("data-" + hashKey(title.innerHTML));
}
function wasUnfolded(q){
let title = q.querySelector('[class="questionCardTitle"]');
return document.querySelector('[data-' + hashKey(title.innerHTML) + '="1"]');
}
// An implementation of Jenkins's one-at-a-time hash
// <http://en.wikipedia.org/wiki/Jenkins_hash_function>
function hashKey(key) {
var hash = 0, i = key.length;
while (i--) {
hash += key.charCodeAt(i);
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
// Menu
function setMenu(){
if ( menuMaxId ){
GM_unregisterMenuCommand(menuMaxId);
}
menuMaxId = GM_registerMenuCommand(varsInMsg({"{qMax}":qMax},msg_menuMax), setMax);
if ( menuToleranceId ){
GM_unregisterMenuCommand(menuToleranceId);
}
menuToleranceId = GM_registerMenuCommand(varsInMsg({"{tolerance}":tolerance},msg_menuTolerance), setTolerance);
if ( menuResetDefaultsId ){
GM_unregisterMenuCommand(menuResetDefaultsId);
}
menuResetDefaultsId = GM_registerMenuCommand(msg_menuResetDefaults, resetDefaults);
}
// Set max number of questions
function setMax() {
let r = window.prompt(msg_inputMax, qMax);
if ( r ){
if ( Number.isInteger(+r) && r>0 ){
if ( r <= tolerance ) {
alert(msgErr_consistency);
return
}
qMax = r;
GM_setValue("qMax", qMax);
setMenu();
location.reload();
}
else {
alert(msgErr_inputMax);
}
}
}
// Set tolerance
function setTolerance() {
let r = window.prompt(varsInMsg({"{maxTolerance}":maxTolerance}, msg_inputTolerance), tolerance);
if ( r ){
if ( Number.isInteger(+r) && r>=0 && r <= maxTolerance ){
if ( r >= qMax ) {
alert(msgErr_consistency);
return
}
tolerance = r;
GM_setValue("tolerance", tolerance);
setMenu();
location.reload();
}
else {
alert(varsInMsg({"{maxTolerance}":maxTolerance},msgErr_inputTolerance));
}
}
}
//Reset defaults
function resetDefaults(){
let r = confirm(msgResetDefaults) ;
if ( r === true ) {
GM_setValue("qMax", defMax);
GM_setValue("tolerance", defTolerance);
location.reload();
}
}
function varsInMsg(vars,string){
let keys = Object.keys(vars);
for ( let k in keys){
string = string.replaceAll(keys[k], vars[keys[k]] );
}
return string;
}
})();