User JS: Difference between revisions
m (Added reddit-x) |
(new filtering script + collapsible code) |
||
Line 21: | Line 21: | ||
* Replaces "Bump" and "Sage" with upvote and downvote icons | * Replaces "Bump" and "Sage" with upvote and downvote icons | ||
<i>Expand to view the script</i> | |||
<pre>// ==UserScript== | {| class="mw-collapsible mw-collapsed" | ||
|+ | |||
|<pre>// ==UserScript== | |||
// @name SoyParty-X | // @name SoyParty-X | ||
// @namespace datamining | // @namespace datamining | ||
Line 208: | Line 210: | ||
})(); | })(); | ||
</pre> | </pre> | ||
|} | |||
====Post Filters==== | |||
Allows you to filter posts based on comments, subject, name, and tripcode | |||
To access filters click on options button. | |||
<i>Expand to view the script</i> | |||
{| class="mw-collapsible mw-collapsed" | |||
|+ | |||
|<pre> | |||
/* | |||
* post-menu.js - adds dropdown menu to posts | |||
* | |||
* Creates a global Menu object with four public methods: | |||
* | |||
* Menu.onclick(fnc) | |||
* registers a function to be executed after button click, before the menu is displayed | |||
* Menu.add_item(id, text[, title]) | |||
* adds an item to the top level of menu | |||
* Menu.add_submenu(id, text) | |||
* creates and returns a List object through which to manipulate the content of the submenu | |||
* Menu.get_submenu(id) | |||
* returns the submenu with the specified id from the top level menu | |||
* | |||
* The List object contains all the methods from Menu except onclick() | |||
* | |||
* Example usage: | |||
* Menu.add_item('filter-menu-hide', 'Hide post'); | |||
* Menu.add_item('filter-menu-unhide', 'Unhide post'); | |||
* | |||
* submenu = Menu.add_submenu('filter-menu-add', 'Add filter'); | |||
* submenu.add_item('filter-add-post-plus', 'Post +', 'Hide post and all replies'); | |||
* submenu.add_item('filter-add-id', 'ID'); | |||
* | |||
* Usage: | |||
* $config['additional_javascript'][] = 'js/jquery.min.js'; | |||
* $config['additional_javascript'][] = 'js/post-menu.js'; | |||
*/ | |||
$(document).ready(function () { | |||
var List = function (menuId, text) { | |||
this.id = menuId; | |||
this.text = text; | |||
this.items = []; | |||
this.add_item = function (itemId, text, title) { | |||
this.items.push(new Item(itemId, text, title)); | |||
}; | |||
this.list_items = function () { | |||
var array = []; | |||
var i, length, obj, $ele; | |||
if ($.isEmptyObject(this.items)) | |||
return; | |||
length = this.items.length; | |||
for (i = 0; i < length; i++) { | |||
obj = this.items[i]; | |||
$ele = $('<li>', {id: obj.id}).text(obj.text); | |||
if ('title' in obj) $ele.attr('title', obj.title); | |||
if (obj instanceof Item) { | |||
$ele.addClass('post-item'); | |||
} else { | |||
$ele.addClass('post-submenu'); | |||
$ele.prepend(obj.list_items()); | |||
$ele.append($('<span>', {class: 'post-menu-arrow'}).text('»')); | |||
} | |||
array.push($ele); | |||
} | |||
return $('<ul>').append(array); | |||
}; | |||
this.add_submenu = function (menuId, text) { | |||
var ele = new List(menuId, text); | |||
this.items.push(ele); | |||
return ele; | |||
}; | |||
this.get_submenu = function (menuId) { | |||
for (var i = 0; i < this.items.length; i++) { | |||
if ((this.items[i] instanceof Item) || this.items[i].id != menuId) continue; | |||
return this.items[i]; | |||
} | |||
}; | |||
}; | |||
var Item = function (itemId, text, title) { | |||
this.id = itemId; | |||
this.text = text; | |||
// optional | |||
if (typeof title != 'undefined') this.title = title; | |||
}; | |||
function buildMenu(e) { | |||
var pos = $(e.target).offset(); | |||
var i, length; | |||
var $menu = $('<div class="post-menu"></div>').append(mainMenu.list_items()); | |||
// execute registered click handlers | |||
length = onclick_callbacks.length; | |||
for (i = 0; i < length; i++) { | |||
onclick_callbacks[i](e, $menu); | |||
} | |||
// set menu position and append to page | |||
$menu.css({top: pos.top, left: pos.left + 20}); | |||
$('body').append($menu); | |||
} | |||
function addButton(post) { | |||
var $ele = $(post); | |||
$ele.find('input.delete').after( | |||
$('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('▶') | |||
); | |||
} | |||
/* * * * * * * * * * | |||
Public methods | |||
* * * * * * * * * */ | |||
var Menu = {}; | |||
var mainMenu = new List(); | |||
var onclick_callbacks = []; | |||
Menu.onclick = function (fnc) { | |||
onclick_callbacks.push(fnc); | |||
}; | |||
Menu.add_item = function (itemId, text, title) { | |||
mainMenu.add_item(itemId, text, title); | |||
}; | |||
Menu.add_submenu = function (menuId, text) { | |||
return mainMenu.add_submenu(menuId, text); | |||
}; | |||
Menu.get_submenu = function (id) { | |||
return mainMenu.get_submenu(id); | |||
}; | |||
window.Menu = Menu; | |||
/* * * * * * * * | |||
Initialize | |||
* * * * * * * */ | |||
/* Styling | |||
*/ | |||
var $ele, cssStyle, cssString; | |||
$ele = $('<div>').addClass('post reply').hide().appendTo('body'); | |||
cssStyle = $ele.css(['border-top-color']); | |||
cssStyle.hoverBg = $('body').css('background-color'); | |||
$ele.remove(); | |||
cssString = | |||
'\n/*** Generated by post-menu ***/\n' + | |||
'.post-menu {position: absolute; font-size: 12px; line-height: 1.3em;}\n' + | |||
'.post-menu ul {\n' + | |||
' background-color: '+ cssStyle['border-top-color'] +'; border: 1px solid #666;\n' + | |||
' list-style: none; padding: 0; margin: 0; white-space: nowrap;\n}\n' + | |||
'.post-menu .post-submenu{white-space: normal; width: 90px;}' + | |||
'.post-menu .post-submenu>ul{white-space: nowrap; width: auto;}' + | |||
'.post-menu li {cursor: pointer; position: relative; padding: 4px 4px; vertical-align: middle;}\n' + | |||
'.post-menu li:hover {background-color: '+ cssStyle.hoverBg +';}\n' + | |||
'.post-menu ul ul {display: none; position: absolute;}\n' + | |||
'.post-menu li:hover>ul {display: block; left: 100%; margin-top: -3px;}\n' + | |||
'.post-menu-arrow {float: right; margin-left: 10px;}\n' + | |||
'.post-menu.hidden, .post-menu .hidden {display: none;}\n' + | |||
'.post-btn {transition: transform 0.1s; width: 15px; text-align: center; font-size: 10pt; opacity: 0.8; text-decoration: none; margin: -6px 0px 0px -5px !important; display: inline-block;}\n' + | |||
'.post-btn:hover {opacity: 1;}\n' + | |||
'.post-btn-open {transform: rotate(90deg);}\n'; | |||
if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head'); | |||
$('style.generated-css').html($('style.generated-css').html() + cssString); | |||
/* Add buttons | |||
*/ | |||
$('.reply:not(.hidden), .thread>.op').each(function () { | |||
addButton(this); | |||
}); | |||
/* event handlers | |||
*/ | |||
$('form[name=postcontrols]').on('click', '.post-btn', function (e) { | |||
e.preventDefault(); | |||
var post = e.target.parentElement.parentElement; | |||
$('.post-menu').remove(); | |||
if ($(e.target).hasClass('post-btn-open')) { | |||
$('.post-btn-open').removeClass('post-btn-open'); | |||
} else { | |||
// close previous button | |||
$('.post-btn-open').removeClass('post-btn-open'); | |||
$(post).find('.post-btn').addClass('post-btn-open'); | |||
buildMenu(e); | |||
} | |||
}); | |||
$(document).on('click', function (e){ | |||
if ($(e.target).hasClass('post-btn') || $(e.target).hasClass('post-submenu')) | |||
return; | |||
$('.post-menu').remove(); | |||
$('.post-btn-open').removeClass('post-btn-open'); | |||
}); | |||
// on new posts | |||
$(document).on('new_post', function (e, post) { | |||
addButton(post); | |||
}); | |||
$(document).trigger('menu_ready'); | |||
}); | |||
// Post Filters | |||
if (active_page === 'thread' || active_page === 'index' || active_page === 'catalog' || active_page === 'ukko') { | |||
$(document).on('menu_ready', function () { | |||
'use strict'; | |||
// returns blacklist object from storage | |||
function getList() { | |||
return JSON.parse(localStorage.postFilter); | |||
} | |||
// stores blacklist into storage and reruns the filter | |||
function setList(blacklist) { | |||
localStorage.postFilter = JSON.stringify(blacklist); | |||
$(document).trigger('filter_page'); | |||
} | |||
// unit: seconds | |||
function timestamp() { | |||
return Math.floor((new Date()).getTime() / 1000); | |||
} | |||
function initList(list, boardId, threadId) { | |||
if (typeof list.postFilter[boardId] == 'undefined') { | |||
list.postFilter[boardId] = {}; | |||
list.nextPurge[boardId] = {}; | |||
} | |||
if (typeof list.postFilter[boardId][threadId] == 'undefined') { | |||
list.postFilter[boardId][threadId] = []; | |||
} | |||
list.nextPurge[boardId][threadId] = {timestamp: timestamp(), interval: 86400}; // 86400 seconds == 1 day | |||
} | |||
function addFilter(type, value, useRegex) { | |||
var list = getList(); | |||
var filter = list.generalFilter; | |||
var obj = { | |||
type: type, | |||
value: value, | |||
regex: useRegex | |||
}; | |||
for (var i=0; i<filter.length; i++) { | |||
if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex) | |||
return; | |||
} | |||
filter.push(obj); | |||
setList(list); | |||
drawFilterList(); | |||
} | |||
function removeFilter(type, value, useRegex) { | |||
var list = getList(); | |||
var filter = list.generalFilter; | |||
for (var i=0; i<filter.length; i++) { | |||
if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex) { | |||
filter.splice(i, 1); | |||
break; | |||
} | |||
} | |||
setList(list); | |||
drawFilterList(); | |||
} | |||
function nameSpanToString(el) { | |||
var s = ''; | |||
$.each($(el).contents(), function(k,v) { | |||
if (v.nodeName === 'IMG') | |||
s=s+$(v).attr('alt') | |||
if (v.nodeName === '#text') | |||
s=s+v.nodeValue | |||
}); | |||
return s.trim(); | |||
} | |||
var blacklist = { | |||
add: { | |||
post: function (boardId, threadId, postId, hideReplies) { | |||
var list = getList(); | |||
var filter = list.postFilter; | |||
initList(list, boardId, threadId); | |||
for (var i in filter[boardId][threadId]) { | |||
if (filter[boardId][threadId][i].post == postId) return; | |||
} | |||
filter[boardId][threadId].push({ | |||
post: postId, | |||
hideReplies: hideReplies | |||
}); | |||
setList(list); | |||
}, | |||
uid: function (boardId, threadId, uniqueId, hideReplies) { | |||
var list = getList(); | |||
var filter = list.postFilter; | |||
initList(list, boardId, threadId); | |||
for (var i in filter[boardId][threadId]) { | |||
if (filter[boardId][threadId][i].uid == uniqueId) return; | |||
} | |||
filter[boardId][threadId].push({ | |||
uid: uniqueId, | |||
hideReplies: hideReplies | |||
}); | |||
setList(list); | |||
} | |||
}, | |||
remove: { | |||
post: function (boardId, threadId, postId) { | |||
var list = getList(); | |||
var filter = list.postFilter; | |||
// thread already pruned | |||
if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined') | |||
return; | |||
for (var i=0; i<filter[boardId][threadId].length; i++) { | |||
if (filter[boardId][threadId][i].post == postId) { | |||
filter[boardId][threadId].splice(i, 1); | |||
break; | |||
} | |||
} | |||
if ($.isEmptyObject(filter[boardId][threadId])) { | |||
delete filter[boardId][threadId]; | |||
delete list.nextPurge[boardId][threadId]; | |||
if ($.isEmptyObject(filter[boardId])) { | |||
delete filter[boardId]; | |||
delete list.nextPurge[boardId]; | |||
} | |||
} | |||
setList(list); | |||
}, | |||
uid: function (boardId, threadId, uniqueId) { | |||
var list = getList(); | |||
var filter = list.postFilter; | |||
// thread already pruned | |||
if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined') | |||
return; | |||
for (var i=0; i<filter[boardId][threadId].length; i++) { | |||
if (filter[boardId][threadId][i].uid == uniqueId) { | |||
filter[boardId][threadId].splice(i, 1); | |||
break; | |||
} | |||
} | |||
if ($.isEmptyObject(filter[boardId][threadId])) { | |||
delete filter[boardId][threadId]; | |||
delete list.nextPurge[boardId][threadId]; | |||
if ($.isEmptyObject(filter[boardId])) { | |||
delete filter[boardId]; | |||
delete list.nextPurge[boardId]; | |||
} | |||
} | |||
setList(list); | |||
} | |||
} | |||
}; | |||
/* | |||
* hide/show the specified thread/post | |||
*/ | |||
function hide(ele) { | |||
var $ele = $(ele); | |||
if ($(ele).data('hidden')) | |||
return; | |||
$(ele).data('hidden', true); | |||
if ($ele.hasClass('op')) { | |||
$ele.parent().find('.body, .files, .video-container').not($ele.children('.reply').children()).hide(); | |||
// hide thread replies on index view | |||
if (active_page == 'index' || active_page == 'ukko') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').hide(); | |||
} else { | |||
// normal posts | |||
$ele.children('.body, .files, .video-container').hide(); | |||
} | |||
} | |||
function show(ele) { | |||
var $ele = $(ele); | |||
$(ele).data('hidden', false); | |||
if ($ele.hasClass('op')) { | |||
$ele.parent().find('.body, .files, .video-container').show(); | |||
if (active_page == 'index') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').show(); | |||
} else { | |||
// normal posts | |||
$ele.children('.body, .files, .video-container').show(); | |||
} | |||
} | |||
/* | |||
* create filter menu when the button is clicked | |||
*/ | |||
function initPostMenu(pageData) { | |||
var Menu = window.Menu; | |||
var submenu; | |||
Menu.add_item('filter-menu-hide', _('Hide post')); | |||
Menu.add_item('filter-menu-unhide', _('Unhide post')); | |||
submenu = Menu.add_submenu('filter-menu-add', _('Add filter')); | |||
submenu.add_item('filter-add-post-plus', _('Post +'), _('Hide post and all replies')); | |||
submenu.add_item('filter-add-id', _('ID')); | |||
submenu.add_item('filter-add-id-plus', _('ID +'), _('Hide ID and all replies')); | |||
submenu.add_item('filter-add-name', _('Name')); | |||
submenu.add_item('filter-add-trip', _('Tripcode')); | |||
submenu = Menu.add_submenu('filter-menu-remove', _('Remove filter')); | |||
submenu.add_item('filter-remove-id', _('ID')); | |||
submenu.add_item('filter-remove-name', _('Name')); | |||
submenu.add_item('filter-remove-trip', _('Tripcode')); | |||
Menu.onclick(function (e, $buffer) { | |||
var ele = e.target.parentElement.parentElement; | |||
var $ele = $(ele); | |||
var threadId = $ele.parent().attr('id').replace('thread_', ''); | |||
var boardId = $ele.parent().data('board'); | |||
var postId = $ele.find('.post_no').not('[id]').text(); | |||
if (pageData.hasUID) { | |||
var postUid = $ele.find('.poster_id').text(); | |||
} | |||
var postName; | |||
var postTrip = ''; | |||
if (!pageData.forcedAnon) { | |||
postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]); | |||
postTrip = $ele.find('.trip').text(); | |||
} | |||
/* display logic and bind click handlers | |||
*/ | |||
// unhide button | |||
if ($ele.data('hidden')) { | |||
$buffer.find('#filter-menu-unhide').click(function () { | |||
// if hidden due to post id, remove it from blacklist | |||
// otherwise just show this post | |||
blacklist.remove.post(boardId, threadId, postId); | |||
show(ele); | |||
}); | |||
$buffer.find('#filter-menu-hide').addClass('hidden'); | |||
} else { | |||
$buffer.find('#filter-menu-unhide').addClass('hidden'); | |||
$buffer.find('#filter-menu-hide').click(function () { | |||
blacklist.add.post(boardId, threadId, postId, false); | |||
}); | |||
} | |||
// post id | |||
if (!$ele.data('hiddenByPost')) { | |||
$buffer.find('#filter-add-post-plus').click(function () { | |||
blacklist.add.post(boardId, threadId, postId, true); | |||
}); | |||
} else { | |||
$buffer.find('#filter-add-post-plus').addClass('hidden'); | |||
} | |||
// UID | |||
if (pageData.hasUID && !$ele.data('hiddenByUid')) { | |||
$buffer.find('#filter-add-id').click(function () { | |||
blacklist.add.uid(boardId, threadId, postUid, false); | |||
}); | |||
$buffer.find('#filter-add-id-plus').click(function () { | |||
blacklist.add.uid(boardId, threadId, postUid, true); | |||
}); | |||
$buffer.find('#filter-remove-id').addClass('hidden'); | |||
} else if (pageData.hasUID) { | |||
$buffer.find('#filter-remove-id').click(function () { | |||
blacklist.remove.uid(boardId, threadId, postUid); | |||
}); | |||
$buffer.find('#filter-add-id').addClass('hidden'); | |||
$buffer.find('#filter-add-id-plus').addClass('hidden'); | |||
} else { | |||
// board doesn't use UID | |||
$buffer.find('#filter-add-id').addClass('hidden'); | |||
$buffer.find('#filter-add-id-plus').addClass('hidden'); | |||
$buffer.find('#filter-remove-id').addClass('hidden'); | |||
} | |||
// name | |||
if (!pageData.forcedAnon && !$ele.data('hiddenByName')) { | |||
$buffer.find('#filter-add-name').click(function () { | |||
addFilter('name', postName, false); | |||
}); | |||
$buffer.find('#filter-remove-name').addClass('hidden'); | |||
} else if (!pageData.forcedAnon) { | |||
$buffer.find('#filter-remove-name').click(function () { | |||
removeFilter('name', postName, false); | |||
}); | |||
$buffer.find('#filter-add-name').addClass('hidden'); | |||
} else { | |||
// board has forced anon | |||
$buffer.find('#filter-remove-name').addClass('hidden'); | |||
$buffer.find('#filter-add-name').addClass('hidden'); | |||
} | |||
// tripcode | |||
if (!pageData.forcedAnon && !$ele.data('hiddenByTrip') && postTrip !== '') { | |||
$buffer.find('#filter-add-trip').click(function () { | |||
addFilter('trip', postTrip, false); | |||
}); | |||
$buffer.find('#filter-remove-trip').addClass('hidden'); | |||
} else if (!pageData.forcedAnon && postTrip !== '') { | |||
$buffer.find('#filter-remove-trip').click(function () { | |||
removeFilter('trip', postTrip, false); | |||
}); | |||
$buffer.find('#filter-add-trip').addClass('hidden'); | |||
} else { | |||
// board has forced anon | |||
$buffer.find('#filter-remove-trip').addClass('hidden'); | |||
$buffer.find('#filter-add-trip').addClass('hidden'); | |||
} | |||
/* hide sub menus if all items are hidden | |||
*/ | |||
if (!$buffer.find('#filter-menu-remove > ul').children().not('.hidden').length) { | |||
$buffer.find('#filter-menu-remove').addClass('hidden'); | |||
} | |||
if (!$buffer.find('#filter-menu-add > ul').children().not('.hidden').length) { | |||
$buffer.find('#filter-menu-add').addClass('hidden'); | |||
} | |||
}); | |||
} | |||
/* | |||
* hide/unhide thread on index view | |||
*/ | |||
function quickToggle(ele, threadId, pageData) { | |||
/*if ($(ele).find('.hide-thread-link').length) | |||
$('.hide-thread-link').remove();*/ | |||
if ($(ele).hasClass('op') && !$(ele).find('.hide-thread-link').length) { | |||
$('<a class="hide-thread-link" style="float:left;margin-right:5px" href="javascript:void(0)">[' + ($(ele).data('hidden') ? '+' : '–') + ']</a>') | |||
.insertBefore($(ele).find(':not(h2,h2 *):first')) | |||
.click(function() { | |||
var postId = $(ele).find('.post_no').not('[id]').text(); | |||
var hidden = $(ele).data('hidden'); | |||
var boardId = $(ele).parents('.thread').data('board'); | |||
if (hidden) { | |||
blacklist.remove.post(boardId, threadId, postId, false); | |||
$(this).html('[–]'); | |||
} else { | |||
blacklist.add.post(boardId, threadId, postId, false); | |||
$(this).text('[+]'); | |||
} | |||
}); | |||
} | |||
} | |||
/* | |||
* determine whether the reply post should be hidden | |||
* - applies to all posts on page load or filtering rule change | |||
* - apply to new posts on thread updates | |||
* - must explicitly set the state of each attributes because filter will reapply to all posts after filtering rule change | |||
*/ | |||
function filter(post, threadId, pageData) { | |||
var $post = $(post); | |||
var list = getList(); | |||
var postId = $post.find('.post_no').not('[id]').text(); | |||
var name, trip, uid, subject, comment; | |||
var i, length, array, rule, pattern; // temp variables | |||
var boardId = $post.data('board'); | |||
if (!boardId) boardId = $post.parents('.thread').data('board'); | |||
var localList = pageData.localList; | |||
var noReplyList = pageData.noReplyList; | |||
var hasUID = pageData.hasUID; | |||
var forcedAnon = pageData.forcedAnon; | |||
var hasTrip = ($post.find('.trip').length > 0); | |||
var hasSub = ($post.find('.subject').length > 0); | |||
$post.data('hidden', false); | |||
$post.data('hiddenByUid', false); | |||
$post.data('hiddenByPost', false); | |||
$post.data('hiddenByName', false); | |||
$post.data('hiddenByTrip', false); | |||
$post.data('hiddenBySubject', false); | |||
$post.data('hiddenByComment', false); | |||
// add post with matched UID to localList | |||
if (hasUID && | |||
typeof list.postFilter[boardId] != 'undefined' && | |||
typeof list.postFilter[boardId][threadId] != 'undefined') { | |||
uid = $post.find('.poster_id').text(); | |||
array = list.postFilter[boardId][threadId]; | |||
for (i=0; i<array.length; i++) { | |||
if (array[i].uid == uid) { | |||
$post.data('hiddenByUid', true); | |||
localList.push(postId); | |||
if (array[i].hideReplies) noReplyList.push(postId); | |||
break; | |||
} | |||
} | |||
} | |||
// match localList | |||
if (localList.length) { | |||
if ($.inArray(postId, localList) != -1) { | |||
if ($post.data('hiddenByUid') !== true) $post.data('hiddenByPost', true); | |||
hide(post); | |||
} | |||
} | |||
// matches generalFilter | |||
if (!forcedAnon) | |||
name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]); | |||
if (!forcedAnon && hasTrip) | |||
trip = $post.find('.trip').text(); | |||
if (hasSub) | |||
subject = $post.find('.subject').text(); | |||
array = $post.find('.body').contents().filter(function () {if ($(this).text() !== '') return true;}).toArray(); | |||
array = $.map(array, function (ele) { | |||
return $(ele).text().trim(); | |||
}); | |||
comment = array.join(' '); | |||
for (i = 0, length = list.generalFilter.length; i < length; i++) { | |||
rule = list.generalFilter[i]; | |||
if (rule.regex) { | |||
pattern = new RegExp(rule.value); | |||
switch (rule.type) { | |||
case 'name': | |||
if (!forcedAnon && pattern.test(name)) { | |||
$post.data('hiddenByName', true); | |||
hide(post); | |||
} | |||
break; | |||
case 'trip': | |||
if (!forcedAnon && hasTrip && pattern.test(trip)) { | |||
$post.data('hiddenByTrip', true); | |||
hide(post); | |||
} | |||
break; | |||
case 'sub': | |||
if (hasSub && pattern.test(subject)) { | |||
$post.data('hiddenBySubject', true); | |||
hide(post); | |||
} | |||
break; | |||
case 'com': | |||
if (pattern.test(comment)) { | |||
$post.data('hiddenByComment', true); | |||
hide(post); | |||
} | |||
break; | |||
} | |||
} else { | |||
switch (rule.type) { | |||
case 'name': | |||
if (!forcedAnon && rule.value == name) { | |||
$post.data('hiddenByName', true); | |||
hide(post); | |||
} | |||
break; | |||
case 'trip': | |||
if (!forcedAnon && hasTrip && rule.value == trip) { | |||
$post.data('hiddenByTrip', true); | |||
hide(post); | |||
} | |||
break; | |||
case 'sub': | |||
pattern = new RegExp('\\b'+ rule.value+ '\\b'); | |||
if (hasSub && pattern.test(subject)) { | |||
$post.data('hiddenBySubject', true); | |||
hide(post); | |||
} | |||
break; | |||
case 'com': | |||
pattern = new RegExp('\\b'+ rule.value+ '\\b'); | |||
if (pattern.test(comment)) { | |||
$post.data('hiddenByComment', true); | |||
hide(post); | |||
} | |||
break; | |||
} | |||
} | |||
} | |||
// check for link to filtered posts | |||
$post.find('.body a').not('[rel="nofollow"]').each(function () { | |||
var replyId = $(this).text().match(/^>>(\d+)$/); | |||
if (!replyId) | |||
return; | |||
replyId = replyId[1]; | |||
if ($.inArray(replyId, noReplyList) != -1) { | |||
hide(post); | |||
} | |||
}); | |||
// post didn't match any filters | |||
if (!$post.data('hidden')) { | |||
show(post); | |||
} | |||
} | |||
/* (re)runs the filter on the entire page | |||
*/ | |||
function filterPage(pageData) { | |||
var list = getList(); | |||
if (active_page != 'catalog') { | |||
// empty the local and no-reply list | |||
pageData.localList = []; | |||
pageData.noReplyList = []; | |||
$('.thread').each(function () { | |||
var $thread = $(this); | |||
// disregard the hidden threads constructed by post-hover.js | |||
if ($thread.css('display') == 'none') | |||
return; | |||
var threadId = $thread.attr('id').replace('thread_', ''); | |||
var boardId = $thread.data('board'); | |||
var op = $thread.children('.op')[0]; | |||
var i, array; // temp variables | |||
// add posts to localList and noReplyList | |||
if (typeof list.postFilter[boardId] != 'undefined' && typeof list.postFilter[boardId][threadId] != 'undefined') { | |||
array = list.postFilter[boardId][threadId]; | |||
for (i=0; i<array.length; i++) { | |||
if ( typeof array[i].post == 'undefined') | |||
continue; | |||
pageData.localList.push(array[i].post); | |||
if (array[i].hideReplies) pageData.noReplyList.push(array[i].post); | |||
} | |||
} | |||
// run filter on OP | |||
filter(op, threadId, pageData); | |||
quickToggle(op, threadId, pageData); | |||
// iterate filter over each post | |||
if (!$(op).data('hidden') || active_page == 'thread') { | |||
$thread.find('.reply').not('.hidden').each(function () { | |||
filter(this, threadId, pageData); | |||
}); | |||
} | |||
}); | |||
} else { | |||
var postFilter = list.postFilter[pageData.boardId]; | |||
var $collection = $('.mix'); | |||
if ($.isEmptyObject(postFilter)) | |||
return; | |||
// for each thread that has filtering rules | |||
// check if filter contains thread OP and remove the thread from catalog | |||
$.each(postFilter, function (key, thread) { | |||
var threadId = key; | |||
$.each(thread, function () { | |||
if (this.post == threadId) { | |||
$collection.filter('[data-id='+ threadId +']').remove(); | |||
} | |||
}); | |||
}); | |||
} | |||
} | |||
function initStyle() { | |||
var $ele, cssStyle, cssString; | |||
$ele = $('<div>').addClass('post reply').hide().appendTo('body'); | |||
cssStyle = $ele.css(['background-color', 'border-color']); | |||
cssStyle.hoverBg = $('body').css('background-color'); | |||
$ele.remove(); | |||
cssString = '\n/*** Generated by post-filter ***/\n' + | |||
'#filter-control input[type=text] {width: 130px;}' + | |||
'#filter-control input[type=checkbox] {vertical-align: middle;}' + | |||
'#filter-control #clear {float: right;}\n' + | |||
'#filter-container {margin-top: 20px; border: 1px solid; height: 270px; overflow: auto;}\n' + | |||
'#filter-list {width: 100%; border-collapse: collapse;}\n' + | |||
'#filter-list th {text-align: center; height: 20px; font-size: 14px; border-bottom: 1px solid;}\n' + | |||
'#filter-list th:nth-child(1) {text-align: center; width: 70px;}\n' + | |||
'#filter-list th:nth-child(2) {text-align: left;}\n' + | |||
'#filter-list th:nth-child(3) {text-align: center; width: 58px;}\n' + | |||
'#filter-list tr:not(#header) {height: 22px;}\n' + | |||
'#filter-list tr:nth-child(even) {background-color:rgba(255, 255, 255, 0.5);}\n' + | |||
'#filter-list td:nth-child(1) {text-align: center; width: 70px;}\n' + | |||
'#filter-list td:nth-child(3) {text-align: center; width: 58px;}\n' + | |||
'#confirm {text-align: right; margin-bottom: -18px; padding-top: 2px; font-size: 14px; color: #FF0000;}'; | |||
if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head'); | |||
$('style.generated-css').html($('style.generated-css').html() + cssString); | |||
} | |||
function drawFilterList() { | |||
var list = getList().generalFilter; | |||
var $ele = $('#filter-list'); | |||
var $row, i, length, obj, val; | |||
var typeName = { | |||
name: 'name', | |||
trip: 'tripcode', | |||
sub: 'subject', | |||
com: 'comment' | |||
}; | |||
$ele.empty(); | |||
$ele.append('<tr id="header"><th>Type</th><th>Content</th><th>Remove</th></tr>'); | |||
for (i = 0, length = list.length; i < length; i++) { | |||
obj = list[i]; | |||
// display formatting | |||
val = (obj.regex) ? '/'+ obj.value +'/' : obj.value; | |||
$row = $('<tr>'); | |||
$row.append( | |||
'<td>'+ typeName[obj.type] +'</td>', | |||
'<td>'+ val +'</td>', | |||
$('<td>').append( | |||
$('<a>').html('X') | |||
.addClass('del-btn') | |||
.attr('href', '#') | |||
.data('type', obj.type) | |||
.data('val', obj.value) | |||
.data('useRegex', obj.regex) | |||
) | |||
); | |||
$ele.append($row); | |||
} | |||
} | |||
function initOptionsPanel() { | |||
if (window.Options && !Options.get_tab('filter')) { | |||
Options.add_tab('filter', 'list', _('Filters')); | |||
Options.extend_tab('filter', | |||
'<div id="filter-control">' + | |||
'<select>' + | |||
'<option value="name">'+_('Name')+'</option>' + | |||
'<option value="trip">'+_('Tripcode')+'</option>' + | |||
'<option value="sub">'+_('Subject')+'</option>' + | |||
'<option value="com">'+_('Comment')+'</option>' + | |||
'</select>' + | |||
'<input type="text">' + | |||
'<input type="checkbox">' + | |||
'regex ' + | |||
'<button id="set-filter">'+_('Add')+'</button>' + | |||
'<button id="clear">'+_('Clear all filters')+'</button>' + | |||
'<div id="confirm" class="hidden">' + | |||
_('This will clear all filtering rules including hidden posts.')+' <a id="confirm-y" href="#">'+_('yes')+'</a> | <a id="confirm-n" href="#">'+_('no')+'</a>' + | |||
'</div>' + | |||
'</div>' + | |||
'<div id="filter-container"><table id="filter-list"></table></div>' | |||
); | |||
drawFilterList(); | |||
// control buttons | |||
$('#filter-control').on('click', '#set-filter', function () { | |||
var type = $('#filter-control select option:selected').val(); | |||
var value = $('#filter-control input[type=text]').val(); | |||
var useRegex = $('#filter-control input[type=checkbox]').prop('checked'); | |||
//clear the input form | |||
$('#filter-control input[type=text]').val(''); | |||
addFilter(type, value, useRegex); | |||
drawFilterList(); | |||
}); | |||
$('#filter-control').on('click', '#clear', function () { | |||
$('#filter-control #clear').addClass('hidden'); | |||
$('#filter-control #confirm').removeClass('hidden'); | |||
}); | |||
$('#filter-control').on('click', '#confirm-y', function (e) { | |||
e.preventDefault(); | |||
$('#filter-control #clear').removeClass('hidden'); | |||
$('#filter-control #confirm').addClass('hidden'); | |||
setList({ | |||
generalFilter: [], | |||
postFilter: {}, | |||
nextPurge: {}, | |||
lastPurge: timestamp() | |||
}); | |||
drawFilterList(); | |||
}); | |||
$('#filter-control').on('click', '#confirm-n', function (e) { | |||
e.preventDefault(); | |||
$('#filter-control #clear').removeClass('hidden'); | |||
$('#filter-control #confirm').addClass('hidden'); | |||
}); | |||
// remove button | |||
$('#filter-list').on('click', '.del-btn', function (e) { | |||
e.preventDefault(); | |||
var $ele = $(e.target); | |||
var type = $ele.data('type'); | |||
var val = $ele.data('val'); | |||
var useRegex = $ele.data('useRegex'); | |||
removeFilter(type, val, useRegex); | |||
}); | |||
} | |||
} | |||
/* | |||
* clear out pruned threads | |||
*/ | |||
function purge() { | |||
var list = getList(); | |||
var board, thread, boardId, threadId; | |||
var deferred; | |||
var requestArray = []; | |||
var successHandler = function (boardId, threadId) { | |||
return function () { | |||
// thread still alive, keep it in the list and increase the time between checks. | |||
var list = getList(); | |||
var thread = list.nextPurge[boardId][threadId]; | |||
thread.timestamp = timestamp(); | |||
thread.interval = Math.floor(thread.interval * 1.5); | |||
setList(list); | |||
}; | |||
}; | |||
var errorHandler = function (boardId, threadId) { | |||
return function (xhr) { | |||
if (xhr.status == 404) { | |||
var list = getList(); | |||
delete list.nextPurge[boardId][threadId]; | |||
delete list.postFilter[boardId][threadId]; | |||
if ($.isEmptyObject(list.nextPurge[boardId])) delete list.nextPurge[boardId]; | |||
if ($.isEmptyObject(list.postFilter[boardId])) delete list.postFilter[boardId]; | |||
setList(list); | |||
} | |||
}; | |||
}; | |||
if ((timestamp() - list.lastPurge) < 86400) // less than 1 day | |||
return; | |||
for (boardId in list.nextPurge) { | |||
board = list.nextPurge[boardId]; | |||
for (threadId in board) { | |||
thread = board[threadId]; | |||
if (timestamp() > (thread.timestamp + thread.interval)) { | |||
// check if thread is pruned | |||
deferred = $.ajax({ | |||
cache: false, | |||
url: '/'+ boardId +'/res/'+ threadId +'.json', | |||
success: successHandler(boardId, threadId), | |||
error: errorHandler(boardId, threadId) | |||
}); | |||
requestArray.push(deferred); | |||
} | |||
} | |||
} | |||
// when all requests complete | |||
$.when.apply($, requestArray).always(function () { | |||
var list = getList(); | |||
list.lastPurge = timestamp(); | |||
setList(list); | |||
}); | |||
} | |||
function init() { | |||
if (typeof localStorage.postFilter === 'undefined') { | |||
localStorage.postFilter = JSON.stringify({ | |||
generalFilter: [], | |||
postFilter: {}, | |||
nextPurge: {}, | |||
lastPurge: timestamp() | |||
}); | |||
} | |||
var pageData = { | |||
boardId: board_name, // get the id from the global variable | |||
localList: [], // all the blacklisted post IDs or UIDs that apply to the current page | |||
noReplyList: [], // any posts that replies to the contents of this list shall be hidden | |||
hasUID: (document.getElementsByClassName('poster_id').length > 0), | |||
forcedAnon: ($('th:contains(Name)').length === 0) // tests by looking for the Name label on the reply form | |||
}; | |||
initStyle(); | |||
initOptionsPanel(); | |||
initPostMenu(pageData); | |||
filterPage(pageData); | |||
// on new posts | |||
$(document).on('new_post', function (e, post) { | |||
var threadId; | |||
if ($(post).hasClass('reply')) { | |||
threadId = $(post).parents('.thread').attr('id').replace('thread_', ''); | |||
} else { | |||
threadId = $(post).attr('id').replace('thread_', ''); | |||
post = $(post).children('.op')[0]; | |||
} | |||
filter(post, threadId, pageData); | |||
quickToggle(post, threadId, pageData); | |||
}); | |||
$(document).on('filter_page', function () { | |||
filterPage(pageData); | |||
}); | |||
// shift+click on catalog to hide thread | |||
if (active_page == 'catalog') { | |||
$(document).on('click', '.mix', function(e) { | |||
if (e.shiftKey) { | |||
var threadId = $(this).data('id').toString(); | |||
var postId = threadId; | |||
blacklist.add.post(pageData.boardId, threadId, postId, false); | |||
} | |||
}); | |||
} | |||
// clear out the old threads | |||
purge(); | |||
} | |||
init(); | |||
}); | |||
if (typeof window.Menu !== "undefined") { | |||
$(document).trigger('menu_ready'); | |||
} | |||
} | |||
</pre> | |||
|} | |||
==== Preview for Youtube Videos ==== | ==== Preview for Youtube Videos ==== | ||
Show preview for youtube video links. <pre>(function youtubeInlinePreviews() { | Show preview for youtube video links. | ||
<i>Expand to view the script</i> | |||
{| class="mw-collapsible mw-collapsed" | |||
|+ | |||
|<pre>(function youtubeInlinePreviews() { | |||
const re = /(?:youtube\.com\/watch\?v=|youtu\.be\/)(.{11})/; | const re = /(?:youtube\.com\/watch\?v=|youtu\.be\/)(.{11})/; | ||
Line 236: | Line 1,322: | ||
})();</pre> | })();</pre> | ||
|} | |||
==== Filter tripchuds ==== | ==== Filter tripchuds ==== | ||
To use it, change "!!TRIP GOES HERE" to any trip you want to filter (ex: !incelchud1 or !!incelchud1), can also be used to filter names if you change <blockquote> (/class="trip">!!TRIP GOES HERE</) </blockquote> to <blockquote> (/class="name">name of the chudcel you want to filter</) </blockquote> | To use it, change "!!TRIP GOES HERE" to any trip you want to filter (ex: !incelchud1 or !!incelchud1), can also be used to filter names if you change <blockquote> (/class="trip">!!TRIP GOES HERE</) </blockquote> to <blockquote> (/class="name">name of the chudcel you want to filter</) </blockquote> | ||
if you want to filter multiple tripchuds, you have to do this <blockquote> (/class="trip">!!FIRST TRIP CHUD|!SECOND TRIPCHUD|!THIRD TRIPCHUD</) </blockquote> | if you want to filter multiple tripchuds, you have to do this <blockquote> (/class="trip">!!FIRST TRIP CHUD|!SECOND TRIPCHUD|!THIRD TRIPCHUD</) </blockquote> | ||
<pre>$(".post") | |||
<i>Expand to view the script</i> | |||
{| class="mw-collapsible mw-collapsed" | |||
|+ | |||
|<pre>$(".post") | |||
.filter(function (index) { | .filter(function (index) { | ||
return this.innerHTML.match(/class="trip">!!TRIP GOES HERE</); | return this.innerHTML.match(/class="trip">!!TRIP GOES HERE</); | ||
Line 255: | Line 1,346: | ||
localStorage.favorites = '[]'; | localStorage.favorites = '[]'; | ||
}</pre> | }</pre> | ||
|} | |||
==== Hide tripchud posts from catalog ==== | ==== Hide tripchud posts from catalog ==== | ||
Hides all tripchud posts from catalog, no exceptions. | Hides all tripchud posts from catalog, no exceptions. | ||
<pre>// --- start: purgeTripsFromCatalog --- | |||
<i>Expand to view the script</i> | |||
{| class="mw-collapsible mw-collapsed" | |||
|+ | |||
|<pre>// --- start: purgeTripsFromCatalog --- | |||
(function purgeTripsFromCatalog() { | (function purgeTripsFromCatalog() { | ||
Line 278: | Line 1,373: | ||
// --- end: purgeTripsFromCatalog ---</pre> | // --- end: purgeTripsFromCatalog ---</pre> | ||
|} |
Revision as of 05:44, 25 October 2021
On soyjak.party users can add custom javascript under Options->User JS in the top right of the page to customize their browsing experience.
Code Snippets
SoyParty-X
Features:
- Detects flood posts and hides them (you can set amount of lines and words to hide)
- Forced anonymity (hides namefags, can be disabled)
- Hides emailfags or sagefags (can be disabled for sagefags)
- Highlights triple parentheses
- Highlights datamining (makes posts glow)
- Inline Youtube Previews (+invidious support)
- Replaces "Gem" and "Coal" with minecraft icons
- Replaces "Bump" and "Sage" with upvote and downvote icons
Expand to view the script
// ==UserScript== // @name SoyParty-X // @namespace datamining // @version 0.2 // @description Cure the cancer that is killing soyjak.party // @author Chud (You) // @match https://soyjak.party/* // @icon https://soyjak.party/static/favicon.png // @grant none // ==/UserScript== /* eslint-env jquery */ /* Changelog: 0.2: - hidePosts() now detects flood posts. 0.1: - hidePosts() - forcedAnonymity() - highlightTripleParentheses() - highlightDatamining() - inlineYoutubePreviews() - replaceCoalAndGemsWithIcon() - replaceBumpAndSageWithIcons() */ (function SoyPartyX() { // true = hide posts where the e-mail is "sage". const _hideMailtoSage = true; // true = scrub email, trip, and force all names to be "Chud". // - Emailfags and tripfags are already hidden. // - Namefags aren't hidden, but turning this on will anonymize them. // false = don't change posts (default). const _enableForcedAnonymity = false; // Sets the limit for a post to be considered a flood post. // If one of the criteria is met, the post is hidden. const floodThresholdLines = 30; const floodThresholdCharacters = 3000; hidePosts(); forcedAnonymity(); // Must come AFTER hidePosts() highlightTripleParentheses(); highlightDatamining(); inlineYoutubePreviews(); replaceCoalAndGemsWithIcon(); replaceBumpAndSageWithIcons(); function hidePosts() { $(".post").each((i, el) => { const $el = $(el); const reasons = []; const isOp = $el.hasClass("op"); if ($el.has(".trip").length) { reasons.push("tripfag"); } if (_hideMailtoSage && $el.has('a.email[href^="mailto:sage"]').length) { reasons.push("sagefag"); } if ($el.has('a.email:not([href^="mailto:sage"])').length) { reasons.push("emailfag"); } const body = $el.has(".body"); const bodyLines = body.html().split("<br>").length; const bodyLength = body.text().length; if ( bodyLines > floodThresholdLines || bodyLength > floodThresholdCharacters ) { reasons.push( `possible flooding: ${bodyLength} characters in ${bodyLines} lines` ); } if (reasons.length) { const $notice = $("<div>") .addClass(`post ${isOp ? "op" : "reply"}`) .html( `<div class='body'><em>Post hidden (${reasons.join( ", " )}). Click to show.</em></div>` ) .after($("<br>")); $notice.click(() => { $el.show(); if (isOp) $el.prev(".files").show(); $notice.hide(); }); $el.after($notice); $el.hide(); if (isOp) $el.prev(".files").hide(); } }); } function forcedAnonymity() { if (!_enableForcedAnonymity) return; // Remove all emails. $("a.email").prop("outerHTML", "<span class='name'>Chud</span>"); // Remove all tripcodes. $(".trip").prop("outerHTML", ""); // Set all names to Chud. // Make sure not to overwrite (You)'s. $(".name") .filter((i, el) => !$(el).has(".own_post").length) .text("Chud"); } function replaceWordWithIcon(re, icon) { const matchesRe = (index, post) => $(post).html().match(re); const template = (match) => `<img src="${icon}" style="max-height:2em; vertical-align:middle">`; const applyTemplate = (index, post) => { const $post = $(post); const html = $post.html(); $post.html(html.replace(re, template)); }; $("div.body").filter(matchesRe).each(applyTemplate); } function replaceCoalAndGemsWithIcon() { replaceWordWithIcon(/coal/gi, "https://i.imgur.com/O9iRcRv.png"); replaceWordWithIcon(/gems?/gi, "https://i.imgur.com/BvjFdau.png"); } function replaceBumpAndSageWithIcons() { // replaceWordWithIcon(/bump/gi, "https://i.imgur.com/zM2xOGh.png"); // replaceWordWithIcon(/sage/gi, "https://i.imgur.com/2bsauzj.png"); replaceWordWithIcon(/bump/gi, "https://i.imgur.com/Y7cpsW0.png"); replaceWordWithIcon(/\bsage\b/gi, "https://i.imgur.com/ZarQtY3.png"); } function highlightTripleParentheses() { const re = /\(\(\(.+?\)\)\)/g; const hasRe = (i, post) => post.innerHTML.match(re); const template = (match) => `<span style='background-color:white;color:#0038B8;font-family:monospace;'>${match}</span>`; const applyTemplate = (i, post) => { post.innerHTML = post.innerHTML.replace(re, template); }; $("div.body").filter(hasRe).each(applyTemplate); } function highlightDatamining() { const reGlowie = /data(\s*|-)min(ing|er|ed)|(sell|selling|sold)\s+(my|our)?\s+data|cuckflare|cloudflare|cloud fleur/i; const hasReGlowie = (i, post) => post.innerHTML.match(reGlowie); const applyTemplate = (i, post) => $(post).css({ backgroundColor: "#D7EFD7", boxShadow: "#66FF66 0 0 2rem 0", }); $(".reply").filter(hasReGlowie).each(applyTemplate); } function inlineYoutubePreviews() { const re = /(?:youtu\.be\/|\/watch\?v=)(.{11})/; const previewTemplate = (videoId) => `<a href="https://youtube.com/watch?v=${videoId}">https://youtube.com/watch?v=${videoId}</a><br><img style="max-width:255px;max-height:255px" src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg" /><br><em>Watch on <a href="https://yewtu.be/${videoId}">Invidious</a> (less datamining)</em><br>`; $(".body a") .filter(function (i) { return $(this).prop("href").match(re); }) .each(function (i) { $(this).prop("outerHTML", previewTemplate(this.href.match(re)[1])); }); } })(); |
Post Filters
Allows you to filter posts based on comments, subject, name, and tripcode To access filters click on options button.
Expand to view the script
/* * post-menu.js - adds dropdown menu to posts * * Creates a global Menu object with four public methods: * * Menu.onclick(fnc) * registers a function to be executed after button click, before the menu is displayed * Menu.add_item(id, text[, title]) * adds an item to the top level of menu * Menu.add_submenu(id, text) * creates and returns a List object through which to manipulate the content of the submenu * Menu.get_submenu(id) * returns the submenu with the specified id from the top level menu * * The List object contains all the methods from Menu except onclick() * * Example usage: * Menu.add_item('filter-menu-hide', 'Hide post'); * Menu.add_item('filter-menu-unhide', 'Unhide post'); * * submenu = Menu.add_submenu('filter-menu-add', 'Add filter'); * submenu.add_item('filter-add-post-plus', 'Post +', 'Hide post and all replies'); * submenu.add_item('filter-add-id', 'ID'); * * Usage: * $config['additional_javascript'][] = 'js/jquery.min.js'; * $config['additional_javascript'][] = 'js/post-menu.js'; */ $(document).ready(function () { var List = function (menuId, text) { this.id = menuId; this.text = text; this.items = []; this.add_item = function (itemId, text, title) { this.items.push(new Item(itemId, text, title)); }; this.list_items = function () { var array = []; var i, length, obj, $ele; if ($.isEmptyObject(this.items)) return; length = this.items.length; for (i = 0; i < length; i++) { obj = this.items[i]; $ele = $('<li>', {id: obj.id}).text(obj.text); if ('title' in obj) $ele.attr('title', obj.title); if (obj instanceof Item) { $ele.addClass('post-item'); } else { $ele.addClass('post-submenu'); $ele.prepend(obj.list_items()); $ele.append($('<span>', {class: 'post-menu-arrow'}).text('»')); } array.push($ele); } return $('<ul>').append(array); }; this.add_submenu = function (menuId, text) { var ele = new List(menuId, text); this.items.push(ele); return ele; }; this.get_submenu = function (menuId) { for (var i = 0; i < this.items.length; i++) { if ((this.items[i] instanceof Item) || this.items[i].id != menuId) continue; return this.items[i]; } }; }; var Item = function (itemId, text, title) { this.id = itemId; this.text = text; // optional if (typeof title != 'undefined') this.title = title; }; function buildMenu(e) { var pos = $(e.target).offset(); var i, length; var $menu = $('<div class="post-menu"></div>').append(mainMenu.list_items()); // execute registered click handlers length = onclick_callbacks.length; for (i = 0; i < length; i++) { onclick_callbacks[i](e, $menu); } // set menu position and append to page $menu.css({top: pos.top, left: pos.left + 20}); $('body').append($menu); } function addButton(post) { var $ele = $(post); $ele.find('input.delete').after( $('<a>', {href: '#', class: 'post-btn', title: 'Post menu'}).text('▶') ); } /* * * * * * * * * * Public methods * * * * * * * * * */ var Menu = {}; var mainMenu = new List(); var onclick_callbacks = []; Menu.onclick = function (fnc) { onclick_callbacks.push(fnc); }; Menu.add_item = function (itemId, text, title) { mainMenu.add_item(itemId, text, title); }; Menu.add_submenu = function (menuId, text) { return mainMenu.add_submenu(menuId, text); }; Menu.get_submenu = function (id) { return mainMenu.get_submenu(id); }; window.Menu = Menu; /* * * * * * * * Initialize * * * * * * * */ /* Styling */ var $ele, cssStyle, cssString; $ele = $('<div>').addClass('post reply').hide().appendTo('body'); cssStyle = $ele.css(['border-top-color']); cssStyle.hoverBg = $('body').css('background-color'); $ele.remove(); cssString = '\n/*** Generated by post-menu ***/\n' + '.post-menu {position: absolute; font-size: 12px; line-height: 1.3em;}\n' + '.post-menu ul {\n' + ' background-color: '+ cssStyle['border-top-color'] +'; border: 1px solid #666;\n' + ' list-style: none; padding: 0; margin: 0; white-space: nowrap;\n}\n' + '.post-menu .post-submenu{white-space: normal; width: 90px;}' + '.post-menu .post-submenu>ul{white-space: nowrap; width: auto;}' + '.post-menu li {cursor: pointer; position: relative; padding: 4px 4px; vertical-align: middle;}\n' + '.post-menu li:hover {background-color: '+ cssStyle.hoverBg +';}\n' + '.post-menu ul ul {display: none; position: absolute;}\n' + '.post-menu li:hover>ul {display: block; left: 100%; margin-top: -3px;}\n' + '.post-menu-arrow {float: right; margin-left: 10px;}\n' + '.post-menu.hidden, .post-menu .hidden {display: none;}\n' + '.post-btn {transition: transform 0.1s; width: 15px; text-align: center; font-size: 10pt; opacity: 0.8; text-decoration: none; margin: -6px 0px 0px -5px !important; display: inline-block;}\n' + '.post-btn:hover {opacity: 1;}\n' + '.post-btn-open {transform: rotate(90deg);}\n'; if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head'); $('style.generated-css').html($('style.generated-css').html() + cssString); /* Add buttons */ $('.reply:not(.hidden), .thread>.op').each(function () { addButton(this); }); /* event handlers */ $('form[name=postcontrols]').on('click', '.post-btn', function (e) { e.preventDefault(); var post = e.target.parentElement.parentElement; $('.post-menu').remove(); if ($(e.target).hasClass('post-btn-open')) { $('.post-btn-open').removeClass('post-btn-open'); } else { // close previous button $('.post-btn-open').removeClass('post-btn-open'); $(post).find('.post-btn').addClass('post-btn-open'); buildMenu(e); } }); $(document).on('click', function (e){ if ($(e.target).hasClass('post-btn') || $(e.target).hasClass('post-submenu')) return; $('.post-menu').remove(); $('.post-btn-open').removeClass('post-btn-open'); }); // on new posts $(document).on('new_post', function (e, post) { addButton(post); }); $(document).trigger('menu_ready'); }); // Post Filters if (active_page === 'thread' || active_page === 'index' || active_page === 'catalog' || active_page === 'ukko') { $(document).on('menu_ready', function () { 'use strict'; // returns blacklist object from storage function getList() { return JSON.parse(localStorage.postFilter); } // stores blacklist into storage and reruns the filter function setList(blacklist) { localStorage.postFilter = JSON.stringify(blacklist); $(document).trigger('filter_page'); } // unit: seconds function timestamp() { return Math.floor((new Date()).getTime() / 1000); } function initList(list, boardId, threadId) { if (typeof list.postFilter[boardId] == 'undefined') { list.postFilter[boardId] = {}; list.nextPurge[boardId] = {}; } if (typeof list.postFilter[boardId][threadId] == 'undefined') { list.postFilter[boardId][threadId] = []; } list.nextPurge[boardId][threadId] = {timestamp: timestamp(), interval: 86400}; // 86400 seconds == 1 day } function addFilter(type, value, useRegex) { var list = getList(); var filter = list.generalFilter; var obj = { type: type, value: value, regex: useRegex }; for (var i=0; i<filter.length; i++) { if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex) return; } filter.push(obj); setList(list); drawFilterList(); } function removeFilter(type, value, useRegex) { var list = getList(); var filter = list.generalFilter; for (var i=0; i<filter.length; i++) { if (filter[i].type == type && filter[i].value == value && filter[i].regex == useRegex) { filter.splice(i, 1); break; } } setList(list); drawFilterList(); } function nameSpanToString(el) { var s = ''; $.each($(el).contents(), function(k,v) { if (v.nodeName === 'IMG') s=s+$(v).attr('alt') if (v.nodeName === '#text') s=s+v.nodeValue }); return s.trim(); } var blacklist = { add: { post: function (boardId, threadId, postId, hideReplies) { var list = getList(); var filter = list.postFilter; initList(list, boardId, threadId); for (var i in filter[boardId][threadId]) { if (filter[boardId][threadId][i].post == postId) return; } filter[boardId][threadId].push({ post: postId, hideReplies: hideReplies }); setList(list); }, uid: function (boardId, threadId, uniqueId, hideReplies) { var list = getList(); var filter = list.postFilter; initList(list, boardId, threadId); for (var i in filter[boardId][threadId]) { if (filter[boardId][threadId][i].uid == uniqueId) return; } filter[boardId][threadId].push({ uid: uniqueId, hideReplies: hideReplies }); setList(list); } }, remove: { post: function (boardId, threadId, postId) { var list = getList(); var filter = list.postFilter; // thread already pruned if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined') return; for (var i=0; i<filter[boardId][threadId].length; i++) { if (filter[boardId][threadId][i].post == postId) { filter[boardId][threadId].splice(i, 1); break; } } if ($.isEmptyObject(filter[boardId][threadId])) { delete filter[boardId][threadId]; delete list.nextPurge[boardId][threadId]; if ($.isEmptyObject(filter[boardId])) { delete filter[boardId]; delete list.nextPurge[boardId]; } } setList(list); }, uid: function (boardId, threadId, uniqueId) { var list = getList(); var filter = list.postFilter; // thread already pruned if (typeof filter[boardId] == 'undefined' || typeof filter[boardId][threadId] == 'undefined') return; for (var i=0; i<filter[boardId][threadId].length; i++) { if (filter[boardId][threadId][i].uid == uniqueId) { filter[boardId][threadId].splice(i, 1); break; } } if ($.isEmptyObject(filter[boardId][threadId])) { delete filter[boardId][threadId]; delete list.nextPurge[boardId][threadId]; if ($.isEmptyObject(filter[boardId])) { delete filter[boardId]; delete list.nextPurge[boardId]; } } setList(list); } } }; /* * hide/show the specified thread/post */ function hide(ele) { var $ele = $(ele); if ($(ele).data('hidden')) return; $(ele).data('hidden', true); if ($ele.hasClass('op')) { $ele.parent().find('.body, .files, .video-container').not($ele.children('.reply').children()).hide(); // hide thread replies on index view if (active_page == 'index' || active_page == 'ukko') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').hide(); } else { // normal posts $ele.children('.body, .files, .video-container').hide(); } } function show(ele) { var $ele = $(ele); $(ele).data('hidden', false); if ($ele.hasClass('op')) { $ele.parent().find('.body, .files, .video-container').show(); if (active_page == 'index') $ele.parent().find('.omitted, .reply:not(.hidden), post_no, .mentioned, br').show(); } else { // normal posts $ele.children('.body, .files, .video-container').show(); } } /* * create filter menu when the button is clicked */ function initPostMenu(pageData) { var Menu = window.Menu; var submenu; Menu.add_item('filter-menu-hide', _('Hide post')); Menu.add_item('filter-menu-unhide', _('Unhide post')); submenu = Menu.add_submenu('filter-menu-add', _('Add filter')); submenu.add_item('filter-add-post-plus', _('Post +'), _('Hide post and all replies')); submenu.add_item('filter-add-id', _('ID')); submenu.add_item('filter-add-id-plus', _('ID +'), _('Hide ID and all replies')); submenu.add_item('filter-add-name', _('Name')); submenu.add_item('filter-add-trip', _('Tripcode')); submenu = Menu.add_submenu('filter-menu-remove', _('Remove filter')); submenu.add_item('filter-remove-id', _('ID')); submenu.add_item('filter-remove-name', _('Name')); submenu.add_item('filter-remove-trip', _('Tripcode')); Menu.onclick(function (e, $buffer) { var ele = e.target.parentElement.parentElement; var $ele = $(ele); var threadId = $ele.parent().attr('id').replace('thread_', ''); var boardId = $ele.parent().data('board'); var postId = $ele.find('.post_no').not('[id]').text(); if (pageData.hasUID) { var postUid = $ele.find('.poster_id').text(); } var postName; var postTrip = ''; if (!pageData.forcedAnon) { postName = (typeof $ele.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($ele.find('.name')[0]); postTrip = $ele.find('.trip').text(); } /* display logic and bind click handlers */ // unhide button if ($ele.data('hidden')) { $buffer.find('#filter-menu-unhide').click(function () { // if hidden due to post id, remove it from blacklist // otherwise just show this post blacklist.remove.post(boardId, threadId, postId); show(ele); }); $buffer.find('#filter-menu-hide').addClass('hidden'); } else { $buffer.find('#filter-menu-unhide').addClass('hidden'); $buffer.find('#filter-menu-hide').click(function () { blacklist.add.post(boardId, threadId, postId, false); }); } // post id if (!$ele.data('hiddenByPost')) { $buffer.find('#filter-add-post-plus').click(function () { blacklist.add.post(boardId, threadId, postId, true); }); } else { $buffer.find('#filter-add-post-plus').addClass('hidden'); } // UID if (pageData.hasUID && !$ele.data('hiddenByUid')) { $buffer.find('#filter-add-id').click(function () { blacklist.add.uid(boardId, threadId, postUid, false); }); $buffer.find('#filter-add-id-plus').click(function () { blacklist.add.uid(boardId, threadId, postUid, true); }); $buffer.find('#filter-remove-id').addClass('hidden'); } else if (pageData.hasUID) { $buffer.find('#filter-remove-id').click(function () { blacklist.remove.uid(boardId, threadId, postUid); }); $buffer.find('#filter-add-id').addClass('hidden'); $buffer.find('#filter-add-id-plus').addClass('hidden'); } else { // board doesn't use UID $buffer.find('#filter-add-id').addClass('hidden'); $buffer.find('#filter-add-id-plus').addClass('hidden'); $buffer.find('#filter-remove-id').addClass('hidden'); } // name if (!pageData.forcedAnon && !$ele.data('hiddenByName')) { $buffer.find('#filter-add-name').click(function () { addFilter('name', postName, false); }); $buffer.find('#filter-remove-name').addClass('hidden'); } else if (!pageData.forcedAnon) { $buffer.find('#filter-remove-name').click(function () { removeFilter('name', postName, false); }); $buffer.find('#filter-add-name').addClass('hidden'); } else { // board has forced anon $buffer.find('#filter-remove-name').addClass('hidden'); $buffer.find('#filter-add-name').addClass('hidden'); } // tripcode if (!pageData.forcedAnon && !$ele.data('hiddenByTrip') && postTrip !== '') { $buffer.find('#filter-add-trip').click(function () { addFilter('trip', postTrip, false); }); $buffer.find('#filter-remove-trip').addClass('hidden'); } else if (!pageData.forcedAnon && postTrip !== '') { $buffer.find('#filter-remove-trip').click(function () { removeFilter('trip', postTrip, false); }); $buffer.find('#filter-add-trip').addClass('hidden'); } else { // board has forced anon $buffer.find('#filter-remove-trip').addClass('hidden'); $buffer.find('#filter-add-trip').addClass('hidden'); } /* hide sub menus if all items are hidden */ if (!$buffer.find('#filter-menu-remove > ul').children().not('.hidden').length) { $buffer.find('#filter-menu-remove').addClass('hidden'); } if (!$buffer.find('#filter-menu-add > ul').children().not('.hidden').length) { $buffer.find('#filter-menu-add').addClass('hidden'); } }); } /* * hide/unhide thread on index view */ function quickToggle(ele, threadId, pageData) { /*if ($(ele).find('.hide-thread-link').length) $('.hide-thread-link').remove();*/ if ($(ele).hasClass('op') && !$(ele).find('.hide-thread-link').length) { $('<a class="hide-thread-link" style="float:left;margin-right:5px" href="javascript:void(0)">[' + ($(ele).data('hidden') ? '+' : '–') + ']</a>') .insertBefore($(ele).find(':not(h2,h2 *):first')) .click(function() { var postId = $(ele).find('.post_no').not('[id]').text(); var hidden = $(ele).data('hidden'); var boardId = $(ele).parents('.thread').data('board'); if (hidden) { blacklist.remove.post(boardId, threadId, postId, false); $(this).html('[–]'); } else { blacklist.add.post(boardId, threadId, postId, false); $(this).text('[+]'); } }); } } /* * determine whether the reply post should be hidden * - applies to all posts on page load or filtering rule change * - apply to new posts on thread updates * - must explicitly set the state of each attributes because filter will reapply to all posts after filtering rule change */ function filter(post, threadId, pageData) { var $post = $(post); var list = getList(); var postId = $post.find('.post_no').not('[id]').text(); var name, trip, uid, subject, comment; var i, length, array, rule, pattern; // temp variables var boardId = $post.data('board'); if (!boardId) boardId = $post.parents('.thread').data('board'); var localList = pageData.localList; var noReplyList = pageData.noReplyList; var hasUID = pageData.hasUID; var forcedAnon = pageData.forcedAnon; var hasTrip = ($post.find('.trip').length > 0); var hasSub = ($post.find('.subject').length > 0); $post.data('hidden', false); $post.data('hiddenByUid', false); $post.data('hiddenByPost', false); $post.data('hiddenByName', false); $post.data('hiddenByTrip', false); $post.data('hiddenBySubject', false); $post.data('hiddenByComment', false); // add post with matched UID to localList if (hasUID && typeof list.postFilter[boardId] != 'undefined' && typeof list.postFilter[boardId][threadId] != 'undefined') { uid = $post.find('.poster_id').text(); array = list.postFilter[boardId][threadId]; for (i=0; i<array.length; i++) { if (array[i].uid == uid) { $post.data('hiddenByUid', true); localList.push(postId); if (array[i].hideReplies) noReplyList.push(postId); break; } } } // match localList if (localList.length) { if ($.inArray(postId, localList) != -1) { if ($post.data('hiddenByUid') !== true) $post.data('hiddenByPost', true); hide(post); } } // matches generalFilter if (!forcedAnon) name = (typeof $post.find('.name').contents()[0] == 'undefined') ? '' : nameSpanToString($post.find('.name')[0]); if (!forcedAnon && hasTrip) trip = $post.find('.trip').text(); if (hasSub) subject = $post.find('.subject').text(); array = $post.find('.body').contents().filter(function () {if ($(this).text() !== '') return true;}).toArray(); array = $.map(array, function (ele) { return $(ele).text().trim(); }); comment = array.join(' '); for (i = 0, length = list.generalFilter.length; i < length; i++) { rule = list.generalFilter[i]; if (rule.regex) { pattern = new RegExp(rule.value); switch (rule.type) { case 'name': if (!forcedAnon && pattern.test(name)) { $post.data('hiddenByName', true); hide(post); } break; case 'trip': if (!forcedAnon && hasTrip && pattern.test(trip)) { $post.data('hiddenByTrip', true); hide(post); } break; case 'sub': if (hasSub && pattern.test(subject)) { $post.data('hiddenBySubject', true); hide(post); } break; case 'com': if (pattern.test(comment)) { $post.data('hiddenByComment', true); hide(post); } break; } } else { switch (rule.type) { case 'name': if (!forcedAnon && rule.value == name) { $post.data('hiddenByName', true); hide(post); } break; case 'trip': if (!forcedAnon && hasTrip && rule.value == trip) { $post.data('hiddenByTrip', true); hide(post); } break; case 'sub': pattern = new RegExp('\\b'+ rule.value+ '\\b'); if (hasSub && pattern.test(subject)) { $post.data('hiddenBySubject', true); hide(post); } break; case 'com': pattern = new RegExp('\\b'+ rule.value+ '\\b'); if (pattern.test(comment)) { $post.data('hiddenByComment', true); hide(post); } break; } } } // check for link to filtered posts $post.find('.body a').not('[rel="nofollow"]').each(function () { var replyId = $(this).text().match(/^>>(\d+)$/); if (!replyId) return; replyId = replyId[1]; if ($.inArray(replyId, noReplyList) != -1) { hide(post); } }); // post didn't match any filters if (!$post.data('hidden')) { show(post); } } /* (re)runs the filter on the entire page */ function filterPage(pageData) { var list = getList(); if (active_page != 'catalog') { // empty the local and no-reply list pageData.localList = []; pageData.noReplyList = []; $('.thread').each(function () { var $thread = $(this); // disregard the hidden threads constructed by post-hover.js if ($thread.css('display') == 'none') return; var threadId = $thread.attr('id').replace('thread_', ''); var boardId = $thread.data('board'); var op = $thread.children('.op')[0]; var i, array; // temp variables // add posts to localList and noReplyList if (typeof list.postFilter[boardId] != 'undefined' && typeof list.postFilter[boardId][threadId] != 'undefined') { array = list.postFilter[boardId][threadId]; for (i=0; i<array.length; i++) { if ( typeof array[i].post == 'undefined') continue; pageData.localList.push(array[i].post); if (array[i].hideReplies) pageData.noReplyList.push(array[i].post); } } // run filter on OP filter(op, threadId, pageData); quickToggle(op, threadId, pageData); // iterate filter over each post if (!$(op).data('hidden') || active_page == 'thread') { $thread.find('.reply').not('.hidden').each(function () { filter(this, threadId, pageData); }); } }); } else { var postFilter = list.postFilter[pageData.boardId]; var $collection = $('.mix'); if ($.isEmptyObject(postFilter)) return; // for each thread that has filtering rules // check if filter contains thread OP and remove the thread from catalog $.each(postFilter, function (key, thread) { var threadId = key; $.each(thread, function () { if (this.post == threadId) { $collection.filter('[data-id='+ threadId +']').remove(); } }); }); } } function initStyle() { var $ele, cssStyle, cssString; $ele = $('<div>').addClass('post reply').hide().appendTo('body'); cssStyle = $ele.css(['background-color', 'border-color']); cssStyle.hoverBg = $('body').css('background-color'); $ele.remove(); cssString = '\n/*** Generated by post-filter ***/\n' + '#filter-control input[type=text] {width: 130px;}' + '#filter-control input[type=checkbox] {vertical-align: middle;}' + '#filter-control #clear {float: right;}\n' + '#filter-container {margin-top: 20px; border: 1px solid; height: 270px; overflow: auto;}\n' + '#filter-list {width: 100%; border-collapse: collapse;}\n' + '#filter-list th {text-align: center; height: 20px; font-size: 14px; border-bottom: 1px solid;}\n' + '#filter-list th:nth-child(1) {text-align: center; width: 70px;}\n' + '#filter-list th:nth-child(2) {text-align: left;}\n' + '#filter-list th:nth-child(3) {text-align: center; width: 58px;}\n' + '#filter-list tr:not(#header) {height: 22px;}\n' + '#filter-list tr:nth-child(even) {background-color:rgba(255, 255, 255, 0.5);}\n' + '#filter-list td:nth-child(1) {text-align: center; width: 70px;}\n' + '#filter-list td:nth-child(3) {text-align: center; width: 58px;}\n' + '#confirm {text-align: right; margin-bottom: -18px; padding-top: 2px; font-size: 14px; color: #FF0000;}'; if (!$('style.generated-css').length) $('<style class="generated-css">').appendTo('head'); $('style.generated-css').html($('style.generated-css').html() + cssString); } function drawFilterList() { var list = getList().generalFilter; var $ele = $('#filter-list'); var $row, i, length, obj, val; var typeName = { name: 'name', trip: 'tripcode', sub: 'subject', com: 'comment' }; $ele.empty(); $ele.append('<tr id="header"><th>Type</th><th>Content</th><th>Remove</th></tr>'); for (i = 0, length = list.length; i < length; i++) { obj = list[i]; // display formatting val = (obj.regex) ? '/'+ obj.value +'/' : obj.value; $row = $('<tr>'); $row.append( '<td>'+ typeName[obj.type] +'</td>', '<td>'+ val +'</td>', $('<td>').append( $('<a>').html('X') .addClass('del-btn') .attr('href', '#') .data('type', obj.type) .data('val', obj.value) .data('useRegex', obj.regex) ) ); $ele.append($row); } } function initOptionsPanel() { if (window.Options && !Options.get_tab('filter')) { Options.add_tab('filter', 'list', _('Filters')); Options.extend_tab('filter', '<div id="filter-control">' + '<select>' + '<option value="name">'+_('Name')+'</option>' + '<option value="trip">'+_('Tripcode')+'</option>' + '<option value="sub">'+_('Subject')+'</option>' + '<option value="com">'+_('Comment')+'</option>' + '</select>' + '<input type="text">' + '<input type="checkbox">' + 'regex ' + '<button id="set-filter">'+_('Add')+'</button>' + '<button id="clear">'+_('Clear all filters')+'</button>' + '<div id="confirm" class="hidden">' + _('This will clear all filtering rules including hidden posts.')+' <a id="confirm-y" href="#">'+_('yes')+'</a> | <a id="confirm-n" href="#">'+_('no')+'</a>' + '</div>' + '</div>' + '<div id="filter-container"><table id="filter-list"></table></div>' ); drawFilterList(); // control buttons $('#filter-control').on('click', '#set-filter', function () { var type = $('#filter-control select option:selected').val(); var value = $('#filter-control input[type=text]').val(); var useRegex = $('#filter-control input[type=checkbox]').prop('checked'); //clear the input form $('#filter-control input[type=text]').val(''); addFilter(type, value, useRegex); drawFilterList(); }); $('#filter-control').on('click', '#clear', function () { $('#filter-control #clear').addClass('hidden'); $('#filter-control #confirm').removeClass('hidden'); }); $('#filter-control').on('click', '#confirm-y', function (e) { e.preventDefault(); $('#filter-control #clear').removeClass('hidden'); $('#filter-control #confirm').addClass('hidden'); setList({ generalFilter: [], postFilter: {}, nextPurge: {}, lastPurge: timestamp() }); drawFilterList(); }); $('#filter-control').on('click', '#confirm-n', function (e) { e.preventDefault(); $('#filter-control #clear').removeClass('hidden'); $('#filter-control #confirm').addClass('hidden'); }); // remove button $('#filter-list').on('click', '.del-btn', function (e) { e.preventDefault(); var $ele = $(e.target); var type = $ele.data('type'); var val = $ele.data('val'); var useRegex = $ele.data('useRegex'); removeFilter(type, val, useRegex); }); } } /* * clear out pruned threads */ function purge() { var list = getList(); var board, thread, boardId, threadId; var deferred; var requestArray = []; var successHandler = function (boardId, threadId) { return function () { // thread still alive, keep it in the list and increase the time between checks. var list = getList(); var thread = list.nextPurge[boardId][threadId]; thread.timestamp = timestamp(); thread.interval = Math.floor(thread.interval * 1.5); setList(list); }; }; var errorHandler = function (boardId, threadId) { return function (xhr) { if (xhr.status == 404) { var list = getList(); delete list.nextPurge[boardId][threadId]; delete list.postFilter[boardId][threadId]; if ($.isEmptyObject(list.nextPurge[boardId])) delete list.nextPurge[boardId]; if ($.isEmptyObject(list.postFilter[boardId])) delete list.postFilter[boardId]; setList(list); } }; }; if ((timestamp() - list.lastPurge) < 86400) // less than 1 day return; for (boardId in list.nextPurge) { board = list.nextPurge[boardId]; for (threadId in board) { thread = board[threadId]; if (timestamp() > (thread.timestamp + thread.interval)) { // check if thread is pruned deferred = $.ajax({ cache: false, url: '/'+ boardId +'/res/'+ threadId +'.json', success: successHandler(boardId, threadId), error: errorHandler(boardId, threadId) }); requestArray.push(deferred); } } } // when all requests complete $.when.apply($, requestArray).always(function () { var list = getList(); list.lastPurge = timestamp(); setList(list); }); } function init() { if (typeof localStorage.postFilter === 'undefined') { localStorage.postFilter = JSON.stringify({ generalFilter: [], postFilter: {}, nextPurge: {}, lastPurge: timestamp() }); } var pageData = { boardId: board_name, // get the id from the global variable localList: [], // all the blacklisted post IDs or UIDs that apply to the current page noReplyList: [], // any posts that replies to the contents of this list shall be hidden hasUID: (document.getElementsByClassName('poster_id').length > 0), forcedAnon: ($('th:contains(Name)').length === 0) // tests by looking for the Name label on the reply form }; initStyle(); initOptionsPanel(); initPostMenu(pageData); filterPage(pageData); // on new posts $(document).on('new_post', function (e, post) { var threadId; if ($(post).hasClass('reply')) { threadId = $(post).parents('.thread').attr('id').replace('thread_', ''); } else { threadId = $(post).attr('id').replace('thread_', ''); post = $(post).children('.op')[0]; } filter(post, threadId, pageData); quickToggle(post, threadId, pageData); }); $(document).on('filter_page', function () { filterPage(pageData); }); // shift+click on catalog to hide thread if (active_page == 'catalog') { $(document).on('click', '.mix', function(e) { if (e.shiftKey) { var threadId = $(this).data('id').toString(); var postId = threadId; blacklist.add.post(pageData.boardId, threadId, postId, false); } }); } // clear out the old threads purge(); } init(); }); if (typeof window.Menu !== "undefined") { $(document).trigger('menu_ready'); } } |
Preview for Youtube Videos
Show preview for youtube video links.
Expand to view the script
(function youtubeInlinePreviews() { const re = /(?:youtube\.com\/watch\?v=|youtu\.be\/)(.{11})/; const previewTemplate = (videoId) => `<img style="max-width:255px;max-height:255px" src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg" />`; Array.from(document.querySelectorAll(".body a")) .filter((link) => link.href.match(re)) .forEach((link) => { const videoId = link.href.match(re)[1]; const inlinePreview = previewTemplate(videoId); link.innerHTML = inlinePreview; }); })(); |
Filter tripchuds
To use it, change "!!TRIP GOES HERE" to any trip you want to filter (ex: !incelchud1 or !!incelchud1), can also be used to filter names if you change
(/class="trip">!!TRIP GOES HERE</)
to
(/class="name">name of the chudcel you want to filter</)
if you want to filter multiple tripchuds, you have to do this
(/class="trip">!!FIRST TRIP CHUD|!SECOND TRIPCHUD|!THIRD TRIPCHUD</)
Expand to view the script
$(".post") .filter(function (index) { return this.innerHTML.match(/class="trip">!!TRIP GOES HERE</); }) .each(function (index) { let whatToHide = "#" + this.id; if (this.id.startsWith("op_")) { whatToHide = "#" + this.id.replace("op_", "thread_"); } $(whatToHide).hide(); }); if (!localStorage.favorites) { localStorage.favorites = '[]'; } |
Hide tripchud posts from catalog
Hides all tripchud posts from catalog, no exceptions.
Expand to view the script
// --- start: purgeTripsFromCatalog --- (function purgeTripsFromCatalog() { // If on a catalog page if (document.location.href.match(/catalog\.html$/)) { // Call the API for that page fetch(document.location.href.replace(".html", ".json")) .then((res) => res.json()) .then((json) => json // Find the threads where OP is using a tripcode .reduce((acc, cur) => [...acc, ...cur.threads], []) .filter((op) => op.trip) // Hide them .forEach((filtered) => $(`div[data-id='${filtered.no}']`).hide()) ); } })(); // --- end: purgeTripsFromCatalog --- |