diff --git a/.gitignore b/.gitignore index e68c102..5c82337 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,21 @@ rollup.config.js .vscode/settings.json key.pem chrome.pem +1641560780544.png +1642033228102.png +a.png +converted.png +cuck.png +dd.png +f106d2459fc348494ae39e33c0905e885f3fe6b4ae7d1f8dc171ad85b94132b1.png +file.png +index.html +localstorage.html +out.png +rtlt7x.png +test.png +chrome/1449696017588.png +firefox/1449696017588.png +rev/index.html +rev/out.png +1641737123922.png diff --git a/PEE-chrome.crx b/PEE-chrome.crx deleted file mode 100644 index 81bfe32..0000000 Binary files a/PEE-chrome.crx and /dev/null differ diff --git a/README.md b/README.md index d253dfc..43fa18c 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ -PNG Extra Embedder (PEE) +PNGExtraEmbedder (PEE) ======================== -*Subsequently 'lolipiss' (**LOL** **I** **p**Want **i**To **s**Kill **s**Jannies)* - Can embed any file in a PNG/WebM/GIF and upload it to a third-party host through 4chan. Requires a userscript manager, such as ViolentMonkey. It should work with 4chan's native extension but 4ChanX is highly recommended as it is much more tested. @@ -27,7 +25,7 @@ Please report any issue you have with those (only for mainstream browsers) Also, use this if you plan to use b4k's archive. - [Install 4chanX (recommended)](https://www.4chan-x.net/builds/4chan-X.user.js) -- Install the correct WebExtension for your Browser ([Firefox](https://git.coom.tech/coomdev/PEE/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.213-an+fx.xpi) or [Chrome-based](https://chrome.google.com/webstore/detail/pngextraembedder/bfhpobiikighljcapcfmfganodihbicj)) +- Install the correct WebExtension for your Browser ([Firefox](https://git.coom.tech/coomdev/PEE/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pngextraembedder-0.222-an+fx.xpi) or [Chrome-based](https://chrome.google.com/webstore/detail/pngextraembedder/bfhpobiikighljcapcfmfganodihbicj)) For FF users, the extension is signed so you can just drag and drop it on your about:addons tab. @@ -38,8 +36,11 @@ How to Build (You only need to care about this section if you're auditing the code or contributing to development) -`npm i` and `npm run build` -then install the generated main.user.js +`npm i` and + +`npm run build` to build the userscript version. +`npm run build_chrome` to build the chromium webextension. +`npm run build_ff` to build the firefox webextension. (You'll have to do the signing yourself, though) How to use ========== @@ -70,11 +71,11 @@ The "Contribute" checkbox makes your browser report posts with embeds you come a ## [NEW] b4k -b4k is a meanie, i disabled lazyloading of thumbnails, heck you person of african american descent. +b4k is a meanie, i disabled lazyloading of thumbnails, heck. **ACK!** -If you want to use b4k, you will get warning prompts. I added a ton of warning screen so it shouldn't happen to you. +If you want to use b4k, you will get warning prompts. I added a ton of warning screens so it shouldn't happen to you. Switch to the beta and enable this setting. If you're using TamperMonkey, it has something similar called "Instant Injection" ![ack](spm.png) @@ -106,13 +107,13 @@ The file-type detection package is huge as it detect many file types, but also d ## How do I know it's not a botnet??? -You're free to audit the code. You don't have to audit the 18000 loc file, you just need to audit the 2000-something lines of typescript code in the `src` folder, build it as instructed, and compare it to the one distributed. If you're incapable of doing that, chances are you're already using unauditable extensions with backdoors anyway? dumb schizoposter. +You're free to audit the code. You don't have to audit the 22000 loc file, you just need to audit the 3000-something lines of typescript code in the `src` folder, build it as instructed, and compare it to the one distributed. ## But embedding is a bannable offense!!!11 Yeah, well use at your own risk, you double baka. -While it is true PEE used to allow you to embed complete files in your uploads, recent changes have made it much less practical (limited to really small files), so it's falling back to linking external content hosted on pomf-clones such as catbox. +While it is true PEE used to allow you to embed complete files in your uploads, recent changes to 4chans have made it much less practical (limited to really small files), so it's falling back to linking external content hosted on pomf-clones such as catbox. Links are much smaller and as some PNG editing software injects their own metadata, 4chan is basically required to allow some little amount of it to go through, lest they inconvenience a lot of their users, so a link-embedding detection method cannot be generalized, meaning they rely on unpaid janny labor to moderate this kind of content. diff --git a/build-chrome.js b/build-chrome.js index e7690bf..ded0296 100644 --- a/build-chrome.js +++ b/build-chrome.js @@ -6,14 +6,14 @@ import { writeFileSync, readFileSync, copyFileSync } from 'fs' import esbuild from "esbuild"; import esbuildSvelte from "esbuild-svelte"; import sveltePreprocess from "svelte-preprocess"; -import path from 'path'; +//import path from 'path'; -import ChromeExtension from 'crx'; +//import ChromeExtension from 'crx'; -const crx = new ChromeExtension({ - codebase: 'https://git.coom.tech/coomdev/PEE/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/PEE-chrome.crx', - privateKey: readFileSync('./key.pem') -}); +//const crx = new ChromeExtension({ +// codebase: 'https://github.com/coomdev/pngextraembedder/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/PEE-chrome.crx', +// privateKey: readFileSync('./key.pem') +//}); let res = spawnSync("git", ["rev-list", "--count", "HEAD"]); let rev = +res.stdout; @@ -58,7 +58,7 @@ const domains = [ const manif = { "manifest_version": 2, - "update_url": "https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/chrome_update.xml", +// "update_url": "https://github.com/coomdev/pngextraembedder/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/chrome_update.xml", "name": "PngExtraEmbedder", "description": "Discover embedded files on 4chan and archives!", "version": "0." + rev, @@ -95,7 +95,7 @@ const manif = { const manif3 = { "manifest_version": 3, - "update_url": "https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/chrome_update.xml", +// "update_url": "https://github.com/coomdev/pngextraembedder/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/chrome_update.xml", "name": "PngExtraEmbedder", "description": "Discover embedded files on 4chan and archives!", "version": "0." + rev, @@ -103,11 +103,11 @@ const manif3 = { "64": "1449696017588.png" }, "permissions": [ - "notifications", - "clipboardWrite", - "activeTab", + //"notifications", + //"clipboardWrite", + //"activeTab", "declarativeNetRequestWithHostAccess", - "contextMenus", + //"contextMenus", ], host_permissions: domains, //"host_permissions":[""], @@ -195,9 +195,9 @@ const manif3 = { writeFileSync('./chrome/manifest.json', JSON.stringify(lmanif, null, 2)); copyFileSync("./logo.png", "./chrome/1449696017588.png"); - const ext = await crx.load('./chrome'); - const crxBuffer = await ext.pack(); - const updateXML = crx.generateUpdateXML(); - writeFileSync('./chrome_update.xml', updateXML); - writeFileSync('./PEE-chrome.crx', crxBuffer); + //const ext = await crx.load('./chrome'); + //const crxBuffer = await ext.pack(); + //const updateXML = crx.generateUpdateXML(); + //writeFileSync('./chrome_update.xml', updateXML); + //writeFileSync('./PEE-chrome.crx', crxBuffer); })(); diff --git a/build-ff.js b/build-ff.js index 56deef2..9220fa8 100644 --- a/build-ff.js +++ b/build-ff.js @@ -54,7 +54,7 @@ const manif = { "manifest_version": 2, "browser_specific_settings": { "gecko": { - "update_url": "https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/firefox_update.json", + "update_url": "https://github.com/coomdev/pngextraembedder/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/firefox_update.json", } }, "name": "PngExtraEmbedder", @@ -161,7 +161,7 @@ const manif = { "updates": [ { "version": manif.version, - "update_link": "https://git.coom.tech/fuckjannies/lolipiss/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pee-firefox.zip", + "update_link": "https://github.com/coomdev/pngextraembedder/raw/branch/%E4%B8%AD%E5%87%BA%E3%81%97/pee-firefox.zip", } ] } diff --git a/chrome/b4k/board.js b/chrome/b4k/board.js new file mode 100644 index 0000000..c729ecb --- /dev/null +++ b/chrome/b4k/board.js @@ -0,0 +1,1544 @@ +var bindFunctions = function() +{ + // the following block of code deals with drag and drop of images for MD5 hashing + var search_dropdown = jQuery('#search_form_image'); + + if (isEventSupported('dragstart') && isEventSupported('drop') && !!window.FileReader) + { + search_dropdown.on('dragover', function(e) { + e.preventDefault(); + e.stopPropagation(); + e.originalEvent.dataTransfer.dropEffect = 'copy'; + }); + + search_dropdown.on('dragenter', function(e) { + e.preventDefault(); + e.stopPropagation(); + }); + + search_dropdown.on('drop', function(event) { + if (event.originalEvent.dataTransfer){ + if (event.originalEvent.dataTransfer.files.length) { + event.preventDefault(); + event.stopPropagation(); + + findSameImageFromFile(event.originalEvent.dataTransfer); + } + } + }); + } + + var is_posting = false; + + var clickCallbacks = { + + checkAll: function(el, post, event) + { + var checkboxes = el.parent().parent().find('input[type=checkbox]'); + checkboxes.each(function(id, element) { + jQuery(element).attr('checked', 'checked'); + }); + el.parent().find('.uncheck').show(); + el.hide(); + }, + + uncheckAll: function(el, post, event) + { + var checkboxes = el.parent().parent().find('input[type=checkbox]'); + checkboxes.each(function(id, element) { + jQuery(element).attr('checked', false); + }); + el.parent().find('.check').show(); + el.hide(); + }, + + hideThread: function(el, post, event) + { + var hiddenBoardThreads = JSON.parse(localStorage.getItem("hiddenBoardThreads/" + el.data("board"))) || {}; + var num = el.data("doc-id"); + + hiddenBoardThreads[num] = true; + localStorage.setItem("hiddenBoardThreads/" + el.data("board"), JSON.stringify(hiddenBoardThreads)); + + jQuery(".doc_id_" + num).hide(); + jQuery(".stub_doc_id_" + num).show(); + }, + + showThread: function(el, post, event) + { + var hiddenBoardThreads = JSON.parse(localStorage.getItem("hiddenBoardThreads/" + el.data("board"))) || {}; + var num = el.data("doc-id"); + + delete hiddenBoardThreads[num]; + localStorage.setItem("hiddenBoardThreads/" + el.data("board"), JSON.stringify(hiddenBoardThreads)); + + jQuery(".doc_id_" + num).show(); + jQuery(".stub_doc_id_" + num).hide(); + }, + + hidePost: function(el, post, event) + { + var hiddenBoardPosts = JSON.parse(localStorage.getItem("hiddenBoardPosts/" + el.data("board"))) || {}; + var num = el.data("doc-id"); + + hiddenBoardPosts[num] = true; + localStorage.setItem("hiddenBoardPosts/" + el.data("board"), JSON.stringify(hiddenBoardPosts)); + + jQuery(".doc_id_" + num).hide(); + jQuery(".stub_doc_id_" + num).show(); + }, + + showPost: function(el, post, event) + { + var hiddenBoardPosts = JSON.parse(localStorage.getItem("hiddenBoardPosts/" + el.data("board"))) || {}; + var num = el.data("doc-id"); + + delete hiddenBoardPosts[num]; + localStorage.setItem("hiddenBoardPosts/" + el.data("board"), JSON.stringify(hiddenBoardPosts)); + + jQuery(".doc_id_" + num).show(); + jQuery(".stub_doc_id_" + num).hide(); + }, + + highlight: function(el, post, event) + { + if (post) + { + toggleHighlight(post); + } + }, + + quote: function(el, post, event) + { + jQuery("#reply_chennodiscursus").insertAtCaret(">>" + post + "\n"); + }, + + comment: function(el, post, event) + { + var file_el = jQuery("#file_image"); + var progress_pos = 0; + var progress_el = jQuery("#reply .progress .bar"); + + // if there's an image and the browser doesn't support FormData, use a normal upload process + if (file_el.val() && window.FormData === undefined) + { + return true; + } + + if (is_posting) + { + return false; + } + + is_posting = true; + + var originalText = el.attr('value'); + var el_parent = el.parent(); + el.attr({'value': backend_vars.gettext['submit_state']}); + el_parent.find('[name=reply_gattai]').attr({disabled:'disabled'}); + el_parent.find('[name=reply_gattai_spoilered]').attr({disabled:'disabled'}); + //el.parent().find('[name=reply_gattai], [name=reply_gattai_spoilered]') + + // to make sure nobody gets pissed off with a blocked button + var buttonTimeout = setTimeout(function(){ + el.attr({'value': originalText}); + el.removeAttr('disabled'); + }, 10000); + + var reply_alert = jQuery('#reply_ajax_notices'); + reply_alert.removeClass('error').removeClass('success'); + + var data_obj = { + reply_numero: jQuery("#reply_numero").val(), + reply_bokunonome: jQuery("#reply_bokunonome").val(), + reply_elitterae: jQuery("#reply_elitterae").val(), + reply_talkingde: jQuery("#reply_talkingde").val(), + reply_chennodiscursus: jQuery("#reply_chennodiscursus").val(), + reply_nymphassword: jQuery("#reply_nymphassword").val(), + reply_postas: jQuery("#reply_postas").val() === undefined ? 'N' : jQuery("#reply_postas").val(), + reply_gattai: 'Submit', + reply_last_limit: typeof backend_vars.last_limit === "undefined" ? null : backend_vars.last_limit, + latest_doc_id: backend_vars.latest_doc_id, + theme: backend_vars.selected_theme + }; + + if(typeof jQuery("#recaptcha_challenge_field").val() !== 'undefined' && typeof jQuery("#recaptcha_response_field").val() !== 'undefined') { + data_obj['recaptcha_challenge_field'] = jQuery("#recaptcha_challenge_field").val(); + data_obj['recaptcha_response_field'] = jQuery("#recaptcha_response_field").val(); + } else if(typeof jQuery("#g-recaptcha-response").val() !== 'undefined') { + data_obj['recaptcha2_response_field'] = jQuery("#g-recaptcha-response").val(); + } + + // sets the type of submit (reply_gattai, reply_gattai_spoilered) + data_obj[el.attr('name')] = true; + + // support for checkbox spoiler + if (el_parent.find('[name=reply_spoiler]:checked').length) + { + data_obj.reply_spoiler = true; + } + + data_obj[backend_vars.csrf_token_key] = getCookie(backend_vars.csrf_token_key); + + progress_el.parent().animate({'opacity': '1.0'}, 300); + + var ajax_object = { + url: backend_vars.site_url + backend_vars.board_shortname + '/submit/' , + dataType: 'json', + type: 'POST', + data: data_obj, + cache: false, + xhr: function() { + var xhr = jQuery.ajaxSettings.xhr(); + if (xhr instanceof window.XMLHttpRequest) { + xhr.upload.addEventListener('progress', function(evt){ + var progress_local = Math.ceil(evt.loaded / evt.total * 100); + if (evt.lengthComputable && progress_pos !== progress_local) + { + progress_pos = progress_local; + progress_el.css('width', (progress_pos) + '%'); + } + }, false); + } + return xhr; + }, + success: function(data, textStatus, jqXHR) { + if (typeof window.Recaptcha !== "undefined") + { + window.Recaptcha.reload(); + } + if (typeof window.grecaptcha !== "undefined") { + grecaptcha.reset(); + } + + jQuery("#recaptcha_response_field").val(''); + if (typeof data.captcha !== "undefined") + { + if(recaptcha2.enabled && typeof window.grecaptcha === "undefined") { + jQuery('.recaptcha_widget').html('

You might be a bot! Enter a reCAPTCHA to continue.

\ +
\ + ') + } + jQuery('.recaptcha_widget').show(); + jQuery('.rules_box').hide(); + return false; + } + + if (typeof data.error !== "undefined") + { + reply_alert.html(data.error); + reply_alert.addClass('error'); // deals with showing the alert + return false; + } + + jQuery('.rules_box').show(); + jQuery('.recaptcha_widget').hide(); + + reply_alert.html(data.success); + reply_alert.addClass('success'); // deals with showing the alert + jQuery("#reply_chennodiscursus").val(""); + jQuery("#reply_nymphassword").val(getCookie('reply_password')); + file_el.replaceWith(''); + + + // redirect in case of new threads + if (data_obj.reply_numero < 1) + { + window.location = backend_vars.site_url + backend_vars.board_shortname + '/thread/' + + data.thread_num + '/'; + return false; + } + + insertPost(data, textStatus, jqXHR); + }, + error: function(jqXHR, textStatus, errorThrown) { + reply_alert.html('Connection error.'); + reply_alert.addClass('error'); + reply_alert.show(); + }, + complete: function() { + // clear button's timeout, we can deal with the rest now + is_posting = false; + clearTimeout(buttonTimeout); + el.attr({'value': originalText}); + el_parent.find('[name=reply_gattai]').removeAttr('disabled'); + el_parent.find('[name=reply_gattai_spoilered]').removeAttr('disabled'); + progress_el.css('width', '0%'); + progress_el.parent().animate({'opacity': '0.0'}, 300); + } + }; + + // if we have FormData support, we can upload files! + if (window.FormData !== undefined) + { + ajax_object.processData = false; + ajax_object.contentType = false; + var data_formdata = new FormData(); + jQuery.each(data_obj, function(id, val){ + data_formdata.append(id, val); + }); + + if (typeof file_el[0] !== 'undefined' && typeof file_el[0].files !== 'undefined') + { + data_formdata.append('file_image', file_el[0].files[0]) + } + + ajax_object.data = data_formdata; + } + + jqxhr = jQuery.ajax(ajax_object); + + event.preventDefault(); + }, + + realtimeThread: function(el, post, event) + { + realtimethread(); + event.preventDefault(); + }, + + expandThread: function(el, post, event) + { + var thread_num = el.data('thread-num'); + + if (! el.data('expanded')) + { + el.spin('small'); + jQuery.ajax({ + url: backend_vars.api_url + '_/api/chan/thread/', + dataType: 'json', + type: 'GET', + data: { + num : thread_num, + board: backend_vars.board_shortname, + theme: backend_vars.selected_theme + }, + success: function(data, textStatus, jqXHR){ + insertPost(data, textStatus, jqXHR); + var post_count = 0; + var media_count = 0; + jQuery.each(data[thread_num].posts, function(id, val){ + post_count++; + if (val.media !== null) + { + media_count++; + } + }); + var thread = jQuery('article.thread[data-thread-num=' + thread_num + '] '); + var displayed_string = post_count + ' posts ' + + (media_count > 0 ? 'and ' + media_count + ' ' + (media_count == 1 ? 'image' : 'images') : '') + ' displayed'; + thread.find('.omitted_text').text(displayed_string); + el.data('expanded', true).html(''); + el.spin(false); + } + }); + } + else + { + var thread = jQuery('article.thread[data-thread-num=' + thread_num + ']'); + var articles = thread.find('aside.posts article'); + articles.slice(0, articles.length - 5).hide(); + var post_count = articles.filter(':hidden').length; + var media_count = articles.find('.thread_image_box:hidden').length; + var omitted_string = post_count + ' posts ' + + (media_count > 0 ? 'and ' + media_count + ' ' + (media_count == 1 ? 'image' : 'images') : '') + ' omitted'; + thread.find('.omitted_text').text(omitted_string); + el.data('expanded', false).html(''); + } + + return false; + }, + + clearSearch: function(el, post, event) + { + var form = jQuery('.advanced_search').find('form'); + form.find(':input').not(':input[type=submit]').not(':input[type=reset]').val(''); + + // keep the first radio set + var done_names = []; + form.find('[type=radio]').each(function (idx) { + if (!jQuery.inArray(jQuery(this).attr('name'), done_names)) + { + jQuery(this).attr('checked', true); + done_names.push(jQuery(this).attr('name')); + } + }); + }, + + mod: function(el, post, event) + { + el.attr({'disabled': 'disabled'}); + _data = { + board: el.data('board'), + id: el.data('id'), + ip: el.data('ip'), + action: el.data('action'), + global: el.data('global'), + theme: backend_vars.selected_theme + }; + _data[backend_vars.csrf_token_key] = getCookie(backend_vars.csrf_token_key); + jQuery.ajax({ + url: backend_vars.api_url + '_/api/chan/mod_actions/', + dataType: 'json', + type: 'POST', + cache: false, + data: _data, + success: function(data){ + el.removeAttr('disabled'); + if (typeof data.error !== "undefined") + { + alert(data.error); + return false; + } + + // might need to be upgraded to array support + switch(el.data('action')) + { + case 'remove_post': + jQuery('.doc_id_' + el.data('id')).remove(); + break; + case 'delete_image': + jQuery('.doc_id_' + el.data('doc-id')).find('.thread_image_box:eq(0) img') + .attr('src', backend_vars.images['missing_image']) + .css({ + width: backend_vars.images['missing_image_width'], + height: backend_vars.images['missing_image_height'] + }); + break; + case 'delete_report': + el.closest('.report_reason').remove(); + break; + case 'ban_user': + jQuery('.doc_id_' + el.data('id')).find('[data-action=ban_user]').text('Banned'); + break; + case 'ban_image': + jQuery('.doc_id_' + el.data('doc-id')).find('.thread_image_box:eq(0) img') + .attr('src', backend_vars.images['banned_image']) + .css({ + width: backend_vars.images['banned_image_width'], + height: backend_vars.images['banned_image_height'] + }); + break; + case 'delete_all_reports': + $(".report_reason").each(function(){ + $(this).remove(); + }); + break; + default: + el.closest('.report_reason').append(data.success); + break; + } + }, + error: function(jqXHR, textStatus, errorThrown) { + + }, + complete: function() { + } + }); + return false; + }, + + activateModeration: function(el, post, event) + { + jQuery('.post_mod_controls button[data-function]').attr({'disabled': 'disabled'}); + setTimeout(function(){ + jQuery('.post_mod_controls button[data-function]').removeAttr('disabled'); + }, 700); + jQuery('.post_mod_controls').show(); + jQuery('button[data-function=activateModeration]').parent().hide(); + }, + + activateExtraMod: function(el, post, event) + { + jQuery('.post_extra_mod button[data-function]').attr({'disabled': 'disabled'}); + setTimeout(function(){ + jQuery('.post_extra_mod button[data-function]').removeAttr('disabled'); + }, 700); + jQuery('.post_extra_mod').show(); + jQuery('button[data-function=activateExtraMod]').parent().hide(); + }, + + closeModal: function(el, post) + { + el.closest(".modal").modal('hide'); + return false; + }, + + 'delete': function(el, post, event) + { + var modal = jQuery("#post_tools_modal"); + var foolfuuka_reply_password = getCookie('foolfuuka_reply_password'); + modal.find(".title").html('Delete » Post No. ' + el.data("post-id")); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").hide(); + modal.find(".modal-information").html('\ + Password\n\ + \n\ + \n\ + '); + modal.find(".submitModal").data("action", 'delete'); + modal.find(".modal-password").val(backend_vars.user_pass); + }, + + report: function(el, post, event) + { + var modal = jQuery("#post_tools_modal"); + modal.find(".title").html('Report » Post No.' + el.data("post-id")); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").hide(); + modal.find(".modal-information").html('Report Multiple\ + \n\ + \n\ + Comment\n\ + \n\ + Note: Requests for content removal and take-downs must be sent via email.'); + modal.find(".submitModal").data("action", 'report'); + }, + + addBulkReport: function(el, post, event) { + jQuery('article.thread, article.post').each(function () { + if (typeof jQuery(this).attr('data-board') != 'undefined') { + jQuery(this).find('a[data-function=report]:eq(0)').replaceWith('' + + 'Report Selected'); + } + }); + el.closest(".modal").modal('hide'); + }, + + bulkReport: function(el, post, event) { + var modal = jQuery("#post_tools_modal"); + modal.find(".title").html('Report Posts'); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").hide(); + modal.find(".modal-information").html('Selected posts:
'); + jQuery('.bulkreportselect:checked').each(function () { + modal.find(".modal-information").append('>>>/' + $(this).attr('data-board') + '/' + $(this).attr('data-num') + '
'); + }); + modal.find(".modal-information").append('
Comment\n\ + \n\ + Note: Requests for content removal and take-downs must be sent via email.'); + modal.find(".submitModal").data("action", 'bulk-report'); + }, + + ban: function(el, post, event) + { + var modal = jQuery("#post_tools_modal"); + modal.find(".title").html('Ban user with IP ' + el.data("ip")); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").hide(); + modal.find(".modal-information").html('\ + \n\ + IP\n\ +
\n\ + Days\n\ +
\n\ + Only this board\n\ +
\n\ + Global\n\ +
\n\ + Comment\n\ + \n\ + \ + \n\ + '); + modal.find(".submitModal").data("action", 'ban'); + }, + + editPost: function(el, post, event) + { + var modal = jQuery("#post_tools_modal"); + modal.find(".title").html('Edit Post No. ' + el.data("post-id")); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").show(); + modal.find(".modal-information").html('Bulk Edit\ +
\ + \n\ + \n\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + \ + '); + modal.find(".submitModal").data("action", 'edit-post'); + jQuery.ajax({ + url: backend_vars.api_url + '_/api/chan/post/' , + dataType: 'json', + type: 'GET', + cache: false, + data: { + board: el.data('board'), + num: el.data('post-id') + }, + success: function(data){ + jQuery("input[name='edit-subject']").val(data.title); + jQuery("input[name='edit-name']").val(data.name); + jQuery("input[name='edit-trip']").val(data.trip); + jQuery("input[name='edit-email']").val(data.email); + jQuery("input[name='edit-country']").val(data.poster_country); + jQuery("input[name='edit-poster_hash']").val(data.poster_hash); + jQuery("select[name='edit-capcode']").val(data.capcode); + jQuery("textarea[name='edit-comment']").val(data.comment); + if(data.media != null) { + modal.find(".modal-information").append('

