// ==UserScript==
// @name JVChat Premium
// @description Outil de discussion instantanée pour les forums de Jeuxvideo.com
// @author Blaff
// @namespace JVChatPremium
// @version 0.1.13
// @match http://*.jeuxvideo.com/forums/42-*
// @match https://*.jeuxvideo.com/forums/42-*
// @match http://*.jeuxvideo.com/forums/1-*
// @match https://*.jeuxvideo.com/forums/1-*
// @grant none
// @downloadURL none
// ==/UserScript==
/*
ROADMAP:
- Si un message contient un gros paragraphe sans retour à la ligne, c'st la grosse merde, la leftbar se réduit au maximum et la horizontale scrollbar s'active
- Smooth transition on append messages + Fade-in
- Détection captcha
- Separator des nouveaux messages au changement d'onglet
- Notification des nouveaux messages dans le titre de l'onglet + erreurs aussi
- Si la page retournée vaut 1: bug des messages supprimés, donc on essaye avec la page d'avant, idem s'il y a 0 messages sur la page
- Bouton actualiser les messages (+ afficher le delai courrant d'actualisation)
- Bouton désactiver JVChat
- Bouton retour liste des sujets
- Citations
- Notification avec @pseudo
- Blacklist
- Pouvoir voir les anciens messages
- La leftbar ne se rétrécie pas si le titre du topic n'a pas d'espaces
*/
let CSS = ``;
let PANEL = `
Profil
Topic
`;
function getForm(doc) {
return doc.getElementsByClassName('form-post-message')[0];
}
let freshForm = getForm(document);
let firstMessageId = undefined;
let allMessagesId = new Set();
let userConnected = undefined;
let updateIntervals = [2, 3, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10, 10, 10, 10, 10, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 30, 30, 30, 30, 30, 30, 30, 30, 60];
let transisitions = [0, 0, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 13, 19, 19, 19, 19, 19, 19, 19, 19, 31];
let updateIntervalIdx = 0;
let updateIntervalMax = updateIntervals.length - 1;
let isLocked = false;
let isError = false;
let isReduced = true;
let storageKey = "jvchat-premium-default-reduced";
let nbNewMessage = 0;
let favicon = makeFavicon();
function getTopicLocked(elem) {
let lock = elem.getElementsByClassName("message-lock-topic")[0];
if (lock === undefined) {
return lock;
}
let reason = lock.getElementsByTagName("span")[0].textContent.trim();
return `Le topic a été vérouillé pour la raison suivante : "${reason}"`;
}
function getTopicError(elem) {
let error = elem.getElementsByClassName("img-erreur")[0];
if (error === undefined) {
return error;
}
return `Le topic présente une erreur: ${error.getAttribute("alt")}`;
}
function tryCatch(func) {
function wrapped(optArg) {
try {
func(optArg);
} catch(err) {
let message = `Une erreur est survenue dans JVChat Premium: '${err.message}' (line ${err.lineNumber})`;
console.error(message);
try {
addAlertbox("danger", message);
} catch(e) {
alert(message);
}
}
}
return wrapped;
}
function toggleTextarea() {
let isDown = isScrollDown();
document.getElementById("bloc-formulaire-forum").getElementsByClassName("jv-editor-toolbar")[0].classList.toggle("jvchat-hide");
document.getElementById("jvchat-enlarge").classList.toggle("jvchat-hide");
document.getElementById("jvchat-reduce").classList.toggle("jvchat-hide");
document.getElementById("jvchat-post").classList.toggle("jvchat-hide");
document.getElementById("bloc-formulaire-forum").classList.toggle("jvchat-reduced");
isReduced = !isReduced;
localStorage.setItem(storageKey, isReduced);
setTextareaHeight();
if (isDown) {
setScrollDown();
}
}
function parseURL(url) {
let regex = /^(.*?)(\/\d+-\d+-\d+-)(\d+)(-\d+-\d+-\d+-)(.*?)(\.htm)(.*)$/i;
let [_, domain, ids, page, nums, title, htm, anchor] = url.match(regex);
return {domain: domain, ids: ids, page: page, nums: nums, title: title, htm: htm, anchor: anchor};
}
function buildURL(dict) {
return `${dict.domain}${dict.ids}${dict.page}${dict.nums}${dict.title}${dict.htm}${dict.anchor}`;
}
function getLastPage(document) {
let blocPages = document.getElementsByClassName("bloc-liste-num-page")[0];
let spans = blocPages.getElementsByTagName("span");
let lastPage = 1;
for (let span of spans) {
let page = parseInt(span.textContent.trim());
if (!isNaN(page) && page > lastPage) {
lastPage = page;
}
}
return lastPage;
}
function parseMessage(elem) {
let author = elem.getElementsByClassName("bloc-pseudo-msg")[0].textContent.trim();
let avatar = elem.getElementsByClassName("user-avatar-msg")[0];
if (avatar !== undefined) {
avatar = avatar.getAttribute("data-srcset");
}
let date = elem.getElementsByClassName("bloc-date-msg")[0].textContent.trim();
let content = fixMessage(elem.getElementsByClassName("text-enrichi-forum")[0]);
let id = parseInt(elem.getAttribute("data-id"));
return {author: author, date: date, avatar: avatar, id: id, content: content};
}
function parseUserInfo(elem) {
let mpBox = elem.getElementsByClassName("jv-account-number-mp")[0];
if (mpBox === undefined) {
return undefined;
}
let notifBox = elem.getElementsByClassName("jv-account-number-notif")[0];
let avatarBox = elem.getElementsByClassName("account-avatar-box")[0];
let authorBox = elem.getElementsByClassName("account-pseudo")[0];
let mp = parseInt(mpBox.getAttribute("data-val"));
let notif = parseInt(notifBox.getAttribute("data-val"));
let avatar = avatarBox.style["background-image"].slice(5, -2).replace("/avatar-md/", "/avatar/");
let author = authorBox.textContent.trim();
return {author: author, avatar: avatar, mp: mp, notif: notif};
}
function parseTopicInfo(elem) {
let title = elem.querySelector("#bloc-title-forum").textContent.trim();
let connected = parseInt(elem.getElementsByClassName("nb-connect-fofo")[0].textContent.trim());
let lastPage = getLastPage(elem);
let pageActive = elem.getElementsByClassName("page-active")[0];
let page = 1;
if (pageActive !== undefined) {
page = parseInt(pageActive.textContent.trim());
}
return {title: title, connected: connected, lastPage: lastPage, page: page};
}
function fixMessage(elem) {
let jvcares = Array.from(elem.getElementsByClassName("JvCare"));
for (let jvcare of jvcares) {
let a = document.createElement("a");
a.setAttribute("target", "_blank");
a.setAttribute("href", jvCake(jvcare.getAttribute("class")));
a.innerHTML = jvcare.innerHTML;
jvcare.outerHTML = a.outerHTML;
}
return elem;
}
function jvCake(cls) {
let base16 = '0A12B34C56D78E9F', lien = '', s = cls.split(' ')[1];
for (let i = 0; i < s.length; i += 2) {
lien += String.fromCharCode(base16.indexOf(s.charAt(i)) * 16 + base16.indexOf(s.charAt(i + 1)));
}
return lien;
}
function clearPage(document) {
let buttons = `
`;
document.head.insertAdjacentHTML("beforeend", CSS);
let messageTopic = document.getElementById("message_topic");
if (messageTopic) {
messageTopic.setAttribute("placeholder", "Hop hop hop, le message ne va pas s'écrire tout seul !");
messageTopic.insertAdjacentHTML("afterend", buttons);
messageTopic.addEventListener("keydown", tryCatch(postMessageIfEnter));
document.getElementById("jvchat-post").addEventListener("click", tryCatch(postMessage));
document.getElementById("jvchat-enlarge").addEventListener("click", tryCatch(toggleTextarea));
document.getElementById("jvchat-reduce").addEventListener("click", tryCatch(toggleTextarea));
setFakeDivForPrettyScaling();
}
document.getElementsByClassName("conteneur-messages-pagi")[0].insertAdjacentHTML("afterbegin", "
");
document.getElementById("content-context").insertAdjacentHTML("afterbegin", PANEL);
document.getElementById("bloc-formulaire-forum").classList.add("jvchat-reduced");
document.getElementById("bloc-formulaire-forum").classList.add("jvchat-hide");
/* Fix Stickers JVC: à cause du flex, je n'arrive pas à limiter leur width sans que cela
casse le comportement de l'affichage souhaité. A savoir : si l'écran est grand, la leftbar est affichée
en 15 rem, jusqu'à un certain point diminuer la width de l'écran diminue la zone de texte, après
une certaine limite, l'avatar commence à se réduire à la place de la zone de texte (celle-ci reste la même),
une fois la leftbar diminuée au max (avatar = 3rem), la zone de texte continue à diminue puis scrollbar
horizontale en dernier recours.
Le problème c'est qu'il y a bcp de stickers qui prennent bcp de place, et ils forcent la réduction
de la leftbar même si l'écran est grand. Solution : flex doit ignorer les stickers pour son calcul
de la width, donc on passe les sticckers en positions absolue et on déinie la taille du parent.
*/
let newStickers = document.getElementsByClassName("new-stickers")[0];
if (newStickers) {
let newStickersParent = newStickers.parentElement
newStickersParent.classList.add("jvchat-new-stickers-parent");
document.getElementById("bloc-formulaire-forum").addEventListener("click", function(event) {
let target = event.target;
if (!target) {
return;
}
if (target.getAttribute("id") === "active-script") {
newStickersParent.classList.toggle("jvchat-hide");
}
});
}
let toolbar = document.getElementById("bloc-formulaire-forum").getElementsByClassName("jv-editor-toolbar")[0];
if (toolbar) {
toolbar.classList.add("jvchat-hide");
}
document.getElementById("jvchat-main").addEventListener("click", tryCatch(dontScrollOnExpand));
document.getElementById("jvchat-alerts").addEventListener("click", tryCatch(closeAlert));
let favs = Array.from(document.querySelectorAll("link[rel='icon'], link[rel='shortcut icon']"));
for (let fav of favs) {
fav.parentElement.removeChild(fav);
}
setFavicon("");
document.addEventListener("visibilitychange", function() {
let hidden = document.hidden;
if (hidden) {
let newHr = document.getElementById("jvchat-ruler-new");
if (newHr) {
newHr.removeAttribute("id");
}
nbNewMessage = 0;
} else if (!isError && !isLocked) {
setFavicon("");
}
});
}
function closeAlert(event) {
let target = event.target;
if (!target) {
return;
}
if (target.classList.contains("jvchat-alert-close")) {
let parent = target.parentElement;
parent.parentElement.removeChild(parent);
}
}
function postMessage() {
let textarea = document.getElementById("message_topic");
if (freshForm === undefined) {
addAlertbox("danger", "Impossible de poster le message, aucun formulaire trouvé");
return;
}
let formData = serializeForm(freshForm);
formData["message_topic"] = textarea.value;
let formulaire = document.getElementById("bloc-formulaire-forum");
formulaire.classList.add("jvchat-disabled-form");
textarea.setAttribute("disabled", "true");
function onSuccess(res) {
formulaire.classList.remove("jvchat-disabled-form");
textarea.removeAttribute("disabled");
let alert = parsePage(res).alert;
if (!alert) {
textarea.value = "";
}
setTextareaHeight();
setScrollDown();
}
function onError(err) {
addAlertbox("danger", err);
formulaire.classList.remove("jvchat-disabled-form");
}
function onTimeout(err) {
addAlertbox("warning", err);
formulaire.classList.remove("jvchat-disabled-form");
}
request("POST", document.URL, onSuccess, onError, onTimeout, makeFormData(formData));
}
function setTextareaHeight(plusOne) {
let textarea = document.getElementById("message_topic");
if (!isReduced) {
textarea.style["height"] = "";
return;
}
plusOne = !!plusOne;
let lines = textarea.value.split(/\r|\r\n|\n/).length;
if (!plusOne && lines === 1) {
textarea.style["height"] = "";
return;
}
if (plusOne) {
lines += 1;
}
let height = 1 * lines + 0.6;
textarea.style["height"] = `${height}rem`;
}
function postMessageIfEnter(event) {
if (isReduced && (event.which == 13 || event.keyCode == 13)) {
if (event.shiftKey) {
let isDown = isScrollDown();
setTextareaHeight(true);
if (isDown) {
setScrollDown();
}
} else {
event.preventDefault();
postMessage();
}
}
}
function serializeForm(form) {
// Useless actually, just use new FormData(form)
let dict = {};
for (let select of form.getElementsByTagName("select")) {
dict[select.name] = select.querySelector("option[selected]").value;
}
for (let input of form.getElementsByTagName("input")) {
dict[input.name] = input.value;
}
for (let textarea of form.getElementsByTagName("textarea")) {
dict[textarea.name] = textarea.value;
}
return dict;
}
function makeFormData(dict) {
var formData = new FormData();
for (let key in dict) {
formData.append(key, dict[key]);
}
return formData;
}
function getMessages(document) {
let blocMessages = document.getElementsByClassName("bloc-message-forum");
let messages = [];
for (let bloc of blocMessages) {
messages.push(parseMessage(bloc));
}
return messages;
}
function addMessages(document, messages) {
let main = document.getElementById("jvchat-main");
let isDown = isScrollDown();
let hasNewMessages = false;
for (let message of messages) {
let id = message.id;
if (firstMessageId === undefined) {
firstMessageId = id;
}
if (allMessagesId.has(id) || id < firstMessageId) {
continue;
}
allMessagesId.add(id);
hasNewMessages = true;
if (nbNewMessage === 0 && document.hidden) {
let hrs = document.getElementsByClassName("jvchat-ruler");
let lastHr = hrs[hrs.length - 1];
lastHr.setAttribute("id", "jvchat-ruler-new");
}
let avatar = message.avatar;
let date = message.date;
let exists = avatar !== undefined;
let author = exists ? message.author : `${message.author}`;
let avatarSrc = exists ? avatar : "http://image.jeuxvideo.com/avatar-sm/default.jpg";
let authorHref = exists ? `href="http://www.jeuxvideo.com/profil/${author.toLowerCase()}?mode=infos"` : "";
let authorTitle = exists ? `title="Ouvrir le profil de ${author}"` : "";
let html =
`
${author}
${date.slice(-8)}
${message.content.outerHTML}
`;
main.insertAdjacentHTML("beforeend", html);
nbNewMessage++;
}
if (isDown) {
let blocMessages = Array.from(main.getElementsByClassName("jvchat-bloc-message"));
for (let i = 0; i + 100 < blocMessages.length; i++) {
main.removeChild(blocMessages[i]);
}
setScrollDown();
}
if (hasNewMessages) {
decreaseUpdateInterval();
if (document.hidden) {
setFavicon(nbNewMessage > 99 ? 99 : nbNewMessage);
}
} else {
increaseUpdateInterval();
}
}
function setUser(document, user) {
let isConnected = (user !== undefined);
let isDown = isScrollDown();
if (isConnected) {
let pseudo = document.getElementById("jvchat-user-pseudo");
let avatar = document.getElementById("jvchat-user-avatar");
let mp = document.getElementById("jvchat-user-mp");
let notif = document.getElementById("jvchat-user-notif");
let avatarLink = document.getElementById("jvchat-user-avatar-link");
let notifLink = document.getElementById("jvchat-user-notif-link");
pseudo.innerHTML = user.author;
avatar.style["background-image"] = `url("${user.avatar}")`;
mp.setAttribute("data-val", user.mp);
if (user.mp > 0) {
mp.classList.add("has-notif");
} else {
mp.classList.remove("has-notif");
}
notif.setAttribute("data-val", user.notif);
if (user.notif > 0) {
notif.classList.add("has-notif");
} else {
notif.classList.remove("has-notif");
}
avatarLink.setAttribute("href", `http://www.jeuxvideo.com/profil/${user.author.toLowerCase()}?mode=infos`);
notifLink.setAttribute("href", `http://www.jeuxvideo.com/profil/${user.author.toLowerCase()}?mode=abonnements`);
}
if ((userConnected === undefined && isConnected) || (userConnected !== undefined && isConnected !== userConnected)) {
document.getElementById("jvchat-profil").classList.toggle("jvchat-hide");
document.getElementById("bloc-formulaire-forum").classList.toggle("jvchat-hide");
if (isDown) {
setScrollDown();
}
}
if (userConnected !== undefined) {
if (isConnected && !userConnected) {
addAlertbox("success", "Vous êtes désormais connecté");
} else if (!isConnected && userConnected) {
addAlertbox("warning", "Vous avez été déconnecté");
}
}
userConnected = isConnected;
}
function setTopicTitle(document, topicTitle) {
document.getElementById("jvchat-topic-title").innerHTML = topicTitle;
}
function setTopicNbConnected(document, nbConnected) {
let txt = `${nbConnected} connectés`;
if (!(nbConnected > 1)) {
if (nbConnected === undefined) {
txt = "? connectés";
} else {
txt = txt.slice(0, -1);
}
}
document.getElementById("jvchat-topic-nb-connected").innerHTML = txt;
}
function setTopicNbMessages(document, nbMessages) {
let txt = `${nbMessages} messages`;
if (!(nbMessages > 1)) {
if (nbMessages === undefined) {
txt = "? messages";
} else {
txt = txt.slice(0, -1);
}
}
document.getElementById("jvchat-topic-nb-messages").innerHTML = txt;
}
function setFakeDivForPrettyScaling() {
/*
Ok, donc je ne sais pas du tout comment c'est censé être codé proprement, mais vu que cette
technique fonctionne et que je n'ai pas trouvé autre chose, let's go.
Ca vient de l'effet observé par JVStickers ou la toolbar, et c'est le comportement souhaité
pour que la vue s'adapte dynamiquement : sur un grand écran, la leftbar est à une taille max
fixée, si on réduit un peu la width la leftbar ne change pas, mais la zone des messages se réduit.
Après, on atteint une limite où la leftbar va commencer à diminuer à la place de la zone des
messages, favorisant cette dernière. Cette "limite" est en fait caractérisée par la width totale
des fake divs ajoutés au DOM. Une fois la leftbar diminuée au maximum, la zone des messages
reprend sa réduction, jusqu'au moment où la vue est vraiment trop petite et qu'une scrollbar
horizontale apparaît.
Le comportement souhaité est sûrement dû à la config "flex", et les fake divs permettent de
donner une plus grande prioritée à la zone des messages.
*/
let fakeDivs = `
${"".repeat(10)}
`
let conteneur = document.getElementById("bloc-formulaire-forum").getElementsByClassName("conteneur-editor")[0];
conteneur.insertAdjacentHTML("afterbegin", fakeDivs);
}
function triggerJVChat() {
let topicUrl = document.URL;
let topic = parseTopicInfo(document);
let user = parseUserInfo(document);
let url = parseURL(topicUrl);
// let currentPage = getCurrentPage(topicUrl);
clearPage(document);
setUser(document, user);
setTopicTitle(document, topic.title);
setTopicNbMessages(document, undefined);
setTopicNbConnected(document, topic.connected);
url.page = 1;
url.anchor = "";
document.getElementById("jvchat-topic-title").setAttribute("href", buildURL(url));
let defaultReduced = localStorage.getItem(storageKey);
if (defaultReduced === "false" || document.getElementById("message_topic").value !== "") {
toggleTextarea();
}
updateMessages(url, topic.lastPage);
}
function updateMessages(url, page) {
url.page = page;
let urlLastPage = buildURL(url);
function scheduleNextUpdate(interval) {
setTimeout(tryCatch(function() { updateMessages(url, page); }), interval);
};
function onSuccess(res) {
let lastPage = parsePage(res).lastPage;
if (page == lastPage || lastPage === undefined) {
scheduleNextUpdate(updateIntervals[updateIntervalIdx] * 1000);
} else if (page < lastPage) {
updateMessages(url, page + 1);
} else if (page > lastPage) {
updateMessages(url, lastPage);
}
}
function onError(err) {
if (!isError) {
isError = true;
setFixedAlert("danger", err);
}
scheduleNextUpdate(60000);
}
function onTimeout(err) {
addAlertbox("warning", err);
scheduleNextUpdate(20000);
}
request("GET", urlLastPage, onSuccess, onError, onTimeout);
}
function parseAlerts(res) {
let alerts = [];
let alertsDiv = res.getElementsByClassName("alert");
for (let a of alertsDiv) {
let type = "danger";
if (a.classList.contains("alert-warning")) {
type = "warning";
} else if (a.classList.contains("alert-success")) {
type = "success";
}
let message = a.getElementsByClassName("alert-row")[0].textContent.trim();
alerts.push({type: type, message: message});
}
return alerts;
}
function increaseUpdateInterval() {
if (updateIntervalIdx < updateIntervalMax) {
updateIntervalIdx++;
}
}
function decreaseUpdateInterval() {
updateIntervalIdx = transisitions[updateIntervalIdx];
}
function parsePage(res) {
let error = getTopicError(res);
if (error !== undefined) {
if (!isError) {
updateIntervalIdx = updateIntervalMax;
isError = true;
setFixedAlert("danger", error);
}
return {lastPage: undefined, alert: true}
} else {
if (isError) {
isError = false;
updateIntervalIdx = 0;
removeFixedAlert("Le topic ne retourne plus d'erreur");
}
let form = getForm(res);
if (form !== undefined) {
freshForm = form;
}
let messages = getMessages(res);
addMessages(document, messages);
let user = parseUserInfo(res);
setUser(document, user);
let topic = parseTopicInfo(res);
let nbMessages = (topic.lastPage - 1) * 20;
if (topic.page == topic.lastPage) {
nbMessages += messages.length;
}
setTopicNbMessages(document, nbMessages);
setTopicNbConnected(document, topic.connected);
let alerts = parseAlerts(res);
for (let alert of alerts) {
addAlertbox(alert.type, alert.message);
}
let locked = getTopicLocked(res);
let isLocked_ = (locked !== undefined);
if (isLocked_ && !isLocked) {
updateInterval = updateIntervalMax;
setFixedAlert("warning", locked);
} else if (!isLocked_ && isLocked) {
updateInterval = 0;
removeFixedAlert("Le topic a été dévérouillé");
}
isLocked = isLocked_;
return {lastPage: topic.lastPage, alert: isLocked_ || (alerts.length > 0)};
}
}
function addAlertbox(type, message) {
// type: success / warning / danger
let alert = `
${message}
`;
document.getElementById("jvchat-fixed-alert").insertAdjacentHTML("afterend", alert);
}
function setFixedAlert(type, message) {
setFavicon("⨯");
document.getElementById("jvchat-fixed-alert").getElementsByClassName("alert-row")[0].innerHTML = message;
document.getElementById("jvchat-fixed-alert").setAttribute("class", `alert alert-${type}`);
}
function removeFixedAlert(message) {
document.getElementById("jvchat-fixed-alert").classList.add("jvchat-hide");
if (message !== undefined) {
addAlertbox("success", message);
}
if (document.hidden && nbNewMessage > 0) {
setFavicon(nbNewMessage > 99 ? 99 : nbNewMessage);
} else {
setFavicon("");
}
}
function makeJVChatButton() {
let cls = 'btn-jvchat';
let text = 'JVChat';
let btn = ``;
return btn;
}
function addJVChatButton(document) {
let css = ``
document.head.insertAdjacentHTML("beforeend", css);
let blocPreRight = document.getElementsByClassName("bloc-pre-right");
let jvchatButton = makeJVChatButton();
for (let bloc of blocPreRight) {
bloc.insertAdjacentHTML('afterbegin', jvchatButton);
}
}
function bindJVChatButton(document) {
let buttons = document.getElementsByClassName('btn-jvchat');
for (let btn of buttons) {
btn.addEventListener('click', tryCatch(triggerJVChat));
}
}
function request(mode, url, callbackSuccess, callbackError, callbackTimeout, data) {
let xhr = new XMLHttpRequest();
xhr.timeout = 20000;
xhr.ontimeout = tryCatch(function() {
callbackTimeout(`La delai d'attente de la requête a expiré`);
});
xhr.onerror = tryCatch(function() {
callbackError(`La requête a échoué (${xhr.status}): ${xhr.statusText}`);
});
xhr.onload = tryCatch(function() {
if (xhr.status !== 200) {
callbackError(`La requête a retourné une erreur (${xhr.status}): ${xhr.statusText}`);
return;
}
let dom = document.createElement("html");
dom.innerHTML = xhr.responseText;
callbackSuccess(dom);
});
if (data === undefined) {
data = null;
}
xhr.open(mode, url, true);
xhr.send(data);
};
// On copie/colle le code de TopicLive et on se sent développeur :)
function makeFavicon() {
let canvas = document.createElement("canvas");
canvas.width = 16;
canvas.height = 16;
let context = canvas.getContext('2d');
let image = new Image();
image.src = '/favicon.ico';
return {canvas: canvas, context: context, image: image};
};
function clearFavicon() {
favicon.context.clearRect(0, 0, favicon.canvas.width, favicon.canvas.height);
favicon.context.drawImage(favicon.image, 0, 0);
};
function fillFavicon(txt) {
clearFavicon();
if (txt !== '') {
let context = favicon.context;
context.fillStyle = 'DodgerBlue';
context.fillRect(0, 0, context.measureText(txt).width + 3, 11);
context.fillStyle = 'white';
context.font = 'bold 10px Verdana';
context.textBaseline = 'bottom';
context.fillText(txt, 1, 11);
}
};
function setFavicon(txt) {
let fav = document.getElementById("jvchat-favicon");
if (fav) {
fav.parentElement.removeChild(fav);
}
fillFavicon(txt);
let url = favicon.canvas.toDataURL('image/png');
let icon = ``;
document.head.insertAdjacentHTML("beforeend", icon);
}
function reverseQuote(blocMessage) {
let author = blocMessage.getElementsByClassName("jvchat-author")[0].textContent.trim();
return author + " :d) ";
}
function insertAtCursor(input, textToInsert) {
const value = input.value;
const start = input.selectionStart;
const end = input.selectionEnd;
input.value = value.slice(0, start) + textToInsert + value.slice(end);
input.selectionStart = input.selectionEnd = start + textToInsert.length;
}
function dontScrollOnExpand(event) {
let target = event.target;
if (!target) {
return;
}
let classes = target.classList;
if (classes.contains("blockquote-jv")) {
let isDown = isScrollDown();
target.setAttribute('data-visible', '1');
if (isDown) {
setScrollDown();
}
} else if (classes.contains("open-spoil")) {
// TODO: Fix scroll down not detected on spoilers
} else if (classes.contains("jvchat-quote")) {
let bloc = target.parentElement.parentElement.parentElement.parentElement.parentElement;
let quote = reverseQuote(bloc);
let textarea = document.getElementById("message_topic");
if (isReduced) {
toggleTextarea();
}
insertAtCursor(textarea, quote);
setScrollDown();
textarea.focus();
}
}
function isScrollDown() {
let element = document.getElementById("jvchat-main");
return element.clientHeight + Math.floor(element.scrollTop) >= element.scrollHeight - 1;
}
function setScrollDown() {
let element = document.getElementById("jvchat-main");
element.scrollTop = element.scrollHeight + 100000;
}
function main() {
addJVChatButton(document);
bindJVChatButton(document);
}
main();