Start of more filters

still todo:
- form in setting smenu to manually input
- make sure the regex filters work
merge-requests/208/head
Thomas Lynch 4 years ago
parent 26dd43f251
commit 649a3d2e56
  1. 235
      gulp/res/js/filters.js
  2. 9
      gulp/res/js/hidefileinput.js
  3. 2
      gulp/res/js/localstorage.js
  4. 33
      gulp/res/js/pugfilters.js
  5. 8
      gulpfile.js
  6. 2
      views/includes/filters.pug
  7. 7
      views/mixins/filters.pug
  8. 14
      views/mixins/modal.pug
  9. 8
      views/mixins/post.pug

@ -1,89 +1,160 @@
const fileInput = document.getElementById('file');
if (fileInput) {
//not using display: none because we still want to show the browser prompt for a "required" file
fileInput.style.position = 'absolute';
fileInput.style.border = 'none';
fileInput.style.height = '0';
fileInput.style.width = '0';
fileInput.style.opacity = '0';
}
const getFiltersFromLocalStorage = () => {
const savedFilters = JSON.parse(localStorage.getItem('filters'));
/* i havent actually checked if the serialization overhead is worth the improvement in filter speed, i only assumed.
So if it turns out this is slower, i'd accept a PR to change it :^) */
return savedFilters.reduce((acc, filter) => {
acc[filter.type].add(filter.type.endsWith('r') ? new RegExp(filter.val) : filter.val);
return acc;
}, {
single: new Set(),
fid: new Set(),
fname: new Set(),
ftrip: new Set(),
fnamer: new Set(),
ftripr: new Set(),
});
};
let { single, fid, fname, ftrip, fnamer, ftripr } = getFiltersFromLocalStorage();
//lists separate until i come up with something better
let hidePostsList;
let { hiddenSingle, filteredId, filteredName, filteredTripcode } = JSON.parse(localStorage.getItem('filters'));
hiddenSingle = new Set(hiddenSingle);
filteredId = new Set(filteredId);
filteredName = new Set(filteredName);
filteredTripcode = new Set(filteredTripcode);
let filtersTable;
const updateFiltersTable = () => {
[...filtersTable.children].slice(2).forEach(row => row.remove());
filtersTable.insertAdjacentHTML('beforeend', filters({filterArr: JSON.parse(localStorage.getItem('filters'))}))
const closeButtons = filtersTable.querySelectorAll('.close');
for (let elem of closeButtons) {
const { type: closeType, data: closeData } = elem.dataset;
elem.addEventListener('click', () => { toggleFilter(closeType, closeData) });
}
}
const updateSavedFilters = () => {
hidePostsList.value = [...hiddenSingle, ...filteredId, ...filteredName, ...filteredTripcode];
setLocalStorage('filters', JSON.stringify({
hiddenSingle: [...hiddenSingle],
filteredId: [...filteredId],
filteredName: [...filteredName],
filteredTripcode: [...filteredTripcode],
}));
setLocalStorage('filters', 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}))),
...([...fnamer].map(x => ({type:'fnamer', val:x.source.toString()}))),
...([...ftripr].map(x => ({type:'ftripr', val:x.source.toString()}))),
]));
updateFiltersTable();
};
const anyFilterMatches = (filteringPost) => {
const { board, postId, userId, name, tripcode } = filteringPost.dataset;
return fid.has(userId)
|| fname.has(name)
|| ftrip.has(tripcode)
// || fnamer.some(r => r.test(name))
// || ftripr.some(r => r.test(tripcode))
}
const togglePostsHidden = (posts, state) => {
for (let elem of posts) {
elem.classList[state ? 'add' : 'remove']('hidden');
if (!state && !anyFilterMatches(elem)) { //possible fix for multiple filters & unhiding conflicts
elem.classList['remove']('hidden');
} else {
elem.classList['add']('hidden');
}
}
}
};
//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 = element.getAttribute(attribute).toString();
if (regex.test(value)) {
matches.push(elem);
}
}
return matches;
};
const getPostsByFilter = (type, data) => {
let posts = [];
switch (type) {
case 'hide':
const post = document.querySelector(`[data-board="${data.board}"][data-post-id="${data.postId}"]`);
posts = post ? [post] : [];
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 'id':
posts = document.querySelectorAll(`[data-user-id="${data.userId}"]`);
case 'fname':
posts = document.querySelectorAll(`[data-name="${CSS.escape(data)}"]`);
break;
case 'name':
posts = document.querySelectorAll(`[data-name="${CSS.escape(data.name)}"]`);
case 'fnamer':
posts = getPostsByRegex('data-name', data);
break;
case 'tripcode':
posts = document.querySelectorAll(`[data-tripcode="${CSS.escape(data.tripcode)}"]`);
case 'ftrip':
posts = document.querySelectorAll(`[data-tripcode="${CSS.escape(data)}"]`);
break;
case 'ftripr':
posts = getPostsByRegex('data-tripcode', data);
break;
default:
break;
}
return [...posts]
}
return [...posts];
};
const setFilterState = (type, data, state) => {
const addOrDelete = state ? 'add' : 'delete';
switch (type) {
case 'hide':
hiddenSingle[state ? 'add' : 'delete'](`${data.board}-${data.postId}`);
case 'single':
single[addOrDelete](data);
break;
case 'id':
filteredId[state ? 'add' : 'delete'](data.userId);
case 'fid':
fid[addOrDelete](data);
break;
case 'name':
filteredName[state ? 'add' : 'delete'](data.name);
case 'fname':
fname[addOrDelete](data);
break;
case 'tripcode':
filteredTripcode[state ? 'add' : 'delete'](data.tripcode);
case 'fnamer':
fnamer[addOrDelete](data.source.toString());
break;
case 'ftrip':
ftrip[addOrDelete](data);
break;
case 'ftripr':
ftripr[addOrDelete](data.source.toString());
break;
default:
break;
}
};
const toggleFilter = (filterType, filterData, state) => {
//console.log('filtering', filterType, filterData, state);
const posts = getPostsByFilter(filterType, filterData);
if (posts.length === 0) { return; }
setFilterState(filterType, filterData, state);
togglePostsHidden(posts, state);
updateSavedFilters();
}
const postMenuChange = function(e) {
const postContainer = this.parentElement.parentElement.parentElement;
const postDataset = postContainer.dataset
const filterType = this.value;
const posts = getPostsByFilter(filterType, postContainer.dataset);
if (posts.length === 0) { return; }
const hiding = !postContainer.classList.contains('hidden');
setFilterState(filterType, postContainer.dataset, hiding);
let filterData;
switch (filterType) {
case 'single':
filterData = `${postDataset.board}-${postDataset.postId}`;
break;
case 'fid':
filterData = postDataset.userId;
break;
case 'fname':
filterData = postDataset.name;
break;
}
toggleFilter(filterType, filterData, hiding);
this.value = '';
togglePostsHidden(posts, hiding);
updateSavedFilters();
}
};
for (let menu of document.getElementsByClassName('postmenu')) {
menu.value = '';
@ -92,53 +163,59 @@ for (let menu of document.getElementsByClassName('postmenu')) {
const getHiddenElems = () => {
let posts = [];
for (let elem of hiddenSingle) {
const [board, postId] = elem.split('-');
posts = posts.concat(getPostsByFilter('hide', { board, postId }));
for (let elem of single) {
posts = posts.concat(getPostsByFilter('single', elem));
}
for (let id of filteredId) {
posts = posts.concat(getPostsByFilter('id', { userId: id }));
for (let id of fid) {
posts = posts.concat(getPostsByFilter('fid', id));
}
for (let name of filteredName) {
posts = posts.concat(getPostsByFilter('name', { name }));
for (let name of fname) {
posts = posts.concat(getPostsByFilter('fname', name));
}
for (let tripcode of filteredTripcode) {
posts = posts.concat(getPostsByFilter('tripcode', { tripcode }));
for (let tripcode of ftrip) {
posts = posts.concat(getPostsByFilter('ftrip', tripcode));
}
// for (let namer of fnamer) {
// posts = posts.concat(getPostsByFilter('fname', namer));
// }
// for (let tripcoder of ftripr) {
// posts = posts.concat(getPostsByFilter('ftrip', tripcoder));
// }
return posts;
}
};
togglePostsHidden(getHiddenElems(), true);
window.addEventListener('addPost', function(e) {
const post = e.detail.post;
const { board, postId, userId, name, tripcode } = post.dataset;
if (filteredId.has(userId)
|| filteredName.has(name)
|| filteredTripcode.has(tripcode)) {
post.classList.add('hidden');
const newPost = e.detail.post;
if (anyFilterMatches(newPost)) {
newPost.classList.add('hidden');
}
const menu = post.querySelector('.postmenu');
const menu = newPost.querySelector('.postmenu');
menu.value = '';
menu.addEventListener('change', postMenuChange, false);
});
window.addEventListener('settingsReady', function(e) {
hidePostsList = document.getElementById('hiddenpostslist-setting');
hidePostsList.value = [...hiddenSingle, ...filteredId, ...filteredName, ...filteredTripcode];
const hidePostsListClearButton = document.getElementById('hiddenpostslist-clear');
const clearhidePostsList = () => {
togglePostsHidden(getHiddenElems(), false);
hidePostsList.value = '';
hiddenSingle = new Set();
filteredId = new Set();
filteredName = new Set();
filteredTripcode = new Set();
filtersTable = document.getElementById('advancedfilters');
updateFiltersTable();
const filterClearButton = document.getElementById('filters-clear');
const clearFilters = () => {
single = new Set(),
fid = new Set(),
fname = new Set(),
ftrip = new Set(),
fnamer = new Set(),
ftripr = new Set(),
updateFiltersTable();
togglePostsHidden(document.querySelectorAll('.post-container'), false);
updateSavedFilters();
console.log('cleared hidden posts');
}
hidePostsListClearButton.addEventListener('click', clearhidePostsList, false);
filterClearButton.addEventListener('click', clearFilters, false);
});

@ -0,0 +1,9 @@
const fileInput = document.getElementById('file');
if (fileInput) {
//not using display: none because we still want to show the browser prompt for a "required" file
fileInput.style.position = 'absolute';
fileInput.style.border = 'none';
fileInput.style.height = '0';
fileInput.style.width = '0';
fileInput.style.opacity = '0';
}

@ -48,7 +48,7 @@ setDefaultLocalStorage('yous-setting', settings.showYous);
setDefaultLocalStorage('dragtop', null);
setDefaultLocalStorage('dragleft', null);
setDefaultLocalStorage('filters', '{"hiddenSingle":[],"filteredId":[],"filteredName":[],"filteredTripcode":[]}');
setDefaultLocalStorage('filters', '[]');
setDefaultLocalStorage('yous', '[]');
setDefaultLocalStorage('name', '');
setDefaultLocalStorage('theme', 'default');

@ -0,0 +1,33 @@
function pug_attr(t,e,n,r){if(!1===e||null==e||!e&&("class"===t||"style"===t))return"";if(!0===e)return" "+(r?t:t+'="'+t+'"');var f=typeof e;return"object"!==f&&"function"!==f||"function"!=typeof e.toJSON||(e=e.toJSON()),"string"==typeof e||(e=JSON.stringify(e),n||-1===e.indexOf('"'))?(n&&(e=pug_escape(e))," "+t+'="'+e+'"'):" "+t+"='"+e.replace(/'/g,"'")+"'"}
function pug_escape(e){var a=""+e,t=pug_match_html.exec(a);if(!t)return e;var r,c,n,s="";for(r=t.index,c=0;r<a.length;r++){switch(a.charCodeAt(r)){case 34:n="&quot;";break;case 38:n="&amp;";break;case 60:n="&lt;";break;case 62:n="&gt;";break;default:continue}c!==r&&(s+=a.substring(c,r)),c=r+1,s+=n}return c!==r?s+a.substring(c,r):s}
var pug_match_html=/["&<>]/;function filters(locals) {var pug_html = "", pug_mixins = {}, pug_interp;;
var locals_for_with = (locals || {});
(function (filterArr) {
pug_mixins["filters"] = pug_interp = function(filterArr){
var block = (this && this.block), attributes = (this && this.attributes) || {};
const filterTypeMap = { single: 'single', fid: 'ID', fname: 'name', ftrip: 'tripcode', fnamer: 'name regex', ftripr: 'tripcode regex' }
// iterate filterArr
;(function(){
var $$obj = filterArr;
if ('number' == typeof $$obj.length) {
for (var pug_index0 = 0, $$l = $$obj.length; pug_index0 < $$l; pug_index0++) {
var filter = $$obj[pug_index0];
pug_html = pug_html + "\u003Ctr\u003E\u003Ctd\u003E" + (pug_escape(null == (pug_interp = filterTypeMap[filter.type]) ? "" : pug_interp)) + "\u003C\u002Ftd\u003E\u003Ctd\u003E" + (pug_escape(null == (pug_interp = filter.val.toString()) ? "" : pug_interp)) + "\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Ca" + (" class=\"close\""+pug_attr("data-type", filter.type, true, false)+pug_attr("data-data", filter.val, true, false)) + "\u003EX\u003C\u002Fa\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E";
}
} else {
var $$l = 0;
for (var pug_index0 in $$obj) {
$$l++;
var filter = $$obj[pug_index0];
pug_html = pug_html + "\u003Ctr\u003E\u003Ctd\u003E" + (pug_escape(null == (pug_interp = filterTypeMap[filter.type]) ? "" : pug_interp)) + "\u003C\u002Ftd\u003E\u003Ctd\u003E" + (pug_escape(null == (pug_interp = filter.val.toString()) ? "" : pug_interp)) + "\u003C\u002Ftd\u003E\u003Ctd\u003E\u003Ca" + (" class=\"close\""+pug_attr("data-type", filter.type, true, false)+pug_attr("data-data", filter.val, true, false)) + "\u003EX\u003C\u002Fa\u003E\u003C\u002Ftd\u003E\u003C\u002Ftr\u003E";
}
}
}).call(this);
};
pug_mixins["filters"](filterArr);
}.call(this, "filterArr" in locals_for_with ?
locals_for_with.filterArr :
typeof filterArr !== 'undefined' ? filterArr : undefined));
;;return pug_html;}

@ -263,6 +263,7 @@ const settings = ${JSON.stringify(configs.frontendScriptDefault)};
fs.writeFileSync('gulp/res/js/post.js', pug.compileFileClient(`${paths.pug.src}/includes/post.pug`, { compileDebug: false, debug: false, name: 'post' }));
fs.writeFileSync('gulp/res/js/modal.js', pug.compileFileClient(`${paths.pug.src}/includes/modal.pug`, { compileDebug: false, debug: false, name: 'modal' }));
fs.writeFileSync('gulp/res/js/uploaditem.js', pug.compileFileClient(`${paths.pug.src}/includes/uploaditem.pug`, { compileDebug: false, debug: false, name: 'uploaditem' }));
fs.writeFileSync('gulp/res/js/pugfilters.js', pug.compileFileClient(`${paths.pug.src}/includes/filters.pug`, { compileDebug: false, debug: false, name: 'filters' }));
fs.writeFileSync('gulp/res/js/captchaformsection.js', pug.compileFileClient(`${paths.pug.src}/includes/captchaformsection.pug`, { compileDebug: false, debug: false, name: 'captchaformsection' }));
fs.symlinkSync(__dirname+'/node_modules/socket.io-client/dist/socket.io.slim.js', __dirname+'/gulp/res/js/socket.io.js', 'file');
} catch (e) {
@ -275,12 +276,14 @@ const settings = ${JSON.stringify(configs.frontendScriptDefault)};
`${paths.scripts.src}/locals.js`,
`${paths.scripts.src}/localstorage.js`,
`${paths.scripts.src}/modal.js`,
`${paths.scripts.src}/pugfilters.js`,
`${paths.scripts.src}/post.js`,
`${paths.scripts.src}/settings.js`,
`${paths.scripts.src}/live.js`,
`${paths.scripts.src}/captcha.js`,
`${paths.scripts.src}/forms.js`,
`${paths.scripts.src}/*.js`,
`!${paths.scripts.src}/hidefileinput.js`,
`!${paths.scripts.src}/dragable.js`,
`!${paths.scripts.src}/filters.js`,
`!${paths.scripts.src}/yous.js`,
@ -290,9 +293,10 @@ const settings = ${JSON.stringify(configs.frontendScriptDefault)};
`!${paths.scripts.src}/timezone.js`,
])
.pipe(concat('all.js'))
.pipe(uglify({compress:false}))
// .pipe(uglify({compress:false}))
.pipe(gulp.dest(paths.scripts.dest));
return gulp.src([
`${paths.scripts.src}/hidefileinput.js`,
`${paths.scripts.src}/dragable.js`,
`${paths.scripts.src}/yous.js`,
`${paths.scripts.src}/filters.js`,
@ -300,7 +304,7 @@ const settings = ${JSON.stringify(configs.frontendScriptDefault)};
`${paths.scripts.src}/time.js`,
])
.pipe(concat('render.js'))
.pipe(uglify({compress:false}))
// .pipe(uglify({compress:false}))
.pipe(gulp.dest(paths.scripts.dest));
}

@ -0,0 +1,2 @@
include ../mixins/filters.pug
+filters(filterArr)

@ -0,0 +1,7 @@
mixin filters(filterArr)
- const filterTypeMap = { single: 'single', fid: 'ID', fname: 'name', ftrip: 'tripcode', fnamer: 'name regex', ftripr: 'tripcode regex' }
each filter in filterArr
tr
td #{filterTypeMap[filter.type]}
td #{filter.val.toString()}
td: a.close(data-type=filter.type data-data=filter.val) X

@ -109,10 +109,6 @@ mixin modal(data)
.label (You)s
input.mr-1#youslist-setting(type='text' readonly)
input#youslist-clear(type='button' value='Clear')
.row
.label Hidden posts
input.mr-1#hiddenpostslist-setting(type='text' readonly)
input#hiddenpostslist-clear(type='button' value='Clear')
.row
.label Cache
input.mr-1#hovercachelist-setting(type='text' readonly)
@ -142,3 +138,13 @@ mixin modal(data)
.row
.label Custom CSS
textarea#customcss-setting(rows=7)
.row
.table-container.text-center
table
tbody#advancedfilters
tr
th(colspan=3) Filters
tr
th Type
th Value
th: input.right#filters-clear(type='button' value='Clear')

@ -54,13 +54,13 @@ mixin post(post, truncate, manage=false, globalmanage=false, ban=false)
span.noselect: a(href=`${postURL}#postform`) [Reply]
|
select.jsonly.postmenu
option(value='hide') Hide
option(value='single') Hide
if post.userId
option(value='id') Filter ID
option(value='fid') Filter ID
if post.name
option(value='name') Filter Name
option(value='fname') Filter Name
if post.tripcode
option(value='tripcode') Filter Tripcode
option(value='ftrip') Filter Tripcode
.post-data
if post.files.length > 0
.post-files(class=(post.files.length > 1 ? 'fn' : ''))

Loading…
Cancel
Save