Integrate Eslint and TypeScript into build & linting process (#142)

This commit is contained in:
David Joseph Guzsik 2021-10-09 21:58:07 +02:00 committed by GitHub
parent 0487a82db6
commit 77e689e29a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 2002 additions and 133 deletions

View file

@ -8,7 +8,8 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- uses: actions/cache@v2 - name: Cache mix deps
uses: actions/cache@v2
with: with:
path: | path: |
_build _build
@ -25,3 +26,28 @@ jobs:
run: | run: |
docker-compose run app mix sobelow --config docker-compose run app mix sobelow --config
docker-compose run app mix deps.audit docker-compose run app mix deps.audit
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: '14'
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v2
with:
path: ./assets/node_modules
key: node_modules-${{ hashFiles('./assets/package-lock.json') }}
- name: Install npm dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci --ignore-scripts
working-directory: ./assets
- name: Run ESLint
run: npm run lint
working-directory: ./assets

2
assets/.eslintignore Normal file
View file

@ -0,0 +1,2 @@
js/vendor/*
webpack.config.js

View file

@ -2,16 +2,24 @@ env:
browser: true browser: true
es6: true es6: true
parser: '@typescript-eslint/parser'
parserOptions: parserOptions:
ecmaVersion: 6 ecmaVersion: 6
sourceType: module sourceType: module
plugins:
- '@typescript-eslint'
globals: globals:
ga: false ga: false
md5: false md5: false
Sortable: false Sortable: false
ActionCable: false ActionCable: false
extends:
- 'plugin:@typescript-eslint/recommended'
rules: rules:
accessor-pairs: 2 accessor-pairs: 2
array-bracket-spacing: 0 array-bracket-spacing: 0
@ -83,7 +91,6 @@ rules:
no-console: 0 no-console: 0
no-const-assign: 2 no-const-assign: 2
no-constant-condition: 2 no-constant-condition: 2
no-continue: 2
no-control-regex: 2 no-control-regex: 2
no-debugger: 2 no-debugger: 2
no-delete-var: 2 no-delete-var: 2
@ -245,3 +252,10 @@ rules:
wrap-regex: 0 wrap-regex: 0
yield-star-spacing: 2 yield-star-spacing: 2
yoda: [2, 'never'] yoda: [2, 'never']
# Disable rules which are impossible to satisfy (types require .ts extension)
overrides:
- files:
- '*.js'
rules:
'@typescript-eslint/explicit-module-boundary-types': 0

View file

@ -1,2 +0,0 @@
vendor/*
match_query.js

View file

@ -81,7 +81,8 @@ function initializeFilters() {
} }
function unmarshal(data) { function unmarshal(data) {
try { return JSON.parse(data); } catch (_) { return data; } try { return JSON.parse(data); }
catch (_) { return data; }
} }
function loadBooruData() { function loadBooruData() {

View file

@ -31,14 +31,16 @@ const actions = {
disable(data) { selectorCb(data.base, data.value, el => el.disabled = true); }, disable(data) { selectorCb(data.base, data.value, el => el.disabled = true); },
copy(data) { document.querySelector(data.value).select(); copy(data) {
document.execCommand('copy'); }, document.querySelector(data.value).select();
document.execCommand('copy');
},
inputvalue(data) { document.querySelector(data.value).value = data.el.dataset.setValue; }, inputvalue(data) { document.querySelector(data.value).value = data.el.dataset.setValue; },
selectvalue(data) { document.querySelector(data.value).value = data.el.querySelector(':checked').dataset.setValue; }, selectvalue(data) { document.querySelector(data.value).value = data.el.querySelector(':checked').dataset.setValue; },
checkall(data) { $$(`${data.value} input[type=checkbox]`).forEach(c => { c.checked = !c.checked; }) }, checkall(data) { $$(`${data.value} input[type=checkbox]`).forEach(c => { c.checked = !c.checked; }); },
focus(data) { document.querySelector(data.value).focus(); }, focus(data) { document.querySelector(data.value).focus(); },

View file

@ -30,10 +30,12 @@ function commentPosted(response) {
if (requestOk) { if (requestOk) {
response.text().then(text => { response.text().then(text => {
if (text.includes('<div class="flash flash--warning">')) if (text.includes('<div class="flash flash--warning">')) {
window.location.reload(); window.location.reload();
else }
else {
displayComments(container, text); displayComments(container, text);
}
}); });
} }
else { else {

View file

@ -47,9 +47,8 @@ function selectVersion(imageWidth, imageHeight, imageSize, imageMime) {
if (imageMime === 'video/webm' || imageSize <= 26214400) { if (imageMime === 'video/webm' || imageSize <= 26214400) {
return 'full'; return 'full';
} }
else {
return 'large'; return 'large';
}
} }
/** /**

View file

@ -6,9 +6,9 @@ import { fetchJson } from './utils/requests';
import { $ } from './utils/dom'; import { $ } from './utils/dom';
const endpoints = { const endpoints = {
vote(imageId) { return `/images/${imageId}/vote` }, vote(imageId) { return `/images/${imageId}/vote`; },
fave(imageId) { return `/images/${imageId}/fave` }, fave(imageId) { return `/images/${imageId}/fave`; },
hide(imageId) { return `/images/${imageId}/hide` }, hide(imageId) { return `/images/${imageId}/hide`; },
}; };
const spoilerDownvoteMsg = const spoilerDownvoteMsg =
@ -34,16 +34,16 @@ function modifyCache(callback) {
cacheEl.value = JSON.stringify(callback(JSON.parse(cacheEl.value))); cacheEl.value = JSON.stringify(callback(JSON.parse(cacheEl.value)));
} }
function cacheStatus(image_id, interaction_type, value) { function cacheStatus(imageId, interactionType, value) {
modifyCache(cache => { modifyCache(cache => {
cache[`${image_id}${interaction_type}`] = { image_id, interaction_type, value }; cache[`${imageId}${interactionType}`] = { imageId, interactionType, value };
return cache; return cache;
}); });
} }
function uncacheStatus(image_id, interaction_type) { function uncacheStatus(imageId, interactionType) {
modifyCache(cache => { modifyCache(cache => {
delete cache[`${image_id}${interaction_type}`]; delete cache[`${imageId}${interactionType}`];
return cache; return cache;
}); });
} }

View file

@ -168,7 +168,7 @@ function wrapSelection(textarea, options) {
}); });
} }
newText = prefix + newText + suffix newText = prefix + newText + suffix;
return { return {
newText, newText,
@ -188,7 +188,7 @@ function wrapLines(textarea, options, eachLine = true) {
: text.split(/\n/g).map(line => prefix + line.trim() + suffix).join('\n'); : text.split(/\n/g).map(line => prefix + line.trim() + suffix).join('\n');
// Force a space at the end of lines with only blockquote markers // Force a space at the end of lines with only blockquote markers
newText = newText.replace(/^((?:>\s+)*)>$/gm, '$1> ') newText = newText.replace(/^((?:>\s+)*)>$/gm, '$1> ');
return { newText, caretOffset: emptyText ? prefix.length : newText.length }; return { newText, caretOffset: emptyText ? prefix.length : newText.length };
}, eachLine); }, eachLine);

View file

@ -5,18 +5,18 @@
const tokenList = [ const tokenList = [
['fuzz', /^~(?:\d+(\.\d+)?|\.\d+)/], ['fuzz', /^~(?:\d+(\.\d+)?|\.\d+)/],
['boost', /^\^[\-\+]?\d+(\.\d+)?/], ['boost', /^\^[-+]?\d+(\.\d+)?/],
['quoted_lit', /^\s*"(?:(?:[^"]|\\")+)"/], ['quoted_lit', /^\s*"(?:[^"]|\\")+"/],
['lparen', /^\s*\(\s*/], ['lparen', /^\s*\(\s*/],
['rparen', /^\s*\)\s*/], ['rparen', /^\s*\)\s*/],
['and_op', /^\s*(?:\&\&|AND)\s+/], ['and_op', /^\s*(?:&&|AND)\s+/],
['and_op', /^\s*,\s*/], ['and_op', /^\s*,\s*/],
['or_op', /^\s*(?:\|\||OR)\s+/], ['or_op', /^\s*(?:\|\||OR)\s+/],
['not_op', /^\s*NOT(?:\s+|(?=\())/], ['not_op', /^\s*NOT(?:\s+|(?=\())/],
['not_op', /^\s*[\!\-]\s*/], ['not_op', /^\s*[!-]\s*/],
['space', /^\s+/], ['space', /^\s+/],
['word', /^(?:[^\s,\(\)\^~]|\\[\s,\(\)\^~])+/], ['word', /^(?:[^\s,()^~]|\\[\s,()^~])+/],
['word', /^(?:[^\s,\(\)]|\\[\s,\(\)])+/] ['word', /^(?:[^\s,()]|\\[\s,()])+/]
], ],
numberFields = ['id', 'width', 'height', 'aspect_ratio', numberFields = ['id', 'width', 'height', 'aspect_ratio',
'comment_count', 'score', 'upvotes', 'downvotes', 'comment_count', 'score', 'upvotes', 'downvotes',
@ -35,6 +35,7 @@ const tokenList = [
id: 'data-image-id', id: 'data-image-id',
width: 'data-width', width: 'data-width',
height: 'data-height', height: 'data-height',
/* eslint-disable camelcase */
aspect_ratio: 'data-aspect-ratio', aspect_ratio: 'data-aspect-ratio',
comment_count: 'data-comment-count', comment_count: 'data-comment-count',
tag_count: 'data-tag-count', tag_count: 'data-tag-count',
@ -43,10 +44,11 @@ const tokenList = [
sha512_hash: 'data-sha512', sha512_hash: 'data-sha512',
orig_sha512_hash: 'data-orig-sha512', orig_sha512_hash: 'data-orig-sha512',
created_at: 'data-created-at' created_at: 'data-created-at'
/* eslint-enable camelcase */
}; };
function SearchTerm(termStr, options) { function SearchTerm(termStr) {
this.term = termStr.trim(); this.term = termStr.trim();
this.parsed = false; this.parsed = false;
} }
@ -57,8 +59,6 @@ SearchTerm.prototype.append = function(substr) {
}; };
SearchTerm.prototype.parseRangeField = function(field) { SearchTerm.prototype.parseRangeField = function(field) {
let qual;
if (numberFields.indexOf(field) !== -1) { if (numberFields.indexOf(field) !== -1) {
return [field, 'eq', 'number']; return [field, 'eq', 'number'];
} }
@ -67,7 +67,7 @@ SearchTerm.prototype.parseRangeField = function(field) {
return [field, 'eq', 'date']; return [field, 'eq', 'date'];
} }
qual = /^(\w+)\.([lg]te?|eq)$/.exec(field); const qual = /^(\w+)\.([lg]te?|eq)$/.exec(field);
if (qual) { if (qual) {
if (numberFields.indexOf(qual[1]) !== -1) { if (numberFields.indexOf(qual[1]) !== -1) {
@ -116,28 +116,29 @@ SearchTerm.prototype.parseRelativeDate = function(dateVal, qual) {
} }
} }
else { else {
throw `Cannot parse date string: ${dateVal}`; throw new Error(`Cannot parse date string: ${dateVal}`);
} }
}; };
SearchTerm.prototype.parseAbsoluteDate = function(dateVal, qual) { SearchTerm.prototype.parseAbsoluteDate = function(dateVal, qual) {
let parseRes = [ const parseRes = [
/^(\d{4})/, /^(\d{4})/,
/^\-(\d{2})/, /^-(\d{2})/,
/^\-(\d{2})/, /^-(\d{2})/,
/^(?:\s+|T|t)(\d{2})/, /^(?:\s+|T|t)(\d{2})/,
/^:(\d{2})/, /^:(\d{2})/,
/^:(\d{2})/ /^:(\d{2})/
], ],
timeZoneOffset = [0, 0], timeZoneOffset = [0, 0],
timeData = [0, 0, 1, 0, 0, 0], timeData = [0, 0, 1, 0, 0, 0],
bottomDate = null, origDateVal = dateVal;
topDate = null, let topDate = null,
i, i,
match, match,
origDateVal = dateVal; bottomDate = null,
localDateVal = origDateVal;
match = /([\+\-])(\d{2}):(\d{2})$/.exec(dateVal); match = /([+-])(\d{2}):(\d{2})$/.exec(localDateVal);
if (match) { if (match) {
timeZoneOffset[0] = parseInt(match[2], 10); timeZoneOffset[0] = parseInt(match[2], 10);
timeZoneOffset[1] = parseInt(match[3], 10); timeZoneOffset[1] = parseInt(match[3], 10);
@ -145,18 +146,18 @@ SearchTerm.prototype.parseAbsoluteDate = function(dateVal, qual) {
timeZoneOffset[0] *= -1; timeZoneOffset[0] *= -1;
timeZoneOffset[1] *= -1; timeZoneOffset[1] *= -1;
} }
dateVal = dateVal.substr(0, dateVal.length - 6); localDateVal = localDateVal.substr(0, localDateVal.length - 6);
} }
else { else {
dateVal = dateVal.replace(/[Zz]$/, ''); localDateVal = localDateVal.replace(/[Zz]$/, '');
} }
for (i = 0; i < parseRes.length; i += 1) { for (i = 0; i < parseRes.length; i += 1) {
if (dateVal.length === 0) { if (localDateVal.length === 0) {
break; break;
} }
match = parseRes[i].exec(dateVal); match = parseRes[i].exec(localDateVal);
if (match) { if (match) {
if (i === 1) { if (i === 1) {
timeData[i] = parseInt(match[1], 10) - 1; timeData[i] = parseInt(match[1], 10) - 1;
@ -164,17 +165,17 @@ SearchTerm.prototype.parseAbsoluteDate = function(dateVal, qual) {
else { else {
timeData[i] = parseInt(match[1], 10); timeData[i] = parseInt(match[1], 10);
} }
dateVal = dateVal.substr( localDateVal = localDateVal.substr(
match[0].length, dateVal.length - match[0].length match[0].length, localDateVal.length - match[0].length
); );
} }
else { else {
throw `Cannot parse date string: ${origDateVal}`; throw new Error(`Cannot parse date string: ${origDateVal}`);
} }
} }
if (dateVal.length > 0) { if (localDateVal.length > 0) {
throw `Cannot parse date string: ${origDateVal}`; throw new Error(`Cannot parse date string: ${origDateVal}`);
} }
// Apply the user-specified time zone offset. The JS Date constructor // Apply the user-specified time zone offset. The JS Date constructor
@ -210,9 +211,8 @@ SearchTerm.prototype.parseDate = function(dateVal, qual) {
} }
}; };
SearchTerm.prototype.parse = function(substr) { SearchTerm.prototype.parse = function() {
let matchArr, let rangeParsing,
rangeParsing,
candidateTermSpace, candidateTermSpace,
termCandidate; termCandidate;
@ -230,7 +230,7 @@ SearchTerm.prototype.parse = function(substr) {
this.termSpace = 'tags'; this.termSpace = 'tags';
this.termType = 'literal'; this.termType = 'literal';
matchArr = this.term.split(':'); const matchArr = this.term.split(':');
if (matchArr.length > 1) { if (matchArr.length > 1) {
candidateTermSpace = matchArr[0]; candidateTermSpace = matchArr[0];
@ -258,7 +258,7 @@ SearchTerm.prototype.parse = function(substr) {
this.term = termCandidate; this.term = termCandidate;
this.termSpace = candidateTermSpace; this.termSpace = candidateTermSpace;
} }
else if (candidateTermSpace == 'my') { else if (candidateTermSpace === 'my') {
this.termType = 'my'; this.termType = 'my';
this.termSpace = termCandidate; this.termSpace = termCandidate;
} }
@ -285,9 +285,9 @@ SearchTerm.prototype.parse = function(substr) {
SearchTerm.prototype._normalizeTerm = function() { SearchTerm.prototype._normalizeTerm = function() {
if (!this.wildcardable) { if (!this.wildcardable) {
return this.term.replace('\"', '"'); return this.term.replace('"', '"');
} }
return this.term.replace(/\\([^\*\?])/g, '$1'); return this.term.replace(/\\([^*?])/g, '$1');
}; };
SearchTerm.prototype.fuzzyMatch = function(targetStr) { SearchTerm.prototype.fuzzyMatch = function(targetStr) {
@ -309,16 +309,16 @@ SearchTerm.prototype.fuzzyMatch = function(targetStr) {
targetDistance = this.fuzz; targetDistance = this.fuzz;
} }
targetStr = targetStr.toLowerCase(); const targetStrLower = targetStr.toLowerCase();
for (i = 0; i <= targetStr.length; i += 1) { for (i = 0; i <= targetStrLower.length; i += 1) {
v1.push(i); v1.push(i);
} }
for (i = 0; i < this.term.length; i += 1) { for (i = 0; i < this.term.length; i += 1) {
v2[0] = i; v2[0] = i;
for (j = 0; j < targetStr.length; j += 1) { for (j = 0; j < targetStrLower.length; j += 1) {
const cost = this.term[i] === targetStr[j] ? 0 : 1; const cost = this.term[i] === targetStrLower[j] ? 0 : 1;
v2[j + 1] = Math.min( v2[j + 1] = Math.min(
// Deletion. // Deletion.
v1[j + 1] + 1, v1[j + 1] + 1,
@ -327,8 +327,8 @@ SearchTerm.prototype.fuzzyMatch = function(targetStr) {
// Substitution or No Change. // Substitution or No Change.
v1[j] + cost v1[j] + cost
); );
if (i > 1 && j > 1 && this.term[i] === targetStr[j - 1] && if (i > 1 && j > 1 && this.term[i] === targetStrLower[j - 1] &&
targetStr[i - 1] === targetStr[j]) { targetStrLower[i - 1] === targetStrLower[j]) {
v2[j + 1] = Math.min(v2[j], v0[j - 1] + cost); v2[j + 1] = Math.min(v2[j], v0[j - 1] + cost);
} }
} }
@ -339,7 +339,7 @@ SearchTerm.prototype.fuzzyMatch = function(targetStr) {
v2 = temp; v2 = temp;
} }
return v1[targetStr.length] <= targetDistance; return v1[targetStrLower.length] <= targetDistance;
}; };
SearchTerm.prototype.exactMatch = function(targetStr) { SearchTerm.prototype.exactMatch = function(targetStr) {
@ -354,10 +354,8 @@ SearchTerm.prototype.interactionMatch = function(imageID, type, interaction, int
let ret = false; let ret = false;
interactions.forEach(v => { interactions.forEach(v => {
if (v.image_id == imageID && v.interaction_type == type && (interaction == null || v.value == interaction)) { if (v.image_id === imageID && v.interaction_type === type && (interaction === null || v.value === interaction)) {
ret = true; ret = true;
return;
} }
}); });
@ -365,8 +363,9 @@ SearchTerm.prototype.interactionMatch = function(imageID, type, interaction, int
}; };
SearchTerm.prototype.match = function(target) { SearchTerm.prototype.match = function(target) {
// eslint-disable-next-line @typescript-eslint/no-this-alias,consistent-this
const ohffs = this;
let ret = false, let ret = false,
ohffs = this,
compFunc, compFunc,
numbuh, numbuh,
date; date;
@ -427,9 +426,9 @@ SearchTerm.prototype.match = function(target) {
} }
else if (this.termType === 'date') { else if (this.termType === 'date') {
// Date matching. // Date matching.
date = (new Date( date = new Date(
target.getAttribute(termSpaceToImageField[this.termSpace]) target.getAttribute(termSpaceToImageField[this.termSpace])
)).getTime(); ).getTime();
switch (this.compare) { switch (this.compare) {
// The open-left, closed-right date range specified by the // The open-left, closed-right date range specified by the
@ -481,23 +480,25 @@ SearchTerm.prototype.match = function(target) {
return ret; return ret;
}; };
function generateLexArray(searchStr, options) { function generateLexArray(searchStr) {
let opQueue = [], const opQueue = [],
searchTerm = null, groupNegate = [],
tokenStack = [];
let searchTerm = null,
boost = null, boost = null,
fuzz = null, fuzz = null,
lparenCtr = 0, lparenCtr = 0,
negate = false, negate = false,
groupNegate = [], boostFuzzStr = '',
tokenStack = [], localSearchStr = searchStr;
boostFuzzStr = '';
while (searchStr.length > 0) { while (localSearchStr.length > 0) {
// eslint-disable-next-line no-loop-func
tokenList.every(tokenArr => { tokenList.every(tokenArr => {
let tokenName = tokenArr[0], const tokenName = tokenArr[0],
tokenRE = tokenArr[1], tokenRE = tokenArr[1];
match = tokenRE.exec(searchStr), let match = tokenRE.exec(localSearchStr),
balanced, op; op;
if (match) { if (match) {
match = match[0]; match = match[0];
@ -560,15 +561,18 @@ function generateLexArray(searchStr, options) {
break; break;
case 'rparen': case 'rparen':
if (lparenCtr > 0) { if (lparenCtr > 0) {
if (searchTerm) {
searchTerm.append(match); searchTerm.append(match);
}
else {
searchTerm = new SearchTerm(match);
}
lparenCtr -= 1; lparenCtr -= 1;
} }
else { else {
balanced = false;
while (opQueue.length) { while (opQueue.length) {
op = opQueue.shift(); op = opQueue.shift();
if (op === 'lparen') { if (op === 'lparen') {
balanced = true;
break; break;
} }
tokenStack.push(op); tokenStack.push(op);
@ -587,7 +591,7 @@ function generateLexArray(searchStr, options) {
boostFuzzStr += match; boostFuzzStr += match;
} }
else { else {
searchTerm = new SearchTerm(match, options); searchTerm = new SearchTerm(match);
} }
break; break;
case 'boost': case 'boost':
@ -596,7 +600,7 @@ function generateLexArray(searchStr, options) {
boostFuzzStr += match; boostFuzzStr += match;
} }
else { else {
searchTerm = new SearchTerm(match, options); searchTerm = new SearchTerm(match);
} }
break; break;
case 'quoted_lit': case 'quoted_lit':
@ -604,7 +608,7 @@ function generateLexArray(searchStr, options) {
searchTerm.append(match); searchTerm.append(match);
} }
else { else {
searchTerm = new SearchTerm(match, options); searchTerm = new SearchTerm(match);
} }
break; break;
case 'word': case 'word':
@ -617,7 +621,7 @@ function generateLexArray(searchStr, options) {
searchTerm.append(match); searchTerm.append(match);
} }
else { else {
searchTerm = new SearchTerm(match, options); searchTerm = new SearchTerm(match);
} }
break; break;
default: default:
@ -628,8 +632,8 @@ function generateLexArray(searchStr, options) {
} }
// Truncate string and restart the token tests. // Truncate string and restart the token tests.
searchStr = searchStr.substr( localSearchStr = localSearchStr.substr(
match.length, searchStr.length - match.length match.length, localSearchStr.length - match.length
); );
// Break since we have found a match. // Break since we have found a match.
@ -652,7 +656,7 @@ function generateLexArray(searchStr, options) {
if (opQueue.indexOf('rparen') !== -1 || if (opQueue.indexOf('rparen') !== -1 ||
opQueue.indexOf('lparen') !== -1) { opQueue.indexOf('lparen') !== -1) {
throw 'Mismatched parentheses.'; throw new Error('Mismatched parentheses.');
} }
// Memory-efficient concatenation of remaining operators queue to the // Memory-efficient concatenation of remaining operators queue to the
@ -663,8 +667,8 @@ function generateLexArray(searchStr, options) {
} }
function parseTokens(lexicalArray) { function parseTokens(lexicalArray) {
let operandStack = [], const operandStack = [];
negate, op1, op2, parsed; let negate, op1, op2;
lexicalArray.forEach((token, i) => { lexicalArray.forEach((token, i) => {
if (token !== 'not_op') { if (token !== 'not_op') {
negate = lexicalArray[i + 1] === 'not_op'; negate = lexicalArray[i + 1] === 'not_op';
@ -674,7 +678,7 @@ function parseTokens(lexicalArray) {
op1 = operandStack.pop(); op1 = operandStack.pop();
if (typeof op1 === 'undefined' || typeof op2 === 'undefined') { if (typeof op1 === 'undefined' || typeof op2 === 'undefined') {
throw 'Missing operand.'; throw new Error('Missing operand.');
} }
operandStack.push(new SearchAST(token, negate, op1, op2)); operandStack.push(new SearchAST(token, negate, op1, op2));
@ -691,7 +695,7 @@ function parseTokens(lexicalArray) {
}); });
if (operandStack.length > 1) { if (operandStack.length > 1) {
throw 'Missing operator.'; throw new Error('Missing operator.');
} }
op1 = operandStack.pop(); op1 = operandStack.pop();
@ -707,8 +711,8 @@ function parseTokens(lexicalArray) {
return op1; return op1;
} }
function parseSearch(searchStr, options) { function parseSearch(searchStr) {
return parseTokens(generateLexArray(searchStr, options)); return parseTokens(generateLexArray(searchStr));
} }
function isTerminal(operand) { function isTerminal(operand) {
@ -724,25 +728,27 @@ function SearchAST(op, negate, leftOperand, rightOperand) {
} }
function combineOperands(ast1, ast2, parentAST) { function combineOperands(ast1, ast2, parentAST) {
let localAst1;
if (parentAST.op === 'and_op') { if (parentAST.op === 'and_op') {
ast1 = ast1 && ast2; localAst1 = ast1 && ast2;
} }
else { else {
ast1 = ast1 || ast2; localAst1 = ast1 || ast2;
} }
if (parentAST.negate) { if (parentAST.negate) {
return !ast1; return !localAst1;
} }
return ast1; return localAst1;
} }
// Evaluation of the AST in regard to a target image // Evaluation of the AST in regard to a target image
SearchAST.prototype.hitsImage = function(image) { SearchAST.prototype.hitsImage = function(image) {
let treeStack = [], const treeStack = [];
// Left side node. // Left side node.
ast1 = this, // eslint-disable-next-line @typescript-eslint/no-this-alias,consistent-this
let ast1 = this,
// Right side node. // Right side node.
ast2, ast2,
// Parent node of the current subtree. // Parent node of the current subtree.
@ -834,9 +840,9 @@ SearchAST.prototype.hitsImage = function(image) {
SearchAST.prototype.dumpTree = function() { SearchAST.prototype.dumpTree = function() {
// Dumps to string a simple diagram of the syntax tree structure // Dumps to string a simple diagram of the syntax tree structure
// (starting with this object as the root) for debugging purposes. // (starting with this object as the root) for debugging purposes.
let retStrArr = [], const retStrArr = [],
treeQueue = [['', this]], treeQueue = [['', this]];
treeArr, let treeArr,
prefix, prefix,
tree; tree;

View file

@ -17,7 +17,7 @@ function makeRequest(verb) {
function bindSubscriptionLinks() { function bindSubscriptionLinks() {
delegate(document, 'fetchcomplete', { delegate(document, 'fetchcomplete', {
'.js-subscription-link': event => { '.js-subscription-link': event => {
const target = $("#js-subscription-target"); const target = $('#js-subscription-target');
event.detail.text().then(text => { event.detail.text().then(text => {
target.outerHTML = text; target.outerHTML = text;
}); });

View file

@ -1,4 +1,4 @@
import { $, $$, clearEl, removeEl, insertBefore } from './utils/dom'; import { $, $$, removeEl} from './utils/dom';
import { delegate, leftClick } from './utils/events'; import { delegate, leftClick } from './utils/events';
function pollOptionRemover(_event, target) { function pollOptionRemover(_event, target) {
@ -28,7 +28,7 @@ function pollOptionCreator() {
const newHtml = prevFieldCopy.outerHTML.replace(/(\d+)/g, `${existingOptionCount}`); const newHtml = prevFieldCopy.outerHTML.replace(/(\d+)/g, `${existingOptionCount}`);
// Insert copy before the button // Insert copy before the button
addPollOptionButton.insertAdjacentHTML("beforebegin", newHtml); addPollOptionButton.insertAdjacentHTML('beforebegin', newHtml);
existingOptionCount++; existingOptionCount++;
} }

View file

@ -90,11 +90,11 @@ function setupPreviews() {
const getCacheKey = () => { const getCacheKey = () => {
return (previewAnon && previewAnon.checked ? 'anon;' : '') + textarea.value; return (previewAnon && previewAnon.checked ? 'anon;' : '') + textarea.value;
} };
const previewedTextAttribute = 'data-previewed-text'; const previewedTextAttribute = 'data-previewed-text';
const updatePreview = () => { const updatePreview = () => {
const cachedValue = getCacheKey() const cachedValue = getCacheKey();
if (previewContent.getAttribute(previewedTextAttribute) === cachedValue) return; if (previewContent.getAttribute(previewedTextAttribute) === cachedValue) return;
previewContent.setAttribute(previewedTextAttribute, cachedValue); previewContent.setAttribute(previewedTextAttribute, cachedValue);

View file

@ -41,8 +41,8 @@ const keyCodes = {
82() { click('.js-rand'); }, // R - go to random image 82() { click('.js-rand'); }, // R - go to random image
83() { click('.js-source-link'); }, // S - go to image source 83() { click('.js-source-link'); }, // S - go to image source
76() { click('.js-tag-sauce-toggle'); }, // L - edit tags 76() { click('.js-tag-sauce-toggle'); }, // L - edit tags
79() { openFullView() }, // O - open original 79() { openFullView(); }, // O - open original
86() { openFullViewNewTab() }, // V - open original in a new tab 86() { openFullViewNewTab(); }, // V - open original in a new tab
70() { // F - favourite image 70() { // F - favourite image
getHover() ? click(`a.interaction--fave[data-image-id="${getHover()}"]`) getHover() ? click(`a.interaction--fave[data-image-id="${getHover()}"]`)
: click('.block__header a.interaction--fave'); : click('.block__header a.interaction--fave');

View file

@ -7,7 +7,7 @@
import { $$ } from './utils/dom'; import { $$ } from './utils/dom';
function hideStaffTools() { function hideStaffTools() {
if (window.booru.hideStaffTools == "true") { if (window.booru.hideStaffTools === 'true') {
$$('.js-staff-action').forEach(el => { $$('.js-staff-action').forEach(el => {
el.classList.add('hidden'); el.classList.add('hidden');
}); });

View file

@ -102,7 +102,7 @@ function setupTagsInput(tagBlock) {
if (name.length === 0 || tags.indexOf(name) !== -1) return; if (name.length === 0 || tags.indexOf(name) !== -1) return;
// Remove instead if the tag name starts with a minus // Remove instead if the tag name starts with a minus
if (name[0] === "-") { if (name[0] === '-') {
name = name.slice(1); // eslint-disable-line no-param-reassign name = name.slice(1); // eslint-disable-line no-param-reassign
const tagLink = $(`[data-tag-name="${escapeCss(name)}"]`, container); const tagLink = $(`[data-tag-name="${escapeCss(name)}"]`, container);

View file

@ -58,11 +58,11 @@ function formRemote(event, target) {
headers: headers(), headers: headers(),
body: new FormData(target) body: new FormData(target)
}).then(response => { }).then(response => {
if (response && response.status == 300) { if (response && response.status === 300) {
window.location.reload(true); window.location.reload(true);
return; return;
} }
fire(target, 'fetchcomplete', response) fire(target, 'fetchcomplete', response);
}); });
} }

View file

@ -122,7 +122,7 @@ function setupImageUpload() {
}); });
// Fetch on "enter" in url field // Fetch on "enter" in url field
remoteUrl.addEventListener("keydown", function(event) { remoteUrl.addEventListener('keydown', event => {
if (event.keyCode === 13) { // Hit enter if (event.keyCode === 13) { // Hit enter
fetchButton.click(); fetchButton.click();
} }

1792
assets/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,28 @@
{ {
"scripts": { "scripts": {
"deploy": "cross-env NODE_ENV=production webpack", "deploy": "cross-env NODE_ENV=production webpack",
"lint": "eslint . --ext .js,.ts",
"watch": "webpack --watch" "watch": "webpack --watch"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4", "@fortawesome/fontawesome-free": "^5.15.4",
"brunch": "^3.0.0", "brunch": "^3.0.0",
"copycat-brunch": "^1.1.1", "copycat-brunch": "^1.1.1",
"postcss": "^8.3.7" "postcss": "^8.3.7",
"tslib": "^2.3.1"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-typescript": "^8.2.5",
"@typescript-eslint/eslint-plugin": "^4.32.0",
"@typescript-eslint/parser": "^4.32.0",
"acorn": "^7.4.1", "acorn": "^7.4.1",
"autoprefixer": "^10.3.5", "autoprefixer": "^10.3.5",
"copy-webpack-plugin": "^6.4.1", "copy-webpack-plugin": "^6.4.1",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"css-loader": "^5.2.7", "css-loader": "^5.2.7",
"css-minimizer-webpack-plugin": "^2.0.0", "css-minimizer-webpack-plugin": "^2.0.0",
"eslint": "^7.32.0",
"eslint-webpack-plugin": "^3.0.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"ignore-emit-webpack-plugin": "^2.0.6", "ignore-emit-webpack-plugin": "^2.0.6",
"mini-css-extract-plugin": "^2.3.0", "mini-css-extract-plugin": "^2.3.0",
@ -33,6 +40,7 @@
"source-map-support": "^0.5.20", "source-map-support": "^0.5.20",
"style-loader": "^1.3.0", "style-loader": "^1.3.0",
"terser-webpack-plugin": "^3.1.0", "terser-webpack-plugin": "^3.1.0",
"typescript": "^4.4",
"webpack": "^5.53.0", "webpack": "^5.53.0",
"webpack-cli": "^4.8.0", "webpack-cli": "^4.8.0",
"webpack-rollup-loader": "^0.8.0" "webpack-rollup-loader": "^0.8.0"

9
assets/tsconfig.json Normal file
View file

@ -0,0 +1,9 @@
{
"compilerOptions": {
"noEmit": true,
"baseUrl": "./js",
"target": "ES2018",
"allowJs": true,
"strict": true
}
}

View file

@ -5,12 +5,14 @@ const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin'); const CopyPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin'); const IgnoreEmitPlugin = require('ignore-emit-webpack-plugin');
const ESLintPlugin = require('eslint-webpack-plugin');
const isDevelopment = process.env.NODE_ENV !== 'production'; const isDevelopment = process.env.NODE_ENV !== 'production';
const includePaths = require('rollup-plugin-includepaths')(); const includePaths = require('rollup-plugin-includepaths')();
const multiEntry = require('rollup-plugin-multi-entry')(); const multiEntry = require('rollup-plugin-multi-entry')();
const buble = require('rollup-plugin-buble')({ transforms: { dangerousForOf: true } }); const buble = require('rollup-plugin-buble')({ transforms: { dangerousForOf: true } });
const typescript = require('@rollup/plugin-typescript')();
let plugins = [ let plugins = [
new IgnoreEmitPlugin(/css\/.*(?<!css)$/), new IgnoreEmitPlugin(/css\/.*(?<!css)$/),
@ -24,7 +26,16 @@ let plugins = [
], ],
}), }),
]; ];
if (!isDevelopment){ if (isDevelopment) {
plugins = plugins.concat([
new ESLintPlugin({
extensions: ['js', 'ts'],
failOnError: true,
failOnWarning: isDevelopment
})
]);
}
else {
plugins = plugins.concat([ plugins = plugins.concat([
new TerserPlugin({ new TerserPlugin({
cache: true, cache: true,
@ -90,6 +101,7 @@ module.exports = {
buble, buble,
includePaths, includePaths,
multiEntry, multiEntry,
typescript,
] ]
} }
}, },