ya4cie

By aeosynth Last update Sep 11, 2009 — Installed 2,162 times. Daily Installs: 11, 13, 15, 7, 6, 7, 15, 7, 10, 8, 4, 11, 15, 11, 2, 9, 9, 9, 16, 11, 12, 10, 8, 15, 12, 19, 12, 16, 14, 11, 23, 6

There are 17 previous versions of this script.

// ==UserScript==
// @name           ya4cie
// @namespace      aeosynth
// @description    Yet another 4chan image expander.
// @include        http://*.4chan.org/*
// @include        http://suptg.thisisnotatrueending.com/archive/*
// @include        http://4chanarchive.org/brchive/*
// @version        1.0.1
// @copyright      2009, James Campos
// @license        GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// ==/UserScript==

//TODO
// instant feedback
// sanity checking
// enter to save
// wtf opera

(function () {

function x (xpath, root) {
	if (!root) root = document.body
	return document.evaluate(xpath, root, null, XPathResult.ANY_UNORDERED_NODE_TYPE, null).singleNodeValue
}

function X (xpath, root) {
	if (!root) root = document.body
	var result = document.evaluate(xpath, root, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)
	var a = [], item
	while (item = result.iterateNext())
		a.push(item)
	return a
}

function tag (el) {
	return document.createElement(el)
}

var images = X(".//img[@md5]/parent::*|.//span[@class='tn_reply' or @class='tn_thread']/parent::*")
if (!images.length) return

var inline = GM_getValue('Inline', true)
var auto_gif = GM_getValue('Load Gifs')
var click_full = GM_getValue('Click Full')
var click_fit = GM_getValue('Click Fit')
var in_filter = GM_getValue('In Filter')
var auto_expand = Number(GM_getValue('Auto'))//we're gonna have to Number() somewhere, and doing it here requires less user interaction for the bugfix.
var reduce = GM_getValue('Reduce', 0)
var maxWidth = GM_getValue('Max Width', 600)

const reply = /res|archive\/|dspl_thread/.test(window.location.pathname)

var controls = tag('span')
controls.innerHTML =
' <select><option>full</option><option>fit width</option><option>fit screen</option></select>\
 <label style="cursor: pointer;">Expand Images<input type="checkbox"></label>'

var expandSize = controls.getElementsByTagName('select')[0]
if (GM_getValue('expandSize') == 'fit width')
	expandSize.childNodes[1].selected = true
else if (GM_getValue('expandSize') == 'fit screen')
	expandSize.childNodes[2].selected = true
var expandImages = controls.getElementsByTagName('input')[0]
if (auto_expand == 2 || auto_expand && reply) {
	expandImages.checked = true
	expandImagesF()
}

expandImages.addEventListener('click', expandImagesF, true)
function expandImagesF() {
	if (expandImages.checked) {
		for(i in images)
			if (images[i].firstChild.style.display != 'none')
				expandSingle(images[i])
	} else
		for(i in images)
			if (images[i].firstChild.style.display == 'none')
				expandSingle(images[i])
}

function expandSingle (anchor, click) {
	var thumb = anchor.firstChild
	var clientHeight = document.body.clientHeight
	if (thumb.style.display) {
		var expanded = anchor.lastChild
		var width = expanded.width
		var height = expanded.height
		if (click && click_full && !expanded.getAttribute('Full') && expanded.getAttribute('width')) {
			expanded.removeAttribute('width')
			expanded.setAttribute('Full', true)
		} else if (click && click_fit && !expanded.getAttribute('Fit') && height > clientHeight) {
			if (!expanded.getAttribute('width'))
				expanded.setAttribute('Full', true)
			expanded.setAttribute('width', Math.floor(width * clientHeight / height))
			expanded.setAttribute('Fit', true)
		} else {//thumb down
			thumb.style.display = ''
			anchor.removeChild(expanded)
			if (anchor.parentNode.nodeName != 'TD') {
				anchor.parentNode.removeChild(anchor.previousSibling)
				var checkbox = x("preceding-sibling::input[1]", anchor)
				anchor.parentNode.insertBefore(anchor, checkbox)
			}
			if (thumb.nodeName == 'SPAN')
				anchor.parentNode.removeChild(anchor.previousSibling)
		}
	} else {//Expand
		thumb.style.display = 'none'
		expanded = tag('img')
		expanded.src = anchor
		expanded.border = 0
		var size = x("preceding-sibling::span[@class='filesize'][1]/text()[2]", anchor).textContent.match(/(\d+)x(\d+)/)
		var width = size[1]
		var clientWidth = document.body.clientWidth - reduce
		if (expandSize.value == 'fit width') {
			if (width > clientWidth)
				expanded.setAttribute('width', clientWidth)
		} else if (expandSize.value == 'fit screen') {
			var height = size[2]
			if (width > clientWidth || height > clientHeight) {
				if (width/height < clientWidth/clientHeight)
					expanded.setAttribute('width', Math.floor(width*clientHeight/height))
				else
					expanded.setAttribute('width', clientWidth)
			}
		}
		anchor.appendChild(expanded)
		if (anchor.parentNode.nodeName != 'TD') {
			if (Number(width) <= maxWidth)
				expanded.align = 'left'
			var nextBQ = x("following-sibling::blockquote", anchor)
			anchor.parentNode.insertBefore(tag('br'), nextBQ)
			anchor.parentNode.insertBefore(anchor, nextBQ)
		}
		if (thumb.nodeName == 'SPAN')
			anchor.parentNode.insertBefore(tag('br'), anchor)
		}
}

expandSize.addEventListener('change',
	function () {
		GM_setValue('expandSize', expandSize.value)
		const clientWidth = document.body.clientWidth - reduce
		const clientHeight = document.body.clientHeight
		const k = clientWidth/clientHeight
		for (var i in images)
			if (images[i].firstChild.style.display == 'none') {
				var size = x("preceding-sibling::span[@class='filesize'][1]/text()[2]", images[i]).textContent.match(/(\d+)x(\d+)/)
				var width = size[1]
				var height = size[2]
				var expanded = images[i].lastChild
				if (expandSize.value == 'fit width') {
					if (width > clientWidth)
						expanded.setAttribute ('width', clientWidth)
				} else if (expandSize.value == 'fit screen') {
					if (width > clientWidth || height > clientHeight) {
						if (width/height < k)
							expanded.setAttribute('width', width * clientHeight/height)
						else
							expanded.setAttribute('width', clientWidth)
					}
				} else
					images[i].lastChild.removeAttribute('width')
			}
	},
	true)

document.body.addEventListener('DOMNodeInserted', function(e) {if (e.target.nodeName=='TABLE') newPosts(e.target)}, true)
function newPosts(el) {
	var newImage = x("descendant::img[@md5]/parent::*|descendant::span[@class='tn_reply']/parent::*", el)
		if (newImage) {
			images.push(newImage)
			if (inline)
				newImage.addEventListener('click', function(e) {if (e.ctrlKey) return; expandSingle(this, true); e.preventDefault()}, true)
			if (auto_gif && /\.gif$/.test(newImage.href))
				newImage.firstChild.src = newImage.href
			if (expandImages.checked)
				expandSingle(newImage)
		}
	}

if (auto_gif)
	for (var i in images)
		if (/\.gif$/.test(images[i].href))
			images[i].firstChild.src = images[i].href

if (inline)
	for (var i in images)
		images[i].addEventListener('click', function(e) {if (e.ctrlKey) return; expandSingle(this, true); e.preventDefault()}, true)

if (in_filter) {
	controls.appendChild(tag('br'))
	var filter = document.getElementById('thread_filter')
	filter.childNodes[1].insertBefore(controls, filter.childNodes[1].firstChild)
	filter.childNodes[2].insertBefore(tag('br'), filter.childNodes[2].firstChild)
} else {
	var filesize = x("//span[@class='filesize']")
	var parent = filesize.parentNode
	if (parent.nodeName == 'DIV') {//threading
		var prev = parent.previousSibling
		if (prev)//4chan X
			prev.parentNode.insertBefore(controls, prev)
		else//4chan extension
			parent.parentNode.insertBefore(controls, parent)
	} else
		filesize.appendChild(controls)
}

GM_registerMenuCommand('ya4cie Options', options)
function options () {
	var div = tag('div')
	div.id = 'ya4cie'
	div.className = 'reply'
	div.style.top = document.body.clientHeight / 2
	div.style.left = document.body.clientWidth / 2
	div.innerHTML = '\
<div>ya4cie</div>\
<div>\
<label>Inline<input type = "checkbox"></label><br>\
<label>Click Full<input type = "checkbox"></label><br>\
<label>Click Fit<input type = "checkbox"></label><br>\
<label>In Filter<input type = "checkbox"></label><br>\
<label>Load Gifs<input type = "checkbox"></label><br>\
<label>Reduce <input size = 3 maxlength = 5></label><br>\
<label>Max Width <input size = 3 maxlength = 5></label><br>\
Auto Expand:<br>\
<label>Nothing<input type = "radio" name = "Auto" value = 0></label><br>\
<label>Replies<input type = "radio" name = "Auto" value = 1></label><br>\
<label>Everything<input type = "radio" name = "Auto" value = 2></label><br>\
<a>save</a> <a>cancel</a>\
</div>'
	var temp = X('.//input[@type="checkbox"]', div)
	temp[0].checked = GM_getValue('Inline', true)
	for (var i = 1, l = temp.length; i < l; i++)//Only Inline is on by default
		temp[i].checked = GM_getValue(temp[i].previousSibling.nodeValue)
	temp = X('.//input[@size]', div)
	temp[0].value = GM_getValue('Reduce', 0)
	temp[1].value = GM_getValue('Max Width', 600)
	x('.//input[@value="' + GM_getValue('Auto', 0) + '"]', div).checked = true
	temp = X('.//a', div)
	temp[0].addEventListener('click',
		function () {//save
			var boxen = X(".//input[@type='checkbox']", div)
			for (var i in boxen)
				GM_setValue(boxen[i].previousSibling.nodeValue, boxen[i].checked)
			var numbs = X(".//input[@size]", div)
			for (var i in numbs)
				GM_setValue(numbs[i].previousSibling.nodeValue.replace(/ $/, ''), numbs[i].value)
			var radio = X(".//input[@type='radio']", div)
			for (var i in radio)
				if (radio[i].checked)
					GM_setValue('Auto', radio[i].value)
			div.parentNode.removeChild(div)
		},
		true)
	temp[1].addEventListener('click',
		function () {//cancel
			div.parentNode.removeChild(div)
		},
		true)
	document.body.appendChild(div)
}

GM_addStyle("\
 #ya4cie {\
	 position: fixed;\
	 color: inherit;\
	 border: 1px ridge;\
	 padding: 5px;\
	 text-align: right;\
 }\
 #ya4cie label,\
 #ya4cie a { cursor: pointer; }\
 #ya4cie div:first-child { text-align: center; }\
 ")

}) ()