Media

\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + '); + jQuery("input[name='media_id']").val(data.media.media_id); + jQuery("input[name='edit-filename']").val(data.media.media_filename); + if(data.media.spoiler == 1) { + jQuery("input[name='edit-spoiler']").click(); + } + jQuery("input[name='edit-media_w']").val(data.media.media_w); + jQuery("input[name='edit-media_h']").val(data.media.media_h); + jQuery("input[name='edit-preview_w']").val(data.media.preview_w); + jQuery("input[name='edit-preview_h']").val(data.media.preview_h); + } + }, + error: function() { + console.log('post not found'); + el.closest(".modal").modal('hide'); + }, + complete: function() { + modal.find(".modal-information").append('
'); + modal.find(".modal-loading").hide(); + } + }); + }, + + addBulkEdit: function(el, post, event) { + jQuery('article.thread, article.post').each(function () { + if (typeof jQuery(this).attr('data-board') != 'undefined') { + jQuery('' + + 'Edit Selected') + .prependTo($(this).find('.post_data:first')); + } + }); + el.closest(".modal").modal('hide'); + }, + + bulkEdit: function(el, post, event) { + var modal = jQuery("#post_tools_modal"); + modal.find(".title").html('Edit Posts'); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").hide(); + modal.find(".modal-information").html('This will only modify specified fields. Leave blank to not modify that field.

