Convert clientside image filtering scripts to TypeScript

This commit is contained in:
Luna D 2024-06-06 20:44:41 -04:00 committed by Liam
parent 19eb5be999
commit bbc1879a1e
3 changed files with 111 additions and 78 deletions

View file

@ -1,76 +0,0 @@
/**
* Client-side image filtering/spoilering.
*/
import { $$, escapeHtml } from './utils/dom';
import { setupInteractions } from './interactions';
import { showThumb, showBlock, spoilerThumb, spoilerBlock, hideThumb } from './utils/image';
import { getHiddenTags, getSpoileredTags, imageHitsTags, imageHitsComplex, displayTags } from './utils/tag';
function runFilter(img, test, runCallback) {
if (!test || test.length === 0) return false;
runCallback(img, test);
// I don't like this.
window.booru.imagesWithDownvotingDisabled.push(img.dataset.imageId);
return true;
}
// ---
function filterThumbSimple(img, tagsHit) { hideThumb(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `[HIDDEN] ${displayTags(tagsHit)}`); }
function spoilerThumbSimple(img, tagsHit) { spoilerThumb(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, displayTags(tagsHit)); }
function filterThumbComplex(img) { hideThumb(img, window.booru.hiddenTag, '[HIDDEN] <i>(Complex Filter)</i>'); }
function spoilerThumbComplex(img) { spoilerThumb(img, window.booru.hiddenTag, '<i>(Complex Filter)</i>'); }
function filterBlockSimple(img, tagsHit) { spoilerBlock(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is hidden by `); }
function spoilerBlockSimple(img, tagsHit) { spoilerBlock(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is spoilered by `); }
function filterBlockComplex(img) { spoilerBlock(img, window.booru.hiddenTag, 'This image was hidden by a complex tag expression in '); }
function spoilerBlockComplex(img) { spoilerBlock(img, window.booru.hiddenTag, 'This image was spoilered by a complex tag expression in '); }
// ---
function thumbTagFilter(tags, img) { return runFilter(img, imageHitsTags(img, tags), filterThumbSimple); }
function thumbComplexFilter(complex, img) { return runFilter(img, imageHitsComplex(img, complex), filterThumbComplex); }
function thumbTagSpoiler(tags, img) { return runFilter(img, imageHitsTags(img, tags), spoilerThumbSimple); }
function thumbComplexSpoiler(complex, img) { return runFilter(img, imageHitsComplex(img, complex), spoilerThumbComplex); }
function blockTagFilter(tags, img) { return runFilter(img, imageHitsTags(img, tags), filterBlockSimple); }
function blockComplexFilter(complex, img) { return runFilter(img, imageHitsComplex(img, complex), filterBlockComplex); }
function blockTagSpoiler(tags, img) { return runFilter(img, imageHitsTags(img, tags), spoilerBlockSimple); }
function blockComplexSpoiler(complex, img) { return runFilter(img, imageHitsComplex(img, complex), spoilerBlockComplex); }
// ---
function filterNode(node = document) {
const hiddenTags = getHiddenTags(), spoileredTags = getSpoileredTags();
const { hiddenFilter, spoileredFilter } = window.booru;
// Image thumb boxes with vote and fave buttons on them
$$('.image-container', node)
.filter(img => !thumbTagFilter(hiddenTags, img))
.filter(img => !thumbComplexFilter(hiddenFilter, img))
.filter(img => !thumbTagSpoiler(spoileredTags, img))
.filter(img => !thumbComplexSpoiler(spoileredFilter, img))
.forEach(img => showThumb(img));
// Individual image pages and images in posts/comments
$$('.image-show-container', node)
.filter(img => !blockTagFilter(hiddenTags, img))
.filter(img => !blockComplexFilter(hiddenFilter, img))
.filter(img => !blockTagSpoiler(spoileredTags, img))
.filter(img => !blockComplexSpoiler(spoileredFilter, img))
.forEach(img => showBlock(img));
}
function initImagesClientside() {
window.booru.imagesWithDownvotingDisabled = [];
// This fills the imagesWithDownvotingDisabled array
filterNode(document);
// Once the array is populated, we can initialize interactions
setupInteractions();
}
export { initImagesClientside, filterNode };

View file

