|
|
|
const getFiltersFromLocalStorage = () => {
|
|
|
|
const savedFilters = JSON.parse(localStorage.getItem('filters1'));
|
|
|
|
return savedFilters.reduce((acc, filter) => {
|
|
|
|
const regexFilter = filter.type.endsWith('r');
|
|
|
|
if (regexFilter) {
|
|
|
|
acc[filter.type].push(new RegExp(filter.val, 'i')); //todo: serialize flags? probs not necessary
|
|
|
|
} else {
|
|
|
|
acc[filter.type].add(filter.val);
|
|
|
|
}
|
|
|
|
return acc;
|
|
|
|
}, {
|
|
|
|
single: new Set(),
|
|
|
|
fid: new Set(),
|
|
|
|
fname: new Set(),
|
|
|
|
fsub: new Set(),
|
|
|
|
ftrip: new Set(),
|
|
|
|
fmsg: new Set(),
|
|
|
|
fnamer: [],
|
|
|
|
ftripr: [],
|
|
|
|
fsubr: [],
|
|
|
|
fmsgr: [],
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
let { single, fid, fname, ftrip, fsub, fmsg, fnamer, ftripr, fsubr, fmsgr } = getFiltersFromLocalStorage();
|
|
|
|
|
|
|
|
let filtersTable;
|
|
|
|
const updateFiltersTable = () => {
|
|
|
|
[...filtersTable.children].slice(3).forEach(row => row.remove());
|
|
|
|
filtersTable.insertAdjacentHTML('beforeend', filters({filterArr: JSON.parse(localStorage.getItem('filters1'))}))
|
|
|
|
const closeButtons = filtersTable.querySelectorAll('.close');
|
|
|
|
for (let elem of closeButtons) {
|
|
|
|
let { type: closeType, data: closeData } = elem.dataset;
|
|
|
|
if (closeType.endsWith('r')) {
|
|
|
|
closeData = new RegExp(closeData, 'i');
|
|
|
|
}
|
|
|
|
elem.addEventListener('click', () => { toggleFilter(closeType, closeData) });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateSavedFilters = () => {
|
|
|
|
setLocalStorage('filters1', JSON.stringify([
|
|
|
|
...([...single].map(x => ({type:'single', val:x}))),
|
|
|
|
...([...fid].map(x => ({type:'fid', val:x}))),
|
|
|
|
...([...ftrip].map(x => ({type:'ftrip', val:x}))),
|
|
|
|
...([...fname].map(x => ({type:'fname', val:x}))),
|
|
|
|
...([...fsub].map(x => ({type:'fsub', val:x}))),
|
|
|
|
...([...fmsg].map(x => ({type:'fmsg', val:x}))),
|
|
|
|
...fnamer.map(x => ({type:'fnamer', val:x.source.toString()})),
|
|
|
|
...ftripr.map(x => ({type:'ftripr', val:x.source.toString()})),
|
|
|
|
...fsubr.map(x => ({type:'fsubr', val:x.source.toString()})),
|
|
|
|
...fmsgr.map(x => ({type:'fmsgr', val:x.source.toString()})),
|
|
|
|
]));
|
|
|
|
updateFiltersTable();
|
|
|
|
};
|
|
|
|
|
|
|
|
const anyFilterMatches = (filteringPost) => {
|
|
|
|
const { board, postId, userId, name, subject, tripcode } = filteringPost.dataset;
|
|
|
|
const postMessage = filteringPost.querySelector('.post-message');
|
|
|
|
const message = postMessage ? postMessage.textContent : null;
|
|
|
|
return single.has(`${board}-${postId}`)
|
|
|
|
|| fid.has(userId)
|
|
|
|
|| fname.has(name)
|
|
|
|
|| ftrip.has(tripcode)
|
|
|
|
|| fsub.has(tripcode)
|
|
|
|
|| fmsg.has(message)
|
|
|
|
|| fnamer.some(r => r.test(name))
|
|
|
|
|| ftripr.some(r => r.test(tripcode))
|
|
|
|
|| fsubr.some(r => r.test(subject))
|
|
|
|
|| fmsgr.some(r => r.test(message))
|
|
|
|
}
|
|
|
|
|
|
|
|
const togglePostsHidden = (posts, state, single) => {
|
|
|
|
for (let elem of posts) {
|
|
|
|
const showing = (!state && (!anyFilterMatches(elem) || single));
|
|
|
|
if (showing) { //possible fix for multiple filters & unhiding conflicts
|
|
|
|
elem.classList['remove']('hidden');
|
|
|
|
} else {
|
|
|
|
elem.classList['add']('hidden');
|
|
|
|
}
|
|
|
|
elem.querySelector('.postmenu').children[0].textContent = (showing ? 'Hide' : 'Show');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
//I wish this wasn't necessary, but css selectors dont support regex :(
|
|
|
|
const getPostsByRegex = (attribute, regex) => {
|
|
|
|
const matches = [];
|
|
|
|
for (let elem of document.querySelectorAll(`[${attribute}]`)) {
|
|
|
|
const value = elem.getAttribute(attribute).toString();
|
|
|
|
if (regex.test(value) === true) {
|
|
|
|
matches.push(elem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return matches;
|
|
|
|
};
|
|
|
|
|
|
|
|
const getPostsByMessage = (data, regex=false) => {
|
|
|
|
//you asked for this
|
|
|
|
const postMessages = [...document.querySelectorAll(`.${isCatalog ? 'catalog-tile': 'post-container' } .post-message`)];
|
|
|
|
const matchingMessages = postMessages.filter(m => (regex ? data.test(m.textContent) : m.textContent.includes(data)));
|
|
|
|
return matchingMessages.map(m => m.closest(`.${isCatalog ? 'catalog-tile': 'post-container' }`));
|
|
|
|
}
|
|
|
|
|
|
|
|
const getPostsByFilter = (type, data) => {
|
|
|
|
let posts = [];
|
|
|
|
switch (type) {
|
|
|
|
case 'single':
|
|
|
|
const [dataBoard, dataPostId] = data.split('-');
|
|
|
|
const singlePost = document.querySelector(`[data-board="${dataBoard}"][data-post-id="${dataPostId}"]`);
|
|
|
|
posts = singlePost ? [singlePost] : [];
|
|
|
|
break;
|
|
|
|
case 'fid':
|
|
|
|
posts = document.querySelectorAll(`[data-user-id="${data}"]`);
|
|
|
|
break;
|
|
|
|
case 'fname':
|
|
|
|
posts = document.querySelectorAll(`[data-name="${CSS.escape(data)}"]`);
|
|
|
|
break;
|
|
|
|
case 'fnamer':
|
|
|
|
posts = getPostsByRegex('data-name', data);
|
|
|
|
break;
|
|
|
|
case 'ftrip':
|
|
|
|
posts = document.querySelectorAll(`[data-tripcode="${CSS.escape(data)}"]`);
|
|
|
|
break;
|
|
|
|
case 'ftripr':
|
|
|
|
posts = getPostsByRegex('data-tripcode', data);
|
|
|
|
break;
|
|
|
|
case 'fsub':
|
|
|
|
posts = document.querySelectorAll(`[data-subject="${CSS.escape(data)}"]`);
|
|
|
|
break;
|
|
|
|
case 'fsubr':
|
|
|
|
posts = getPostsByRegex('data-subject', data);
|
|
|
|
break;
|
|
|
|
case 'fmsg':
|
|
|
|
posts = getPostsByMessage(data);
|
|
|
|
break;
|
|
|
|
case 'fmsgr':
|
|
|
|
posts = getPostsByMessage(data, true);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
return [...posts];
|
|
|
|
};
|
|
|
|
|
|
|
|
const setFilterState = (type, data, state) => {
|
|
|
|
const addOrDelete = state ? 'add' : 'delete';
|
|
|
|
switch (type) {
|
|
|
|
case 'single':
|
|
|
|
single[addOrDelete](data);
|
|
|
|
break;
|
|
|
|
case 'fid':
|
|
|
|
fid[addOrDelete](data);
|
|
|
|
break;
|
|
|
|
case 'fname':
|
|
|
|
fname[addOrDelete](data);
|
|
|
|
break;
|
|
|
|
case 'fnamer':
|
|
|
|
fnamer = fnamer.filter(r => r.source != data.source);
|
|
|
|
if (state) {
|
|
|
|
fnamer.push(data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'ftrip':
|
|
|
|
ftrip[addOrDelete](data);
|
|
|
|
break;
|
|
|
|
case 'ftripr':
|
|
|
|
ftripr = ftripr.filter(r => r.source != data.source);
|
|
|
|
if (state) {
|
|
|
|
ftripr.push(data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'fsub':
|
|
|
|
fsub[addOrDelete](data);
|
|
|
|
break;
|
|
|
|
case 'fsubr':
|
|
|
|
fsubr = fsubr.filter(r => r.source != data.source);
|
|
|
|
if (state) {
|
|
|
|
fsubr.push(data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'fmsg':
|
|
|
|
fmsg[addOrDelete](data);
|
|
|
|
break;
|
|
|
|
case 'fmsgr':
|
|
|
|
fmsgr = fmsgr.filter(r => r.source != data.source);
|
|
|
|
if (state) {
|
|
|
|
fmsgr.push(data);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const toggleFilter = (filterType, filterData, state) => {
|
|
|
|
const posts = getPostsByFilter(filterType, filterData);
|
|
|
|
setFilterState(filterType, filterData, state);
|
|
|
|
togglePostsHidden(posts, state, filterType === 'single');
|
|
|
|
updateSavedFilters();
|
|
|
|
}
|
|
|
|
|
|
|
|
//i guess this works, lmfao and saves ton of time
|
|
|
|
let actionForm, modalBg, moderatingPost;
|
|
|
|
const cancelModeratePost = (e) => {
|
|
|
|
if (!moderatingPost) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
e.preventDefault();
|
|
|
|
moderatingPost.querySelector('.post-check').checked = false;
|
|
|
|
moderatingPost.style.zIndex = 'unset';
|
|
|
|
if (moderatingPost.classList.contains('op')) {
|
|
|
|
moderatingPost.style.background = 'unset';
|
|
|
|
}
|
|
|
|
modalBg.style.display = 'none';
|
|
|
|
modalBg.style.zIndex = 4;
|
|
|
|
actionForm.removeAttribute('open');
|
|
|
|
moderatingPost = null;
|
|
|
|
}
|
|
|
|
const moderatePost = (postContainer) => {
|
|
|
|
moderatingPost = postContainer;
|
|
|
|
actionForm.classList.add('floatactions');
|
|
|
|
actionForm.setAttribute('open', 'open');
|
|
|
|
actionForm.style.zIndex = 3;
|
|
|
|
postContainer.style.zIndex = 3;
|
|
|
|
if (postContainer.classList.contains('op')) {
|
|
|
|
postContainer.style.background = 'var(--post-color)';
|
|
|
|
}
|
|
|
|
modalBg.style.display = 'unset';
|
|
|
|
modalBg.style.zIndex = 3;
|
|
|
|
const actionCaptcha = actionForm.querySelector('.captchafield');
|
|
|
|
const postCheck = postContainer.querySelector('.post-check');
|
|
|
|
Array.from(postCheck.form.elements)
|
|
|
|
.filter(e => e.name === 'checkedposts')
|
|
|
|
.forEach(e => e.checked = false);
|
|
|
|
postCheck.checked = true;
|
|
|
|
if (actionCaptcha) {
|
|
|
|
captchaController.loadCaptcha(actionCaptcha);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const postMenuChange = function(e) {
|
|
|
|
const postContainer = this.closest(isCatalog ? '.catalog-tile': '.post-container');
|
|
|
|
const postDataset = postContainer.dataset
|
|
|
|
const filterType = this.value;
|
|
|
|
const hiding = !postContainer.classList.contains('hidden');
|
|
|
|
this.value = '';
|
|
|
|
let filterData;
|
|
|
|
switch (filterType) {
|
|
|
|
case 'single':
|
|
|
|
filterData = `${postDataset.board}-${postDataset.postId}`;
|
|
|
|
break;
|
|
|
|
case 'fid':
|
|
|
|
filterData = postDataset.userId;
|
|
|
|
break;
|
|
|
|
case 'fname':
|
|
|
|
filterData = postDataset.name;
|
|
|
|
break;
|
|
|
|
case 'ftrip':
|
|
|
|
filterData = postDataset.tripcode;
|
|
|
|
break;
|
|
|
|
case 'fsub':
|
|
|
|
filterData = postDataset.subject;
|
|
|
|
break;
|
|
|
|
case 'moderate':
|
|
|
|
return moderatePost(postContainer);
|
|
|
|
case 'watch':
|
|
|
|
const postMessage = postContainer.querySelector('.post-message');
|
|
|
|
const watcherSubject = (postDataset.subject || (postMessage && postMessage.textContent) || "No subject").substring(0, 25);
|
|
|
|
threadWatcher.add(postDataset.board, postDataset.postId, { subject: watcherSubject, unread: 0, updatedDate: new Date() });
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
toggleFilter(filterType, filterData, hiding);
|
|
|
|
};
|
|
|
|
|
|
|
|
for (let menu of document.getElementsByClassName('postmenu')) {
|
|
|
|
menu.value = '';
|
|
|
|
menu.addEventListener('change', postMenuChange, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
const getHiddenElems = () => {
|
|
|
|
let posts = [];
|
|
|
|
for (let elem of single) {
|
|
|
|
posts = posts.concat(getPostsByFilter('single', elem));
|
|
|
|
}
|
|
|
|
for (let id of fid) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fid', id));
|
|
|
|
}
|
|
|
|
for (let name of fname) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fname', name));
|
|
|
|
}
|
|
|
|
for (let subject of fsub) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fsub', subject));
|
|
|
|
}
|
|
|
|
for (let message of fmsg) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fmsg', message));
|
|
|
|
}
|
|
|
|
for (let tripcode of ftrip) {
|
|
|
|
posts = posts.concat(getPostsByFilter('ftrip', tripcode));
|
|
|
|
}
|
|
|
|
for (let namer of fnamer) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fnamer', namer));
|
|
|
|
}
|
|
|
|
for (let tripcoder of ftripr) {
|
|
|
|
posts = posts.concat(getPostsByFilter('ftripr', tripcoder));
|
|
|
|
}
|
|
|
|
for (let subr of fsubr) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fsubr', subr));
|
|
|
|
}
|
|
|
|
for (let messager of fmsgr) {
|
|
|
|
posts = posts.concat(getPostsByFilter('fmsgr', messager));
|
|
|
|
}
|
|
|
|
return posts;
|
|
|
|
};
|
|
|
|
|
|
|
|
togglePostsHidden(getHiddenElems(), true);
|
|
|
|
|
|
|
|
window.addEventListener('addPost', function(e) {
|
|
|
|
const newPost = e.detail.post;
|
|
|
|
if (anyFilterMatches(newPost)) {
|
|
|
|
newPost.classList.add('hidden');
|
|
|
|
}
|
|
|
|
if (e.detail.hover) { return; }
|
|
|
|
const menu = newPost.querySelector('.postmenu');
|
|
|
|
menu.value = '';
|
|
|
|
menu.addEventListener('change', postMenuChange, false);
|
|
|
|
});
|
|
|
|
|
|
|
|
window.addEventListener('updatePostMessage', function(e) {
|
|
|
|
const newPost = e.detail.post;
|
|
|
|
if (anyFilterMatches(newPost)) {
|
|
|
|
newPost.classList.add('hidden');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
window.addEventListener('settingsReady', function(e) {
|
|
|
|
|
|
|
|
actionForm = document.getElementById('actionform');
|
|
|
|
if (actionForm) {
|
|
|
|
modalBg = document.querySelector('.modal-bg');
|
|
|
|
actionForm.firstChild.addEventListener('click', cancelModeratePost);
|
|
|
|
modalBg.addEventListener('click', cancelModeratePost, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
filtersTable = document.getElementById('advancedfilters');
|
|
|
|
updateFiltersTable();
|
|
|
|
|
|
|
|
const filtersForm = document.getElementById('filter-form');
|
|
|
|
filtersForm.addEventListener('submit', (e) => {
|
|
|
|
e.preventDefault();
|
|
|
|
const isRegex = filtersForm.elements.regex.checked;
|
|
|
|
const type = `${filtersForm.elements.type.value}${isRegex ? 'r' : ''}`;
|
|
|
|
const val = isRegex ? new RegExp(filtersForm.elements.value.value, 'i') : filtersForm.elements.value.value;
|
|
|
|
console.log('adding filter', type, val);
|
|
|
|
toggleFilter(type, val, true);
|
|
|
|
})
|
|
|
|
|
|
|
|
const filterClearButton = document.getElementById('filters-clear');
|
|
|
|
const clearFilters = () => {
|
|
|
|
single = new Set(),
|
|
|
|
fid = new Set(),
|
|
|
|
fname = new Set(),
|
|
|
|
fsub = new Set(),
|
|
|
|
fmsg = new Set(),
|
|
|
|
ftrip = new Set(),
|
|
|
|
fnamer = [],
|
|
|
|
ftripr = [],
|
|
|
|
fsubr = [],
|
|
|
|
fmsgr = [],
|
|
|
|
updateFiltersTable();
|
|
|
|
togglePostsHidden(document.querySelectorAll(`.${isCatalog ? 'catalog-tile': 'post-container' }`), false);
|
|
|
|
updateSavedFilters();
|
|
|
|
console.log('cleared hidden posts');
|
|
|
|
}
|
|
|
|
filterClearButton.addEventListener('click', clearFilters, false);
|
|
|
|
|
|
|
|
});
|