' + + 'Selected posts:
'); + jQuery('.bulkselect:checked').each(function () { + modal.find(".modal-information").append('>>>/' + $(this).attr('data-board') + '/' + $(this).attr('data-num') + '
'); + }); + modal.find(".modal-information").append('
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + \ +

Media

\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ +
\ + '); + modal.find(".submitModal").data("action", 'bulk-edit'); + }, + + addBulkMod: function(el, post, event) { + jQuery('article.thread, article.post').each(function () { + if (typeof jQuery(this).attr('data-board') != 'undefined') { + jQuery(this).find('a[data-function=delete]:eq(0)').replaceWith('' + + 'Mod Selected'); + } + }); + }, + + bulkMod: function(el, post, event) { + var modal = jQuery("#post_tools_modal"); + modal.find(".title").html('Mod Posts'); + modal.find(".modal-error").html(''); + modal.find(".modal-loading").hide(); + modal.find(".modal-information").html('Selected posts:
'); + jQuery('.bulkmodselect:checked').each(function () { + modal.find(".modal-information").append('>>>/' + $(this).attr('data-board') + '/' + $(this).attr('data-num') + '
'); + }); + modal.find(".modal-information").append('
Select action
' + + '
\ + \ + \ + \ + \ +
'); + modal.find(".submitModal").data("action", 'bulk-mod'); + }, + + submitModal: function(el, post, event) + { + var modal = jQuery("#post_tools_modal"); + var loading = modal.find(".modal-loading"); + var action = el.data("action"); + var _board = modal.find(".modal-board").val(); + var _doc_id = modal.find(".modal-post-id").val(); + var _href = backend_vars.api_url+'_/api/chan/user_actions/'; + var _data = {}; + + if (action == 'report') { + _data = { + action: 'report', + board: _board, + doc_id: _doc_id, + reason: modal.find(".modal-comment").val(), + csrf_fool: backend_vars.csrf_hash + }; + } + else if (action == 'report_media') { + _data = { + action: 'report_media', + board: _board, + media_id: modal.find(".modal-media-id").val(), + reason: modal.find(".modal-comment").val(), + csrf_fool: backend_vars.csrf_hash + }; + } + else if (action == 'delete') { + _data = { + action: 'delete', + board: _board, + doc_id: _doc_id, + password: modal.find(".modal-password").val(), + csrf_fool: backend_vars.csrf_hash + }; + } + else if (action == 'ban') + { + _href = backend_vars.api_url+'_/api/chan/mod_actions/'; + _data = { + action: 'ban_user', + board: modal.find('.modal-board').val(), + board_ban: modal.find('input:radio[name=board]:checked').val(), + length: modal.find('.modal-days').val() * 24 * 60 * 60, + ip: modal.find('.modal-ip').val(), + reason: modal.find('.modal-comment').val(), + doc_id: _doc_id + }; + if ($('input[name=delete_user]').is(':checked')) { + _data.delete_user = true; + } + if ($('input[name=ban_public]').is(':checked')) { + _data.ban_public = true; + } + } + else if (action == 'edit-post') + { + _href = backend_vars.api_url+'_/api/chan/edit_post/'; + _data = { + action: 'edit_post', + board: modal.find('.modal-board').val(), + doc_id: modal.find('.modal-post-id').val(), + subject: modal.find("input[name='edit-subject']").val(), + name: modal.find("input[name='edit-name']").val(), + trip: modal.find("input[name='edit-trip']").val(), + email: modal.find("input[name='edit-email']").val(), + poster_country: modal.find("input[name='edit-country']").val(), + poster_hash: modal.find("input[name='edit-poster_hash']").val(), + capcode: modal.find("select[name='edit-capcode']").val(), + comment: modal.find("textarea[name='edit-comment']").val(), + csrf_fool: backend_vars.csrf_hash + }; + if ($('input[name=transparency]').is(':checked')) { + _data.transparency = true; + } + if(modal.find("input[name='media_id']").val() != null) { + _data.media_edit = true; + _data.filename = modal.find("input[name='edit-filename']").val(); + _data.media_w = modal.find("input[name='edit-media_w']").val(); + _data.media_h = modal.find("input[name='edit-media_h']").val(); + _data.preview_w = modal.find("input[name='edit-preview_w']").val(); + _data.preview_h = modal.find("input[name='edit-preview_h']").val(); + if ($('input[name=edit-spoiler]').is(':checked')) { + _data.spoiler = 1; + } else { + _data.spoiler = 0; + } + } + } + else if (action == 'bulk-edit') { + _href = backend_vars.api_url + '_/api/chan/edit_post/'; + + _data = { + action: 'bulk_edit', + subject: modal.find("input[name='edit-subject']").val(), + name: modal.find("input[name='edit-name']").val(), + trip: modal.find("input[name='edit-trip']").val(), + email: modal.find("input[name='edit-email']").val(), + poster_country: modal.find("input[name='edit-country']").val(), + poster_hash: modal.find("input[name='edit-poster_hash']").val(), + capcode: modal.find("select[name='edit-capcode']").val(), + comment: modal.find("textarea[name='edit-comment']").val(), + filename: modal.find("input[name='edit-filename']").val(), + media_w: modal.find("input[name='edit-media_w']").val(), + media_h: modal.find("input[name='edit-media_h']").val(), + preview_w: modal.find("input[name='edit-preview_w']").val(), + preview_h: modal.find("input[name='edit-preview_h']").val(), + csrf_fool: backend_vars.csrf_hash, + posts: [] + }; + if ($('input[name=edit-spoiler]').is(':checked')) { + _data.spoiler = 1; + } else { + _data.spoiler = 0; + } + jQuery('.bulkselect:checked').each(function () { + _data.posts.push({ + radix: $(this).attr('data-board'), + doc_id: $(this).attr('data-doc-id') + } + ); + }); + } + else if (action == 'bulk-report') { + _data = { + action: 'bulk_report', + reason: modal.find(".modal-comment").val(), + csrf_fool: backend_vars.csrf_hash, + posts: [] + }; + jQuery('.bulkreportselect:checked').each(function () { + _data.posts.push({ + radix: $(this).attr('data-board'), + doc_id: $(this).attr('data-doc-id'), + num: $(this).attr('data-num') + } + ); + }); + } + else if (action == 'bulk-mod') { + _href = backend_vars.api_url+'_/api/chan/bulk_mod/'; + _data = { + action: 'bulk_mod', + mod_function: $('input[name=modaction]:checked').val(), + csrf_fool: backend_vars.csrf_hash, + posts: [] + }; + jQuery('.bulkmodselect:checked').each(function () { + _data.posts.push({ + radix: $(this).attr('data-board'), + doc_id: $(this).attr('data-doc-id'), + num: $(this).attr('data-num') + } + ); + }); + } + else { + // Stop It! Unable to determine which action to use. + return false; + } + + _data[backend_vars.csrf_token_key] = getCookie(backend_vars.csrf_token_key); + + jQuery.post(_href, _data, function(result) { + loading.hide(); + if (typeof result.error !== 'undefined') { + modal.find(".modal-error").html('
×