@ -0,0 +1,109 @@
/**
* Client-side image filtering/spoilering.
*/
import { $$, escapeHtml } from './utils/dom';
import { setupInteractions } from './interactions';
import { showThumb, showBlock, spoilerThumb, spoilerBlock, hideThumb } from './utils/image';
import { TagData, getHiddenTags, getSpoileredTags, imageHitsTags, imageHitsComplex, displayTags } from './utils/tag';
import { AstMatcher } from './query/types';
type CallbackType = 'tags' | 'complex';
type RunCallback = (img: HTMLDivElement, tags: TagData[], type: CallbackType) => void;
function run(
img: HTMLDivElement,
tags: TagData[],
complex: AstMatcher,
callback: RunCallback,
): boolean {
const hit = (() => {
// Check tags array first to provide more precise filter explanations
const hitTags = imageHitsTags(img, tags);
if (hitTags.length !== 0) {
callback(img, hitTags, 'tags');
return true;
}
// No tags matched, try complex filter AST
const hitComplex = imageHitsComplex(img, complex);
if (hitComplex) {
callback(img, hitTags, 'complex');
return true;
}
// Nothing matched at all, image can be shown
return false;
})();
if (hit && img.dataset.imageId) {
// Disallow negative interaction on image which is not visible
window.booru.imagesWithDownvotingDisabled.push(img.dataset.imageId);
}
return hit;
}
function bannerImage(tagsHit: TagData[]) {
if (tagsHit.length > 0) {
return tagsHit[0].spoiler_image_uri || window.booru.hiddenTag;
}
return window.booru.hiddenTag;
}
// TODO: this approach is not suitable for translations because it depends on
// markup embedded in the page adjacent to this text
/* eslint-disable indent */
function hideThumbTyped(img: HTMLDivElement, tagsHit: TagData[], type: CallbackType) {
const bannerText = type === 'tags' ? `[HIDDEN] ${displayTags(tagsHit)}`
: '[HIDDEN] <i>(Complex Filter)</i>';
hideThumb(img, bannerImage(tagsHit), bannerText);
}
function spoilerThumbTyped(img: HTMLDivElement, tagsHit: TagData[], type: CallbackType) {
const bannerText = type === 'tags' ? displayTags(tagsHit)
: '<i>(Complex Filter)</i>';
spoilerThumb(img, bannerImage(tagsHit), bannerText);
}
function hideBlockTyped(img: HTMLDivElement, tagsHit: TagData[], type: CallbackType) {
const bannerText = type === 'tags' ? `This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is hidden by `
: 'This image was hidden by a complex tag expression in ';
spoilerBlock(img, bannerImage(tagsHit), bannerText);
}
function spoilerBlockTyped(img: HTMLDivElement, tagsHit: TagData[], type: CallbackType) {
const bannerText = type === 'tags' ? `This image is tagged <code>${escapeHtml(tagsHit[0].name)}</code>, which is spoilered by `
: 'This image was spoilered by a complex tag expression in ';
spoilerBlock(img, bannerImage(tagsHit), bannerText);
}
/* eslint-enable indent */
export function filterNode(node: Pick<Document, 'querySelectorAll'>) {
const hiddenTags = getHiddenTags(), spoileredTags = getSpoileredTags();
const { hiddenFilter, spoileredFilter } = window.booru;
// Image thumb boxes with vote and fave buttons on them
$$<HTMLDivElement>('.image-container', node)
.filter(img => !run(img, hiddenTags, hiddenFilter, hideThumbTyped))
.filter(img => !run(img, spoileredTags, spoileredFilter, spoilerThumbTyped))
.forEach(img => showThumb(img));
// Individual image pages and images in posts/comments
$$<HTMLDivElement>('.image-show-container', node)
.filter(img => !run(img, hiddenTags, hiddenFilter, hideBlockTyped))
.filter(img => !run(img, spoileredTags, spoileredFilter, spoilerBlockTyped))
.forEach(img => showBlock(img));
}
export function initImagesClientside() {
window.booru.imagesWithDownvotingDisabled = [];
// This fills the imagesWithDownvotingDisabled array
filterNode(document);
// Once the array is populated, we can initialize interactions
setupInteractions();
}

View file

@ -28,13 +28,13 @@ function sortTags(hidden: boolean, a: TagData, b: TagData): number {
return a.spoiler_image_uri ? -1 : 1;
}
export function getHiddenTags() {
export function getHiddenTags(): TagData[] {
return unique(window.booru.hiddenTagList)
.map(tagId => getTag(tagId))
.sort(sortTags.bind(null, true));
}
export function getSpoileredTags() {
export function getSpoileredTags(): TagData[] {
if (window.booru.spoilerType === 'off') return [];
return unique(window.booru.spoileredTagList)