// ==UserScript== // @name Backwash, Opera edition // @author Anonymous // @version 1.2c // @description Displays posts in their own hovering window when moving the mouse over quotes. Originally by tkirby, aeosynth, VIPPER for Firefox. // @ujs:documentation http://sector-5.net/archives/4chan-backwash-for-opera/ // @ujs:download http://files.sector-5.net/4chan-backwash.js // ==/UserScript== if (/^http:\/\/boards\.4chan\.org\/\w{1,3}\//.test(window.location.href)) { // Pack everything into an anonymous function block to avoid functions and // variables from leaking out. (function() { const VIEW_X_OFFSET = 45, VIEW_Y_OFFSET = 120; /// Adds event listeners to all quotes for mouseover and mouseout function addListenerToQuotes(root) { if (root.getElementsByClassName) { // Find all quotes var quotes = root.getElementsByClassName('quotelink'); for (var i = 0; i < quotes.length; ++i) { quotes[i].onmouseover = showQuote; quotes[i].onmouseout = hideQuote; } } } /// Creates the viewport for displaying quoted posts /// and adds it to the document. function setupViewport() { var container = document.createElement('div'); container.id = 'backwash-viewport'; container.style.position = 'absolute'; container.style.border = '1px solid black' container.style.display = 'none'; container.style.backgroundColor = 'white'; var table = document.createElement('table'); container.appendChild(table) var row = document.createElement('tr'); table.appendChild(row); document.body.appendChild(container); } /// Shows the quote viewport function showQuote(event) { var quotedId = event.currentTarget.textContent; if (quotedId.search(/^>>\d/) != -1) { var quotedPost = document.getElementById(quotedId.substring(2)); if (quotedPost != null) { var viewport = document.getElementById('backwash-viewport'); var row = viewport.firstChild.firstChild; // Add new content row.appendChild(quotedPost.cloneNode(true)); row.firstChild.id = ''; // Calculate viewport position moveViewport(event, viewport); viewport.style.display = 'block'; } else { // The quoted ID was not found, check if the opening post matches the ID // First, find the appropriate parent element. This should be for replies. var container = event.currentTarget.parentNode; while (container != null) { if (container.tagName == 'TABLE') break; else container = container.parentNode; } // Quoted link was indeed inside a reply; now find out the thread id if (container != null) { var threadId = null; var sibling = container.previousSibling; while (sibling != null && sibling.tagName != 'HR') { if (sibling.tagName == 'SPAN' && sibling.hasAttribute('id') && sibling.getAttribute('id').indexOf('nothread') == 0) { threadId = sibling.getAttribute('id').substring(8); break; } else sibling = sibling.previousSibling; } if (quotedId.substring(2) == threadId) { // Create our own post replica var tableCell = document.createElement('tr'); tableCell.setAttribute('class', 'reply'); // sibling will now be the tag, append all previous siblings up until
var previousNode = sibling; while (previousNode != null) { if (previousNode.tagName == 'HR') break; else { // The filesize line should appear last if (previousNode.tagName == 'SPAN' && previousNode.hasAttribute('class') && previousNode.getAttribute('class') == 'filesize') { tableCell.appendChild(document.createElement('br')); tableCell.appendChild(previousNode.cloneNode(true)) } // Skip
tags else if (previousNode.tagName == 'BR') ; else tableCell.insertBefore(previousNode.cloneNode(true), tableCell.firstChild); previousNode = previousNode.previousSibling; } } // Add the actual text tableCell.appendChild(sibling.nextSibling.nextSibling.cloneNode(true)); // Make viewport visible var viewport = document.getElementById('backwash-viewport'); var row = viewport.firstChild.firstChild; row.appendChild(tableCell); moveViewport(event, viewport); viewport.style.display = 'block'; } } } } } /// Hides the quote viewport function hideQuote(event) { var viewport = document.getElementById('backwash-viewport'); viewport.style.display = 'none'; var row = viewport.firstChild.firstChild; // Empty the viewport while (row.hasChildNodes()) row.removeChild(row.firstChild); } /// Positions the quoted post. /// From the original /b/ackwash script; God, I hate positioning. function moveViewport(event, viewport) { const viewportHeight = parseInt( document.defaultView.getComputedStyle( viewport, '').getPropertyValue('height')); const cursorRelY = event.pageY - window.scrollY; const viewportAbsBottom = event.pageY - VIEW_Y_OFFSET + viewportHeight; const windowHeight = window.innerHeight; const windowBottom = window.scrollY + windowHeight; viewport.style.top = (cursorRelY < VIEW_Y_OFFSET || viewportHeight > windowHeight) ? event.pageY - cursorRelY : (viewportAbsBottom > windowBottom) ? event.pageY - VIEW_Y_OFFSET - (viewportAbsBottom - windowBottom) : event.pageY - VIEW_Y_OFFSET + 'px'; viewport.style.left = event.pageX + VIEW_X_OFFSET + 'px'; } document.addEventListener('DOMContentLoaded', function(e) { // Add event listeners to quotes addListenerToQuotes(document); // Create the viewport setupViewport(); }, false); document.addEventListener('DOMNodeInserted', function(e) { // Re-add event listener to any quotes addListenerToQuotes(e.target); // If the viewport was removed, re-create it. if (document.getElementById('backwash-viewport') == null) setupViewport(); }, false); })(); }