-
-
Notifications
You must be signed in to change notification settings - Fork 56
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge remote-tracking branch 'upstream/master'
- Loading branch information
Showing
4 changed files
with
273 additions
and
78 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
import { filterNode, initImagesClientside } from '../imagesclientside'; | ||
import { parseSearch } from '../match_query'; | ||
import { matchNone } from '../query/boolean'; | ||
import { assertNotNull } from '../utils/assert'; | ||
import { $ } from '../utils/dom'; | ||
|
||
describe('filterNode', () => { | ||
beforeEach(() => { | ||
window.booru.hiddenTagList = []; | ||
window.booru.spoileredTagList = []; | ||
window.booru.ignoredTagList = []; | ||
window.booru.imagesWithDownvotingDisabled = []; | ||
|
||
window.booru.hiddenFilter = matchNone(); | ||
window.booru.spoileredFilter = matchNone(); | ||
}); | ||
|
||
function makeMediaContainer() { | ||
const element = document.createElement('div'); | ||
element.innerHTML = ` | ||
<div class="image-container" data-image-id="1" data-image-tags="[1]"> | ||
<div class="js-spoiler-info-overlay"></div> | ||
<picture><img src=""/></picture> | ||
</div> | ||
`; | ||
return [ element, assertNotNull($<HTMLDivElement>('.js-spoiler-info-overlay', element)) ]; | ||
} | ||
|
||
it('should show image media boxes not matching any filter', () => { | ||
const [ container, spoilerOverlay ] = makeMediaContainer(); | ||
|
||
filterNode(container); | ||
expect(spoilerOverlay).not.toContainHTML('(Complex Filter)'); | ||
expect(spoilerOverlay).not.toContainHTML('(unknown tag)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).not.toContain('1'); | ||
}); | ||
|
||
it('should spoiler media boxes spoilered by a tag filter', () => { | ||
const [ container, spoilerOverlay ] = makeMediaContainer(); | ||
window.booru.spoileredTagList = [1]; | ||
|
||
filterNode(container); | ||
expect(spoilerOverlay).toContainHTML('(unknown tag)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
it('should spoiler media boxes spoilered by a complex filter', () => { | ||
const [ container, spoilerOverlay ] = makeMediaContainer(); | ||
window.booru.spoileredFilter = parseSearch('id:1'); | ||
|
||
filterNode(container); | ||
expect(spoilerOverlay).toContainHTML('(Complex Filter)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
it('should hide media boxes hidden by a tag filter', () => { | ||
const [ container, spoilerOverlay ] = makeMediaContainer(); | ||
window.booru.hiddenTagList = [1]; | ||
|
||
filterNode(container); | ||
expect(spoilerOverlay).toContainHTML('[HIDDEN]'); | ||
expect(spoilerOverlay).toContainHTML('(unknown tag)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
it('should hide media boxes hidden by a complex filter', () => { | ||
const [ container, spoilerOverlay ] = makeMediaContainer(); | ||
window.booru.hiddenFilter = parseSearch('id:1'); | ||
|
||
filterNode(container); | ||
expect(spoilerOverlay).toContainHTML('[HIDDEN]'); | ||
expect(spoilerOverlay).toContainHTML('(Complex Filter)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
function makeImageBlock(): HTMLElement[] { | ||
const element = document.createElement('div'); | ||
element.innerHTML = ` | ||
<div class="image-show-container" data-image-id="1" data-image-tags="[1]"> | ||
<div class="image-filtered hidden"> | ||
<img src=""/> | ||
<span class="filter-explanation"></span> | ||
</div> | ||
<div class="image-show hidden"> | ||
<picture><img src=""/></picture> | ||
</div> | ||
</div> | ||
`; | ||
return [ | ||
element, | ||
assertNotNull($<HTMLDivElement>('.image-filtered', element)), | ||
assertNotNull($<HTMLDivElement>('.image-show', element)), | ||
assertNotNull($<HTMLSpanElement>('.filter-explanation', element)) | ||
]; | ||
} | ||
|
||
it('should show image blocks not matching any filter', () => { | ||
const [ container, imageFiltered, imageShow ] = makeImageBlock(); | ||
|
||
filterNode(container); | ||
expect(imageFiltered).toHaveClass('hidden'); | ||
expect(imageShow).not.toHaveClass('hidden'); | ||
expect(window.booru.imagesWithDownvotingDisabled).not.toContain('1'); | ||
}); | ||
|
||
it('should spoiler image blocks spoilered by a tag filter', () => { | ||
const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); | ||
window.booru.spoileredTagList = [1]; | ||
|
||
filterNode(container); | ||
expect(imageFiltered).not.toHaveClass('hidden'); | ||
expect(imageShow).toHaveClass('hidden'); | ||
expect(filterExplanation).toContainHTML('spoilered by'); | ||
expect(filterExplanation).toContainHTML('(unknown tag)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
it('should spoiler image blocks spoilered by a complex filter', () => { | ||
const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); | ||
window.booru.spoileredFilter = parseSearch('id:1'); | ||
|
||
filterNode(container); | ||
expect(imageFiltered).not.toHaveClass('hidden'); | ||
expect(imageShow).toHaveClass('hidden'); | ||
expect(filterExplanation).toContainHTML('spoilered by'); | ||
expect(filterExplanation).toContainHTML('complex tag expression'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
it('should hide image blocks hidden by a tag filter', () => { | ||
const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); | ||
window.booru.hiddenTagList = [1]; | ||
|
||
filterNode(container); | ||
expect(imageFiltered).not.toHaveClass('hidden'); | ||
expect(imageShow).toHaveClass('hidden'); | ||
expect(filterExplanation).toContainHTML('hidden by'); | ||
expect(filterExplanation).toContainHTML('(unknown tag)'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
it('should hide image blocks hidden by a complex filter', () => { | ||
const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); | ||
window.booru.hiddenFilter = parseSearch('id:1'); | ||
|
||
filterNode(container); | ||
expect(imageFiltered).not.toHaveClass('hidden'); | ||
expect(imageShow).toHaveClass('hidden'); | ||
expect(filterExplanation).toContainHTML('hidden by'); | ||
expect(filterExplanation).toContainHTML('complex tag expression'); | ||
expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); | ||
}); | ||
|
||
}); | ||
|
||
describe('initImagesClientside', () => { | ||
it('should initialize the imagesWithDownvotingDisabled array', () => { | ||
initImagesClientside(); | ||
expect(window.booru.imagesWithDownvotingDisabled).toEqual([]); | ||
}); | ||
}); |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
/** | ||
* Client-side image filtering/spoilering. | ||
*/ | ||
|
||
import { assertNotUndefined } from './utils/assert'; | ||
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, | ||
runCallback: RunCallback | ||
): boolean { | ||
const hit = (() => { | ||
// Check tags array first to provide more precise filter explanations | ||
const hitTags = imageHitsTags(img, tags); | ||
if (hitTags.length !== 0) { | ||
runCallback(img, hitTags, 'tags'); | ||
return true; | ||
} | ||
|
||
// No tags matched, try complex filter AST | ||
const hitComplex = imageHitsComplex(img, complex); | ||
if (hitComplex) { | ||
runCallback(img, hitTags, 'complex'); | ||
return true; | ||
} | ||
|
||
// Nothing matched at all, image can be shown | ||
return false; | ||
})(); | ||
|
||
if (hit) { | ||
// Disallow negative interaction on image which is not visible | ||
window.booru.imagesWithDownvotingDisabled.push(assertNotUndefined(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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters