Ciągle ulepszam moją skryptozakładkę do Facebooka, mam już jej trzecią wersję, i pomysły na kolejne piętnaście.
To czego nie mam, to koncepcji sensownego jej wersjonowania - i w ogóle całego kodu na stronie. Teoretycznie mogę wrzucać każdą wersję jako osobny plik, bo każdy wpis i tak żyje w swoim własnym folderze, ale jeszcze nie pogodziłem się z tym pomysłem, bo bardzo lubię Gita. Ale stawianie repo kłóci się z moim podejściem upraszczania do granic możliwości. No i czymże jest Git, jeśli po prostu nie kolejnymi kopiami tego samego zaczarowanymi w skomplikowane komendy? Na razie wrzucam jako osobny plik.
Dodałem "tryb skupienia" dla pojedynczego posta. Żeby dobrze działało, to post trzeba otworzyć w osobnej zakładce, bo skrypt dosłownie wyszukuje nagłówka postu, a następnie wychodzi trzynaście div
ów do góry i robi czary za pomocą stylu CSS visibility
. Oraz przyciski do rozwijania komentarzy są od teraz klikane dopiero wtedy, gdy pojawią się na ekranie. I są zaznaczane na czerwono, żeby było widoczne, co się klika.
javascript: (function () {
const PANEL_ID = "control-panel";
const FOCUS_LEVEL = 13;
const FOCUS_MODE_STYLES = `
<style id="focus-mode-styles">
.focus-hidden { visibility: hidden !important; }
.focus-visible { visibility: visible !important; }
</style>
`;
const COMMENT_BUTTON_PATTERNS = [
/Wyświetl wszystkie.*odpowiedzi/,
/Wyświetl więcej/,
/Pokaż więcej odpowiedzi/,
/Wyświetl 1 odpowiedź/,
/odpowiedzia(ł|ła|\(a\))/,
];
/* if panel already exists, do nothing */
if (document.getElementById(PANEL_ID)) return;
let domWatcher = null;
let visibilityObserver = null;
let dragOffset = null;
let clickedButtonsCount = 0;
let focusEnabled = false;
/* add focus mode CSS styles */
document.head.insertAdjacentHTML("beforeend", FOCUS_MODE_STYLES);
/* build and display control panel */
const panel = createPanel();
const title = panel.querySelector("#panel-title");
const commentButton = panel.querySelector("#comment-button");
const counterDisplay = panel.querySelector("#counter-display");
const focusModeButton = panel.querySelector("#focus-mode-button");
const closeButton = panel.querySelector("#close-button");
document.body.appendChild(panel);
/* event handlers */
title.onmousedown = (e) => startDragging(e);
document.onmousemove = (e) => dragPanel(e);
document.onmouseup = () => stopDragging();
commentButton.onclick = () => (visibilityObserver ? stopWatching() : startWatching());
focusModeButton.onclick = () => (focusEnabled ? deactivateFocusMode() : activateFocusMode());
closeButton.onclick = closePanel;
/* === MAIN FUNCTIONS === */
function startWatching() {
updateButton(commentButton, "Wyłącz rozwijanie komentarzy", "crimson");
visibilityObserver = new IntersectionObserver((entries) => clickIntersectingEntries(entries));
observeNode(document.body, visibilityObserver);
domWatcher = new MutationObserver((m) => observeMutations(m, visibilityObserver));
domWatcher.observe(document.body, { childList: true, subtree: true });
}
function stopWatching() {
updateButton(commentButton, "Włącz rozwijanie komentarzy");
if (visibilityObserver) {
visibilityObserver.disconnect();
visibilityObserver = null;
}
if (domWatcher) {
domWatcher.disconnect();
domWatcher = null;
}
}
function activateFocusMode() {
const postContainer = findPostContainer();
if (!postContainer) {
alert("Nie udało się znaleźć kontenera posta");
return;
}
updateButton(focusModeButton, "Wyłącz tryb skupienia", "darkorange");
focusEnabled = true;
document.body.classList.add("focus-hidden");
panel.classList.add("focus-visible");
postContainer.classList.add("focus-visible");
}
function deactivateFocusMode() {
updateButton(focusModeButton, "Włącz tryb skupienia");
focusEnabled = false;
const focusElements = document.querySelectorAll(".focus-hidden, .focus-visible");
focusElements.forEach((e) => e.classList.remove("focus-hidden", "focus-visible"));
}
function clickButton(button) {
const textElements = [button, ...button.querySelectorAll("*")];
setElementColors(textElements, "red");
setTimeout(() => {
button.click();
clickedButtonsCount++;
updateCounterDisplay();
}, 500);
}
/* === OBSERVER FUNCTIONS === */
function observeMutations(mutations, visibilityObserver) {
mutations.forEach((m) => {
m.addedNodes.forEach((n) => observeNode(n, visibilityObserver));
m.removedNodes.forEach((n) => unobserveNode(n, visibilityObserver));
});
}
function clickIntersectingEntries(entries) {
entries.filter((e) => e.isIntersecting).forEach((e) => clickButton(e.target));
}
function observeNode(node, visibilityObserver) {
const buttons = findMatchingButtons(node);
buttons.forEach((b) => visibilityObserver.observe(b));
}
function unobserveNode(node, visibilityObserver) {
const buttons = findMatchingButtons(node);
buttons.forEach((b) => visibilityObserver.unobserve(b));
}
/* === UTILITIES === */
function findPostContainer() {
const headers = document.querySelectorAll("h2 span");
const header = Array.from(headers).find((header) => /^Post /.test(header.textContent));
if (!header) return;
let container = header;
for (let i = 0; i < FOCUS_LEVEL; i++) {
container = container.parentElement;
if (!container) return;
}
return container;
}
function isMatchingButton(element) {
return (
element.nodeType === Node.ELEMENT_NODE &&
element.getAttribute?.("role") === "button" &&
COMMENT_BUTTON_PATTERNS.some((pattern) => pattern.test(element.textContent))
);
}
function findMatchingButtons(node) {
if (node.nodeType !== Node.ELEMENT_NODE) return [];
const matchingButtons = [];
if (isMatchingButton(node)) matchingButtons.push(node);
const descendantButtons = Array.from(node.querySelectorAll('div[role="button"]')).filter(
isMatchingButton
);
matchingButtons.push(...descendantButtons);
return matchingButtons;
}
/* === PANEL CONTROL FUNCTIONS === */
function closePanel() {
stopWatching();
document.onmousemove = document.onmouseup = null;
panel.remove();
}
function startDragging(event) {
if (event.button !== 0) return; /* left click only */
dragOffset = {
x: event.clientX - panel.offsetLeft,
y: event.clientY - panel.offsetTop,
};
}
function dragPanel(event) {
if (!dragOffset) return;
panel.style.left = `${event.clientX - dragOffset.x}px`;
panel.style.top = `${event.clientY - dragOffset.y}px`;
}
function stopDragging() {
dragOffset = null;
}
/* === UI FUNCTIONS === */
function createPanel() {
const panelHTML = `
<div id="${PANEL_ID}" style="
position: fixed;
top: 100px;
left: 100px;
background: #fff;
border: 1px solid #000;
padding: 8px;
z-index: 999999;
display: grid;
gap: 5px;
">
<h1 id="panel-title" style="cursor: move; text-align: center;">Asystent FB</h1>
<button id="comment-button" style="cursor: pointer;">Włącz rozwijanie komentarzy</button>
<span id="counter-display">Kliknięte przyciski: 0</span>
<button id="focus-mode-button" style="cursor: pointer;">Włącz tryb skupienia</button>
<button id="close-button" style="cursor: pointer;">Zamknij</button>
</div>
`;
const tempDiv = document.createElement("div");
tempDiv.innerHTML = panelHTML;
return tempDiv.firstElementChild;
}
function updateButton(button, text, color) {
button.textContent = text;
button.style.backgroundColor = color ?? "black";
}
function updateCounterDisplay() {
counterDisplay.textContent = `Kliknięte przyciski: ${clickedButtonsCount}`;
}
function setElementColors(elements, color) {
elements.forEach((e) => e.style.setProperty("color", color, "important"));
}
})();
PS: odkrycie dnia - kod skryptozakładki można pobierać dynamicznie z lokalne serwera, a samą ją odpalać taki prostym snippetem! Moje życie po raz kolejny stało się prostsze.
javascript: (function () {
var s = document.createElement("script");
s.src = "http://localhost:1111/script.js";
document.body.appendChild(s);
})();