var loadedPreviews = []; var loadingPreviews = []; var loadedContent = {}; var quoteReference = {}; var playableTypes = [ 'video/webm', 'audio/mpeg', 'video/mp4', 'video/ogg', 'audio/ogg', 'audio/webm' ]; var videoTypes = [ 'video/webm', 'video/mp4', 'video/ogg' ]; var knownPosts = {}; var idsRelation = {}; var setImmediates=0 // so every 100 items, take 100ms break // lets old machines/browsers not die function setImmediate(task) { // not as good, but a large poly fill doesn't sounds great either setImmediates++ // every 10 (2=50%, 10=10%, 20=5%) // 100%@0 makes it 13s // 50%@0 makes it 8s // 10%@0 makes it under 1-3s // 5%@10 makes it under 1-3s // 0.1%@100 makes it under 1-3s // well 600 causes noticeable pauses // (<1) 600@100 under 1-3s (still stalls for 2 secs) // 1%@100 makes it under 1-3s if (setImmediates===100) { setTimeout(task, 100) setImmediates=0 // reset } else { task() } } // probably can calculate average iterator time too // how is this not serial map? different api :^p // why is this good at all? it's just an iterator // oh we use it with setImmediate // but maybe setImmediate should be built in? function ioFor(options, code, cb) { // cb is optional if (typeof(options)==='number') { var max=options var options={ done: max, } } if (options.setup===undefined) { options.startTime=Date.now() //console.log('setting up options', options) if (options.start===undefined) options.start=0 if (options.step===undefined) options.step=1 if (typeof(options.done)==='number') { var max=options.done options.done=function(i) { return i>=max } } if (options.step===undefined) { options.step=function(i) { return i++ } } else if (typeof(options.step)==='number') { var inc=options.step options.step=function(i) { return i+=inc } } options.i=options.start //console.log('options', options) options.setup=true } if (options.done(options.i)) { if (options.startTime) { var diff=Date.now()-options.startTime // only care if the avg cycle is greater than 50ms var cycleAvg=diff; if (options.i) cycleAvg/=options.i; if (cycleAvg>6) { //if (diff>500) { // console.log('ioFor(', options.i, ') complete in', diff, 'ms (', cycleAvg, ' avg cycle)') } } if (cb) cb() return } code(options.i, function() { //console.log('old i', options.i) options.i=options.step(options.i) //console.log('new i', options.i) ioFor(options, code, cb) }) } if (!DISABLE_JS) { if (document.getElementById('deleteJsButton')) { document.getElementById('deleteJsButton').style.display = 'inline'; document.getElementById('reportJsButton').style.display = 'inline'; if (!board && document.getElementById('divMod')) { document.getElementById('banJsButton').style.display = 'inline'; document.getElementById('spoilJsButton').style.display = 'inline'; document.getElementById('inputBan').style.display = 'none'; document.getElementById('inputSpoil').style.display = 'none'; } document.getElementById('reportFormButton').style.display = 'none'; document.getElementById('deleteFormButton').style.display = 'none'; } var imageLinks = document.getElementsByClassName('imgLink'); var fuckYou = []; for (var i = 0; i < imageLinks.length; i++) { fuckYou.push(imageLinks[i]); } for (i = 0; i < fuckYou.length; i++) { processImageLink(fuckYou[i]); } var posts = document.getElementsByClassName('postCell'); for (i = 0; i < posts.length; i++) { addToKnownPostsForBackLinks(posts[i]) } var threads = document.getElementsByClassName('opCell'); for (i = 0; i < threads.length; i++) { addToKnownPostsForBackLinks(threads[i]) } var quotes = document.getElementsByClassName('quoteLink'); for (i = 0; i < quotes.length; i++) { var quote = quotes[i]; processQuote(quote); } // handle IDs var postingQuotes = document.getElementsByClassName('linkQuote'); /* for (var i = 0; i < postingQuotes.length; i++) { processPostingQuote(postingQuotes[i]); } */ ioFor(postingQuotes.length, function(i, next) { processPostingQuote(postingQuotes[i]); setImmediate(next); }) var ids = document.getElementsByClassName('labelId'); //console.log('threadjs found', ids.length, 'labelIds'); // build idsRelation //for (i = 0; i < ids.length; i++) { ioFor(ids.length, function(i, next) { processIdLabel(ids[i]); setImmediate(next); }); updateIdLabels(); } function addToKnownPostsForBackLinks(posting) { var postBoard = posting.dataset.boarduri; //console.log('addToKnownPostsForBackLinks', posting, postBoard, posting.id) var list = knownPosts[postBoard] || {}; knownPosts[postBoard] = list; list[posting.id] = { added : [], container : posting.getElementsByClassName('panelBacklinks')[0] }; } /* Expanded images have the class 'imgExpanded' */ function setClickableImage(link) { link.onclick = function(mouseEvent) { return expandImage(mouseEvent, link); }; } /* mouseEvent.target -> link */ function expandImage(mouseEvent, link) { /* return: false -> Don't follow link, true -> Follow link */ /* * If event was fired by middle mouse button or combined with the ctrl key, * act as a normal link */ if (mouseEvent.which === 2 || mouseEvent.ctrlKey) { return true; } var thumb = link.getElementsByTagName('img')[0]; var expandedSrc = link.href; // If the thumb is the same image as the source, do nothing if (thumb.src === expandedSrc) { return false; } /* If image is expanded */ if (thumb.style.display === 'none') { link.getElementsByClassName('imgExpanded')[0].style.display = 'none'; thumb.style.display = ''; return false; } thumb.style.display = 'none'; var expanded = link.getElementsByClassName('imgExpanded')[0]; if (expanded) { // If image has already been expanded in the past, don't create another expanded.style.display = ''; } else { expanded = document.createElement('img'); expanded.setAttribute('src', expandedSrc); expanded.setAttribute('class', 'imgExpanded'); link.appendChild(expanded); } return false; } function setPlayer(link, mime) { var path = link.href; var parent = link.parentNode; var src = document.createElement('source'); src.setAttribute('src', link.href); src.setAttribute('type', mime); var video = document.createElement(videoTypes.indexOf(mime) > -1 ? 'video' : 'audio'); video.setAttribute('controls', true); video.style.display = 'none'; var videoContainer = document.createElement('span'); var hideLink = document.createElement('a'); hideLink.innerHTML = '[ - ]'; hideLink.style.cursor = 'pointer'; hideLink.style.display = 'none'; hideLink.setAttribute('class', 'hideLink'); hideLink.onclick = function() { newThumb.style.display = 'inline'; video.style.display = 'none'; hideLink.style.display = 'none'; video.pause(); }; // preserve previous h/w var newThumb = link.childNodes[0].cloneNode(true); //var newThumb = document.createElement('img'); //newThumb.src = link.childNodes[0].src; newThumb.onclick = function() { if (!video.childNodes.count) { video.appendChild(src); } newThumb.style.display = 'none'; video.style.display = 'inline'; hideLink.style.display = 'inline'; video.play(); }; newThumb.style.cursor = 'pointer'; videoContainer.appendChild(hideLink); videoContainer.appendChild(video); videoContainer.appendChild(newThumb); parent.replaceChild(videoContainer, link); } function processImageLink(link) { var mime = link.dataset.filemime; if (mime.indexOf('image/') > -1) { setClickableImage(link); } else if (playableTypes.indexOf(mime) > -1) { setPlayer(link, mime); } } function setFullBorder(tooltip) { var innerPost = tooltip.getElementsByClassName('innerPost')[0]; //var parent = innerPost.parentNode; var temp = document.createElement('div'); temp.appendChild(innerPost); tooltip.innerHTML = ''; tooltip.appendChild(innerPost); tooltip.style['background-color'] = 'transparent'; innerPost.style['font-size'] = '75%'; innerPost.style['border-style'] = 'solid solid solid solid'; innerPost.style['border-width'] = '1px 1px 1px 1px'; innerPost.style['border-color'] = '#B7C5D9 #B7C5D9 #B7C5D9 #B7C5D9'; } function addBackLink(quoteUrl, quote) { var matches = quoteUrl.match(/\/(\w+)\/res\/\d+\.html\#(\d+)/); var board = matches[1]; var post = matches[2]; var knownBoard = knownPosts[board]; if (knownBoard) { var knownBackLink = knownBoard[post]; if (knownBackLink) { var containerPost = quote.parentNode.parentNode; if (containerPost.className !== 'opCell') { containerPost = containerPost.parentNode; } var sourceBoard = containerPost.dataset.boarduri; var sourcePost = containerPost.id; if (sourceBoard === undefined) { // console.log('sourceBoard', sourceBoard, 'sourcePost', sourcePost) containerPost = quote.parentNode.parentNode.parentNode.parentNode sourceBoard = containerPost.dataset.boarduri sourcePost = containerPost.id } var sourceId = sourceBoard + '_' + sourcePost; if (knownBackLink.added.indexOf(sourceId) > -1) { return; } else { knownBackLink.added.push(sourceId); } var innerHTML = '>>'; if (sourceBoard != board) { innerHTML += '/' + containerPost.dataset.boarduri + '/'; } innerHTML += sourcePost; var myPosts=getMyPosts() var isMine=0 for(var i in myPosts) { var myPost=myPosts[i] //console.log('looking at', myPost, 'type', typeof(myPost)) if (typeof(myPost)==='object') { if (myPost.b==board && myPost.p==post) { isMine=1 break } } } //console.log(board+'/'+post, 'isMine', isMine) if (isMine) { innerHTML+=' (you)'; } var backLink = document.createElement('a'); backLink.innerHTML = innerHTML; var superContainer = containerPost.parentNode; var backLinkUrl = '/' + sourceBoard + '/res/'; if (superContainer && superContainer.className === 'divPosts') { backLinkUrl += containerPost.parentNode.parentNode.id; backLinkUrl += '.html#' + sourcePost; } else { //console.log('superContainer', superContainer); backLinkUrl += sourcePost + '.html#' + sourcePost; } backLink.href = backLinkUrl; knownBackLink.container.appendChild(backLink); processQuote(backLink, true); } } } function processQuote(quote, backLink) { var quoteUrl = quote.href; var matches = quoteUrl.match(/\/(\w+)\/res\/\d+\.html\#(\d+)/); if (matches===null) { // undefined quote return; } var board = matches[1]; var post = matches[2]; // handle (you)s var myPosts = getMyPosts() var isMine = 0 for(var i in myPosts) { var myPost = myPosts[i] //console.log('looking at', myPost, 'type', typeof(myPost)) if (typeof(myPost) === 'object') { if (myPost.b == board && myPost.p == post) { isMine = 1 break } } } if (isMine) { //console.log('changing innerHTML', quote.innerHTML) quote.innerHTML+=' (you)'; } var tooltip = document.createElement('div'); tooltip.style.display = 'none'; tooltip.className = "quoteblock"; tooltip.style.position = 'absolute'; tooltip.style['background-color'] = '#ffffff'; document.body.appendChild(tooltip); if (!backLink) { //console.log('addBackLink', quoteUrl, quote) addBackLink(quoteUrl, quote); } // what board are we on? //console.log('on board', boardUri) // are we on a thread? if so which? //console.log('thread', threadId) // some are quoteLink and some are not.. //console.log('quote', quote, 'quoteUrl', quoteUrl) //console.log('board', qBoard, 'thread', qThread, 'post', qPost) /* if (!loadedContent[quoteUrl]) { // && threadId == qThread if (qBoard === boardUri) { var qElem = document.getElementById(qPost) //console.log('we do have', qPost, !!elem) if (qElem) { //console.log('cacheing', quoteUrl) loadedPreviews.push(quoteUrl) loadedContent[quoteUrl] = qElem.innerHTML } } } */ if (loadedContent[quoteUrl]) { tooltip.innerHTML = loadedContent[quoteUrl]; setFullBorder(tooltip); } else { // not loaded var referenceList = quoteReference[quoteUrl] || []; referenceList.push(tooltip); quoteReference[quoteUrl] = referenceList; tooltip.innerHTML = 'Loading'; } quote.onmouseenter = function() { var rect = quote.getBoundingClientRect(); var previewOrigin = { x : rect.right + 10 + window.scrollX, y : rect.top + window.scrollY }; tooltip.style.left = previewOrigin.x + 'px'; tooltip.style.top = previewOrigin.y + 'px'; tooltip.style.display = 'inline'; if (!loadedContent[quoteUrl]) { var parts = quoteUrl.split('#') var qPost = parts.pop() parts = parts.join('#').split('.') parts.pop() parts = parts.join('.').split('/') var qThread = parts.pop() parts.pop() var qBoard = parts.pop() if (boardUri === undefined) { console.log('posting::processQuote - overboard?') } // && threadId == qThread if (qBoard === boardUri) { var qElem = document.getElementById(qPost) if (qElem) { //console.log('accelerated hoverpost - using', qPost) if (qThread === qPost) { var tmpNode = qElem.cloneNode(true) var innerElem = tmpNode.querySelector('.innerOP') innerElem.className = 'innerPost' var postsElem = tmpNode.querySelector('.divPosts') postsElem.innerHTML = '' //console.log('using', tmpNode.innerHTML) tooltip.innerHTML = tmpNode.innerHTML } else { tooltip.innerHTML = qElem.innerHTML } setFullBorder(tooltip) return //} else { //console.log('slow loading hoverpost ['+qPost+']', quoteUrl) } } } // not loaded and not loading if (loadedPreviews.indexOf(quoteUrl) < 0 && loadingPreviews.indexOf(quoteUrl) < 0) { // fetch quote loadedContent[quoteUrl] = false loadQuote(tooltip, quoteUrl); } }; quote.onmouseout = function() { tooltip.style.display = 'none'; }; if (!board) { var matches = quote.href.match(/\#(\d+)/); quote.onclick = function() { markPost(matches[1]); }; } } function loadQuote(tooltip, quoteUrl) { var matches = quoteUrl.match(/\/(\w+)\/res\/\d+\.html\#(\d+)/); var board = matches[1]; var post = matches[2]; var previewUrl = '/' + board + '/preview/' + post + '.html'; localRequest(previewUrl, function receivedData(error, data) { if (error) { loadingPreviews.splice(loadingPreviews.indexOf(quoteUrl), 1); } else { var referenceList = quoteReference[quoteUrl]; for (var i = 0; i < referenceList.length; i++) { referenceList[i].innerHTML = data; setFullBorder(referenceList[i]); } loadedContent[quoteUrl] = data; loadedPreviews.push(quoteUrl); loadingPreviews.splice(loadingPreviews.indexOf(quoteUrl), 1); } }); loadingPreviews.push(quoteUrl); } function spoilFiles() { apiRequest('spoilFiles', { postings : getSelectedContent() }, function requestComplete(status, data) { if (status === 'ok') { alert('Files spoiled'); } else { alert(status + ': ' + JSON.stringify(data)); } }); } function applyBans(captcha) { var typedReason = document.getElementById('reportFieldReason').value.trim(); var typedDuration = document.getElementById('fieldDuration').value.trim(); var typedMessage = document.getElementById('fieldbanMessage').value.trim(); var banType = document.getElementById('selectBanType').selectedIndex; var toBan = getSelectedContent(); apiRequest('banUsers', { reason : typedReason, captcha : captcha, banType : banType, duration : typedDuration, banMessage : typedMessage, global : document.getElementById('checkboxGlobal').checked, postings : toBan }, function requestComplete(status, data) { if (status === 'ok') { alert('Bans applied'); } else { alert(status + ': ' + JSON.stringify(data)); } }); } function banPosts() { if (!document.getElementsByClassName('panelRange').length) { applyBans(); return; } var typedCaptcha = document.getElementById('fieldCaptchaReport').value.trim(); /* if (typedCaptcha.length !== 6 && typedCaptcha.length !== 24) { alert('Captchas are exactly 6 (24 if no cookies) characters long.'); return; } else if (/\W/.test(typedCaptcha)) { alert('Invalid captcha.'); return; } */ if (typedCaptcha.length == 24) { applyBans(typedCaptcha); } else { var parsedCookies = getCookies(); apiRequest('solveCaptcha', { captchaId : parsedCookies.captchaid, answer : typedCaptcha }, function solvedCaptcha(status, data) { applyBans(parsedCookies.captchaid); reloadCaptcha(); }); } } function getSelectedContent() { var selectedContent = []; var checkBoxes = document.getElementsByClassName('deletionCheckBox'); for (var i = 0; i < checkBoxes.length; i++) { var checkBox = checkBoxes[i]; if (checkBox.checked) { var splitName = checkBox.name.split('-'); var toAdd = { board : splitName[0], thread : splitName[1] }; if (splitName.length > 2) { toAdd.post = splitName[2]; } selectedContent.push(toAdd); } } return selectedContent; } function reportPosts() { var typedReason = document.getElementById('reportFieldReason').value.trim(); var typedCaptcha = document.getElementById('fieldCaptchaReport').value.trim(); var toReport = getSelectedContent(); /* if (typedCaptcha.length !== 6 && typedCaptcha.length !== 24) { alert('Captchas are exactly 6 (24 if no cookies) characters long.'); return; } else if (/\W/.test(typedCaptcha)) { alert('Invalid captcha.'); return; } */ apiRequest('reportContent', { reason : typedReason, captcha : typedCaptcha, global : document.getElementById('checkboxGlobal').checked, postings : toReport }, function requestComplete(status, data) { if (status === 'ok') { alert('Content reported'); } else { alert(status + ': ' + JSON.stringify(data)); } }); } function deletePosts() { var typedPassword = document.getElementById('deletionFieldPassword').value .trim(); var toDelete = getSelectedContent(); if (!toDelete.length) { alert('Nothing selected'); return; } var redirect = '/' + toDelete[0].board + '/'; apiRequest('deleteContent', { password : typedPassword, deleteMedia : document.getElementById('checkboxMediaDeletion').checked, deleteUploads : document.getElementById('checkboxOnlyFiles').checked, postings : toDelete }, function requestComplete(status, data) { if (status === 'ok') { alert(data.removedThreads + ' threads and ' + data.removedPosts + ' posts were successfully deleted.'); window.location.pathname = redirect; } else { alert(status + ': ' + JSON.stringify(data)); } }); } function deleteFromIpOnBoard() { var selected = getSelectedContent(); var redirect = '/' + selected[0].board + '/'; apiRequest('deleteFromIpOnBoard', { postings : selected }, function requestComplete(status, data) { if (status === 'ok') { alert('Content deleted'); window.location.pathname = redirect; } else { alert(status + ': ' + JSON.stringify(data)); } }); } // // these are moved from thread.js // // called from addPost (TODO: call this also in expansion.js) function processPostCell(postCell) { // common processing for ".postCell" Element postCell.setAttribute('data-boarduri', boardUri); // media processing var imgLinks = postCell.getElementsByClassName('imgLink'); for (var i = 0; i < imgLinks.length; i++) { // inline image expansion, inline player processImageLink(imgLinks[i]); } // backlinks //console.log('processPostCell', postCell) addToKnownPostsForBackLinks(postCell); // quote processing var quotes = postCell.getElementsByClassName('quoteLink'); for (var i = 0; i < quotes.length; i++) { var quote = quotes[i]; processQuote(quote); } processPostingQuote(postCell.getElementsByClassName('linkQuote')[0]); // other theme extensions adjustPostTime(postCell); applyShowHidePost(postCell); }; function processPostingQuote(link) { link.onclick = function() { var toQuote = link.href.match(/#q(\d+)/); if (typeof add_quick_reply_quote != "undefined") { add_quick_reply_quote(toQuote[1]); } document.getElementById('fieldMessage').value += '>>' + toQuote[1] + '\n'; return false }; } function updateIdLabels() { var ids = document.getElementsByClassName('labelId'); // create partial doc var nsn=document.createElement('span'); nsn.className='labelNav'; var prevLink=document.createElement('a'); prevLink.className='prevLabel'; prevLink.style.cursor='pointer'; prevLink.appendChild(document.createTextNode('Prev')); var nextLink=document.createElement('a'); nextLink.className='nextLabel'; nextLink.style.cursor='pointer'; nextLink.appendChild(document.createTextNode('Next')); var space=document.createTextNode(' '); nsn.appendChild(space); nsn.appendChild(prevLink); nsn.appendChild(space); nsn.appendChild(nextLink); // update UI //for (i = 0; i < ids.length; i++) { ioFor(ids.length, function(i, next) { var label=ids[i]; // only the ID on the first pull if (!label.postUserID) { label.postUserID=label.innerHTML.toString() } var id = label.postUserID var array = idsRelation[id] || []; while(label.hasChildNodes()) { label.removeChild(label.lastChild); } //var ns=document.createElement('span'); //ns.appendChild(document.createTextNode(id+' ('+array.length+')')); //label.appendChild(ns); label.appendChild(document.createTextNode(id+' ('+array.length+')')); // need to detect if label.parentNode already has this id set if (array.length!==1) { label.style.cursor='pointer'; var onsn=nsn.cloneNode(true); var scope=function(label, array, id) { function commonClick(pos) { //console.log('moving to', pos, array[pos], 'al', array.length); // activate almonds array[pos].parentNode.previousSibling.scrollIntoView(true); var index = highLightedIds.indexOf(id); if (index===-1) { label.onclick(); } } var oPrev=onsn.querySelector('.prevLabel'); oPrev.onclick=function() { // we need an element handle to figure out our current position var posElem=label.parentNode.parentNode; var pos=array.indexOf(posElem); if (pos===-1) { console.log('cant find', posElem, 'in', array); return; } //console.log('at pos', pos); // FIXME: op maybe 0, if so, we won't be able to jump to it (-1) pos--; if (pos<0) pos+=array.length; commonClick(pos); } var oNext=onsn.querySelector('.nextLabel'); oNext.onclick=function() { var posElem=label.parentNode.parentNode; var pos=array.indexOf(posElem); if (pos===-1) { console.log('cant find', posElem, 'in', array); return; } //console.log('at pos', pos); // FIXME: op maybe 0, if so, we won't be able to jump to it, so +1 pos++; if (pos===array.length) pos-=array.length; commonClick(pos); } }(label, array, id); // we need to clear it if it exists //console.log('label.parentNode.childrens', label.parentNode.childElementCount, 'test', onsn.className) if (label.parentNode.childElementCount===2) { label.parentNode.removeChild(label.parentNode.lastChild); } label.parentNode.appendChild(onsn); } // flushes all open timers before continuing setImmediate(next); }) } // this set ups idsRelation // can we call it twice on the same label? function processIdLabel(label) { // only the ID on the first pull if (!label.postUserID) { label.postUserID=label.innerHTML } var id = label.postUserID; var array = idsRelation[id] || []; idsRelation[id] = array; var cell = label.parentNode.parentNode; // make sure we don't add the same cell more than once if (array.indexOf(cell)===-1) { // new change 170216 array.push(cell); } label.onclick = function() { var index = highLightedIds.indexOf(id); if (index > -1) { // if it's already highlighted remove it highLightedIds.splice(index, 1); } else { // if it's not highlighted yet, add it to the highlighting highLightedIds.push(id); } // for all labels with this id ioFor(array.length, function(i, next) { //for (var i = 0; i < array.length; i++) { var cellToChange = array[i]; // if there's already an innerOP, pass on it if (cellToChange.className === 'innerOP') { //continue; setImmediate(next); return; } // change array element to innerPost or markedPost cellToChange.className = index > -1 ? 'innerPost' : 'markedPost'; // flushes all open timers before continuing setImmediate(next); }); }; } // // end move //