diff --git a/assets/js/booru.js b/assets/js/booru.js index e6884dd0..62c7aeea 100644 --- a/assets/js/booru.js +++ b/assets/js/booru.js @@ -1,90 +1,10 @@ import { $ } from './utils/dom'; -import parseSearch from './match_query'; -import store from './utils/store'; - -/* Store a tag locally, marking the retrieval time */ -function persistTag(tagData) { - tagData.fetchedAt = new Date().getTime() / 1000; - store.set(`bor_tags_${tagData.id}`, tagData); -} - -function isStale(tag) { - const now = new Date().getTime() / 1000; - return tag.fetchedAt === null || tag.fetchedAt < (now - 604800); -} - -function clearTags() { - Object.keys(localStorage).forEach(key => { - if (key.substring(0, 9) === 'bor_tags_') { - store.remove(key); - } - }); -} - -/* Returns a single tag, or a dummy tag object if we don't know about it yet */ -function getTag(tagId) { - const stored = store.get(`bor_tags_${tagId}`); - - if (stored) { - return stored; - } - - return { - id: tagId, - name: '(unknown tag)', - images: 0, - spoiler_image_uri: null, - }; -} - -/* Fetches lots of tags in batches and stores them locally */ -function fetchAndPersistTags(tagIds) { - if (!tagIds.length) return; - - const ids = tagIds.slice(0, 40); - const remaining = tagIds.slice(41); - - fetch(`/tags/fetch?ids[]=${ids.join('&ids[]=')}`) - .then(response => response.json()) - .then(data => data.tags.forEach(tag => persistTag(tag))) - .then(() => fetchAndPersistTags(remaining)); -} - -/* Figure out which tags in the list we don't know about */ -function fetchNewOrStaleTags(tagIds) { - const fetchIds = []; - - tagIds.forEach(t => { - const stored = store.get(`bor_tags_${t}`); - if (!stored || isStale(stored)) { - fetchIds.push(t); - } - }); - - fetchAndPersistTags(fetchIds); -} - -function verifyTagsVersion(latest) { - if (store.get('bor_tags_version') !== latest) { - clearTags(); - store.set('bor_tags_version', latest); - } -} - -function initializeFilters() { - const tags = window.booru.spoileredTagList - .concat(window.booru.hiddenTagList) - .filter((a, b, c) => c.indexOf(a) === b); - - verifyTagsVersion(window.booru.tagsVersion); - fetchNewOrStaleTags(tags); -} function unmarshal(data) { try { return JSON.parse(data); } catch (_) { return data; } } -function loadBooruData() { +export function loadBooruData() { const booruData = document.querySelector('.js-datastore').dataset; // Assign all elements to booru because lazy @@ -92,22 +12,8 @@ function loadBooruData() { window.booru[prop] = unmarshal(booruData[prop]); } - window.booru.hiddenFilter = parseSearch(window.booru.hiddenFilter); - window.booru.spoileredFilter = parseSearch(window.booru.spoileredFilter); - - // Fetch tag metadata and set up filtering - initializeFilters(); - // CSRF window.booru.csrfToken = $('meta[name="csrf-token"]').content; } -function BooruOnRails() { - this.apiEndpoint = '/api/v2/'; - this.hiddenTag = '/images/tagblocked.svg'; - this.tagsVersion = 5; -} - -window.booru = new BooruOnRails(); - -export { getTag, loadBooruData }; +window.booru = {}; diff --git a/assets/js/boorujs.js b/assets/js/boorujs.js index 666c3005..3aa1686c 100644 --- a/assets/js/boorujs.js +++ b/assets/js/boorujs.js @@ -6,7 +6,6 @@ import { $, $$ } from './utils/dom'; import { fetchHtml, handleError } from './utils/requests'; -import { showBlock } from './utils/image'; import { addTag } from './tagsinput'; // Event types and any qualifying conditions - return true to not run action @@ -72,10 +71,7 @@ const actions = { .catch(() => newTab.textContent = 'Error!'); } - }, - - unfilter(data) { showBlock(data.el.closest('.image-show-container')); }, - + } }; // Use this function to apply a callback to elements matching the selectors diff --git a/assets/js/comment.js b/assets/js/comment.js index eb52a92d..dcd8635a 100644 --- a/assets/js/comment.js +++ b/assets/js/comment.js @@ -4,7 +4,6 @@ import { $ } from './utils/dom'; import { showOwnedComments } from './communications/comment'; -import { filterNode } from './imagesclientside'; import { fetchHtml } from './utils/requests'; function handleError(response) { @@ -94,9 +93,6 @@ function insertParentPost(data, clickedLink, fullComment) { // Add class active_reply_link to the clicked link clickedLink.classList.add('active_reply_link'); - // Filter images (if any) in the loaded comment - filterNode(fullComment.previousSibling); - } function clearParentPost(clickedLink, fullComment) { @@ -125,9 +121,6 @@ function displayComments(container, commentsHtml) { // Execute timeago on comments window.booru.timeAgo(document.getElementsByTagName('time')); - // Filter images in the comments - filterNode(container); - // Show options on own comments showOwnedComments(); @@ -171,7 +164,6 @@ function setupComments() { loadComments(false); } else { - filterNode(comments); showOwnedComments(); } } diff --git a/assets/js/imagesclientside.js b/assets/js/imagesclientside.js deleted file mode 100644 index 7dc3fb31..00000000 --- a/assets/js/imagesclientside.js +++ /dev/null @@ -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] (Complex Filter)'); } -function spoilerThumbComplex(img) { spoilerThumb(img, window.booru.hiddenTag, '(Complex Filter)'); } - -function filterBlockSimple(img, tagsHit) { spoilerBlock(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `This image is tagged ${escapeHtml(tagsHit[0].name)}, which is hidden by `); } -function spoilerBlockSimple(img, tagsHit) { spoilerBlock(img, tagsHit[0].spoiler_image_uri || window.booru.hiddenTag, `This image is tagged ${escapeHtml(tagsHit[0].name)}, 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 }; diff --git a/assets/js/match_query.js b/assets/js/match_query.js deleted file mode 100644 index 1d3d9c93..00000000 --- a/assets/js/match_query.js +++ /dev/null @@ -1,871 +0,0 @@ -/** -* booru.match_query: A port and modification of the search_parser library for -* performing client-side filtering. -*/ - -const tokenList = [ - ['fuzz', /^~(?:\d+(\.\d+)?|\.\d+)/], - ['boost', /^\^[\-\+]?\d+(\.\d+)?/], - ['quoted_lit', /^\s*"(?:(?:[^"]|\\")+)"/], - ['lparen', /^\s*\(\s*/], - ['rparen', /^\s*\)\s*/], - ['and_op', /^\s*(?:\&\&|AND)\s+/], - ['and_op', /^\s*,\s*/], - ['or_op', /^\s*(?:\|\||OR)\s+/], - ['not_op', /^\s*NOT(?:\s+|(?=\())/], - ['not_op', /^\s*[\!\-]\s*/], - ['space', /^\s+/], - ['word', /^(?:[^\s,\(\)\^~]|\\[\s,\(\)\^~])+/], - ['word', /^(?:[^\s,\(\)]|\\[\s,\(\)])+/] - ], - numberFields = ['id', 'width', 'height', 'aspect_ratio', - 'comment_count', 'score', 'upvotes', 'downvotes', - 'faves'], - dateFields = ['created_at'], - literalFields = ['tags', 'orig_sha512_hash', 'sha512_hash', - 'score', 'uploader', 'source_url', 'description'], - termSpaceToImageField = { - tags: 'data-image-tag-aliases', - score: 'data-score', - upvotes: 'data-upvotes', - downvotes: 'data-downvotes', - uploader: 'data-uploader', - // Yeah, I don't think this is reasonably supportable. - // faved_by: 'data-faved-by', - id: 'data-image-id', - width: 'data-width', - height: 'data-height', - aspect_ratio: 'data-aspect-ratio', - comment_count: 'data-comment-count', - source_url: 'data-source-url', - faves: 'data-faves', - sha512_hash: 'data-sha512', - orig_sha512_hash: 'data-orig-sha512', - created_at: 'data-created-at' - }; - - -function SearchTerm(termStr, options) { - this.term = termStr.trim(); - this.parsed = false; -} - -SearchTerm.prototype.append = function(substr) { - this.term += substr; - this.parsed = false; -}; - -SearchTerm.prototype.parseRangeField = function(field) { - let qual; - - if (numberFields.indexOf(field) !== -1) { - return [field, 'eq', 'number']; - } - - if (dateFields.indexOf(field) !== -1) { - return [field, 'eq', 'date']; - } - - qual = /^(\w+)\.([lg]te?|eq)$/.exec(field); - - if (qual) { - if (numberFields.indexOf(qual[1]) !== -1) { - return [qual[1], qual[2], 'number']; - } - - if (dateFields.indexOf(qual[1]) !== -1) { - return [qual[1], qual[2], 'date']; - } - } - - return null; -}; - -SearchTerm.prototype.parseRelativeDate = function(dateVal, qual) { - const match = /(\d+) (second|minute|hour|day|week|month|year)s? ago/.exec(dateVal); - const bounds = { - second: 1000, - minute: 60000, - hour: 3600000, - day: 86400000, - week: 604800000, - month: 2592000000, - year: 31536000000 - }; - - if (match) { - const amount = parseInt(match[1], 10); - const scale = bounds[match[2]]; - - const now = new Date().getTime(); - const bottomDate = new Date(now - (amount * scale)); - const topDate = new Date(now - ((amount - 1) * scale)); - - switch (qual) { - case 'lte': - return [bottomDate, 'lt']; - case 'gte': - return [bottomDate, 'gte']; - case 'lt': - return [bottomDate, 'lt']; - case 'gt': - return [bottomDate, 'gte']; - default: - return [[bottomDate, topDate], 'eq']; - } - } - else { - throw `Cannot parse date string: ${dateVal}`; - } -}; - -SearchTerm.prototype.parseAbsoluteDate = function(dateVal, qual) { - let parseRes = [ - /^(\d{4})/, - /^\-(\d{2})/, - /^\-(\d{2})/, - /^(?:\s+|T|t)(\d{2})/, - /^:(\d{2})/, - /^:(\d{2})/ - ], - timeZoneOffset = [0, 0], - timeData = [0, 0, 1, 0, 0, 0], - bottomDate = null, - topDate = null, - i, - match, - origDateVal = dateVal; - - match = /([\+\-])(\d{2}):(\d{2})$/.exec(dateVal); - if (match) { - timeZoneOffset[0] = parseInt(match[2], 10); - timeZoneOffset[1] = parseInt(match[3], 10); - if (match[1] === '-') { - timeZoneOffset[0] *= -1; - timeZoneOffset[1] *= -1; - } - dateVal = dateVal.substr(0, dateVal.length - 6); - } - else { - dateVal = dateVal.replace(/[Zz]$/, ''); - } - - for (i = 0; i < parseRes.length; i += 1) { - if (dateVal.length === 0) { - break; - } - - match = parseRes[i].exec(dateVal); - if (match) { - if (i === 1) { - timeData[i] = parseInt(match[1], 10) - 1; - } - else { - timeData[i] = parseInt(match[1], 10); - } - dateVal = dateVal.substr( - match[0].length, dateVal.length - match[0].length - ); - } - else { - throw `Cannot parse date string: ${origDateVal}`; - } - } - - if (dateVal.length > 0) { - throw `Cannot parse date string: ${origDateVal}`; - } - - // Apply the user-specified time zone offset. The JS Date constructor - // is very flexible here. - timeData[3] -= timeZoneOffset[0]; - timeData[4] -= timeZoneOffset[1]; - - switch (qual) { - case 'lte': - timeData[i - 1] += 1; - return [Date.UTC.apply(Date, timeData), 'lt']; - case 'gte': - return [Date.UTC.apply(Date, timeData), 'gte']; - case 'lt': - return [Date.UTC.apply(Date, timeData), 'lt']; - case 'gt': - timeData[i - 1] += 1; - return [Date.UTC.apply(Date, timeData), 'gte']; - default: - bottomDate = Date.UTC.apply(Date, timeData); - timeData[i - 1] += 1; - topDate = Date.UTC.apply(Date, timeData); - return [[bottomDate, topDate], 'eq']; - } -}; - -SearchTerm.prototype.parseDate = function(dateVal, qual) { - try { - return this.parseAbsoluteDate(dateVal, qual); - } - catch (_) { - return this.parseRelativeDate(dateVal, qual); - } -}; - -SearchTerm.prototype.parse = function(substr) { - let matchArr, - rangeParsing, - candidateTermSpace, - termCandidate; - - this.wildcardable = !this.fuzz && !/^"([^"]|\\")+"$/.test(this.term); - - if (!this.wildcardable && !this.fuzz) { - this.term = this.term.substr(1, this.term.length - 2); - } - - this.term = this._normalizeTerm(); - - // N.B.: For the purposes of this parser, boosting effects are ignored. - - // Default. - this.termSpace = 'tags'; - this.termType = 'literal'; - - matchArr = this.term.split(':'); - - if (matchArr.length > 1) { - candidateTermSpace = matchArr[0]; - termCandidate = matchArr.slice(1).join(':'); - rangeParsing = this.parseRangeField(candidateTermSpace); - - if (rangeParsing) { - this.termSpace = rangeParsing[0]; - this.termType = rangeParsing[2]; - - if (this.termType === 'date') { - rangeParsing = this.parseDate(termCandidate, rangeParsing[1]); - this.term = rangeParsing[0]; - this.compare = rangeParsing[1]; - } - else { - this.term = parseFloat(termCandidate); - this.compare = rangeParsing[1]; - } - - this.wildcardable = false; - } - else if (literalFields.indexOf(candidateTermSpace) !== -1) { - this.termType = 'literal'; - this.term = termCandidate; - this.termSpace = candidateTermSpace; - } - else if (candidateTermSpace == 'my') { - this.termType = 'my'; - this.termSpace = termCandidate; - } - } - - if (this.wildcardable) { - // Transforms wildcard match into regular expression. - // A custom NFA with caching may be more sophisticated but not - // likely to be faster. - this.term = new RegExp( - `^${ - this.term.replace(/([.+^$[\]\\(){}|-])/g, '\\$1') - .replace(/([^\\]|[^\\](?:\\\\)+)\*/g, '$1.*') - .replace(/^(?:\\\\)*\*/g, '.*') - .replace(/([^\\]|[^\\](?:\\\\)+)\?/g, '$1.?') - .replace(/^(?:\\\\)*\?/g, '.?') - }$`, 'i' - ); - } - - // Update parse status flag to indicate the new properties are ready. - this.parsed = true; -}; - -SearchTerm.prototype._normalizeTerm = function() { - if (!this.wildcardable) { - return this.term.replace('\"', '"'); - } - return this.term.replace(/\\([^\*\?])/g, '$1'); -}; - -SearchTerm.prototype.fuzzyMatch = function(targetStr) { - let targetDistance, - i, - j, - // Work vectors, representing the last three populated - // rows of the dynamic programming matrix of the iterative - // optimal string alignment calculation. - v0 = [], - v1 = [], - v2 = [], - temp; - - if (this.fuzz < 1.0) { - targetDistance = targetStr.length * (1.0 - this.fuzz); - } - else { - targetDistance = this.fuzz; - } - - targetStr = targetStr.toLowerCase(); - - for (i = 0; i <= targetStr.length; i += 1) { - v1.push(i); - } - - for (i = 0; i < this.term.length; i += 1) { - v2[0] = i; - for (j = 0; j < targetStr.length; j += 1) { - const cost = this.term[i] === targetStr[j] ? 0 : 1; - v2[j + 1] = Math.min( - // Deletion. - v1[j + 1] + 1, - // Insertion. - v2[j] + 1, - // Substitution or No Change. - v1[j] + cost - ); - if (i > 1 && j > 1 && this.term[i] === targetStr[j - 1] && - targetStr[i - 1] === targetStr[j]) { - v2[j + 1] = Math.min(v2[j], v0[j - 1] + cost); - } - } - // Rotate dem vec pointers bra. - temp = v0; - v0 = v1; - v1 = v2; - v2 = temp; - } - - return v1[targetStr.length] <= targetDistance; -}; - -SearchTerm.prototype.exactMatch = function(targetStr) { - return this.term.toLowerCase() === targetStr.toLowerCase(); -}; - -SearchTerm.prototype.wildcardMatch = function(targetStr) { - return this.term.test(targetStr); -}; - -SearchTerm.prototype.interactionMatch = function(imageID, type, interaction, interactions) { - let ret = false; - - interactions.forEach(v => { - if (v.image_id == imageID && v.interaction_type == type && (interaction == null || v.value == interaction)) { - ret = true; - - return; - } - }); - - return ret; -}; - -SearchTerm.prototype.match = function(target) { - let ret = false, - ohffs = this, - compFunc, - numbuh, - date; - - if (!this.parsed) { - this.parse(); - } - - if (this.termType === 'literal') { - // Literal matching. - if (this.fuzz) { - compFunc = this.fuzzyMatch; - } - else if (this.wildcardable) { - compFunc = this.wildcardMatch; - } - else { - compFunc = this.exactMatch; - } - - if (this.termSpace === 'tags') { - target.getAttribute('data-image-tag-aliases').split(', ').every( - str => { - if (compFunc.call(ohffs, str)) { - ret = true; - return false; - } - return true; - } - ); - } - else { - ret = compFunc.call( - this, target.getAttribute(termSpaceToImageField[this.termSpace]) - ); - } - } - else if (this.termType === 'my' && window.booru.interactions.length > 0) { - // Should work with most my:conditions except watched. - switch (this.termSpace) { - case 'faves': - ret = this.interactionMatch(target.getAttribute('data-image-id'), 'faved', null, window.booru.interactions); - - break; - case 'upvotes': - ret = this.interactionMatch(target.getAttribute('data-image-id'), 'voted', 'up', window.booru.interactions); - - break; - case 'downvotes': - ret = this.interactionMatch(target.getAttribute('data-image-id'), 'voted', 'down', window.booru.interactions); - - break; - default: - ret = false; // Other my: interactions aren't supported, return false to prevent them from triggering spoiler. - - break; - } - } - else if (this.termType === 'date') { - // Date matching. - date = (new Date( - target.getAttribute(termSpaceToImageField[this.termSpace]) - )).getTime(); - - switch (this.compare) { - // The open-left, closed-right date range specified by the - // date/time format limits the types of comparisons that are - // done compared to numeric ranges. - case 'lt': - ret = this.term > date; - break; - case 'gte': - ret = this.term <= date; - break; - default: - ret = this.term[0] <= date && this.term[1] > date; - } - } - else { - // Range matching. - numbuh = parseFloat( - target.getAttribute(termSpaceToImageField[this.termSpace]) - ); - - if (isNaN(this.term)) { - ret = false; - } - else if (this.fuzz) { - ret = this.term <= numbuh + this.fuzz && - this.term + this.fuzz >= numbuh; - } - else { - switch (this.compare) { - case 'lt': - ret = this.term > numbuh; - break; - case 'gt': - ret = this.term < numbuh; - break; - case 'lte': - ret = this.term >= numbuh; - break; - case 'gte': - ret = this.term <= numbuh; - break; - default: - ret = this.term === numbuh; - } - } - } - - return ret; -}; - -function generateLexArray(searchStr, options) { - let opQueue = [], - searchTerm = null, - boost = null, - fuzz = null, - lparenCtr = 0, - negate = false, - groupNegate = false, - tokenStack = [], - boostFuzzStr = ''; - - while (searchStr.length > 0) { - tokenList.every(tokenArr => { - let tokenName = tokenArr[0], - tokenRE = tokenArr[1], - match = tokenRE.exec(searchStr), - balanced, op; - - if (match) { - match = match[0]; - - if (Boolean(searchTerm) && ( - ['and_op', 'or_op'].indexOf(tokenName) !== -1 || - tokenName === 'rparen' && lparenCtr === 0)) { - // Set options. - searchTerm.boost = boost; - searchTerm.fuzz = fuzz; - // Push to stack. - tokenStack.push(searchTerm); - // Reset term and options data. - searchTerm = fuzz = boost = null; - boostFuzzStr = ''; - lparenCtr = 0; - - if (negate) { - tokenStack.push('not_op'); - negate = false; - } - } - - switch (tokenName) { - case 'and_op': - while (opQueue[0] === 'and_op') { - tokenStack.push(opQueue.shift()); - } - opQueue.unshift('and_op'); - break; - case 'or_op': - while (opQueue[0] === 'and_op' || opQueue[0] === 'or_op') { - tokenStack.push(opQueue.shift()); - } - opQueue.unshift('or_op'); - break; - case 'not_op': - if (searchTerm) { - // We're already inside a search term, so it does - // not apply, obv. - searchTerm.append(match); - } - else { - negate = !negate; - } - break; - case 'lparen': - if (searchTerm) { - // If we are inside the search term, do not error - // out just yet; instead, consider it as part of - // the search term, as a user convenience. - searchTerm.append(match); - lparenCtr += 1; - } - else { - opQueue.unshift('lparen'); - groupNegate = negate; - negate = false; - } - break; - case 'rparen': - if (lparenCtr > 0) { - searchTerm.append(match); - lparenCtr -= 1; - } - else { - balanced = false; - while (opQueue.length) { - op = opQueue.shift(); - if (op === 'lparen') { - balanced = true; - break; - } - tokenStack.push(op); - } - } - if (groupNegate) { - tokenStack.push('not_op'); - groupNegate = false; - } - break; - case 'fuzz': - if (searchTerm) { - // For this and boost operations, we store the - // current match so far to a temporary string in - // case this is actually inside the term. - fuzz = parseFloat(match.substr(1)); - boostFuzzStr += match; - } - else { - searchTerm = new SearchTerm(match, options); - } - break; - case 'boost': - if (searchTerm) { - boost = match.substr(1); - boostFuzzStr += match; - } - else { - searchTerm = new SearchTerm(match, options); - } - break; - case 'quoted_lit': - if (searchTerm) { - searchTerm.append(match); - } - else { - searchTerm = new SearchTerm(match, options); - } - break; - case 'word': - if (searchTerm) { - if (fuzz || boost) { - boost = fuzz = null; - searchTerm.append(boostFuzzStr); - boostFuzzStr = ''; - } - searchTerm.append(match); - } - else { - searchTerm = new SearchTerm(match, options); - } - break; - default: - // Append extra spaces within search terms. - if (searchTerm) { - searchTerm.append(match); - } - } - - // Truncate string and restart the token tests. - searchStr = searchStr.substr( - match.length, searchStr.length - match.length - ); - - // Break since we have found a match. - return false; - } - - return true; - }); - } - - // Append final tokens to the stack, starting with the search term. - if (searchTerm) { - searchTerm.boost = boost; - searchTerm.fuzz = fuzz; - tokenStack.push(searchTerm); - } - if (negate) { - tokenStack.push('not_op'); - } - - if (opQueue.indexOf('rparen') !== -1 || - opQueue.indexOf('lparen') !== -1) { - throw 'Mismatched parentheses.'; - } - - // Memory-efficient concatenation of remaining operators queue to the - // token stack. - tokenStack.push.apply(tokenStack, opQueue); - - return tokenStack; -} - -function parseTokens(lexicalArray) { - let operandStack = [], - negate, op1, op2, parsed; - lexicalArray.forEach((token, i) => { - if (token !== 'not_op') { - negate = lexicalArray[i + 1] === 'not_op'; - - if (typeof token === 'string') { - op2 = operandStack.pop(); - op1 = operandStack.pop(); - - if (typeof op1 === 'undefined' || typeof op2 === 'undefined') { - throw 'Missing operand.'; - } - - operandStack.push(new SearchAST(token, negate, op1, op2)); - } - else { - if (negate) { - operandStack.push(new SearchAST(null, true, token)); - } - else { - operandStack.push(token); - } - } - } - }); - - if (operandStack.length > 1) { - throw 'Missing operator.'; - } - - op1 = operandStack.pop(); - - if (typeof op1 === 'undefined') { - return new SearchAST(); - } - - if (isTerminal(op1)) { - return new SearchAST(null, false, op1); - } - - return op1; -} - -function parseSearch(searchStr, options) { - return parseTokens(generateLexArray(searchStr, options)); -} - -function isTerminal(operand) { - // Whether operand is a terminal SearchTerm. - return typeof operand.term !== 'undefined'; -} - -function SearchAST(op, negate, leftOperand, rightOperand) { - this.negate = Boolean(negate); - this.leftOperand = leftOperand || null; - this.op = op || null; - this.rightOperand = rightOperand || null; -} - -function combineOperands(ast1, ast2, parentAST) { - if (parentAST.op === 'and_op') { - ast1 = ast1 && ast2; - } - else { - ast1 = ast1 || ast2; - } - - if (parentAST.negate) { - return !ast1; - } - - return ast1; -} - -// Evaluation of the AST in regard to a target image -SearchAST.prototype.hitsImage = function(image) { - let treeStack = [], - // Left side node. - ast1 = this, - // Right side node. - ast2, - // Parent node of the current subtree. - parentAST; - - // Build the initial tree node traversal stack, of the "far left" side. - // The general idea is to accumulate from the bottom and make stacks - // of right-hand subtrees that themselves accumulate upward. The left - // side node, ast1, will always be a Boolean representing the left-side - // evaluated value, up to the current subtree (parentAST). - while (!isTerminal(ast1)) { - treeStack.push(ast1); - ast1 = ast1.leftOperand; - - if (!ast1) { - // Empty tree. - return false; - } - } - - ast1 = ast1.match(image); - treeStack.push(ast1); - - while (treeStack.length > 0) { - parentAST = treeStack.pop(); - - if (parentAST === null) { - // We are at the end of a virtual stack for a right node - // subtree. We switch the result of this stack from left - // (ast1) to right (ast2), pop the original left node, - // and finally pop the parent subtree itself. See near the - // end of this function to view how this is populated. - ast2 = ast1; - ast1 = treeStack.pop(); - parentAST = treeStack.pop(); - } - else { - // First, check to see if we can do a short-circuit - // evaluation to skip evaluating the right side entirely. - if (!ast1 && parentAST.op === 'and_op') { - ast1 = parentAST.negate; - continue; - } - - if (ast1 && parentAST.op === 'or_op') { - ast1 = !parentAST.negate; - continue; - } - - // If we are not at the end of a stack, grab the right - // node. The left node (ast1) is currently a terminal Boolean. - ast2 = parentAST.rightOperand; - } - - if (typeof ast2 === 'boolean') { - ast1 = combineOperands(ast1, ast2, parentAST); - } - else if (!ast2) { - // A subtree with a single node. This is generally the case - // for negated tokens. - if (parentAST.negate) { - ast1 = !ast1; - } - } - else if (isTerminal(ast2)) { - // We are finally at a leaf and can evaluate. - ast2 = ast2.match(image); - ast1 = combineOperands(ast1, ast2, parentAST); - } - else { - // We are at a node whose right side is a new subtree. - // We will build a new "virtual" stack, but instead of - // building a new Array, we can insert a null object as a - // marker. - treeStack.push(parentAST, ast1, null); - - do { - treeStack.push(ast2); - ast2 = ast2.leftOperand; - } while (!isTerminal(ast2)); - - ast1 = ast2.match(image); - } - } - - return ast1; -}; - -SearchAST.prototype.dumpTree = function() { - // Dumps to string a simple diagram of the syntax tree structure - // (starting with this object as the root) for debugging purposes. - let retStrArr = [], - treeQueue = [['', this]], - treeArr, - prefix, - tree; - - while (treeQueue.length > 0) { - treeArr = treeQueue.shift(); - prefix = treeArr[0]; - tree = treeArr[1]; - - if (isTerminal(tree)) { - retStrArr.push(`${prefix}-> ${tree.term}`); - } - else { - if (tree.negate) { - retStrArr.push(`${prefix}+ NOT_OP`); - prefix += '\t'; - } - if (tree.op) { - retStrArr.push(`${prefix}+ ${tree.op.toUpperCase()}`); - prefix += '\t'; - treeQueue.unshift([prefix, tree.rightOperand]); - treeQueue.unshift([prefix, tree.leftOperand]); - } - else { - treeQueue.unshift([prefix, tree.leftOperand]); - } - } - } - - return retStrArr.join('\n'); -}; - -export default parseSearch; diff --git a/assets/js/preview.js b/assets/js/preview.js index 068120ba..2f760e93 100644 --- a/assets/js/preview.js +++ b/assets/js/preview.js @@ -3,7 +3,6 @@ */ import { fetchJson } from './utils/requests'; -import { filterNode } from './imagesclientside'; function handleError(response) { const errorMessage = '
Preview failed to load!
'; @@ -42,7 +41,6 @@ function getPreview(body, anonymous, previewTab, isImage = false) { .then(handleError) .then(data => { previewTab.innerHTML = data; - filterNode(previewTab); }); } @@ -83,4 +81,4 @@ function setupPreviews() { }); } -export { setupPreviews }; \ No newline at end of file +export { setupPreviews }; diff --git a/assets/js/utils/image.js b/assets/js/utils/image.js deleted file mode 100644 index fec1bd2b..00000000 --- a/assets/js/utils/image.js +++ /dev/null @@ -1,128 +0,0 @@ -import { clearEl } from './dom'; -import store from './store'; - -function showVideoThumb(img) { - const size = img.dataset.size; - const uris = JSON.parse(img.dataset.uris); - const thumbUri = uris[size]; - - const vidEl = img.querySelector('video'); - if (!vidEl) return false; - - const imgEl = img.querySelector('img'); - if (!imgEl || imgEl.classList.contains('hidden')) return false; - - imgEl.classList.add('hidden'); - - vidEl.innerHTML = ` - - - `; - vidEl.classList.remove('hidden'); - vidEl.play(); - - img.querySelector('.js-spoiler-info-overlay').classList.add('hidden'); - - return true; -} - -function showThumb(img) { - const size = img.dataset.size; - const uris = JSON.parse(img.dataset.uris); - const thumbUri = uris[size].replace(/webm$/, 'gif'); - - const picEl = img.querySelector('picture'); - if (!picEl) return showVideoThumb(img); - - const imgEl = picEl.querySelector('img'); - if (!imgEl || imgEl.src.indexOf(thumbUri) !== -1) return false; - - if (store.get('serve_hidpi') && !thumbUri.endsWith('.gif')) { - // Check whether the HiDPI option is enabled, and make an exception for GIFs due to their size - imgEl.srcset = `${thumbUri} 1x, ${uris.medium} 2x`; - } - - imgEl.src = thumbUri; - if (uris[size].indexOf('.webm') !== -1) { - const overlay = img.querySelector('.js-spoiler-info-overlay'); - overlay.classList.remove('hidden'); - overlay.innerHTML = 'WebM'; - } - else { - img.querySelector('.js-spoiler-info-overlay').classList.add('hidden'); - } - - return true; -} - -function showBlock(img) { - img.querySelector('.image-filtered').classList.add('hidden'); - const imageShowClasses = img.querySelector('.image-show').classList; - imageShowClasses.remove('hidden'); - imageShowClasses.add('spoiler-pending'); -} - -function hideVideoThumb(img, spoilerUri, reason) { - const vidEl = img.querySelector('video'); - if (!vidEl) return; - - const imgEl = img.querySelector('img'); - const imgOverlay = img.querySelector('.js-spoiler-info-overlay'); - if (!imgEl) return; - - imgEl.classList.remove('hidden'); - imgEl.src = spoilerUri; - imgOverlay.innerHTML = reason; - imgOverlay.classList.remove('hidden'); - - clearEl(vidEl); - vidEl.classList.add('hidden'); - vidEl.pause(); -} - -function hideThumb(img, spoilerUri, reason) { - const picEl = img.querySelector('picture'); - if (!picEl) return hideVideoThumb(img, spoilerUri, reason); - - const imgEl = picEl.querySelector('img'); - const imgOverlay = img.querySelector('.js-spoiler-info-overlay'); - - if (!imgEl || imgEl.src.indexOf(spoilerUri) !== -1) return; - - imgEl.srcset = ''; - imgEl.src = spoilerUri; - imgOverlay.innerHTML = reason; - imgOverlay.classList.remove('hidden'); -} - -function spoilerThumb(img, spoilerUri, reason) { - hideThumb(img, spoilerUri, reason); - - switch (window.booru.spoilerType) { - case 'click': - img.addEventListener('click', event => { if (showThumb(img)) event.preventDefault(); }); - img.addEventListener('mouseleave', () => hideThumb(img, spoilerUri, reason)); - break; - case 'hover': - img.addEventListener('mouseenter', () => showThumb(img)); - img.addEventListener('mouseleave', () => hideThumb(img, spoilerUri, reason)); - break; - default: - break; - } -} - -function spoilerBlock(img, spoilerUri, reason) { - const imgEl = img.querySelector('.image-filtered img'); - const imgReason = img.querySelector('.filter-explanation'); - - if (!imgEl) return; - - imgEl.src = spoilerUri; - imgReason.innerHTML = reason; - - img.querySelector('.image-show').classList.add('hidden'); - img.querySelector('.image-filtered').classList.remove('hidden'); -} - -export { showThumb, showBlock, spoilerThumb, spoilerBlock, hideThumb }; diff --git a/assets/js/utils/tag.js b/assets/js/utils/tag.js deleted file mode 100644 index 699b0e57..00000000 --- a/assets/js/utils/tag.js +++ /dev/null @@ -1,58 +0,0 @@ -import { escapeHtml } from './dom'; -import { getTag } from '../booru'; - -function unique(array) { - return array.filter((a, b, c) => c.indexOf(a) === b); -} - -function sortTags(hidden, a, b) { - // If both tags have a spoiler image, sort by images count desc (hidden) or asc (spoilered) - if (a.spoiler_image_uri && b.spoiler_image_uri) { - return hidden ? b.images - a.images : a.images - b.images; - } - // If neither has a spoiler image, sort by images count desc - else if (!a.spoiler_image_uri && !b.spoiler_image_uri) { - return b.images - a.images; - } - - // Tag with spoiler image takes precedence - return a.spoiler_image_uri ? -1 : 1; -} - -function getHiddenTags() { - return unique(window.booru.hiddenTagList) - .map(tagId => getTag(tagId)) - .sort(sortTags.bind(null, true)); -} - -function getSpoileredTags() { - if (window.booru.spoilerType === 'off') return []; - - return unique(window.booru.spoileredTagList) - .filter(tagId => window.booru.ignoredTagList.indexOf(tagId) === -1) - .map(tagId => getTag(tagId)) - .sort(sortTags.bind(null, false)); -} - -function imageHitsTags(img, matchTags) { - const imageTags = JSON.parse(img.dataset.imageTags); - return matchTags.filter(t => imageTags.indexOf(t.id) !== -1); -} - -function imageHitsComplex(img, matchComplex) { - return matchComplex.hitsImage(img); -} - -function displayTags(tags) { - const mainTag = tags[0], otherTags = tags.slice(1); - let list = escapeHtml(mainTag.name), extras; - - if (otherTags.length > 0) { - extras = otherTags.map(tag => escapeHtml(tag.name)).join(', '); - list += `, ${extras}`; - } - - return list; -} - -export { getHiddenTags, getSpoileredTags, imageHitsTags, imageHitsComplex, displayTags }; diff --git a/assets/js/when-ready.js b/assets/js/when-ready.js index 4f52a875..d53aeebe 100644 --- a/assets/js/when-ready.js +++ b/assets/js/when-ready.js @@ -16,8 +16,8 @@ import { setupComments } from './comment'; import { setupDupeReports } from './duplicate_reports.js'; import { setFingerprintCookie } from './fingerprint'; import { setupGalleryEditing } from './galleries'; -import { initImagesClientside } from './imagesclientside'; import { bindImageTarget } from './image_expansion'; +import { setupInteractions } from './interactions'; import { setupEvents } from './misc'; import { setupNotifications } from './notifications'; import { setupPreviews } from './preview'; @@ -44,7 +44,7 @@ whenReady(() => { registerEvents(); setupBurgerMenu(); bindCaptchaLinks(); - initImagesClientside(); + setupInteractions(); setupComments(); setupDupeReports(); setFingerprintCookie(); diff --git a/lib/philomena/search/evaluator.ex b/lib/philomena/search/evaluator.ex deleted file mode 100644 index 25ef2337..00000000 --- a/lib/philomena/search/evaluator.ex +++ /dev/null @@ -1,169 +0,0 @@ -defmodule Philomena.Search.Evaluator do - # TODO: rethink the necessity of this module. - # Can we do this in elasticsearch instead? - - def hits?(doc, %{bool: bool_query}) do - must(doc, bool_query[:must]) and - must(doc, bool_query[:filter]) and - should(doc, bool_query[:should]) and - not should(doc, bool_query[:must_not]) - end - - def hits?(doc, %{range: range_query}) do - [term] = Map.keys(range_query) - doc_values = wrap(doc[atomify(term)]) - - range_query[term] - |> Enum.all?(fn - {:gt, query_val} -> - Enum.any?(doc_values, &(&1 > query_val)) - - {:gte, query_val} -> - Enum.any?(doc_values, &(&1 >= query_val)) - - {:lt, query_val} -> - Enum.any?(doc_values, &(&1 < query_val)) - - {:lte, query_val} -> - Enum.any?(doc_values, &(&1 <= query_val)) - end) - end - - def hits?(doc, %{fuzzy: fuzzy_query}) do - [{term, %{value: query_val, fuzziness: fuzziness}}] = Enum.to_list(fuzzy_query) - - wrap(doc[atomify(term)]) - |> Enum.any?(fn doc_val -> - cond do - fuzziness >= 1 -> - levenshtein(query_val, doc_val) <= fuzziness - - fuzziness >= 0 -> - levenshtein(query_val, doc_val) <= trunc((1 - fuzziness) * byte_size(query_val)) - - true -> - false - end - end) - end - - def hits?(doc, %{wildcard: wildcard_query}) do - [{term, query_val}] = Enum.to_list(wildcard_query) - query_re = wildcard_to_regex(query_val) - - wrap(doc[atomify(term)]) - |> Enum.any?(&Regex.match?(query_re, &1 || "")) - end - - def hits?(doc, %{match_phrase: phrase_query}) do - # This is wildly inaccurate but practically unavoidable as - # there is no good reason to import a term stemmer - [{term, query_val}] = Enum.to_list(phrase_query) - - wrap(doc[atomify(term)]) - |> Enum.any?(&String.contains?(&1, query_val)) - end - - def hits?(_doc, %{nested: _}) do - # No way to tell without a wildly expensive database query - false - end - - def hits?(doc, %{term: term_query}) do - [{term, query_val}] = Enum.to_list(term_query) - - wrap(doc[atomify(term)]) - |> Enum.member?(query_val) - end - - def hits?(doc, %{terms: terms_query}) do - [{term, query_vals}] = Enum.to_list(terms_query) - - wrap(doc[atomify(term)]) - |> Enum.any?(&Enum.member?(query_vals, &1)) - end - - def hits?(_doc, %{match_all: %{}}), do: true - def hits?(_doc, %{match_none: %{}}), do: false - def hits?(doc, %{function_score: %{query: query}}), do: hits?(doc, query) - - defp must(_doc, nil), do: true - defp must(doc, queries) when is_list(queries), do: Enum.all?(queries, &hits?(doc, &1)) - defp must(doc, query), do: hits?(doc, query) - - defp should(_doc, nil), do: false - defp should(doc, queries) when is_list(queries), do: Enum.any?(queries, &hits?(doc, &1)) - defp should(doc, query), do: hits?(doc, query) - - defp wrap(list) when is_list(list), do: list - defp wrap(object), do: [object] - - defp atomify(atom) when is_atom(atom), do: atom - defp atomify(string) when is_binary(string), do: String.to_existing_atom(string) - - def levenshtein(s1, s2) do - {dist, _lookup} = levenshtein_lookup(s1, s2, %{}, 0) - - dist - end - - defp levenshtein_lookup(s1, s2, lookup, times) do - case lookup[{s1, s2}] do - nil -> - levenshtein_execute(s1, s2, lookup, times) - - val -> - {val, lookup} - end - end - - # Avoid pursuing excessively time-consuming substrings - defp levenshtein_execute(s1, s2, lookup, times) when times > 2, - do: {max(byte_size(s1), byte_size(s2)), lookup} - - defp levenshtein_execute("", s2, lookup, _times), do: {byte_size(s2), lookup} - defp levenshtein_execute(s1, "", lookup, _times), do: {byte_size(s1), lookup} - defp levenshtein_execute(s1, s1, lookup, _times), do: {0, lookup} - - defp levenshtein_execute(s1, s2, lookup, times) do - {deletion, lookup} = levenshtein_lookup(chop(s1), s2, lookup, times + 1) - {insertion, lookup} = levenshtein_lookup(s1, chop(s2), lookup, times + 1) - {substitution, lookup} = levenshtein_lookup(chop(s1), chop(s2), lookup, times + 1) - - min = - Enum.min([ - deletion + 1, - insertion + 1, - substitution + last_bytes_different?(s1, s2) - ]) - - lookup = Map.put(lookup, {s1, s2}, min) - - {min, lookup} - end - - defp chop(str) when is_binary(str), do: binary_part(str, 0, byte_size(str) - 1) - - defp last_bytes_different?(s1, s2) - when binary_part(s1, byte_size(s1) - 1, 1) == binary_part(s2, byte_size(s2) - 1, 1), - do: 0 - - defp last_bytes_different?(_s1, _s2), do: 1 - - defp wildcard_to_regex(input) do - re = - input - # escape regex metacharacters - |> String.replace(~r/([.+^$\[\]\\\(\){}|-])/, "\\\\\\1") - # * -> .* (kleene star) - |> String.replace(~r/([^\\]|[^\\](?:\\\\)+)\*/, "\\1.*") - # * -> .* (kleene star) - |> String.replace(~r/\A(?:\\\\)*\*/, ".*") - # ? -> .? (concatenation/alternation) - |> String.replace(~r/([^\\]|[^\\](?:\\\\)+)\?/, "\\1.?") - # ? -> .? (concatenation/alternation) - |> String.replace(~r/\A(?:\\\\)*\?/, ".?") - - Regex.compile!("\\A#{re}\\z", "im") - end -end diff --git a/lib/philomena_web/controllers/image/comment_controller.ex b/lib/philomena_web/controllers/image/comment_controller.ex index 216123fd..2d89f812 100644 --- a/lib/philomena_web/controllers/image/comment_controller.ex +++ b/lib/philomena_web/controllers/image/comment_controller.ex @@ -27,7 +27,6 @@ defmodule PhilomenaWeb.Image.CommentController do preload: [:tags] plug :verify_authorized when action in [:show] - plug PhilomenaWeb.FilterForcedUsersPlug when action in [:create, :edit, :update] # Undo the previous private parameter screwery plug PhilomenaWeb.LoadCommentPlug, [param: "id", show_hidden: true] when action in [:show] diff --git a/lib/philomena_web/controllers/image/fave_controller.ex b/lib/philomena_web/controllers/image/fave_controller.ex index 26c2fb8f..d043ae11 100644 --- a/lib/philomena_web/controllers/image/fave_controller.ex +++ b/lib/philomena_web/controllers/image/fave_controller.ex @@ -15,8 +15,6 @@ defmodule PhilomenaWeb.Image.FaveController do persisted: true, preload: [:tags] - plug PhilomenaWeb.FilterForcedUsersPlug - def create(conn, _params) do user = conn.assigns.current_user image = conn.assigns.image diff --git a/lib/philomena_web/controllers/image/vote_controller.ex b/lib/philomena_web/controllers/image/vote_controller.ex index 53275371..4b1f0e7d 100644 --- a/lib/philomena_web/controllers/image/vote_controller.ex +++ b/lib/philomena_web/controllers/image/vote_controller.ex @@ -15,8 +15,6 @@ defmodule PhilomenaWeb.Image.VoteController do persisted: true, preload: [:tags] - plug PhilomenaWeb.FilterForcedUsersPlug - def create(conn, params) do user = conn.assigns.current_user image = conn.assigns.image diff --git a/lib/philomena_web/plugs/filter_forced_users_plug.ex b/lib/philomena_web/plugs/filter_forced_users_plug.ex deleted file mode 100644 index 17d1886c..00000000 --- a/lib/philomena_web/plugs/filter_forced_users_plug.ex +++ /dev/null @@ -1,61 +0,0 @@ -defmodule PhilomenaWeb.FilterForcedUsersPlug do - @moduledoc """ - Halts the request pipeline if the current image belongs to the conn's - "forced filter". - """ - - import Phoenix.Controller - import Plug.Conn - alias Philomena.Search.String, as: SearchString - alias Philomena.Search.Evaluator - alias Philomena.Images.Query - alias PhilomenaWeb.ImageView - - def init(_opts) do - [] - end - - def call(conn, _opts) do - maybe_fetch_forced(conn, conn.assigns.forced_filter) - end - - defp maybe_fetch_forced(conn, nil), do: conn - - defp maybe_fetch_forced(conn, forced) do - maybe_halt(conn, matches_filter?(conn.assigns.current_user, conn.assigns.image, forced)) - end - - defp maybe_halt(conn, false), do: conn - - defp maybe_halt(conn, true) do - conn - |> put_flash(:error, "You have been blocked from performing this action on this image.") - |> redirect(external: conn.assigns.referrer) - |> halt() - end - - defp matches_filter?(user, image, filter) do - matches_tag_filter?(image, filter.hidden_tag_ids) or - matches_complex_filter?(user, image, filter.hidden_complex_str) - end - - defp matches_tag_filter?(image, tag_ids) do - image.tags - |> MapSet.new(& &1.id) - |> MapSet.intersection(MapSet.new(tag_ids)) - |> Enum.any?() - end - - defp matches_complex_filter?(user, image, search_string) do - image - |> ImageView.image_filter_data() - |> Evaluator.hits?(compile_filter(user, search_string)) - end - - defp compile_filter(user, search_string) do - case Query.compile(user, SearchString.normalize(search_string)) do - {:ok, query} -> query - _error -> %{match_all: %{}} - end - end -end diff --git a/lib/philomena_web/templates/image/_image_target.html.slime b/lib/philomena_web/templates/image/_image_target.html.slime index 36a4ab22..db084a79 100644 --- a/lib/philomena_web/templates/image/_image_target.html.slime +++ b/lib/philomena_web/templates/image/_image_target.html.slime @@ -1,6 +1,6 @@ - size = assigns[:size] || :full -= content_tag :div, [data: image_container_data(@conn, @image, size), class: "image-show-container"] do += content_tag :div, [class: "image-show-container"] do .block.block--fixed.block--warning.block--no-margin.image-filtered.hidden strong = link("This image is blocked by your current filter - click here to display it anyway", to: "#", data: [click_unfilter: @image.id]) diff --git a/lib/philomena_web/views/image_view.ex b/lib/philomena_web/views/image_view.ex index 8b536da4..e20a69d9 100644 --- a/lib/philomena_web/views/image_view.ex +++ b/lib/philomena_web/views/image_view.ex @@ -123,31 +123,8 @@ defmodule PhilomenaWeb.ImageView do Application.get_env(:philomena, :image_url_root) end - def image_container_data(conn, image, size) do - [ - image_id: image.id, - image_tags: Jason.encode!(Enum.map(image.tags, & &1.id)), - image_tag_aliases: image.tag_list_plus_alias_cache, - score: image.score, - faves: image.faves_count, - upvotes: image.upvotes_count, - downvotes: image.downvotes_count, - comment_count: image.comments_count, - created_at: NaiveDateTime.to_iso8601(image.created_at), - source_url: image.source_url, - uris: Jason.encode!(thumb_urls(image, can?(conn, :show, image))), - width: image.image_width, - height: image.image_height, - aspect_ratio: image.image_aspect_ratio, - size: size - ] - end - - def image_container(conn, image, size, block) do - content_tag(:div, block.(), - class: "image-container #{size}", - data: image_container_data(conn, image, size) - ) + def image_container(_conn, _image, size, block) do + content_tag(:div, block.(), class: "image-container #{size}") end def display_order(tags) do @@ -216,26 +193,6 @@ defmodule PhilomenaWeb.ImageView do defp thumb_format(_, :rendered, _download), do: "png" defp thumb_format(format, _name, _download), do: format - def image_filter_data(image) do - %{ - id: image.id, - "namespaced_tags.name": String.split(image.tag_list_plus_alias_cache || "", ", "), - score: image.score, - faves: image.faves_count, - upvotes: image.upvotes_count, - downvotes: image.downvotes_count, - comment_count: image.comments_count, - created_at: image.created_at, - first_seen_at: image.first_seen_at, - source_url: image.source_url, - width: image.image_width, - height: image.image_height, - aspect_ratio: image.image_aspect_ratio, - sha512_hash: image.image_sha512_hash, - orig_sha512_hash: image.image_orig_sha512_hash - } - end - def filter_or_spoiler_value(conn, image) do spoilered(conn)[image.id] end diff --git a/lib/philomena_web/views/layout_view.ex b/lib/philomena_web/views/layout_view.ex index 17e5b560..241efc60 100644 --- a/lib/philomena_web/views/layout_view.ex +++ b/lib/philomena_web/views/layout_view.ex @@ -24,11 +24,6 @@ defmodule PhilomenaWeb.LayoutView do host |> to_string end - defp ignored_tag_list(nil), do: [] - defp ignored_tag_list([]), do: [] - defp ignored_tag_list([{tag, _body, _dnp_entries}]), do: [tag.id] - defp ignored_tag_list(tags), do: Enum.map(tags, & &1.id) - def clientside_data(conn) do conn = Conn.fetch_cookies(conn) @@ -40,9 +35,7 @@ defmodule PhilomenaWeb.LayoutView do data = [ filter_id: filter.id, hidden_tag_list: Jason.encode!(filter.hidden_tag_ids), - hidden_filter: Philomena.Search.String.normalize(filter.hidden_complex_str || ""), spoilered_tag_list: Jason.encode!(filter.spoilered_tag_ids), - spoilered_filter: Philomena.Search.String.normalize(filter.spoilered_complex_str || ""), user_id: if(user, do: user.id, else: nil), user_name: if(user, do: user.name, else: nil), user_slug: if(user, do: user.slug, else: nil), @@ -53,7 +46,6 @@ defmodule PhilomenaWeb.LayoutView do fancy_tag_edit: if(user, do: user.fancy_tag_field_on_edit, else: true), fancy_tag_upload: if(user, do: user.fancy_tag_field_on_upload, else: true), interactions: Jason.encode!(interactions), - ignored_tag_list: Jason.encode!(ignored_tag_list(conn.assigns[:tags])), hide_staff_tools: conn.cookies["hide_staff_tools"] ]