' + result.error + '

').show(); + return false; + } + modal.modal('hide'); + + if (action == 'delete') { + jQuery('.doc_id_' + _doc_id).hide(); + } + if (action == 'report') { + jQuery('.doc_id_' + _doc_id).find('.text:eq(0)').after('
' + result.success + '
'); + } + if (action == 'edit-post') { + jQuery('.doc_id_' + _doc_id).find('.text:eq(0)').after('
' + result.success + '
'); + } + if (action == 'bulk-edit') { + jQuery('div.container').after('
' + result.success + '
'); + } + if (action == 'bulk-report') { + jQuery('div.container').after('
' + result.success + '
'); + } + if (action == 'bulk-mod') { + jQuery('div.container').after('
' + result.success + '
'); + } + }, 'json'); + return false; + }, + + clearLatestSearches: function(el, post, event) + { + setCookie('search_latest_5', '', 0, '/', backend_vars.cookie_domain); + jQuery('li.latest_search').each(function(idx){ + jQuery(this).remove(); + }); + }, + + searchUser: function(el, post, event) + { + window.location.href = backend_vars.site_url + el.data('board') + + '/search/poster_ip/' + el.data('poster-ip'); + }, + + searchUserGlobal: function(el, post, event) + { + window.location.href = backend_vars.site_url + '_/search/poster_ip/' + el.data('poster-ip'); + }, + + toggleExif: function(el, post, event) + { + jQuery(".exiftable."+post).toggle(); + }, + + searchhilight: function(el, post, event) + { + if (el.is(':checked')) { + setCookie("searchhilight_enabled", true, 90, '/', backend_vars.cookie_domain); + if (jQuery("span[data-markjs='true']").length) { + jQuery("span[data-markjs='true']").addClass("highlight"); + } else if (typeof backend_vars.search_args !== "undefined") { + highlightSearchResults(); + } + } else { + setCookie("searchhilight_enabled", false, 90, '/', backend_vars.cookie_domain); + jQuery("span[data-markjs='true']").removeClass("highlight"); + } + } + }; + + + // unite all the onclick functions in here + jQuery(document.body).on("click", "a[data-function], button[data-function], input[data-function]", function(event) { + var el = jQuery(this); + var post = el.data("post"); + return clickCallbacks[el.data("function")](el, post, event); + }); + + jQuery(document.body).on("mousedown touchstart", ".search_box, .search-query", function(event) { + event.stopPropagation(); + }); + + jQuery(document.body).on("mousedown touchstart", function(event) { + var search_input = jQuery('#search_form_comment'); + jQuery('.search-query').val(search_input.val()); + jQuery('.search_box').hide(); + }); + + jQuery(document.body).on("mousedown touchstart", ".search-query", function() { + var el = jQuery(this); + var offset = el.offset(); + var width = el.outerWidth(); + var search_box = jQuery('.search_box'); + var comment_wrap = search_box.find('.comment_wrap'); + var comment_wrap_pos = comment_wrap.position(); + search_box.css({top: (offset.top - 11) + 'px', right: (jQuery(window).width() - (offset.left + width) - 16) + 'px'}).show(); + el.parents('.open').removeClass('open'); + search_box.find('input[name=text]').focus(); + return false; + }); + + // how could we make it work well on cellphones? + if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) + { + return false; + } + + + // variable for ajax backlinks that we can clear them if the mouse hovered out + var backlink_jqxhr; + var timeout; + var posts_being_fetched = []; // posts that are being xhr'd right now + var show_post_board; + var show_post_num; // the number of the post that has to show in the xhr callback, when 0 don't run popups + + // hover functions go here + jQuery("#main").on("mouseover mouseout", "article a[data-backlink]", function(event) + { + + if (event.type == "mouseover") + { + + var backlink = jQuery("#backlink"); + var el = jQuery(this); + + var pos = el.offset(); + var height = el.height(); + var width = el.width(); + + if (el.attr('data-backlink') != 'true') + { + // gallery + var thread_id = el.attr('data-backlink'); + quote = backend_vars.threads_data[thread_id]; + backlink.css('display', 'block'); + backlink.html(quote.formatted); + } + else if (jQuery('#' + el.data('post')).hasClass('post')) + { + // normal posts + var toClone = jQuery('#' + el.data('post')); + if (toClone.length == 0) + return false; + backlink.css('display', 'block'); + backlink.html(toClone.clone().show()); + } + else if (typeof backend_vars.loaded_posts[el.data('board') + ':' + el.data('post')] !== 'undefined') + { + // if we know the post doesn't exist + if (backend_vars.loaded_posts[el.data('board') + ':' + el.data('post')] === false) + { + shakeBacklink(el); + return false; + } + var data = backend_vars.loaded_posts[el.data('board') + ':' + el.data('post')]; + backlink.html(data.formatted); + backlink.css('display', 'block'); + } + else + { + show_post_board = el.data('board'); + show_post_num = el.data('post'); + + // avoid multiple connections + if (posts_being_fetched[el.data('board') + ':' + el.data('post')]) { + return false; + } + + timeout = setTimeout(function() { + var backlink_spin = el; + backlink_spin.spin('small'); + backlink_jqxhr = jQuery.ajax({ + url: backend_vars.api_url + '_/api/chan/post/' , + dataType: 'json', + type: 'GET', + cache: false, + data: { + board: el.data('board'), + num: el.data('post'), + theme: backend_vars.selected_theme + }, + success: function(data){ + backlink_spin.spin(false); + if (typeof data.error !== "undefined") + { + backend_vars.loaded_posts[el.data('board') + ':' + el.data('post')] = false; + shakeBacklink(el); + return false; + } + backend_vars.loaded_posts[el.data('board') + ':' + el.data('post')] = data; + + // avoid showing the post if the mouse is not there anymore + if (show_post_board === el.data('board') && show_post_num === el.data('post')) + { + backlink.html(data.formatted); + backlink.find("time").localize('ddd dd mmm yyyy HH:MM:ss'); + backlink.css('display', 'block'); + showBacklink(backlink, pos, height, width); + } + }, + error: function() { + shakeBacklink(el); + }, + complete: function() { + posts_being_fetched[el.data('board') + ':' + el.data('post')] = false; + } + }); + return false; + }, 50); + } + + backlink.find("time").localize('ddd dd mmm yyyy HH:MM:ss'); + showBacklink(backlink, pos, height, width); + } + else + { + show_post_board = false; + show_post_num = false; + clearTimeout(timeout); + jQuery("#backlink").css('display', 'none').html(''); + } + }); +}; + +var hideThreads = function() +{ + if (typeof backend_vars.board_shortname !== "undefined" && typeof backend_vars.thread_id === "undefined") + { + var hiddenBoardThreads = JSON.parse(localStorage.getItem("hiddenBoardThreads/" + backend_vars.board_shortname)) || {}; + + for (var num in hiddenBoardThreads) + { + jQuery(".doc_id_" + num).hide(); + jQuery(".stub_doc_id_" + num).show(); + } + } +}; + +var hidePosts = function() +{ + if (typeof backend_vars.board_shortname !== "undefined") + { + var hiddenBoardPosts = JSON.parse(localStorage.getItem("hiddenBoardPosts/" + backend_vars.board_shortname)) || {}; + + for (var num in hiddenBoardPosts) + { + jQuery(".doc_id_" + num).hide(); + jQuery(".stub_doc_id_" + num).show(); + } + } +}; + +var shakeBacklink = function(el) +{ + el.css({position:'relative'}); + el.animate({left: '-5px'},100) + .animate({left: '+5px'}, 100) + .animate({left: '-5px'}, 100) + .animate({left: '+5px'}, 100) + .animate({left: '+0px'}, 100, 'linear', function(){ + el.css({position:'static'}); + }); + +}; + +var showBacklink = function(backlink, pos, height, width) +{ + if (jQuery(window).width()/2 < pos.left + width/2) + { + backlink.css({ + right: (jQuery(window).width() - pos.left - width) + 'px', + top: (pos.top + height + 3) + 'px', + left: 'auto' + }); + } + else + { + backlink.css({ + left: (pos.left) + 'px', + top: (pos.top + height + 3) + 'px', + right: 'auto' + }); + } + + backlink.find('.stub').remove(); +}; + +var backlinkify = function(elem, post_id, subnum) +{ + var backlinks = {}; + if (subnum > 0) + { + post_id += "_" + subnum; + } + + elem.find("a[data-backlink=true]").each(function(idx, post) { + if (jQuery(post).text().indexOf('/') >= 0) + { + return true; + } + + var p_id = jQuery(post).attr('data-post'); + var board_shortname = jQuery(post).attr('data-board'); + + if (typeof backlinks[p_id] === "undefined") + { + backlinks[p_id] = []; + } + + if (typeof backend_vars.last_limit === "undefined") + { + if (typeof backend_vars.thread_id === "undefined") + { + backlinks[p_id].push('>>' + post_id.replace('_', ',') + ''); + + // convert /post/ links to real urls + if (jQuery('#' + p_id).length) + { + jQuery(post).attr('href', backend_vars.site_url + board_shortname + '/post/' + p_id); + } + } + else + { + backlinks[p_id].push('>>' + post_id.replace('_', ',') + ''); + + // convert /post/ links to real urls + if (jQuery('#' + p_id).length) + { + if (backend_vars.thread_id == p_id) + { + jQuery(post).addClass('op'); + } + + jQuery(post).attr('href', backend_vars.site_url + board_shortname + '/thread/' + backend_vars.thread_id + '/#' + p_id); + } + } + } + else + { + backlinks[p_id].push('>>' + post_id.replace('_', ',') + ''); + + // convert /post/ links to real urls + if (jQuery('#' + p_id).length) + { + jQuery(post).attr('href', backend_vars.site_url + board_shortname + '/last/' + backend_vars.last_limit + '/' + backend_vars.thread_id + '/#' + p_id); + } + } + + backlinks[p_id] = eliminateDuplicates(backlinks[p_id]); + }); + + jQuery.each(backlinks, function(key, val){ + var post = jQuery("#" + key); + if (post.length == 0) + return false; + + var post_backlink = post.find(".post_backlink:eq(0)"); + var already_backlinked = post_backlink.text().replace('>>', '').split(' '); + jQuery.each(already_backlinked, function(i,v){ + if (typeof val[v] !== "undefined") + { + delete val[v]; + } + }); + + if (post_backlink.find('[data-post=' + post_id + ']').length == 0) + { + post_backlink.html(post_backlink.html() + ((post_backlink.html().length > 0)?" ":"") + val.join(" ")); + post_backlink.parent().show(); + } + }); +}; + +var timelapse = 10; +var currentlapse = 0; +var realtimethread = function(){ + clearTimeout(currentlapse); + jQuery.ajax({ + url: backend_vars.api_url + '_/api/chan/thread/', + dataType: 'json', + type: 'GET', + data: { + num : backend_vars.thread_id, + board: backend_vars.board_shortname, + latest_doc_id: backend_vars.latest_doc_id, + theme: backend_vars.selected_theme, + last_limit: typeof backend_vars.last_limit === "undefined" ? null : backend_vars.last_limit + }, + success: insertPost, + error: function(jqXHR, textStatus, errorThrown) { + //timelapse = 10; + }, + complete: function() { + currentlapse = setTimeout(realtimethread, timelapse*1000); + } + }); + + return false; +}; + +var ghost = false; +var insertPost = function(data, textStatus, jqXHR) +{ + var w_height = jQuery(document).height(); + var found_posts = false; + + if (data !== null) + { + jQuery.each(data, function(id, val) + { + if (typeof val.posts !== "undefined") + { + var aside = jQuery('article.thread[data-thread-num=' + id + '] aside.posts'); + jQuery.each(val.posts, function(idx, value) + { + found_posts = true; + var post = jQuery(value.formatted); + + post.find("time").localize('ddd dd mmm yyyy HH:MM:ss'); + post.find('[rel=tooltip]').tooltip({ + placement: 'top', + delay: 200 + }); + + post.find('[rel=tooltip_right]').tooltip({ + placement: 'right', + delay: 200 + }); + + // avoid inserting twice + if (jQuery('.doc_id_' + value.doc_id).length != 0) + { + jQuery('.doc_id_' + value.doc_id).remove(); + } + + aside.append(post); + backlinkify(post, value.num, value.subnum); + + $('pre,code').each(function(i, block) { + hljs.highlightBlock(block); + }); + + if(backend_vars.latest_doc_id < value.doc_id) + { + backend_vars.latest_doc_id = value.doc_id; + } + + // update comment box when encountering ghost posts + if (ghost === false && value.subnum > 0) + { + jQuery("#file_image").parent().remove(); + jQuery("#reply_chennodiscursus").attr("placeholder", backend_vars.gettext['ghost_mode']); + + ghost = true; + } + }); + } + }); + } + + if (found_posts) + { + if (jQuery('#reply :focus').length > 0) + { + window.scrollBy(0, jQuery(document).height() - w_height); + } + + enableRealtimeThread(); + + timelapse = 10; + } + else + { + if (timelapse < 120) + { + timelapse += 5; + } + } +}; + +var findSameImageFromFile = function(obj) +{ + var reader = new FileReader(); + reader.onloadend = function(evt){ + if (evt.target.readyState == FileReader.DONE) { + var fileContents = evt.target.result; + var digestBytes = Crypto.MD5(Crypto.charenc.Binary.stringToBytes(fileContents), { + asBytes: true + }); + var digestBase64 = Crypto.util.bytesToBase64(digestBytes); + var digestBase64URL = digestBase64.replace('==', '').replace(/\//g, '_').replace(/\+/g, '-'); + jQuery('#search_form_image').val(digestBase64URL); + } + }; + + reader.readAsBinaryString(obj.files[0]); +}; + +var toggleHighlight = function(id) +{ + var classn = 'highlight'; + jQuery("article").each(function() { + var post = jQuery(this); + + if (post.hasClass(classn)) + { + post.removeClass(classn); + } + + if (post.attr("id") == id) + { + post.addClass(classn); + } + }) +}; + +var realtime = false; +var enableRealtimeThread = function() +{ + if (realtime == false) + { + realtime = true; + + jQuery('.js_hook_realtimethread').html(backend_vars.gettext['thread_is_real_time'] + ' ' + backend_vars.gettext['update_now'] + ''); + setTimeout(realtimethread, 10000); + } +}; + +var highlightSearchResults = function() +{ + jQuery.each(backend_vars.search_args, function(id, val) + { + var selector; + if (id == "text") { + selector = "div.text"; + } else if (id == "filename") { + selector = "a.post_file_filename" + } else if (id == "username") { + selector = "span.post_author" + } else if (id == "subject") { + selector = "h2.post_title"; + } else if (id == "tripcode") { + selector = "span.post_tripcode"; + } else if (id == "uid") { + selector = "span.poster_hash"; + } else { + return true; + } + if (selector != "") { + val = val.replace(/[\.\*\^\|'"!]/g, " "); + jQuery( selector ).mark(val, { + "element": "span", + "className": "highlight" + }); + } + }); +}; + +jQuery(document).ready(function() { + + // settings + jQuery.support.cors = true; + backend_vars.loaded_posts = []; + + // check if input[date] is supported, so we can use by default input[text] with placeholder without breaking w3 + var i = document.createElement("input"); + i.setAttribute("type", "date"); + if (i.type !== "text") + { + jQuery('#date_end').replaceWith(jQuery('').attr({id: 'date_end', name: 'end', type: 'date'})); + jQuery('#date_start').replaceWith(jQuery('').attr({id: 'date_start', name: 'start', type: 'date'})); + } + + // opera doesn't play well with the modal transition + if (navigator.appName == "Opera" && navigator.userAgent.match(/Version\/12\./)) + { + $('#post_tools_modal').removeClass('fade'); + } + + // firefox sucks at styling input, so we need to add size="", that guess what? It's not w3 compliant! + jQuery('#file_image').attr({size: '16'}); + + var post = location.href.split(/#/); + if (post[1]) { + if (post[1].match(/^q\d+(_\d+)?$/)) { + post[1] = post[1].replace('q', '').replace('_', ','); + jQuery("#reply_chennodiscursus").append(">>" + post[1] + "\n"); + post[1] = post[1].replace(',', '_'); + + } + + toggleHighlight(post[1]); + jQuery('#'+post[1].replace('q', ''))[0].scrollIntoView( true ); + } + + if (typeof backend_vars.thread_id !== "undefined" && (Math.round(new Date().getTime() / 1000) - backend_vars.latest_timestamp < 6 * 60 * 60)) + { + enableRealtimeThread(); + } + + bindFunctions(); + hideThreads(); + hidePosts(); + + // localize and add 4chan tooltip where title + jQuery("article time").localize('ddd dd mmm yyyy HH:MM:ss').filter('[title]').tooltip({ + placement: 'top', + delay: 300, + animation: false + }); + + jQuery('input[title]').tooltip({ + placement: 'right', + delay: 200, + animation: false + }); + + jQuery('li.latest_search').tooltip({ + placement: 'left', + animation: false + }); + + jQuery('#thread_o_matic .thread_image_box').tooltip({ + placement: 'bottom', + animation: true + }); + + if (typeof backend_vars.search_args !== "undefined" && getCookie("searchhilight_enabled") == "true") { + highlightSearchResults(); + } +}); + +$.fn.extend({ + insertAtCaret: function(text){ + var obj; + + if (typeof this[0].name !== 'undefined') + { + obj = this[0]; + } + else + { + obj = this; + } + + var insPos = obj.selectionStart, endPos = obj.selectionEnd; + + obj.value = obj.value.substring(0, insPos) + text + obj.value.substring(endPos, obj.value.length); + obj.selectionStart = insPos + text.length; + obj.selectionEnd = insPos + text.length; + } +}); diff --git a/chrome/b4k/bootstrap.min.js b/chrome/b4k/bootstrap.min.js new file mode 100644 index 0000000..6eeb15c --- /dev/null +++ b/chrome/b4k/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! +* Bootstrap.js by @fat & @mdo +* Copyright 2012 Twitter, Inc. +* http://www.apache.org/licenses/LICENSE-2.0.txt +*/ +!function($){"use strict";$(function(){$.support.transition=function(){var transitionEnd=function(){var name,el=document.createElement("bootstrap"),transEndEventNames={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(name in transEndEventNames)if(void 0!==el.style[name])return transEndEventNames[name]}();return transitionEnd&&{end:transitionEnd}}()})}(window.jQuery),!function($){"use strict";var dismiss='[data-dismiss="alert"]',Alert=function(el){$(el).on("click",dismiss,this.close)};Alert.prototype.close=function(e){function removeElement(){$parent.trigger("closed").remove()}var $parent,$this=$(this),selector=$this.attr("data-target");selector||(selector=$this.attr("href"),selector=selector&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),e&&e.preventDefault(),$parent.length||($parent=$this.hasClass("alert")?$this:$this.parent()),$parent.trigger(e=$.Event("close")),e.isDefaultPrevented()||($parent.removeClass("in"),$.support.transition&&$parent.hasClass("fade")?$parent.on($.support.transition.end,removeElement):removeElement())};var old=$.fn.alert;$.fn.alert=function(option){return this.each(function(){var $this=$(this),data=$this.data("alert");data||$this.data("alert",data=new Alert(this)),"string"==typeof option&&data[option].call($this)})},$.fn.alert.Constructor=Alert,$.fn.alert.noConflict=function(){return $.fn.alert=old,this},$(document).on("click.alert.data-api",dismiss,Alert.prototype.close)}(window.jQuery),!function($){"use strict";var Button=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.button.defaults,options)};Button.prototype.setState=function(state){var d="disabled",$el=this.$element,data=$el.data(),val=$el.is("input")?"val":"html";state+="Text",data.resetText||$el.data("resetText",$el[val]()),$el[val](data[state]||this.options[state]),setTimeout(function(){"loadingText"==state?$el.addClass(d).attr(d,d):$el.removeClass(d).removeAttr(d)},0)},Button.prototype.toggle=function(){var $parent=this.$element.closest('[data-toggle="buttons-radio"]');$parent&&$parent.find(".active").removeClass("active"),this.$element.toggleClass("active")};var old=$.fn.button;$.fn.button=function(option){return this.each(function(){var $this=$(this),data=$this.data("button"),options="object"==typeof option&&option;data||$this.data("button",data=new Button(this,options)),"toggle"==option?data.toggle():option&&data.setState(option)})},$.fn.button.defaults={loadingText:"loading..."},$.fn.button.Constructor=Button,$.fn.button.noConflict=function(){return $.fn.button=old,this},$(document).on("click.button.data-api","[data-toggle^=button]",function(e){var $btn=$(e.target);$btn.hasClass("btn")||($btn=$btn.closest(".btn")),$btn.button("toggle")})}(window.jQuery),!function($){"use strict";var Carousel=function(element,options){this.$element=$(element),this.options=options,"hover"==this.options.pause&&this.$element.on("mouseenter",$.proxy(this.pause,this)).on("mouseleave",$.proxy(this.cycle,this))};Carousel.prototype={cycle:function(e){return e||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval($.proxy(this.next,this),this.options.interval)),this},to:function(pos){var $active=this.$element.find(".item.active"),children=$active.parent().children(),activePos=children.index($active),that=this;if(!(pos>children.length-1||0>pos))return this.sliding?this.$element.one("slid",function(){that.to(pos)}):activePos==pos?this.pause().cycle():this.slide(pos>activePos?"next":"prev",$(children[pos]))},pause:function(e){return e||(this.paused=!0),this.$element.find(".next, .prev").length&&$.support.transition.end&&(this.$element.trigger($.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){return this.sliding?void 0:this.slide("next")},prev:function(){return this.sliding?void 0:this.slide("prev")},slide:function(type,next){var e,$active=this.$element.find(".item.active"),$next=next||$active[type](),isCycling=this.interval,direction="next"==type?"left":"right",fallback="next"==type?"first":"last",that=this;if(this.sliding=!0,isCycling&&this.pause(),$next=$next.length?$next:this.$element.find(".item")[fallback](),e=$.Event("slide",{relatedTarget:$next[0]}),!$next.hasClass("active")){if($.support.transition&&this.$element.hasClass("slide")){if(this.$element.trigger(e),e.isDefaultPrevented())return;$next.addClass(type),$next[0].offsetWidth,$active.addClass(direction),$next.addClass(direction),this.$element.one($.support.transition.end,function(){$next.removeClass([type,direction].join(" ")).addClass("active"),$active.removeClass(["active",direction].join(" ")),that.sliding=!1,setTimeout(function(){that.$element.trigger("slid")},0)})}else{if(this.$element.trigger(e),e.isDefaultPrevented())return;$active.removeClass("active"),$next.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return isCycling&&this.cycle(),this}}};var old=$.fn.carousel;$.fn.carousel=function(option){return this.each(function(){var $this=$(this),data=$this.data("carousel"),options=$.extend({},$.fn.carousel.defaults,"object"==typeof option&&option),action="string"==typeof option?option:options.slide;data||$this.data("carousel",data=new Carousel(this,options)),"number"==typeof option?data.to(option):action?data[action]():options.interval&&data.cycle()})},$.fn.carousel.defaults={interval:5e3,pause:"hover"},$.fn.carousel.Constructor=Carousel,$.fn.carousel.noConflict=function(){return $.fn.carousel=old,this},$(document).on("click.carousel.data-api","[data-slide]",function(e){var href,$this=$(this),$target=$($this.attr("data-target")||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,"")),options=$.extend({},$target.data(),$this.data());$target.carousel(options),e.preventDefault()})}(window.jQuery),!function($){"use strict";var Collapse=function(element,options){this.$element=$(element),this.options=$.extend({},$.fn.collapse.defaults,options),this.options.parent&&(this.$parent=$(this.options.parent)),this.options.toggle&&this.toggle()};Collapse.prototype={constructor:Collapse,dimension:function(){var hasWidth=this.$element.hasClass("width");return hasWidth?"width":"height"},show:function(){var dimension,scroll,actives,hasData;if(!this.transitioning){if(dimension=this.dimension(),scroll=$.camelCase(["scroll",dimension].join("-")),actives=this.$parent&&this.$parent.find("> .accordion-group > .in"),actives&&actives.length){if(hasData=actives.data("collapse"),hasData&&hasData.transitioning)return;actives.collapse("hide"),hasData||actives.data("collapse",null)}this.$element[dimension](0),this.transition("addClass",$.Event("show"),"shown"),$.support.transition&&this.$element[dimension](this.$element[0][scroll])}},hide:function(){var dimension;this.transitioning||(dimension=this.dimension(),this.reset(this.$element[dimension]()),this.transition("removeClass",$.Event("hide"),"hidden"),this.$element[dimension](0))},reset:function(size){var dimension=this.dimension();return this.$element.removeClass("collapse")[dimension](size||"auto")[0].offsetWidth,this.$element[null!==size?"addClass":"removeClass"]("collapse"),this},transition:function(method,startEvent,completeEvent){var that=this,complete=function(){"show"==startEvent.type&&that.reset(),that.transitioning=0,that.$element.trigger(completeEvent)};this.$element.trigger(startEvent),startEvent.isDefaultPrevented()||(this.transitioning=1,this.$element[method]("in"),$.support.transition&&this.$element.hasClass("collapse")?this.$element.one($.support.transition.end,complete):complete())},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}};var old=$.fn.collapse;$.fn.collapse=function(option){return this.each(function(){var $this=$(this),data=$this.data("collapse"),options="object"==typeof option&&option;data||$this.data("collapse",data=new Collapse(this,options)),"string"==typeof option&&data[option]()})},$.fn.collapse.defaults={toggle:!0},$.fn.collapse.Constructor=Collapse,$.fn.collapse.noConflict=function(){return $.fn.collapse=old,this},$(document).on("click.collapse.data-api","[data-toggle=collapse]",function(e){var href,$this=$(this),target=$this.attr("data-target")||e.preventDefault()||(href=$this.attr("href"))&&href.replace(/.*(?=#[^\s]+$)/,""),option=$(target).data("collapse")?"toggle":$this.data();$this[$(target).hasClass("in")?"addClass":"removeClass"]("collapsed"),$(target).collapse(option)})}(window.jQuery),!function($){"use strict";function clearMenus(){$(toggle).each(function(){getParent($(this)).removeClass("open")})}function getParent($this){var $parent,selector=$this.attr("data-target");return selector||(selector=$this.attr("href"),selector=selector&&/#/.test(selector)&&selector.replace(/.*(?=#[^\s]*$)/,"")),$parent=$(selector),$parent.length||($parent=$this.parent()),$parent}var toggle="[data-toggle=dropdown]",Dropdown=function(element){var $el=$(element).on("click.dropdown.data-api",this.toggle);$("html").on("click.dropdown.data-api",function(){$el.parent().removeClass("open")})};Dropdown.prototype={constructor:Dropdown,toggle:function(){var $parent,isActive,$this=$(this);if(!$this.is(".disabled, :disabled"))return $parent=getParent($this),isActive=$parent.hasClass("open"),clearMenus(),isActive||$parent.toggleClass("open"),$this.focus(),!1},keydown:function(e){var $this,$items,$parent,isActive,index;if(/(38|40|27)/.test(e.keyCode)&&($this=$(this),e.preventDefault(),e.stopPropagation(),!$this.is(".disabled, :disabled"))){if($parent=getParent($this),isActive=$parent.hasClass("open"),!isActive||isActive&&27==e.keyCode)return $this.click();$items=$("[role=menu] li:not(.divider):visible a",$parent),$items.length&&(index=$items.index($items.filter(":focus")),38==e.keyCode&&index>0&&index--,40==e.keyCode&&$items.length-1>index&&index++,~index||(index=0),$items.eq(index).focus())}}};var old=$.fn.dropdown;$.fn.dropdown=function(option){return this.each(function(){var $this=$(this),data=$this.data("dropdown");data||$this.data("dropdown",data=new Dropdown(this)),"string"==typeof option&&data[option].call($this)})},$.fn.dropdown.Constructor=Dropdown,$.fn.dropdown.noConflict=function(){return $.fn.dropdown=old,this},$(document).on("click.dropdown.data-api touchstart.dropdown.data-api",clearMenus).on("click.dropdown touchstart.dropdown.data-api",".dropdown form",function(e){e.stopPropagation()}).on("touchstart.dropdown.data-api",".dropdown-menu",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",toggle,Dropdown.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",toggle+", [role=menu]",Dropdown.prototype.keydown)}(window.jQuery),!function($){"use strict";var Modal=function(element,options){this.options=options,this.$element=$(element).delegate('[data-dismiss="modal"]',"click.dismiss.modal",$.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};Modal.prototype={constructor:Modal,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var that=this,e=$.Event("show");this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.escape(),this.backdrop(function(){var transition=$.support.transition&&that.$element.hasClass("fade");that.$element.parent().length||that.$element.appendTo(document.body),that.$element.show(),transition&&that.$element[0].offsetWidth,that.$element.addClass("in").attr("aria-hidden",!1),that.enforceFocus(),transition?that.$element.one($.support.transition.end,function(){that.$element.focus().trigger("shown")}):that.$element.focus().trigger("shown")}))},hide:function(e){e&&e.preventDefault(),e=$.Event("hide"),this.$element.trigger(e),this.isShown&&!e.isDefaultPrevented()&&(this.isShown=!1,this.escape(),$(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),$.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal())},enforceFocus:function(){var that=this;$(document).on("focusin.modal",function(e){that.$element[0]===e.target||that.$element.has(e.target).length||that.$element.focus()})},escape:function(){var that=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(e){27==e.which&&that.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var that=this,timeout=setTimeout(function(){that.$element.off($.support.transition.end),that.hideModal()},500);this.$element.one($.support.transition.end,function(){clearTimeout(timeout),that.hideModal()})},hideModal:function(){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(callback){var animate=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var doAnimate=$.support.transition&&animate;this.$backdrop=$('