diff --git a/assets/.prettierrc.yml b/assets/.prettierrc.yml index 546aecb0..05ab046f 100644 --- a/assets/.prettierrc.yml +++ b/assets/.prettierrc.yml @@ -2,6 +2,13 @@ tabWidth: 2 useTabs: false printWidth: 120 semi: true -singleQuote: false +singleQuote: true bracketSpacing: true endOfLine: lf +quoteProps: as-needed +trailingComma: all +arrowParens: always +overrides: + - files: "*.css" + options: + singleQuote: false diff --git a/assets/eslint.config.js b/assets/eslint.config.js index 166da758..c927efb6 100644 --- a/assets/eslint.config.js +++ b/assets/eslint.config.js @@ -1,9 +1,11 @@ import tsEslint from 'typescript-eslint'; import vitestPlugin from 'eslint-plugin-vitest'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; import globals from 'globals'; export default tsEslint.config( ...tsEslint.configs.recommended, + eslintPluginPrettierRecommended, { name: 'PhilomenaConfig', files: ['**/*.js', '**/*.ts'], @@ -12,24 +14,22 @@ export default tsEslint.config( sourceType: 'module', parserOptions: { ecmaVersion: 6, - sourceType: 'module' + sourceType: 'module', }, globals: { - ...globals.browser - } + ...globals.browser, + }, }, rules: { 'accessor-pairs': 2, 'array-bracket-spacing': 0, 'array-callback-return': 2, 'arrow-body-style': 0, - 'arrow-parens': [2, 'as-needed'], 'arrow-spacing': 2, 'block-scoped-var': 2, 'block-spacing': 2, - 'brace-style': [2, 'stroustrup', {allowSingleLine: true}], 'callback-return': 0, - camelcase: [2, {allow: ['camo_url', 'spoiler_image_uri', 'image_ids']}], + camelcase: [2, { allow: ['camo_url', 'spoiler_image_uri', 'image_ids'] }], 'class-methods-use-this': 0, 'comma-dangle': [2, 'only-multiline'], 'comma-spacing': 2, @@ -42,7 +42,7 @@ export default tsEslint.config( curly: [2, 'multi-line', 'consistent'], 'default-case': 2, 'dot-location': [2, 'property'], - 'dot-notation': [2, {allowKeywords: true}], + 'dot-notation': [2, { allowKeywords: true }], 'eol-last': 2, eqeqeq: 2, 'func-call-spacing': 0, @@ -56,7 +56,6 @@ export default tsEslint.config( 'id-blacklist': 0, 'id-length': 0, 'id-match': 2, - indent: [2, 2, {SwitchCase: 1, VariableDeclarator: {var: 2, let: 2, const: 3}}], 'init-declarations': 0, 'jsx-quotes': 0, 'key-spacing': 0, @@ -110,7 +109,7 @@ export default tsEslint.config( 'no-extra-bind': 2, 'no-extra-boolean-cast': 2, 'no-extra-label': 2, - 'no-extra-parens': [2, 'all', {nestedBinaryExpressions: false}], + 'no-extra-parens': [2, 'all', { nestedBinaryExpressions: false }], 'no-extra-semi': 2, 'no-fallthrough': 2, 'no-floating-decimal': 2, @@ -136,7 +135,7 @@ export default tsEslint.config( 'no-mixed-spaces-and-tabs': 2, 'no-multi-spaces': 0, 'no-multi-str': 2, - 'no-multiple-empty-lines': [2, {max: 3, maxBOF: 0, maxEOF: 1}], + 'no-multiple-empty-lines': [2, { max: 3, maxBOF: 0, maxEOF: 1 }], 'no-native-reassign': 2, 'no-negated-condition': 0, 'no-negated-in-lhs': 2, @@ -190,9 +189,9 @@ export default tsEslint.config( 'no-unreachable': 2, 'no-unsafe-finally': 2, 'no-unsafe-negation': 2, - 'no-unused-expressions': [2, {allowShortCircuit: true, allowTernary: true}], + 'no-unused-expressions': [2, { allowShortCircuit: true, allowTernary: true }], 'no-unused-labels': 2, - 'no-unused-vars': [2, {vars: 'all', args: 'after-used', varsIgnorePattern: '^_', argsIgnorePattern: '^_'}], + 'no-unused-vars': [2, { vars: 'all', args: 'after-used', varsIgnorePattern: '^_', argsIgnorePattern: '^_' }], 'no-use-before-define': [2, 'nofunc'], 'no-useless-call': 2, 'no-useless-computed-key': 2, @@ -222,21 +221,19 @@ export default tsEslint.config( 'prefer-spread': 0, 'prefer-template': 2, 'quote-props': [2, 'as-needed'], - quotes: [2, 'single'], radix: 2, 'require-jsdoc': 0, 'require-yield': 2, 'rest-spread-spacing': 2, - 'semi-spacing': [2, {before: false, after: true}], + 'semi-spacing': [2, { before: false, after: true }], semi: 2, 'sort-imports': 0, 'sort-keys': 0, 'sort-vars': 0, 'space-before-blocks': [2, 'always'], - 'space-before-function-paren': [2, 'never'], 'space-in-parens': [2, 'never'], 'space-infix-ops': 2, - 'space-unary-ops': [2, {words: true, nonwords: false}], + 'space-unary-ops': [2, { words: true, nonwords: false }], 'spaced-comment': 0, strict: [2, 'function'], 'symbol-description': 2, @@ -251,18 +248,15 @@ export default tsEslint.config( 'yield-star-spacing': 2, yoda: [2, 'never'], }, - ignores: [ - 'js/vendor/*', - 'vite.config.ts' - ] + ignores: ['js/vendor/*', 'vite.config.ts'], }, { files: ['**/*.js'], rules: { '@typescript-eslint/explicit-module-boundary-types': 'off', '@typescript-eslint/no-unused-expressions': 'off', - '@typescript-eslint/no-unused-vars': 'off' - } + '@typescript-eslint/no-unused-vars': 'off', + }, }, { files: ['**/*.ts'], @@ -271,15 +265,18 @@ export default tsEslint.config( 'no-unused-vars': 'off', 'no-redeclare': 'off', 'no-shadow': 'off', - '@typescript-eslint/no-unused-vars': [2, {vars: 'all', args: 'after-used', varsIgnorePattern: '^_.*', argsIgnorePattern: '^_.*'}], + '@typescript-eslint/no-unused-vars': [ + 2, + { vars: 'all', args: 'after-used', varsIgnorePattern: '^_.*', argsIgnorePattern: '^_.*' }, + ], '@typescript-eslint/no-redeclare': 2, - '@typescript-eslint/no-shadow': 2 - } + '@typescript-eslint/no-shadow': 2, + }, }, { files: ['**/*.spec.ts', '**/test/*.ts'], plugins: { - vitest: vitestPlugin + vitest: vitestPlugin, }, rules: { ...vitestPlugin.configs.recommended.rules, @@ -287,7 +284,7 @@ export default tsEslint.config( 'no-undefined': 'off', 'no-unused-expressions': 0, 'vitest/valid-expect': 0, - '@typescript-eslint/no-unused-expressions': 0 - } - } + '@typescript-eslint/no-unused-expressions': 0, + }, + }, ); diff --git a/assets/js/__tests__/imagesclientside.spec.ts b/assets/js/__tests__/imagesclientside.spec.ts index 3f3feb88..f4c73ace 100644 --- a/assets/js/__tests__/imagesclientside.spec.ts +++ b/assets/js/__tests__/imagesclientside.spec.ts @@ -23,11 +23,11 @@ describe('filterNode', () => { `; - return [ element, assertNotNull($('.js-spoiler-info-overlay', element)) ]; + return [element, assertNotNull($('.js-spoiler-info-overlay', element))]; } it('should show image media boxes not matching any filter', () => { - const [ container, spoilerOverlay ] = makeMediaContainer(); + const [container, spoilerOverlay] = makeMediaContainer(); filterNode(container); expect(spoilerOverlay).not.toContainHTML('(Complex Filter)'); @@ -36,7 +36,7 @@ describe('filterNode', () => { }); it('should spoiler media boxes spoilered by a tag filter', () => { - const [ container, spoilerOverlay ] = makeMediaContainer(); + const [container, spoilerOverlay] = makeMediaContainer(); window.booru.spoileredTagList = [1]; filterNode(container); @@ -45,7 +45,7 @@ describe('filterNode', () => { }); it('should spoiler media boxes spoilered by a complex filter', () => { - const [ container, spoilerOverlay ] = makeMediaContainer(); + const [container, spoilerOverlay] = makeMediaContainer(); window.booru.spoileredFilter = parseSearch('id:1'); filterNode(container); @@ -54,7 +54,7 @@ describe('filterNode', () => { }); it('should hide media boxes hidden by a tag filter', () => { - const [ container, spoilerOverlay ] = makeMediaContainer(); + const [container, spoilerOverlay] = makeMediaContainer(); window.booru.hiddenTagList = [1]; filterNode(container); @@ -64,7 +64,7 @@ describe('filterNode', () => { }); it('should hide media boxes hidden by a complex filter', () => { - const [ container, spoilerOverlay ] = makeMediaContainer(); + const [container, spoilerOverlay] = makeMediaContainer(); window.booru.hiddenFilter = parseSearch('id:1'); filterNode(container); @@ -90,12 +90,12 @@ describe('filterNode', () => { element, assertNotNull($('.image-filtered', element)), assertNotNull($('.image-show', element)), - assertNotNull($('.filter-explanation', element)) + assertNotNull($('.filter-explanation', element)), ]; } it('should show image blocks not matching any filter', () => { - const [ container, imageFiltered, imageShow ] = makeImageBlock(); + const [container, imageFiltered, imageShow] = makeImageBlock(); filterNode(container); expect(imageFiltered).toHaveClass('hidden'); @@ -104,7 +104,7 @@ describe('filterNode', () => { }); it('should spoiler image blocks spoilered by a tag filter', () => { - const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + const [container, imageFiltered, imageShow, filterExplanation] = makeImageBlock(); window.booru.spoileredTagList = [1]; filterNode(container); @@ -116,7 +116,7 @@ describe('filterNode', () => { }); it('should spoiler image blocks spoilered by a complex filter', () => { - const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + const [container, imageFiltered, imageShow, filterExplanation] = makeImageBlock(); window.booru.spoileredFilter = parseSearch('id:1'); filterNode(container); @@ -128,7 +128,7 @@ describe('filterNode', () => { }); it('should hide image blocks hidden by a tag filter', () => { - const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + const [container, imageFiltered, imageShow, filterExplanation] = makeImageBlock(); window.booru.hiddenTagList = [1]; filterNode(container); @@ -140,7 +140,7 @@ describe('filterNode', () => { }); it('should hide image blocks hidden by a complex filter', () => { - const [ container, imageFiltered, imageShow, filterExplanation ] = makeImageBlock(); + const [container, imageFiltered, imageShow, filterExplanation] = makeImageBlock(); window.booru.hiddenFilter = parseSearch('id:1'); filterNode(container); @@ -150,7 +150,6 @@ describe('filterNode', () => { expect(filterExplanation).toContainHTML('complex tag expression'); expect(window.booru.imagesWithDownvotingDisabled).toContain('1'); }); - }); describe('initImagesClientside', () => { diff --git a/assets/js/__tests__/input-duplicator.spec.ts b/assets/js/__tests__/input-duplicator.spec.ts index 55233ee7..09115573 100644 --- a/assets/js/__tests__/input-duplicator.spec.ts +++ b/assets/js/__tests__/input-duplicator.spec.ts @@ -5,7 +5,9 @@ import { fireEvent } from '@testing-library/dom'; describe('Input duplicator functionality', () => { beforeEach(() => { - document.documentElement.insertAdjacentHTML('beforeend', `
+ document.documentElement.insertAdjacentHTML( + 'beforeend', + `
3
@@ -16,7 +18,8 @@ describe('Input duplicator functionality', () => {
- `); + `, + ); }); afterEach(() => { diff --git a/assets/js/__tests__/ujs.spec.ts b/assets/js/__tests__/ujs.spec.ts index b5b3d231..282eb404 100644 --- a/assets/js/__tests__/ujs.spec.ts +++ b/assets/js/__tests__/ujs.spec.ts @@ -29,7 +29,7 @@ describe('Remote utilities', () => { } describe('a[data-remote]', () => { - const submitA = ({ setMethod }: { setMethod: boolean; }) => { + const submitA = ({ setMethod }: { setMethod: boolean }) => { const a = document.createElement('a'); a.href = mockEndpoint; a.dataset.remote = 'remote'; @@ -51,8 +51,8 @@ describe('Remote utilities', () => { credentials: 'same-origin', headers: { 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'XMLHttpRequest' - } + 'x-requested-with': 'XMLHttpRequest', + }, }); }); @@ -64,21 +64,22 @@ describe('Remote utilities', () => { credentials: 'same-origin', headers: { 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'XMLHttpRequest' - } + 'x-requested-with': 'XMLHttpRequest', + }, }); }); - it('should emit fetchcomplete event', () => new Promise(resolve => { - let a: HTMLAnchorElement | null = null; + it('should emit fetchcomplete event', () => + new Promise((resolve) => { + let a: HTMLAnchorElement | null = null; - addOneShotEventListener('fetchcomplete', event => { - expect(event.target).toBe(a); - resolve(); - }); + addOneShotEventListener('fetchcomplete', (event) => { + expect(event.target).toBe(a); + resolve(); + }); - a = submitA({ setMethod: true }); - })); + a = submitA({ setMethod: true }); + })); }); describe('a[data-method]', () => { @@ -93,24 +94,25 @@ describe('Remote utilities', () => { return a; }; - it('should submit a form with the given action', () => new Promise(resolve => { - addOneShotEventListener('submit', event => { - event.preventDefault(); + it('should submit a form with the given action', () => + new Promise((resolve) => { + addOneShotEventListener('submit', (event) => { + event.preventDefault(); - const target = assertType(event.target, HTMLFormElement); - const [ csrf, method ] = target.querySelectorAll('input'); + const target = assertType(event.target, HTMLFormElement); + const [csrf, method] = target.querySelectorAll('input'); - expect(csrf.name).toBe('_csrf_token'); - expect(csrf.value).toBe(window.booru.csrfToken); + expect(csrf.name).toBe('_csrf_token'); + expect(csrf.value).toBe(window.booru.csrfToken); - expect(method.name).toBe('_method'); - expect(method.value).toBe(mockVerb); + expect(method.name).toBe('_method'); + expect(method.value).toBe(mockVerb); - resolve(); - }); + resolve(); + }); - submitA(); - })); + submitA(); + })); }); describe('form[data-remote]', () => { @@ -167,7 +169,7 @@ describe('Remote utilities', () => { credentials: 'same-origin', headers: { 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'XMLHttpRequest' + 'x-requested-with': 'XMLHttpRequest', }, body: new FormData(), }); @@ -183,25 +185,26 @@ describe('Remote utilities', () => { credentials: 'same-origin', headers: { 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'XMLHttpRequest' + 'x-requested-with': 'XMLHttpRequest', }, body: new FormData(), }); }); - it('should emit fetchcomplete event', () => new Promise(resolve => { - let form: HTMLFormElement | null = null; + it('should emit fetchcomplete event', () => + new Promise((resolve) => { + let form: HTMLFormElement | null = null; - addOneShotEventListener('fetchcomplete', event => { - expect(event.target).toBe(form); - resolve(); - }); + addOneShotEventListener('fetchcomplete', (event) => { + expect(event.target).toBe(form); + resolve(); + }); - form = submitForm(); - })); + form = submitForm(); + })); it('should reload the page on 300 multiple choices response', () => { - vi.spyOn(global, 'fetch').mockResolvedValue(new Response('', { status: 300})); + vi.spyOn(global, 'fetch').mockResolvedValue(new Response('', { status: 300 })); submitForm(); return waitFor(() => expect(window.location.reload).toHaveBeenCalledTimes(1)); @@ -211,7 +214,7 @@ describe('Remote utilities', () => { describe('Form utilities', () => { beforeEach(() => { - vi.spyOn(window, 'requestAnimationFrame').mockImplementation(cb => { + vi.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => { cb(1); return 1; }); @@ -257,7 +260,7 @@ describe('Form utilities', () => { // jsdom has no implementation for HTMLFormElement.prototype.submit // and will return an error if the event's default isn't prevented - form.addEventListener('submit', event => event.preventDefault()); + form.addEventListener('submit', (event) => event.preventDefault()); const button = document.createElement('button'); button.type = 'submit'; @@ -267,7 +270,7 @@ describe('Form utilities', () => { form.insertAdjacentElement('beforeend', button); document.documentElement.insertAdjacentElement('beforeend', form); - return [ form, button ]; + return [form, button]; }; const submitText = 'Submit'; @@ -276,7 +279,7 @@ describe('Form utilities', () => { const loadingMarkup = 'Loading...'; it('should disable submit button containing a text child on click', () => { - const [ , button ] = createFormAndButton(submitText, loadingText); + const [, button] = createFormAndButton(submitText, loadingText); fireEvent.click(button); expect(button.textContent).toEqual(' Loading...'); @@ -284,7 +287,7 @@ describe('Form utilities', () => { }); it('should disable submit button containing element children on click', () => { - const [ , button ] = createFormAndButton(submitMarkup, loadingMarkup); + const [, button] = createFormAndButton(submitMarkup, loadingMarkup); fireEvent.click(button); expect(button.innerHTML).toEqual(loadingMarkup); @@ -292,7 +295,7 @@ describe('Form utilities', () => { }); it('should not disable anything when the form is invalid', () => { - const [ form, button ] = createFormAndButton(submitText, loadingText); + const [form, button] = createFormAndButton(submitText, loadingText); form.insertAdjacentHTML('afterbegin', ''); fireEvent.click(button); @@ -301,7 +304,7 @@ describe('Form utilities', () => { }); it('should reset submit button containing a text child on completion', () => { - const [ form, button ] = createFormAndButton(submitText, loadingText); + const [form, button] = createFormAndButton(submitText, loadingText); fireEvent.click(button); fireEvent(form, new CustomEvent('reset', { bubbles: true })); @@ -310,7 +313,7 @@ describe('Form utilities', () => { }); it('should reset submit button containing element children on completion', () => { - const [ form, button ] = createFormAndButton(submitMarkup, loadingMarkup); + const [form, button] = createFormAndButton(submitMarkup, loadingMarkup); fireEvent.click(button); fireEvent(form, new CustomEvent('reset', { bubbles: true })); @@ -319,7 +322,7 @@ describe('Form utilities', () => { }); it('should reset disabled form elements on pageshow', () => { - const [ , button ] = createFormAndButton(submitText, loadingText); + const [, button] = createFormAndButton(submitText, loadingText); fireEvent.click(button); fireEvent(window, new CustomEvent('pageshow')); diff --git a/assets/js/__tests__/upload.spec.ts b/assets/js/__tests__/upload.spec.ts index 4f671666..1003b522 100644 --- a/assets/js/__tests__/upload.spec.ts +++ b/assets/js/__tests__/upload.spec.ts @@ -29,12 +29,16 @@ describe('Image upload form', () => { let mockPng: File; let mockWebm: File; - beforeAll(async() => { + beforeAll(async () => { const mockPngPath = join(__dirname, 'upload-test.png'); const mockWebmPath = join(__dirname, 'upload-test.webm'); - mockPng = new File([(await promises.readFile(mockPngPath, { encoding: null })).buffer], 'upload-test.png', { type: 'image/png' }); - mockWebm = new File([(await promises.readFile(mockWebmPath, { encoding: null })).buffer], 'upload-test.webm', { type: 'video/webm' }); + mockPng = new File([(await promises.readFile(mockPngPath, { encoding: null })).buffer], 'upload-test.png', { + type: 'image/png', + }); + mockWebm = new File([(await promises.readFile(mockWebmPath, { encoding: null })).buffer], 'upload-test.webm', { + type: 'video/webm', + }); }); beforeAll(() => { @@ -47,7 +51,6 @@ describe('Image upload form', () => { fixEventListeners(window); - let form: HTMLFormElement; let imgPreviews: HTMLDivElement; let fileField: HTMLInputElement; @@ -63,7 +66,9 @@ describe('Image upload form', () => { }; beforeEach(() => { - document.documentElement.insertAdjacentHTML('beforeend', ` + document.documentElement.insertAdjacentHTML( + 'beforeend', + `
@@ -75,7 +80,8 @@ describe('Image upload form', () => {
- `); + `, + ); form = assertNotNull($('form')); imgPreviews = assertNotNull($('#js-image-upload-previews')); @@ -121,7 +127,7 @@ describe('Image upload form', () => { }); }); - it('should block navigation away after an image file is attached, but not after form submission', async() => { + it('should block navigation away after an image file is attached, but not after form submission', async () => { fireEvent.change(fileField, { target: { files: [mockPng] } }); await waitFor(() => { assertFetchButtonIsDisabled(); @@ -131,8 +137,8 @@ describe('Image upload form', () => { const failedUnloadEvent = new Event('beforeunload', { cancelable: true }); expect(fireEvent(window, failedUnloadEvent)).toBe(false); - await new Promise(resolve => { - form.addEventListener('submit', event => { + await new Promise((resolve) => { + form.addEventListener('submit', (event) => { event.preventDefault(); resolve(); }); @@ -143,11 +149,11 @@ describe('Image upload form', () => { expect(fireEvent(window, succeededUnloadEvent)).toBe(true); }); - it('should scrape images when the fetch button is clicked', async() => { + it('should scrape images when the fetch button is clicked', async () => { fetchMock.mockResolvedValue(new Response(JSON.stringify(scrapeResponse), { status: 200 })); fireEvent.input(remoteUrl, { target: { value: 'http://localhost/images/1' } }); - await new Promise(resolve => { + await new Promise((resolve) => { tagsEl.addEventListener('addtag', (event: Event) => { expect((event as CustomEvent).detail).toEqual({ name: 'artist:test' }); resolve(); diff --git a/assets/js/autocomplete.js b/assets/js/autocomplete.js index 1551a6f8..01ea2893 100644 --- a/assets/js/autocomplete.js +++ b/assets/js/autocomplete.js @@ -10,12 +10,12 @@ import store from './utils/store'; const cache = {}; /** @type {HTMLInputElement} */ let inputField, - /** @type {string} */ - originalTerm, - /** @type {string} */ - originalQuery, - /** @type {TermContext} */ - selectedTerm; + /** @type {string} */ + originalTerm, + /** @type {string} */ + originalQuery, + /** @type {TermContext} */ + selectedTerm; function removeParent() { const parent = document.querySelector('.autocomplete'); @@ -52,15 +52,16 @@ function applySelectedValue(selection) { } function changeSelected(firstOrLast, current, sibling) { - if (current && sibling) { // if the currently selected item has a sibling, move selection to it + if (current && sibling) { + // if the currently selected item has a sibling, move selection to it current.classList.remove('autocomplete__item--selected'); sibling.classList.add('autocomplete__item--selected'); - } - else if (current) { // if the next keypress will take the user outside the list, restore the unautocompleted term + } else if (current) { + // if the next keypress will take the user outside the list, restore the unautocompleted term restoreOriginalValue(); removeSelected(); - } - else if (firstOrLast) { // if no item in the list is selected, select the first or last + } else if (firstOrLast) { + // if no item in the list is selected, select the first or last firstOrLast.classList.add('autocomplete__item--selected'); } } @@ -74,15 +75,16 @@ function isSelectionOutsideCurrentTerm() { function keydownHandler(event) { const selected = document.querySelector('.autocomplete__item--selected'), - firstItem = document.querySelector('.autocomplete__item:first-of-type'), - lastItem = document.querySelector('.autocomplete__item:last-of-type'); + firstItem = document.querySelector('.autocomplete__item:first-of-type'), + lastItem = document.querySelector('.autocomplete__item:last-of-type'); if (isSearchField()) { // Prevent submission of the search field when Enter was hit if (selected && event.keyCode === 13) event.preventDefault(); // Enter // Close autocompletion popup when text cursor is outside current tag - if (selectedTerm && firstItem && (event.keyCode === 37 || event.keyCode === 39)) { // ArrowLeft || ArrowRight + if (selectedTerm && firstItem && (event.keyCode === 37 || event.keyCode === 39)) { + // ArrowLeft || ArrowRight requestAnimationFrame(() => { if (isSelectionOutsideCurrentTerm()) removeParent(); }); @@ -92,7 +94,8 @@ function keydownHandler(event) { if (event.keyCode === 38) changeSelected(lastItem, selected, selected && selected.previousSibling); // ArrowUp if (event.keyCode === 40) changeSelected(firstItem, selected, selected && selected.nextSibling); // ArrowDown if (event.keyCode === 13 || event.keyCode === 27 || event.keyCode === 188) removeParent(); // Enter || Esc || Comma - if (event.keyCode === 38 || event.keyCode === 40) { // ArrowUp || ArrowDown + if (event.keyCode === 38 || event.keyCode === 40) { + // ArrowUp || ArrowDown const newSelected = document.querySelector('.autocomplete__item--selected'); if (newSelected) applySelectedValue(newSelected.dataset.value); event.preventDefault(); @@ -123,8 +126,8 @@ function createItem(list, suggestion) { type: 'click', label: suggestion.label, value: suggestion.value, - } - }) + }, + }), ); }); @@ -133,10 +136,10 @@ function createItem(list, suggestion) { function createList(suggestions) { const parent = document.querySelector('.autocomplete'), - list = document.createElement('ul'); + list = document.createElement('ul'); list.className = 'autocomplete__list'; - suggestions.forEach(suggestion => createItem(list, suggestion)); + suggestions.forEach((suggestion) => createItem(list, suggestion)); parent.appendChild(list); } @@ -173,7 +176,7 @@ function showAutocomplete(suggestions, fetchedTerm, targetInput) { function getSuggestions(term) { // In case source URL was not given at all, do not try sending the request. if (!inputField.dataset.acSource) return []; - return fetch(`${inputField.dataset.acSource}${term}`).then(response => response.json()); + return fetch(`${inputField.dataset.acSource}${term}`).then((response) => response.json()); } function getSelectedTerm() { @@ -193,8 +196,7 @@ function toggleSearchAutocomplete() { for (const searchField of document.querySelectorAll('input[data-ac-mode=search]')) { if (enable) { searchField.autocomplete = 'off'; - } - else { + } else { searchField.removeAttribute('data-ac'); searchField.autocomplete = 'on'; } @@ -210,7 +212,7 @@ function listenAutocomplete() { document.addEventListener('focusin', fetchLocalAutocomplete); - document.addEventListener('input', event => { + document.addEventListener('input', (event) => { removeParent(); fetchLocalAutocomplete(event); window.clearTimeout(timeout); @@ -230,12 +232,13 @@ function listenAutocomplete() { } originalTerm = selectedTerm[1].toLowerCase(); - } - else { + } else { originalTerm = `${inputField.value}`.toLowerCase(); } - const suggestions = localAc.topK(originalTerm, suggestionsCount).map(({ name, imageCount }) => ({ label: `${name} (${imageCount})`, value: name })); + const suggestions = localAc + .topK(originalTerm, suggestionsCount) + .map(({ name, imageCount }) => ({ label: `${name} (${imageCount})`, value: name })); if (suggestions.length) { return showAutocomplete(suggestions, originalTerm, event.target); @@ -248,15 +251,14 @@ function listenAutocomplete() { originalTerm = inputField.value; const fetchedTerm = inputField.value; - const {ac, acMinLength, acSource} = inputField.dataset; + const { ac, acMinLength, acSource } = inputField.dataset; - if (ac && acSource && (fetchedTerm.length >= acMinLength)) { + if (ac && acSource && fetchedTerm.length >= acMinLength) { if (cache[fetchedTerm]) { showAutocomplete(cache[fetchedTerm], fetchedTerm, event.target); - } - else { + } else { // inputField could get overwritten while the suggestions are being fetched - use event.target - getSuggestions(fetchedTerm).then(suggestions => { + getSuggestions(fetchedTerm).then((suggestions) => { if (fetchedTerm === event.target.value) { showAutocomplete(suggestions, fetchedTerm, event.target); } @@ -267,7 +269,7 @@ function listenAutocomplete() { }); // If there's a click outside the inputField, remove autocomplete - document.addEventListener('click', event => { + document.addEventListener('click', (event) => { if (event.target && event.target !== inputField) removeParent(); if (event.target === inputField && isSearchField() && isSelectionOutsideCurrentTerm()) removeParent(); }); @@ -281,8 +283,10 @@ function listenAutocomplete() { fetch(`/autocomplete/compiled?vsn=2&key=${cacheKey}`, { credentials: 'omit', cache: 'force-cache' }) .then(handleError) - .then(resp => resp.arrayBuffer()) - .then(buf => localAc = new LocalAutocompleter(buf)); + .then((resp) => resp.arrayBuffer()) + .then((buf) => { + localAc = new LocalAutocompleter(buf); + }); } } diff --git a/assets/js/booru.js b/assets/js/booru.js index 3e1e7dac..45f30680 100644 --- a/assets/js/booru.js +++ b/assets/js/booru.js @@ -23,11 +23,11 @@ function persistTag(tagData) { */ function isStale(tag) { const now = new Date().getTime() / 1000; - return tag.fetchedAt === null || tag.fetchedAt < (now - 604800); + return tag.fetchedAt === null || tag.fetchedAt < now - 604800; } function clearTags() { - Object.keys(localStorage).forEach(key => { + Object.keys(localStorage).forEach((key) => { if (key.substring(0, 9) === 'bor_tags_') { store.remove(key); } @@ -40,11 +40,13 @@ function clearTags() { */ function isValidStoredTag(value) { if (value !== null && 'id' in value && 'name' in value && 'images' in value && 'spoiler_image_uri' in value) { - return typeof value.id === 'number' - && typeof value.name === 'string' - && typeof value.images === 'number' - && (value.spoiler_image_uri === null || typeof value.spoiler_image_uri === 'string') - && (value.fetchedAt === null || typeof value.fetchedAt === 'number'); + return ( + typeof value.id === 'number' && + typeof value.name === 'string' && + typeof value.images === 'number' && + (value.spoiler_image_uri === null || typeof value.spoiler_image_uri === 'string') && + (value.fetchedAt === null || typeof value.fetchedAt === 'number') + ); } return false; @@ -82,8 +84,8 @@ function fetchAndPersistTags(tagIds) { const remaining = tagIds.slice(41); fetch(`/fetch/tags?ids[]=${ids.join('&ids[]=')}`) - .then(response => response.json()) - .then(data => data.tags.forEach(tag => persistTag(tag))) + .then((response) => response.json()) + .then((data) => data.tags.forEach((tag) => persistTag(tag))) .then(() => fetchAndPersistTags(remaining)); } @@ -94,7 +96,7 @@ function fetchAndPersistTags(tagIds) { function fetchNewOrStaleTags(tagIds) { const fetchIds = []; - tagIds.forEach(t => { + tagIds.forEach((t) => { const stored = store.get(`bor_tags_${t}`); if (!stored || isStale(stored)) { fetchIds.push(t); @@ -112,17 +114,18 @@ function verifyTagsVersion(latest) { } function initializeFilters() { - const tags = window.booru.spoileredTagList - .concat(window.booru.hiddenTagList) - .filter((a, b, c) => c.indexOf(a) === b); + 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; } + try { + return JSON.parse(data); + } catch { + return data; + } } function loadBooruData() { diff --git a/assets/js/boorujs.js b/assets/js/boorujs.js index 6763358f..6ccfa0e4 100644 --- a/assets/js/boorujs.js +++ b/assets/js/boorujs.js @@ -11,40 +11,72 @@ import { addTag } from './tagsinput'; // Event types and any qualifying conditions - return true to not run action const types = { - click(event) { return event.button !== 0; /* Left-click only */ }, + click(event) { + return event.button !== 0; /* Left-click only */ + }, - change() { /* No qualifier */ }, + change() { + /* No qualifier */ + }, - fetchcomplete() { /* No qualifier */ }, + fetchcomplete() { + /* No qualifier */ + }, }; const actions = { - hide(data) { selectorCb(data.base, data.value, el => el.classList.add('hidden')); }, + hide(data) { + selectorCb(data.base, data.value, (el) => el.classList.add('hidden')); + }, - tabHide(data) { selectorCbChildren(data.base, data.value, el => el.classList.add('hidden')); }, + tabHide(data) { + selectorCbChildren(data.base, data.value, (el) => el.classList.add('hidden')); + }, - show(data) { selectorCb(data.base, data.value, el => el.classList.remove('hidden')); }, + show(data) { + selectorCb(data.base, data.value, (el) => el.classList.remove('hidden')); + }, - toggle(data) { selectorCb(data.base, data.value, el => el.classList.toggle('hidden')); }, + toggle(data) { + selectorCb(data.base, data.value, (el) => el.classList.toggle('hidden')); + }, - submit(data) { selectorCb(data.base, data.value, el => el.submit()); }, + submit(data) { + selectorCb(data.base, data.value, (el) => el.submit()); + }, - 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(); 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(); + }, - preventdefault() { /* The existence of this entry is enough */ }, + preventdefault() { + /* The existence of this entry is enough */ + }, addtag(data) { addTag(document.querySelector(data.el.closest('[data-target]').dataset.target), data.el.dataset.tagName); @@ -52,8 +84,8 @@ const actions = { tab(data) { const block = data.el.parentNode.parentNode, - newTab = $(`.block__tab[data-tab="${data.value}"]`), - loadTab = data.el.dataset.loadTab; + newTab = $(`.block__tab[data-tab="${data.value}"]`), + loadTab = data.el.dataset.loadTab; // Switch tab const selectedTab = block.querySelector('.selected'); @@ -70,16 +102,22 @@ const actions = { if (loadTab && !newTab.dataset.loaded) { fetchHtml(loadTab) .then(handleError) - .then(response => response.text()) - .then(response => newTab.innerHTML = response) - .then(() => newTab.dataset.loaded = true) - .catch(() => newTab.textContent = 'Error!'); + .then((response) => response.text()) + .then((response) => { + newTab.innerHTML = response; + }) + .then(() => { + newTab.dataset.loaded = true; + }) + .catch(() => { + newTab.textContent = 'Error!'; + }); } - }, - unfilter(data) { showBlock(data.el.closest('.image-show-container')); }, - + unfilter(data) { + showBlock(data.el.closest('.image-show-container')); + }, }; // Use this function to apply a callback to elements matching the selectors @@ -100,16 +138,14 @@ function selectorCbChildren(base = document, selector, cb) { function matchAttributes(event) { if (!types[event.type](event)) { for (const action in actions) { - const attr = `data-${event.type}-${action.toLowerCase()}`, - el = event.target && event.target.closest(`[${attr}]`), - value = el && el.getAttribute(attr); + el = event.target && event.target.closest(`[${attr}]`), + value = el && el.getAttribute(attr); if (el) { // Return true if you don't want to preventDefault actions[action]({ attr, el, value }) || event.preventDefault(); } - } } } diff --git a/assets/js/burger.ts b/assets/js/burger.ts index d82d032f..a22b06ad 100644 --- a/assets/js/burger.ts +++ b/assets/js/burger.ts @@ -40,7 +40,7 @@ function copyUserLinksTo(burger: HTMLElement) { } }; - $$('.js-burger-links').forEach(container => copy(container.children)); + $$('.js-burger-links').forEach((container) => copy(container.children)); } export function setupBurgerMenu() { @@ -53,14 +53,13 @@ export function setupBurgerMenu() { copyUserLinksTo(burger); - toggle.addEventListener('click', event => { + toggle.addEventListener('click', (event) => { event.stopPropagation(); event.preventDefault(); if (content.classList.contains('open')) { close(burger, content, body, root); - } - else { + } else { open(burger, content, body, root); } }); diff --git a/assets/js/captcha.ts b/assets/js/captcha.ts index 44d0d9b6..5c83cd14 100644 --- a/assets/js/captcha.ts +++ b/assets/js/captcha.ts @@ -5,8 +5,8 @@ import { clearEl, makeEl } from './utils/dom'; function insertCaptcha(_event: Event, target: HTMLInputElement) { const parentElement = assertNotNull(target.parentElement); - const script = makeEl('script', {src: 'https://hcaptcha.com/1/api.js', async: true, defer: true}); - const frame = makeEl('div', {className: 'h-captcha'}); + const script = makeEl('script', { src: 'https://hcaptcha.com/1/api.js', async: true, defer: true }); + const frame = makeEl('div', { className: 'h-captcha' }); frame.dataset.sitekey = target.dataset.sitekey; @@ -17,5 +17,5 @@ function insertCaptcha(_event: Event, target: HTMLInputElement) { } export function bindCaptchaLinks() { - delegate(document, 'click', {'.js-captcha': leftClick(insertCaptcha)}); + delegate(document, 'click', { '.js-captcha': leftClick(insertCaptcha) }); } diff --git a/assets/js/comment.js b/assets/js/comment.js index feb05b59..b60a6fec 100644 --- a/assets/js/comment.js +++ b/assets/js/comment.js @@ -18,25 +18,23 @@ function handleError(response) { } function commentPosted(response) { - const commentEditTab = $('#js-comment-form a[data-click-tab="write"]'), - commentEditForm = $('#js-comment-form'), - container = document.getElementById('comments'), - requestOk = response.ok; + const commentEditTab = $('#js-comment-form a[data-click-tab="write"]'), + commentEditForm = $('#js-comment-form'), + container = document.getElementById('comments'), + requestOk = response.ok; commentEditTab.click(); commentEditForm.reset(); if (requestOk) { - response.text().then(text => { + response.text().then((text) => { if (text.includes('
')) { window.location.reload(); - } - else { + } else { displayComments(container, text); } }); - } - else { + } else { window.location.reload(); window.scrollTo(0, 0); // Error message is displayed at the top of the page (flash) } @@ -44,10 +42,10 @@ function commentPosted(response) { function loadParentPost(event) { const clickedLink = event.target, - // Find the comment containing the link that was clicked - fullComment = clickedLink.closest('article.block'), - // Look for a potential image and comment ID - commentMatches = /(\w+)#comment_(\w+)$/.exec(clickedLink.getAttribute('href')); + // Find the comment containing the link that was clicked + fullComment = clickedLink.closest('article.block'), + // Look for a potential image and comment ID + commentMatches = /(\w+)#comment_(\w+)$/.exec(clickedLink.getAttribute('href')); // If the clicked link is already active, just clear the parent comments if (clickedLink.classList.contains('active_reply_link')) { @@ -58,11 +56,11 @@ function loadParentPost(event) { if (commentMatches) { // If the regex matched, get the image and comment ID - const [ , imageId, commentId ] = commentMatches; + const [, imageId, commentId] = commentMatches; fetchHtml(`/images/${imageId}/comments/${commentId}`) .then(handleError) - .then(data => { + .then((data) => { clearParentPost(clickedLink, fullComment); insertParentPost(data, clickedLink, fullComment); }); @@ -99,7 +97,7 @@ function clearParentPost(clickedLink, fullComment) { } // Remove class active_reply_link from all links in the comment - [].slice.call(fullComment.getElementsByClassName('active_reply_link')).forEach(link => { + [].slice.call(fullComment.getElementsByClassName('active_reply_link')).forEach((link) => { link.classList.remove('active_reply_link'); }); @@ -121,14 +119,17 @@ function displayComments(container, commentsHtml) { function loadComments(event) { const container = document.getElementById('comments'), - hasHref = event.target && event.target.getAttribute('href'), - hasHash = window.location.hash && window.location.hash.match(/#comment_([a-f0-9]+)/), - getURL = hasHref || (hasHash ? `${container.dataset.currentUrl}?comment_id=${window.location.hash.substring(9, window.location.hash.length)}` - : container.dataset.currentUrl); + hasHref = event.target && event.target.getAttribute('href'), + hasHash = window.location.hash && window.location.hash.match(/#comment_([a-f0-9]+)/), + getURL = + hasHref || + (hasHash + ? `${container.dataset.currentUrl}?comment_id=${window.location.hash.substring(9, window.location.hash.length)}` + : container.dataset.currentUrl); fetchHtml(getURL) .then(handleError) - .then(data => { + .then((data) => { displayComments(container, data); // Make sure the :target CSS selector applies to the inserted content @@ -143,17 +144,16 @@ function loadComments(event) { } function setupComments() { - const comments = document.getElementById('comments'), - hasHash = window.location.hash && window.location.hash.match(/^#comment_([a-f0-9]+)$/), - targetOnPage = hasHash ? Boolean($(window.location.hash)) : true; + const comments = document.getElementById('comments'), + hasHash = window.location.hash && window.location.hash.match(/^#comment_([a-f0-9]+)$/), + targetOnPage = hasHash ? Boolean($(window.location.hash)) : true; // Load comments over AJAX if we are on a page with element #comments if (comments) { if (!comments.dataset.loaded || !targetOnPage) { // There is no event associated with the initial load, so use false loadComments(false); - } - else { + } else { filterNode(comments); } } @@ -165,8 +165,9 @@ function setupComments() { '#js-refresh-comments': loadComments, }; - document.addEventListener('click', event => { - if (event.button === 0) { // Left-click only + document.addEventListener('click', (event) => { + if (event.button === 0) { + // Left-click only for (const target in targets) { if (event.target && event.target.closest(target)) { targets[target](event) && event.preventDefault(); @@ -175,7 +176,7 @@ function setupComments() { } }); - document.addEventListener('fetchcomplete', event => { + document.addEventListener('fetchcomplete', (event) => { if (event.target.id === 'js-comment-form') commentPosted(event.detail); }); } diff --git a/assets/js/duplicate_reports.ts b/assets/js/duplicate_reports.ts index 55cdfeb1..623aaf8b 100644 --- a/assets/js/duplicate_reports.ts +++ b/assets/js/duplicate_reports.ts @@ -15,7 +15,7 @@ export function setupDupeReports() { } function setupSwipe(swipe: SVGSVGElement) { - const [ clip, divider ] = $$('#clip rect, #divider', swipe); + const [clip, divider] = $$('#clip rect, #divider', swipe); const { width } = swipe.viewBox.baseVal; function moveDivider({ clientX }: MouseEvent) { diff --git a/assets/js/fp.ts b/assets/js/fp.ts index 65f0c583..25fc7bd9 100644 --- a/assets/js/fp.ts +++ b/assets/js/fp.ts @@ -11,19 +11,19 @@ const storageKey = 'cached_ses_value'; declare global { interface Keyboard { - getLayoutMap: () => Promise> + getLayoutMap: () => Promise>; } interface UserAgentData { - brands: [{brand: string, version: string}], - mobile: boolean, - platform: string, + brands: [{ brand: string; version: string }]; + mobile: boolean; + platform: string; } interface Navigator { - deviceMemory: number | undefined, - keyboard: Keyboard | undefined, - userAgentData: UserAgentData | undefined, + deviceMemory: number | undefined; + keyboard: Keyboard | undefined; + userAgentData: UserAgentData | undefined; } } @@ -45,10 +45,10 @@ function cyrb53(str: string, seed: number = 0x16fe7b0a): number { h2 = Math.imul(h2 ^ ch, 1597334677); } - h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507); - h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909); - h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507); - h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909); + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507); + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909); + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507); + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909); return 4294967296 * (2097151 & h2) + (h1 >>> 0); } @@ -102,8 +102,8 @@ function getUserAgentBrands(): string { // NB: Chromium implements GREASE protocol to prevent ossification of // the "Not a brand" string - see https://stackoverflow.com/a/64443187 brands = data.brands - .filter(e => !e.brand.match(/.*ot.*rand.*/gi)) - .map(e => `${e.brand}${e.version}`) + .filter((e) => !e.brand.match(/.*ot.*rand.*/gi)) + .map((e) => `${e.brand}${e.version}`) .sort() .join(''); } @@ -161,9 +161,7 @@ async function createFp(): Promise { new Date().getTimezoneOffset().toString(), ]; - return cyrb53(prints.join('')) - .toString(16) - .padStart(14, '0'); + return cyrb53(prints.join('')).toString(16).padStart(14, '0'); } /** diff --git a/assets/js/galleries.ts b/assets/js/galleries.ts index 386bb278..73f0a0b6 100644 --- a/assets/js/galleries.ts +++ b/assets/js/galleries.ts @@ -11,7 +11,7 @@ import { fetchJson } from './utils/requests'; export function setupGalleryEditing() { if (!$('.rearrange-button')) return; - const [ rearrangeEl, saveEl ] = $$('.rearrange-button'); + const [rearrangeEl, saveEl] = $$('.rearrange-button'); const sortableEl = assertNotNull($('#sortable')); const containerEl = assertNotNull($('.js-resizable-media-container')); @@ -22,7 +22,9 @@ export function setupGalleryEditing() { initDraggables(); - $$('.media-box', containerEl).forEach(i => i.draggable = true); + $$('.media-box', containerEl).forEach((i) => { + i.draggable = true; + }); rearrangeEl.addEventListener('click', () => { sortableEl.classList.add('editing'); @@ -33,8 +35,9 @@ export function setupGalleryEditing() { sortableEl.classList.remove('editing'); containerEl.classList.remove('drag-container'); - newImages = $$('.image-container', containerEl) - .map(i => parseInt(assertNotUndefined(i.dataset.imageId), 10)); + newImages = $$('.image-container', containerEl).map((i) => + parseInt(assertNotUndefined(i.dataset.imageId), 10), + ); // If nothing changed, don't bother. if (arraysEqual(newImages, oldImages)) return; @@ -43,7 +46,9 @@ export function setupGalleryEditing() { fetchJson('PATCH', reorderPath, { image_ids: newImages, - // copy the array again so that we have the newly updated set - }).then(() => oldImages = newImages.slice()); + // copy the array again so that we have the newly updated set + }).then(() => { + oldImages = newImages.slice(); + }); }); } diff --git a/assets/js/graph.ts b/assets/js/graph.ts index 5014600c..035e33ed 100644 --- a/assets/js/graph.ts +++ b/assets/js/graph.ts @@ -22,7 +22,7 @@ function graphSlice(el: SVGSVGElement, width: number, offset: number) { } function resizeGraphs() { - $$('#js-graph-svg').forEach(el => { + $$('#js-graph-svg').forEach((el) => { const parent: HTMLElement | null = el.parentElement; if (parent) { @@ -34,7 +34,7 @@ function resizeGraphs() { function scaleGraph(target: HTMLElement, min: number, max: number) { const targetSvg = $('#js-graph-svg', target); - if (!targetSvg) { return; } + if (!targetSvg) return; const cw = target.clientWidth; const diff = 100 - (max - min); @@ -47,14 +47,14 @@ function scaleGraph(target: HTMLElement, min: number, max: number) { } function setupSliders() { - $$('#js-graph-slider').forEach(el => { + $$('#js-graph-slider').forEach((el) => { const targetId = el.getAttribute('data-target'); - if (!targetId) { return; } + if (!targetId) return; const target = $(targetId); - if (!target) { return; } + if (!target) return; el.addEventListener('input', () => { const min = Number(el.getAttribute('valuemin') || '0'); diff --git a/assets/js/image_expansion.js b/assets/js/image_expansion.js index 7bd6cb26..78d90ee3 100644 --- a/assets/js/image_expansion.js +++ b/assets/js/image_expansion.js @@ -5,7 +5,7 @@ const imageVersions = { // [width, height] small: [320, 240], medium: [800, 600], - large: [1280, 1024] + large: [1280, 1024], }; /** @@ -14,7 +14,7 @@ const imageVersions = { */ function selectVersion(imageWidth, imageHeight, imageSize, imageMime) { let viewWidth = document.documentElement.clientWidth, - viewHeight = document.documentElement.clientHeight; + viewHeight = document.documentElement.clientHeight; // load hires if that's what you asked for if (store.get('serve_hidpi')) { @@ -31,9 +31,9 @@ function selectVersion(imageWidth, imageHeight, imageSize, imageMime) { // .find() is not supported in older browsers, using a loop for (let i = 0, versions = Object.keys(imageVersions); i < versions.length; ++i) { const version = versions[i], - dimensions = imageVersions[version], - versionWidth = Math.min(imageWidth, dimensions[0]), - versionHeight = Math.min(imageHeight, dimensions[1]); + dimensions = imageVersions[version], + versionWidth = Math.min(imageWidth, dimensions[0]), + versionHeight = Math.min(imageHeight, dimensions[1]); if (versionWidth > viewWidth || versionHeight > viewHeight) { return version; } @@ -57,11 +57,11 @@ function selectVersion(imageWidth, imageHeight, imageSize, imageMime) { */ function pickAndResize(elem) { const imageWidth = parseInt(elem.dataset.width, 10), - imageHeight = parseInt(elem.dataset.height, 10), - imageSize = parseInt(elem.dataset.imageSize, 10), - imageMime = elem.dataset.mimeType, - scaled = elem.dataset.scaled, - uris = JSON.parse(elem.dataset.uris); + imageHeight = parseInt(elem.dataset.height, 10), + imageSize = parseInt(elem.dataset.imageSize, 10), + imageMime = elem.dataset.mimeType, + scaled = elem.dataset.scaled, + uris = JSON.parse(elem.dataset.uris); let version = 'full'; @@ -91,7 +91,8 @@ function pickAndResize(elem) { if (imageFormat === 'mp4') { elem.classList.add('full-height'); - elem.insertAdjacentHTML('afterbegin', + elem.insertAdjacentHTML( + 'afterbegin', `` + `, ); - } - else if (imageFormat === 'webm') { - elem.insertAdjacentHTML('afterbegin', + } else if (imageFormat === 'webm') { + elem.insertAdjacentHTML( + 'afterbegin', `` + `, ); const video = elem.querySelector('video'); if (scaled === 'true') { video.className = 'image-scaled'; - } - else if (scaled === 'partscaled') { + } else if (scaled === 'partscaled') { video.className = 'image-partscaled'; } - } - else { + } else { let image; if (scaled === 'true') { image = ``; - } - else if (scaled === 'partscaled') { + } else if (scaled === 'partscaled') { image = ``; - } - else { + } else { image = ``; } if (elem.innerHTML === image) return; @@ -148,11 +145,9 @@ function bindImageForClick(target) { target.addEventListener('click', () => { if (target.getAttribute('data-scaled') === 'true') { target.setAttribute('data-scaled', 'partscaled'); - } - else if (target.getAttribute('data-scaled') === 'partscaled') { + } else if (target.getAttribute('data-scaled') === 'partscaled') { target.setAttribute('data-scaled', 'false'); - } - else { + } else { target.setAttribute('data-scaled', 'true'); } @@ -161,7 +156,7 @@ function bindImageForClick(target) { } function bindImageTarget(node = document) { - $$('.image-target', node).forEach(target => { + $$('.image-target', node).forEach((target) => { pickAndResize(target); if (target.dataset.mimeType === 'video/webm') { diff --git a/assets/js/imagesclientside.ts b/assets/js/imagesclientside.ts index add108c9..2d6d8141 100644 --- a/assets/js/imagesclientside.ts +++ b/assets/js/imagesclientside.ts @@ -12,12 +12,7 @@ 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 { +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); @@ -56,49 +51,48 @@ function bannerImage(tagsHit: TagData[]) { // 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] (Complex Filter)'; + const bannerText = type === 'tags' ? `[HIDDEN] ${displayTags(tagsHit)}` : '[HIDDEN] (Complex Filter)'; hideThumb(img, bannerImage(tagsHit), bannerText); } function spoilerThumbTyped(img: HTMLDivElement, tagsHit: TagData[], type: CallbackType) { - const bannerText = type === 'tags' ? displayTags(tagsHit) - : '(Complex Filter)'; + const bannerText = type === 'tags' ? displayTags(tagsHit) : '(Complex Filter)'; spoilerThumb(img, bannerImage(tagsHit), bannerText); } function hideBlockTyped(img: HTMLDivElement, tagsHit: TagData[], type: CallbackType) { - const bannerText = type === 'tags' ? `This image is tagged ${escapeHtml(tagsHit[0].name)}, which is hidden by ` - : 'This image was hidden by a complex tag expression in '; + const bannerText = + type === 'tags' + ? `This image is tagged ${escapeHtml(tagsHit[0].name)}, 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 ${escapeHtml(tagsHit[0].name)}, which is spoilered by ` - : 'This image was spoilered by a complex tag expression in '; + const bannerText = + type === 'tags' + ? `This image is tagged ${escapeHtml(tagsHit[0].name)}, 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) { - const hiddenTags = getHiddenTags(), spoileredTags = getSpoileredTags(); + 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 => !run(img, hiddenTags, hiddenFilter, hideThumbTyped)) - .filter(img => !run(img, spoileredTags, spoileredFilter, spoilerThumbTyped)) - .forEach(img => showThumb(img)); + .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 $$('.image-show-container', node) - .filter(img => !run(img, hiddenTags, hiddenFilter, hideBlockTyped)) - .filter(img => !run(img, spoileredTags, spoileredFilter, spoilerBlockTyped)) - .forEach(img => showBlock(img)); + .filter((img) => !run(img, hiddenTags, hiddenFilter, hideBlockTyped)) + .filter((img) => !run(img, spoileredTags, spoileredFilter, spoilerBlockTyped)) + .forEach((img) => showBlock(img)); } export function initImagesClientside() { diff --git a/assets/js/input-duplicator.ts b/assets/js/input-duplicator.ts index e82c892d..6ce8a502 100644 --- a/assets/js/input-duplicator.ts +++ b/assets/js/input-duplicator.ts @@ -13,7 +13,7 @@ export function inputDuplicatorCreator({ addButtonSelector, fieldSelector, maxInputCountSelector, - removeButtonSelector + removeButtonSelector, }: InputDuplicatorOptions) { const addButton = $(addButtonSelector); if (!addButton) { @@ -35,14 +35,13 @@ export function inputDuplicatorCreator({ }; delegate(form, 'click', { - [removeButtonSelector]: leftClick(fieldRemover) + [removeButtonSelector]: leftClick(fieldRemover), }); - const maxOptionCountElement = assertNotNull($(maxInputCountSelector, form)); const maxOptionCount = parseInt(maxOptionCountElement.innerHTML, 10); - addButton.addEventListener('click', e => { + addButton.addEventListener('click', (e) => { e.preventDefault(); const existingFields = $$(fieldSelector, form); @@ -53,7 +52,7 @@ export function inputDuplicatorCreator({ const prevField = existingFields[existingFieldsLength - 1]; const prevFieldCopy = prevField.cloneNode(true) as HTMLElement; - $$('input', prevFieldCopy).forEach(prevFieldCopyInput => { + $$('input', prevFieldCopy).forEach((prevFieldCopyInput) => { // Reset new input's value prevFieldCopyInput.value = ''; prevFieldCopyInput.removeAttribute('value'); diff --git a/assets/js/interactions.js b/assets/js/interactions.js index 95d3bad0..fac8b5e9 100644 --- a/assets/js/interactions.js +++ b/assets/js/interactions.js @@ -6,18 +6,22 @@ import { fetchJson } from './utils/requests'; import { $ } from './utils/dom'; const endpoints = { - vote(imageId) { return `/images/${imageId}/vote`; }, - fave(imageId) { return `/images/${imageId}/fave`; }, - hide(imageId) { return `/images/${imageId}/hide`; }, + vote(imageId) { + return `/images/${imageId}/vote`; + }, + fave(imageId) { + return `/images/${imageId}/fave`; + }, + hide(imageId) { + return `/images/${imageId}/hide`; + }, }; -const spoilerDownvoteMsg = - 'Neigh! - Remove spoilered tags from your filters to downvote from thumbnails'; +const spoilerDownvoteMsg = 'Neigh! - Remove spoilered tags from your filters to downvote from thumbnails'; /* Quick helper function to less verbosely iterate a QSA */ function onImage(id, selector, cb) { - [].forEach.call( - document.querySelectorAll(`${selector}[data-image-id="${id}"]`), cb); + [].forEach.call(document.querySelectorAll(`${selector}[data-image-id="${id}"]`), cb); } /* Since JS modifications to webpages, except form inputs, are not stored @@ -35,28 +39,35 @@ function modifyCache(callback) { } function cacheStatus(imageId, interactionType, value) { - modifyCache(cache => { + modifyCache((cache) => { cache[`${imageId}${interactionType}`] = { imageId, interactionType, value }; return cache; }); } function uncacheStatus(imageId, interactionType) { - modifyCache(cache => { + modifyCache((cache) => { delete cache[`${imageId}${interactionType}`]; return cache; }); } function setScore(imageId, data) { - onImage(imageId, '.score', - el => el.textContent = data.score); - onImage(imageId, '.favorites', - el => el.textContent = data.faves); - onImage(imageId, '.upvotes', - el => el.textContent = data.upvotes); - onImage(imageId, '.downvotes', - el => el.textContent = data.downvotes); + onImage(imageId, '.score', (el) => { + el.textContent = data.score; + }); + + onImage(imageId, '.favorites', (el) => { + el.textContent = data.faves; + }); + + onImage(imageId, '.upvotes', (el) => { + el.textContent = data.upvotes; + }); + + onImage(imageId, '.downvotes', (el) => { + el.textContent = data.downvotes; + }); } /* These change the visual appearance of interaction links. @@ -64,58 +75,50 @@ function setScore(imageId, data) { function showUpvoted(imageId) { cacheStatus(imageId, 'voted', 'up'); - onImage(imageId, '.interaction--upvote', - el => el.classList.add('active')); + onImage(imageId, '.interaction--upvote', (el) => el.classList.add('active')); } function showDownvoted(imageId) { cacheStatus(imageId, 'voted', 'down'); - onImage(imageId, '.interaction--downvote', - el => el.classList.add('active')); + onImage(imageId, '.interaction--downvote', (el) => el.classList.add('active')); } function showFaved(imageId) { cacheStatus(imageId, 'faved', ''); - onImage(imageId, '.interaction--fave', - el => el.classList.add('active')); + onImage(imageId, '.interaction--fave', (el) => el.classList.add('active')); } function showHidden(imageId) { cacheStatus(imageId, 'hidden', ''); - onImage(imageId, '.interaction--hide', - el => el.classList.add('active')); + onImage(imageId, '.interaction--hide', (el) => el.classList.add('active')); } function resetVoted(imageId) { uncacheStatus(imageId, 'voted'); - onImage(imageId, '.interaction--upvote', - el => el.classList.remove('active')); + onImage(imageId, '.interaction--upvote', (el) => el.classList.remove('active')); - onImage(imageId, '.interaction--downvote', - el => el.classList.remove('active')); + onImage(imageId, '.interaction--downvote', (el) => el.classList.remove('active')); } function resetFaved(imageId) { uncacheStatus(imageId, 'faved'); - onImage(imageId, '.interaction--fave', - el => el.classList.remove('active')); + onImage(imageId, '.interaction--fave', (el) => el.classList.remove('active')); } function resetHidden(imageId) { uncacheStatus(imageId, 'hidden'); - onImage(imageId, '.interaction--hide', - el => el.classList.remove('active')); + onImage(imageId, '.interaction--hide', (el) => el.classList.remove('active')); } function interact(type, imageId, method, data = {}) { return fetchJson(method, endpoints[type](imageId), data) - .then(res => res.json()) - .then(res => setScore(imageId, res)); + .then((res) => res.json()) + .then((res) => setScore(imageId, res)); } function displayInteractionSet(interactions) { - interactions.forEach(i => { + interactions.forEach((i) => { switch (i.interaction_type) { case 'faved': showFaved(i.image_id); @@ -131,7 +134,6 @@ function displayInteractionSet(interactions) { } function loadInteractions() { - /* Set up the actual interactions */ displayInteractionSet(window.booru.interactions); displayInteractionSet(getCache()); @@ -141,68 +143,71 @@ function loadInteractions() { if (!document.getElementById('imagelist-container')) return; /* Users will blind downvote without this */ - window.booru.imagesWithDownvotingDisabled.forEach(i => { - onImage(i, '.interaction--downvote', a => { - + window.booru.imagesWithDownvotingDisabled.forEach((i) => { + onImage(i, '.interaction--downvote', (a) => { // TODO Use a 'js-' class to target these instead const icon = a.querySelector('i') || a.querySelector('.oc-icon-small'); icon.setAttribute('title', spoilerDownvoteMsg); a.classList.add('disabled'); - a.addEventListener('click', event => { - event.stopPropagation(); - event.preventDefault(); - }, true); - + a.addEventListener( + 'click', + (event) => { + event.stopPropagation(); + event.preventDefault(); + }, + true, + ); }); }); - } const targets = { - /* Active-state targets first */ '.interaction--upvote.active'(imageId) { - interact('vote', imageId, 'DELETE') - .then(() => resetVoted(imageId)); + interact('vote', imageId, 'DELETE').then(() => resetVoted(imageId)); }, '.interaction--downvote.active'(imageId) { - interact('vote', imageId, 'DELETE') - .then(() => resetVoted(imageId)); + interact('vote', imageId, 'DELETE').then(() => resetVoted(imageId)); }, '.interaction--fave.active'(imageId) { - interact('fave', imageId, 'DELETE') - .then(() => resetFaved(imageId)); + interact('fave', imageId, 'DELETE').then(() => resetFaved(imageId)); }, '.interaction--hide.active'(imageId) { - interact('hide', imageId, 'DELETE') - .then(() => resetHidden(imageId)); + interact('hide', imageId, 'DELETE').then(() => resetHidden(imageId)); }, /* Inactive targets */ '.interaction--upvote:not(.active)'(imageId) { - interact('vote', imageId, 'POST', { up: true }) - .then(() => { resetVoted(imageId); showUpvoted(imageId); }); + interact('vote', imageId, 'POST', { up: true }).then(() => { + resetVoted(imageId); + showUpvoted(imageId); + }); }, '.interaction--downvote:not(.active)'(imageId) { - interact('vote', imageId, 'POST', { up: false }) - .then(() => { resetVoted(imageId); showDownvoted(imageId); }); + interact('vote', imageId, 'POST', { up: false }).then(() => { + resetVoted(imageId); + showDownvoted(imageId); + }); }, '.interaction--fave:not(.active)'(imageId) { - interact('fave', imageId, 'POST') - .then(() => { resetVoted(imageId); showFaved(imageId); showUpvoted(imageId); }); + interact('fave', imageId, 'POST').then(() => { + resetVoted(imageId); + showFaved(imageId); + showUpvoted(imageId); + }); }, '.interaction--hide:not(.active)'(imageId) { - interact('hide', imageId, 'POST') - .then(() => { showHidden(imageId); }); + interact('hide', imageId, 'POST').then(() => { + showHidden(imageId); + }); }, - }; function bindInteractions() { - document.addEventListener('click', event => { - - if (event.button === 0) { // Is it a left-click? + document.addEventListener('click', (event) => { + if (event.button === 0) { + // Is it a left-click? for (const target in targets) { /* Event delegation doesn't quite grab what we want here. */ const link = event.target && event.target.closest(target); @@ -213,21 +218,20 @@ function bindInteractions() { } } } - }); } function loggedOutInteractions() { - [].forEach.call(document.querySelectorAll('.interaction--fave,.interaction--upvote,.interaction--downvote'), - a => a.setAttribute('href', '/sessions/new')); + [].forEach.call(document.querySelectorAll('.interaction--fave,.interaction--upvote,.interaction--downvote'), (a) => + a.setAttribute('href', '/sessions/new'), + ); } function setupInteractions() { if (window.booru.userIsSignedIn) { bindInteractions(); loadInteractions(); - } - else { + } else { loggedOutInteractions(); } } diff --git a/assets/js/markdowntoolbar.js b/assets/js/markdowntoolbar.js index 0c8c7d8a..93ce1af9 100644 --- a/assets/js/markdowntoolbar.js +++ b/assets/js/markdowntoolbar.js @@ -7,19 +7,19 @@ import { $, $$ } from './utils/dom'; const markdownSyntax = { bold: { action: wrapSelection, - options: { prefix: '**', shortcutKey: 'b' } + options: { prefix: '**', shortcutKey: 'b' }, }, italics: { action: wrapSelection, - options: { prefix: '*', shortcutKey: 'i' } + options: { prefix: '*', shortcutKey: 'i' }, }, under: { action: wrapSelection, - options: { prefix: '__', shortcutKey: 'u' } + options: { prefix: '__', shortcutKey: 'u' }, }, spoiler: { action: wrapSelection, - options: { prefix: '||', shortcutKey: 's' } + options: { prefix: '||', shortcutKey: 's' }, }, code: { action: wrapSelectionOrLines, @@ -29,57 +29,56 @@ const markdownSyntax = { prefixMultiline: '```\n', suffixMultiline: '\n```', singleWrap: true, - shortcutKey: 'e' - } + shortcutKey: 'e', + }, }, strike: { action: wrapSelection, - options: { prefix: '~~' } + options: { prefix: '~~' }, }, superscript: { action: wrapSelection, - options: { prefix: '^' } + options: { prefix: '^' }, }, subscript: { action: wrapSelection, - options: { prefix: '%' } + options: { prefix: '%' }, }, quote: { action: wrapLines, - options: { prefix: '> ' } + options: { prefix: '> ' }, }, link: { action: insertLink, - options: { shortcutKey: 'l' } + options: { shortcutKey: 'l' }, }, image: { action: insertLink, - options: { image: true, shortcutKey: 'k' } + options: { image: true, shortcutKey: 'k' }, }, escape: { action: escapeSelection, - options: { escapeChar: '\\' } - } + options: { escapeChar: '\\' }, + }, }; function getSelections(textarea, linesOnly = false) { let { selectionStart, selectionEnd } = textarea, - selection = textarea.value.substring(selectionStart, selectionEnd), - leadingSpace = '', - trailingSpace = '', - caret; + selection = textarea.value.substring(selectionStart, selectionEnd), + leadingSpace = '', + trailingSpace = '', + caret; const processLinesOnly = linesOnly instanceof RegExp ? linesOnly.test(selection) : linesOnly; if (processLinesOnly) { const explorer = /\n/g; let startNewlineIndex = 0, - endNewlineIndex = textarea.value.length; + endNewlineIndex = textarea.value.length; while (explorer.exec(textarea.value)) { const { lastIndex } = explorer; if (lastIndex <= selectionStart) { startNewlineIndex = lastIndex; - } - else if (lastIndex > selectionEnd) { + } else if (lastIndex > selectionEnd) { endNewlineIndex = lastIndex - 1; break; } @@ -96,8 +95,7 @@ function getSelections(textarea, linesOnly = false) { } selectionEnd = endNewlineIndex; selection = textarea.value.substring(selectionStart, selectionEnd); - } - else { + } else { // Deselect trailing space and line break for (caret = selection.length - 1; caret > 0; caret--) { if (selection[caret] !== ' ' && selection[caret] !== '\n') break; @@ -117,22 +115,23 @@ function getSelections(textarea, linesOnly = false) { processLinesOnly, selectedText: selection, beforeSelection: textarea.value.substring(0, selectionStart) + leadingSpace, - afterSelection: trailingSpace + textarea.value.substring(selectionEnd) + afterSelection: trailingSpace + textarea.value.substring(selectionEnd), }; } function transformSelection(textarea, transformer, eachLine) { const { selectedText, beforeSelection, afterSelection, processLinesOnly } = getSelections(textarea, eachLine), - // For long comments, record scrollbar position to restore it later - { scrollTop } = textarea; + // For long comments, record scrollbar position to restore it later + { scrollTop } = textarea; const { newText, caretOffset } = transformer(selectedText, processLinesOnly); textarea.value = beforeSelection + newText + afterSelection; - const newSelectionStart = caretOffset >= 1 - ? beforeSelection.length + caretOffset - : textarea.value.length - afterSelection.length - caretOffset; + const newSelectionStart = + caretOffset >= 1 + ? beforeSelection.length + caretOffset + : textarea.value.length - afterSelection.length - caretOffset; textarea.selectionStart = newSelectionStart; textarea.selectionEnd = newSelectionStart; @@ -151,19 +150,19 @@ function insertLink(textarea, options) { } const prefix = options.image ? '![' : '[', - suffix = `](${hyperlink})`; + suffix = `](${hyperlink})`; wrapSelection(textarea, { prefix, suffix }); } function wrapSelection(textarea, options) { - transformSelection(textarea, selectedText => { + transformSelection(textarea, (selectedText) => { const { text = selectedText, prefix = '', suffix = options.prefix } = options, - emptyText = text === ''; + emptyText = text === ''; let newText = text; if (!emptyText) { - newText = text.replace(/(\n{2,})/g, match => { + newText = text.replace(/(\n{2,})/g, (match) => { return suffix + match + prefix; }); } @@ -172,26 +171,33 @@ function wrapSelection(textarea, options) { return { newText, - caretOffset: emptyText ? prefix.length : newText.length + caretOffset: emptyText ? prefix.length : newText.length, }; }); } function wrapLines(textarea, options, eachLine = true) { - transformSelection(textarea, (selectedText, processLinesOnly) => { - const { text = selectedText, singleWrap = false } = options, - prefix = (processLinesOnly && options.prefixMultiline) || options.prefix || '', - suffix = (processLinesOnly && options.suffixMultiline) || options.suffix || '', - emptyText = text === ''; - let newText = singleWrap - ? prefix + text.trim() + suffix - : text.split(/\n/g).map(line => prefix + line.trim() + suffix).join('\n'); + transformSelection( + textarea, + (selectedText, processLinesOnly) => { + const { text = selectedText, singleWrap = false } = options, + prefix = (processLinesOnly && options.prefixMultiline) || options.prefix || '', + suffix = (processLinesOnly && options.suffixMultiline) || options.suffix || '', + emptyText = text === ''; + let newText = singleWrap + ? prefix + text.trim() + suffix + : text + .split(/\n/g) + .map((line) => prefix + line.trim() + suffix) + .join('\n'); - // Force a space at the end of lines with only blockquote markers - newText = newText.replace(/^((?:>\s+)*)>$/gm, '$1> '); + // Force a space at the end of lines with only blockquote markers + newText = newText.replace(/^((?:>\s+)*)>$/gm, '$1> '); - return { newText, caretOffset: emptyText ? prefix.length : newText.length }; - }, eachLine); + return { newText, caretOffset: emptyText ? prefix.length : newText.length }; + }, + eachLine, + ); } function wrapSelectionOrLines(textarea, options) { @@ -199,9 +205,9 @@ function wrapSelectionOrLines(textarea, options) { } function escapeSelection(textarea, options) { - transformSelection(textarea, selectedText => { + transformSelection(textarea, (selectedText) => { const { text = selectedText } = options, - emptyText = text === ''; + emptyText = text === ''; if (emptyText) return; @@ -209,7 +215,7 @@ function escapeSelection(textarea, options) { return { newText, - caretOffset: newText.length + caretOffset: newText.length, }; }); } @@ -218,20 +224,27 @@ function clickHandler(event) { const button = event.target.closest('.communication__toolbar__button'); if (!button) return; const toolbar = button.closest('.communication__toolbar'), - // There may be multiple toolbars present on the page, - // in the case of image pages with description edit active - // we target the textarea that shares the same parent as the toolbar - textarea = $('.js-toolbar-input', toolbar.parentNode), - id = button.dataset.syntaxId; + // There may be multiple toolbars present on the page, + // in the case of image pages with description edit active + // we target the textarea that shares the same parent as the toolbar + textarea = $('.js-toolbar-input', toolbar.parentNode), + id = button.dataset.syntaxId; markdownSyntax[id].action(textarea, markdownSyntax[id].options); textarea.focus(); } function shortcutHandler(event) { - if (!event.ctrlKey || (window.navigator.platform === 'MacIntel' && !event.metaKey) || event.shiftKey || event.altKey) return; + if ( + !event.ctrlKey || + (window.navigator.platform === 'MacIntel' && !event.metaKey) || + event.shiftKey || + event.altKey + ) { + return; + } const textarea = event.target, - key = event.key.toLowerCase(); + key = event.key.toLowerCase(); for (const id in markdownSyntax) { if (key === markdownSyntax[id].options.shortcutKey) { @@ -242,10 +255,10 @@ function shortcutHandler(event) { } function setupToolbar() { - $$('.communication__toolbar').forEach(toolbar => { + $$('.communication__toolbar').forEach((toolbar) => { toolbar.addEventListener('click', clickHandler); }); - $$('.js-toolbar-input').forEach(textarea => { + $$('.js-toolbar-input').forEach((textarea) => { textarea.addEventListener('keydown', shortcutHandler); }); } diff --git a/assets/js/misc.ts b/assets/js/misc.ts index 25c61212..ef9eb7f9 100644 --- a/assets/js/misc.ts +++ b/assets/js/misc.ts @@ -9,10 +9,10 @@ import '../types/ujs'; let touchMoved = false; -function formResult({target, detail}: FetchcompleteEvent) { +function formResult({ target, detail }: FetchcompleteEvent) { const elements: Record = { '#description-form': '.image-description', - '#uploader-form': '.image-uploader' + '#uploader-form': '.image-uploader', }; function showResult(formEl: HTMLFormElement, resultEl: HTMLElement, response: string) { @@ -20,17 +20,17 @@ function formResult({target, detail}: FetchcompleteEvent) { hideEl(formEl); showEl(resultEl); - $$('input[type="submit"],button', formEl).forEach(button => { + $$('input[type="submit"],button', formEl).forEach((button) => { button.disabled = false; }); } - for (const [ formSelector, resultSelector ] of Object.entries(elements)) { + for (const [formSelector, resultSelector] of Object.entries(elements)) { if (target.matches(formSelector)) { const form = assertType(target, HTMLFormElement); const result = assertNotNull($(resultSelector)); - detail.text().then(text => showResult(form, result, text)); + detail.text().then((text) => showResult(form, result, text)); } } } @@ -85,11 +85,13 @@ export function setupEvents() { } if (store.get('hide_score')) { - $$('.upvotes,.score,.downvotes').forEach(s => hideEl(s)); + $$('.upvotes,.score,.downvotes').forEach((s) => hideEl(s)); } document.addEventListener('fetchcomplete', formResult); document.addEventListener('click', revealSpoiler); document.addEventListener('touchend', revealSpoiler); - document.addEventListener('touchmove', () => touchMoved = true); + document.addEventListener('touchmove', () => { + touchMoved = true; + }); } diff --git a/assets/js/notifications.ts b/assets/js/notifications.ts index d76cf533..4d4757b9 100644 --- a/assets/js/notifications.ts +++ b/assets/js/notifications.ts @@ -9,17 +9,17 @@ import { assertNotNull, assertNotUndefined } from './utils/assert'; import store from './utils/store'; const NOTIFICATION_INTERVAL = 600000, - NOTIFICATION_EXPIRES = 300000; + NOTIFICATION_EXPIRES = 300000; function bindSubscriptionLinks() { delegate(document, 'fetchcomplete', { - '.js-subscription-link': event => { + '.js-subscription-link': (event) => { const target = assertNotNull(event.target.closest('.js-subscription-target')); - event.detail.text().then(text => { + event.detail.text().then((text) => { target.outerHTML = text; }); - } + }, }); } @@ -30,7 +30,7 @@ function getNewNotifications() { fetchJson('GET', '/notifications/unread') .then(handleError) - .then(response => response.json()) + .then((response) => response.json()) .then(({ notifications }) => { updateNotificationTicker(notifications); storeNotificationCount(notifications); diff --git a/assets/js/pmwarning.ts b/assets/js/pmwarning.ts index 23772dff..a8e0bf3f 100644 --- a/assets/js/pmwarning.ts +++ b/assets/js/pmwarning.ts @@ -18,8 +18,7 @@ export function warnAboutPMs() { if (value.match(imageEmbedRegex)) { showEl(warning); - } - else if (!warning.classList.contains('hidden')) { + } else if (!warning.classList.contains('hidden')) { hideEl(warning); } }); diff --git a/assets/js/preview.js b/assets/js/preview.js index a3968762..6988b3c0 100644 --- a/assets/js/preview.js +++ b/assets/js/preview.js @@ -47,7 +47,7 @@ function getPreview(body, anonymous, previewLoading, previewIdle, previewContent fetchJson('POST', path, { body, anonymous }) .then(handleError) - .then(data => { + .then((data) => { previewContent.innerHTML = data; filterNode(previewContent); bindImageTarget(previewContent); @@ -110,13 +110,14 @@ function setupPreviews() { // Fire handler for automatic resizing if textarea contains text on page load (e.g. editing) if (textarea.value) textarea.dispatchEvent(new Event('change')); - previewAnon && previewAnon.addEventListener('click', () => { - if (previewContent.classList.contains('hidden')) return; + previewAnon && + previewAnon.addEventListener('click', () => { + if (previewContent.classList.contains('hidden')) return; - updatePreview(); - }); + updatePreview(); + }); - document.addEventListener('click', event => { + document.addEventListener('click', (event) => { if (event.target && event.target.closest('.post-reply')) { const link = event.target.closest('.post-reply'); commentReply(link.dataset.author, link.getAttribute('href'), textarea, link.dataset.post); diff --git a/assets/js/query/__tests__/date.spec.ts b/assets/js/query/__tests__/date.spec.ts index 0c205d4d..d9f8336d 100644 --- a/assets/js/query/__tests__/date.spec.ts +++ b/assets/js/query/__tests__/date.spec.ts @@ -97,7 +97,9 @@ describe('Date parsing', () => { }); it('should not match malformed absolute date expressions', () => { - expect(() => makeDateMatcher('2024-06-21T06:21:30+01:3020', 'eq')).toThrow('Cannot parse date string: 2024-06-21T06:21:30+01:3020'); + expect(() => makeDateMatcher('2024-06-21T06:21:30+01:3020', 'eq')).toThrow( + 'Cannot parse date string: 2024-06-21T06:21:30+01:3020', + ); }); it('should not match malformed relative date expressions', () => { diff --git a/assets/js/query/__tests__/user.spec.ts b/assets/js/query/__tests__/user.spec.ts index 52545d0c..044b13ea 100644 --- a/assets/js/query/__tests__/user.spec.ts +++ b/assets/js/query/__tests__/user.spec.ts @@ -4,10 +4,10 @@ describe('User field parsing', () => { beforeEach(() => { /* eslint-disable camelcase */ window.booru.interactions = [ - {image_id: 0, user_id: 0, interaction_type: 'faved', value: null}, - {image_id: 0, user_id: 0, interaction_type: 'voted', value: 'up'}, - {image_id: 1, user_id: 0, interaction_type: 'voted', value: 'down'}, - {image_id: 2, user_id: 0, interaction_type: 'hidden', value: null}, + { image_id: 0, user_id: 0, interaction_type: 'faved', value: null }, + { image_id: 0, user_id: 0, interaction_type: 'voted', value: 'up' }, + { image_id: 1, user_id: 0, interaction_type: 'voted', value: 'down' }, + { image_id: 2, user_id: 0, interaction_type: 'hidden', value: null }, ]; /* eslint-enable camelcase */ }); diff --git a/assets/js/query/boolean.ts b/assets/js/query/boolean.ts index 68a0b107..6f2afe96 100644 --- a/assets/js/query/boolean.ts +++ b/assets/js/query/boolean.ts @@ -1,11 +1,11 @@ import { AstMatcher } from './types'; export function matchAny(...matchers: AstMatcher[]): AstMatcher { - return (e: HTMLElement) => matchers.some(matcher => matcher(e)); + return (e: HTMLElement) => matchers.some((matcher) => matcher(e)); } export function matchAll(...matchers: AstMatcher[]): AstMatcher { - return (e: HTMLElement) => matchers.every(matcher => matcher(e)); + return (e: HTMLElement) => matchers.every((matcher) => matcher(e)); } export function matchNot(matcher: AstMatcher): AstMatcher { diff --git a/assets/js/query/date.ts b/assets/js/query/date.ts index b2dd4033..17b1e589 100644 --- a/assets/js/query/date.ts +++ b/assets/js/query/date.ts @@ -17,16 +17,16 @@ function makeMatcher(bottomDate: PosixTimeMs, topDate: PosixTimeMs, qual: RangeE // done compared to numeric ranges. switch (qual) { case 'lte': - return v => new Date(v).getTime() < topDate; + return (v) => new Date(v).getTime() < topDate; case 'gte': - return v => new Date(v).getTime() >= bottomDate; + return (v) => new Date(v).getTime() >= bottomDate; case 'lt': - return v => new Date(v).getTime() < bottomDate; + return (v) => new Date(v).getTime() < bottomDate; case 'gt': - return v => new Date(v).getTime() >= topDate; + return (v) => new Date(v).getTime() >= topDate; case 'eq': default: - return v => { + return (v) => { const t = new Date(v).getTime(); return t >= bottomDate && t < topDate; }; @@ -44,7 +44,7 @@ function makeRelativeDateMatcher(dateVal: string, qual: RangeEqualQualifier): Fi day: 86400000, week: 604800000, month: 2592000000, - year: 31536000000 + year: 31536000000, }; const amount = parseInt(match[1], 10); @@ -58,14 +58,7 @@ function makeRelativeDateMatcher(dateVal: string, qual: RangeEqualQualifier): Fi } function makeAbsoluteDateMatcher(dateVal: string, qual: RangeEqualQualifier): FieldMatcher { - const parseRes: RegExp[] = [ - /^(\d{4})/, - /^-(\d{2})/, - /^-(\d{2})/, - /^(?:\s+|T|t)(\d{2})/, - /^:(\d{2})/, - /^:(\d{2})/ - ]; + const parseRes: RegExp[] = [/^(\d{4})/, /^-(\d{2})/, /^-(\d{2})/, /^(?:\s+|T|t)(\d{2})/, /^:(\d{2})/, /^:(\d{2})/]; const timeZoneOffset: TimeZoneOffset = [0, 0]; const timeData: AbsoluteDate = [0, 0, 1, 0, 0, 0]; @@ -81,8 +74,7 @@ function makeAbsoluteDateMatcher(dateVal: string, qual: RangeEqualQualifier): Fi timeZoneOffset[1] *= -1; } localDateVal = localDateVal.substring(0, localDateVal.length - 6); - } - else { + } else { localDateVal = localDateVal.replace(/[Zz]$/, ''); } @@ -97,16 +89,14 @@ function makeAbsoluteDateMatcher(dateVal: string, qual: RangeEqualQualifier): Fi if (matchIndex === 1) { // Months are offset by 1. timeData[matchIndex] = parseInt(componentMatch[1], 10) - 1; - } - else { + } else { // All other components are not offset. timeData[matchIndex] = parseInt(componentMatch[1], 10); } // Truncate string. localDateVal = localDateVal.substring(componentMatch[0].length); - } - else { + } else { throw new ParseError(`Cannot parse date string: ${origDateVal}`); } } diff --git a/assets/js/query/fields.ts b/assets/js/query/fields.ts index 0c1f82e0..0b666f7c 100644 --- a/assets/js/query/fields.ts +++ b/assets/js/query/fields.ts @@ -2,16 +2,23 @@ import { FieldName } from './types'; type AttributeName = string; -export const numberFields: FieldName[] = - ['id', 'width', 'height', 'aspect_ratio', - 'comment_count', 'score', 'upvotes', 'downvotes', - 'faves', 'tag_count', 'score']; +export const numberFields: FieldName[] = [ + 'id', + 'width', + 'height', + 'aspect_ratio', + 'comment_count', + 'score', + 'upvotes', + 'downvotes', + 'faves', + 'tag_count', + 'score', +]; export const dateFields: FieldName[] = ['created_at']; -export const literalFields = - ['tags', 'orig_sha512_hash', 'sha512_hash', - 'uploader', 'source_url', 'description']; +export const literalFields = ['tags', 'orig_sha512_hash', 'sha512_hash', 'uploader', 'source_url', 'description']; export const termSpaceToImageField: Record = { tags: 'data-image-tag-aliases', @@ -32,7 +39,7 @@ export const termSpaceToImageField: Record = { faves: 'data-faves', sha512_hash: 'data-sha512', orig_sha512_hash: 'data-orig-sha512', - created_at: 'data-created-at' + created_at: 'data-created-at', /* eslint-enable camelcase */ }; diff --git a/assets/js/query/lex.ts b/assets/js/query/lex.ts index 2c950bd1..80a2ce98 100644 --- a/assets/js/query/lex.ts +++ b/assets/js/query/lex.ts @@ -17,7 +17,7 @@ const tokenList: Token[] = [ ['not_op', /^\s*[!-]\s*/], ['space', /^\s+/], ['word', /^(?:\\[\s,()^~]|[^\s,()^~])+/], - ['word', /^(?:\\[\s,()]|[^\s,()])+/] + ['word', /^(?:\\[\s,()]|[^\s,()])+/], ]; export type ParseTerm = (term: string, fuzz: number, boost: number) => AstMatcher; @@ -26,14 +26,14 @@ export type Range = [number, number]; export type TermContext = [Range, string]; export interface LexResult { - tokenList: TokenList, - termContexts: TermContext[], - error: ParseError | null + tokenList: TokenList; + termContexts: TermContext[]; + error: ParseError | null; } export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexResult { const opQueue: string[] = [], - groupNegate: boolean[] = []; + groupNegate: boolean[] = []; let searchTerm: string | null = null; let boostFuzzStr = ''; @@ -49,7 +49,7 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR const ret: LexResult = { tokenList: [], termContexts: [], - error: null + error: null, }; const beginTerm = (token: string) => { @@ -86,7 +86,10 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR const token = match[0]; - if (searchTerm !== null && (['and_op', 'or_op'].indexOf(tokenName) !== -1 || tokenName === 'rparen' && lparenCtr === 0)) { + if ( + searchTerm !== null && + (['and_op', 'or_op'].indexOf(tokenName) !== -1 || (tokenName === 'rparen' && lparenCtr === 0)) + ) { endTerm(); } @@ -107,8 +110,7 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR if (searchTerm) { // We're already inside a search term, so it does not apply, obv. searchTerm += token; - } - else { + } else { negate = !negate; } break; @@ -118,8 +120,7 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR // instead, consider it as part of the search term, as a user convenience. searchTerm += token; lparenCtr += 1; - } - else { + } else { opQueue.unshift('lparen'); groupNegate.push(negate); negate = false; @@ -129,8 +130,7 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR if (lparenCtr > 0) { searchTerm = assertNotNull(searchTerm) + token; lparenCtr -= 1; - } - else { + } else { while (opQueue.length > 0) { const op = assertNotUndefined(opQueue.shift()); if (op === 'lparen') { @@ -149,8 +149,7 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR // to a temporary string in case this is actually inside the term. fuzz = parseFloat(token.substring(1)); boostFuzzStr += token; - } - else { + } else { beginTerm(token); } break; @@ -158,16 +157,14 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR if (searchTerm) { boost = parseFloat(token.substring(1)); boostFuzzStr += token; - } - else { + } else { beginTerm(token); } break; case 'quoted_lit': if (searchTerm) { searchTerm += token; - } - else { + } else { beginTerm(token); } break; @@ -180,8 +177,7 @@ export function generateLexResult(searchStr: string, parseTerm: ParseTerm): LexR boostFuzzStr = ''; } searchTerm += token; - } - else { + } else { beginTerm(token); } break; diff --git a/assets/js/query/literal.ts b/assets/js/query/literal.ts index 76bfd54c..3694a20f 100644 --- a/assets/js/query/literal.ts +++ b/assets/js/query/literal.ts @@ -23,11 +23,13 @@ function makeWildcardMatcher(term: string): FieldMatcher { // A custom NFA with caching may be more sophisticated but not // likely to be faster. const wildcard = new RegExp( - `^${term.replace(/([.+^$[\]\\(){}|-])/g, '\\$1') + `^${term + .replace(/([.+^$[\]\\(){}|-])/g, '\\$1') .replace(/([^\\]|[^\\](?:\\\\)+)\*/g, '$1.*') .replace(/^(?:\\\\)*\*/g, '.*') .replace(/([^\\]|[^\\](?:\\\\)+)\?/g, '$1.?') - .replace(/^(?:\\\\)*\?/g, '.?')}$`, 'i' + .replace(/^(?:\\\\)*\?/g, '.?')}$`, + 'i', ); return (v, name) => { @@ -69,10 +71,9 @@ function fuzzyMatch(term: string, targetStr: string, fuzz: number): boolean { // Insertion. v2[j] + 1, // Substitution or No Change. - v1[j] + cost + v1[j] + cost, ); - if (i > 1 && j > 1 && term[i] === targetStrLower[j - 1] && - targetStrLower[i - 1] === targetStrLower[j]) { + if (i > 1 && j > 1 && term[i] === targetStrLower[j - 1] && targetStrLower[i - 1] === targetStrLower[j]) { v2[j + 1] = Math.min(v2[j], v0[j - 1] + cost); } } diff --git a/assets/js/query/matcher.ts b/assets/js/query/matcher.ts index d8e67df2..c16b6010 100644 --- a/assets/js/query/matcher.ts +++ b/assets/js/query/matcher.ts @@ -6,10 +6,10 @@ import { makeUserMatcher } from './user'; import { FieldMatcher, RangeEqualQualifier } from './types'; export interface MatcherFactory { - makeDateMatcher: (dateVal: string, qual: RangeEqualQualifier) => FieldMatcher, - makeLiteralMatcher: (term: string, fuzz: number, wildcardable: boolean) => FieldMatcher, - makeNumberMatcher: (term: number, fuzz: number, qual: RangeEqualQualifier) => FieldMatcher, - makeUserMatcher: (term: string) => FieldMatcher + makeDateMatcher: (dateVal: string, qual: RangeEqualQualifier) => FieldMatcher; + makeLiteralMatcher: (term: string, fuzz: number, wildcardable: boolean) => FieldMatcher; + makeNumberMatcher: (term: number, fuzz: number, qual: RangeEqualQualifier) => FieldMatcher; + makeUserMatcher: (term: string) => FieldMatcher; } export const defaultMatcher: MatcherFactory = { diff --git a/assets/js/query/number.ts b/assets/js/query/number.ts index 8c42db30..5fe08e72 100644 --- a/assets/js/query/number.ts +++ b/assets/js/query/number.ts @@ -2,7 +2,7 @@ import { FieldMatcher, RangeEqualQualifier } from './types'; export function makeNumberMatcher(term: number, fuzz: number, qual: RangeEqualQualifier): FieldMatcher { // Range matching. - return v => { + return (v) => { const attrVal = parseFloat(v); if (isNaN(attrVal)) { diff --git a/assets/js/query/parse.ts b/assets/js/query/parse.ts index fea7659b..7f725c6c 100644 --- a/assets/js/query/parse.ts +++ b/assets/js/query/parse.ts @@ -23,19 +23,16 @@ export function parseTokens(lexicalArray: TokenList): AstMatcher { if (token === 'and_op') { intermediate = matchAll(op1, op2); - } - else { + } else { intermediate = matchAny(op1, op2); } - } - else { + } else { intermediate = token; } if (lexicalArray[i + 1] === 'not_op') { operandStack.push(matchNot(intermediate)); - } - else { + } else { operandStack.push(intermediate); } } diff --git a/assets/js/query/term.ts b/assets/js/query/term.ts index 22b161eb..1708b810 100644 --- a/assets/js/query/term.ts +++ b/assets/js/query/term.ts @@ -67,11 +67,9 @@ function makeTermMatcher(term: string, fuzz: number, factory: MatcherFactory): [ } return [fieldName, factory.makeNumberMatcher(parseFloat(termCandidate), fuzz, rangeType)]; - } - else if (literalFields.indexOf(candidateTermSpace) !== -1) { + } else if (literalFields.indexOf(candidateTermSpace) !== -1) { return [candidateTermSpace, factory.makeLiteralMatcher(termCandidate, fuzz, wildcardable)]; - } - else if (candidateTermSpace === 'my') { + } else if (candidateTermSpace === 'my') { return [candidateTermSpace, factory.makeUserMatcher(termCandidate)]; } } diff --git a/assets/js/query/user.ts b/assets/js/query/user.ts index 3f383425..2b8dbfe8 100644 --- a/assets/js/query/user.ts +++ b/assets/js/query/user.ts @@ -1,8 +1,15 @@ import { Interaction, InteractionType, InteractionValue } from '../../types/booru-object'; import { FieldMatcher } from './types'; -function interactionMatch(imageId: number, type: InteractionType, value: InteractionValue, interactions: Interaction[]): boolean { - return interactions.some(v => v.image_id === imageId && v.interaction_type === type && (value === null || v.value === value)); +function interactionMatch( + imageId: number, + type: InteractionType, + value: InteractionValue, + interactions: Interaction[], +): boolean { + return interactions.some( + (v) => v.image_id === imageId && v.interaction_type === type && (value === null || v.value === value), + ); } export function makeUserMatcher(term: string): FieldMatcher { diff --git a/assets/js/quick-tag.js b/assets/js/quick-tag.js index 055c4408..e43b460e 100644 --- a/assets/js/quick-tag.js +++ b/assets/js/quick-tag.js @@ -9,56 +9,54 @@ import { fetchJson, handleError } from './utils/requests'; const imageQueueStorage = 'quickTagQueue'; const currentTagStorage = 'quickTagName'; -function currentQueue() { return store.get(imageQueueStorage) || []; } +function currentQueue() { + return store.get(imageQueueStorage) || []; +} -function currentTags() { return store.get(currentTagStorage) || ''; } +function currentTags() { + return store.get(currentTagStorage) || ''; +} -function getTagButton() { return $('.js-quick-tag'); } +function getTagButton() { + return $('.js-quick-tag'); +} -function setTagButton(text) { $('.js-quick-tag--submit span').textContent = text; } +function setTagButton(text) { + $('.js-quick-tag--submit span').textContent = text; +} function toggleActiveState() { - - toggleEl($('.js-quick-tag'), - $('.js-quick-tag--abort'), - $('.js-quick-tag--all'), - $('.js-quick-tag--submit')); + toggleEl($('.js-quick-tag'), $('.js-quick-tag--abort'), $('.js-quick-tag--all'), $('.js-quick-tag--submit')); setTagButton(`Submit (${currentTags()})`); - $$('.media-box__header').forEach(el => el.classList.toggle('media-box__header--unselected')); - $$('.media-box__header').forEach(el => el.classList.remove('media-box__header--selected')); - currentQueue().forEach(id => $$(`.media-box__header[data-image-id="${id}"]`).forEach(el => el.classList.add('media-box__header--selected'))); - + $$('.media-box__header').forEach((el) => el.classList.toggle('media-box__header--unselected')); + $$('.media-box__header').forEach((el) => el.classList.remove('media-box__header--selected')); + currentQueue().forEach((id) => + $$(`.media-box__header[data-image-id="${id}"]`).forEach((el) => el.classList.add('media-box__header--selected')), + ); } function activate() { - store.set(currentTagStorage, window.prompt('A comma-delimited list of tags you want to add:')); if (currentTags()) toggleActiveState(); - } function reset() { - store.remove(currentTagStorage); store.remove(imageQueueStorage); toggleActiveState(); - } function promptReset() { - if (window.confirm('Are you sure you want to abort batch tagging?')) { reset(); } - } function submit() { - setTagButton(`Wait... (${currentTags()})`); fetchJson('PUT', '/admin/batch/tags', { @@ -66,32 +64,28 @@ function submit() { image_ids: currentQueue(), }) .then(handleError) - .then(r => r.json()) - .then(data => { - + .then((r) => r.json()) + .then((data) => { if (data.failed.length) window.alert(`Failed to add tags to the images with these IDs: ${data.failed}`); reset(); - }); - } function modifyImageQueue(mediaBox) { - if (currentTags()) { - const imageId = mediaBox.dataset.imageId, - queue = currentQueue(), - isSelected = queue.includes(imageId); + const imageId = mediaBox.dataset.imageId, + queue = currentQueue(), + isSelected = queue.includes(imageId); - isSelected ? queue.splice(queue.indexOf(imageId), 1) - : queue.push(imageId); + isSelected ? queue.splice(queue.indexOf(imageId), 1) : queue.push(imageId); - $$(`.media-box__header[data-image-id="${imageId}"]`).forEach(el => el.classList.toggle('media-box__header--selected')); + $$(`.media-box__header[data-image-id="${imageId}"]`).forEach((el) => + el.classList.toggle('media-box__header--selected'), + ); store.set(imageQueueStorage, queue); } - } function toggleAllImages() { @@ -99,7 +93,6 @@ function toggleAllImages() { } function clickHandler(event) { - const targets = { '.js-quick-tag': activate, '.js-quick-tag--abort': promptReset, @@ -114,14 +107,11 @@ function clickHandler(event) { currentTags() && event.preventDefault(); } } - } function setupQuickTag() { - if (getTagButton() && currentTags()) toggleActiveState(); if (getTagButton()) onLeftClick(clickHandler); - } export { setupQuickTag }; diff --git a/assets/js/search.ts b/assets/js/search.ts index 7de65e5a..351105ea 100644 --- a/assets/js/search.ts +++ b/assets/js/search.ts @@ -2,7 +2,7 @@ import { $, $$ } from './utils/dom'; import { addTag } from './tagsinput'; function showHelp(subject: string, type: string | null) { - $$('[data-search-help]').forEach(helpBox => { + $$('[data-search-help]').forEach((helpBox) => { if (helpBox.getAttribute('data-search-help') === type) { const searchSubject = $('.js-search-help-subject', helpBox); @@ -11,8 +11,7 @@ function showHelp(subject: string, type: string | null) { } helpBox.classList.remove('hidden'); - } - else { + } else { helpBox.classList.add('hidden'); } }); @@ -21,7 +20,8 @@ function showHelp(subject: string, type: string | null) { function prependToLast(field: HTMLInputElement, value: string) { const separatorIndex = field.value.lastIndexOf(','); const advanceBy = field.value[separatorIndex + 1] === ' ' ? 2 : 1; - field.value = field.value.slice(0, separatorIndex + advanceBy) + value + field.value.slice(separatorIndex + advanceBy); + field.value = + field.value.slice(0, separatorIndex + advanceBy) + value + field.value.slice(separatorIndex + advanceBy); } function selectLast(field: HTMLInputElement, characterCount: number) { @@ -32,14 +32,18 @@ function selectLast(field: HTMLInputElement, characterCount: number) { } function executeFormHelper(e: PointerEvent) { - if (!e.target) { return; } + if (!e.target) { + return; + } const searchField = $('.js-search-field'); const attr = (name: string) => e.target && (e.target as HTMLElement).getAttribute(name); if (attr('data-search-add')) addTag(searchField, attr('data-search-add')); if (attr('data-search-show-help')) showHelp((e.target as Node).textContent || '', attr('data-search-show-help')); - if (attr('data-search-select-last') && searchField) selectLast(searchField, parseInt(attr('data-search-select-last') || '', 10)); + if (attr('data-search-select-last') && searchField) { + selectLast(searchField, parseInt(attr('data-search-select-last') || '', 10)); + } if (attr('data-search-prepend') && searchField) prependToLast(searchField, attr('data-search-prepend') || ''); } diff --git a/assets/js/settings.ts b/assets/js/settings.ts index 0bc8a9d7..ed4256b4 100644 --- a/assets/js/settings.ts +++ b/assets/js/settings.ts @@ -7,21 +7,18 @@ import { $, $$ } from './utils/dom'; import store from './utils/store'; export function setupSettings() { - if (!$('#js-setting-table')) return; const localCheckboxes = $$('[data-tab="local"] input[type="checkbox"]'); const themeSelect = assertNotNull($('#user_theme_name')); const themeColorSelect = assertNotNull($('#user_theme_color')); const themePaths: Record = JSON.parse( - assertNotUndefined( - assertNotNull($('#js-theme-paths')).dataset.themePaths - ) + assertNotUndefined(assertNotNull($('#js-theme-paths')).dataset.themePaths), ); const styleSheet = assertNotNull($('#js-theme-stylesheet')); // Local settings - localCheckboxes.forEach(checkbox => { + localCheckboxes.forEach((checkbox) => { checkbox.addEventListener('change', () => { store.set(checkbox.id.replace('user_', ''), checkbox.checked); }); diff --git a/assets/js/shortcuts.ts b/assets/js/shortcuts.ts index 48551a3b..dc7d920f 100644 --- a/assets/js/shortcuts.ts +++ b/assets/js/shortcuts.ts @@ -37,28 +37,48 @@ function click(selector: string) { } function isOK(event: KeyboardEvent): boolean { - return !event.altKey && !event.ctrlKey && !event.metaKey && - document.activeElement !== null && - document.activeElement.tagName !== 'INPUT' && - document.activeElement.tagName !== 'TEXTAREA'; + return ( + !event.altKey && + !event.ctrlKey && + !event.metaKey && + document.activeElement !== null && + document.activeElement.tagName !== 'INPUT' && + document.activeElement.tagName !== 'TEXTAREA' + ); } const keyCodes: ShortcutKeyMap = { - 'j'() { click('.js-prev'); }, // J - go to previous image - 'i'() { click('.js-up'); }, // I - go to index page - 'k'() { click('.js-next'); }, // K - go to next image - 'r'() { click('.js-rand'); }, // R - go to random image - 's'() { click('.js-source-link'); }, // S - go to image source - 'l'() { click('.js-tag-sauce-toggle'); }, // L - edit tags - 'o'() { openFullView(); }, // O - open original - 'v'() { openFullViewNewTab(); }, // V - open original in a new tab - 'f'() { // F - favourite image - click(getHover() ? `a.interaction--fave[data-image-id="${getHover()}"]` - : '.block__header a.interaction--fave'); + j() { + click('.js-prev'); + }, // J - go to previous image + i() { + click('.js-up'); + }, // I - go to index page + k() { + click('.js-next'); + }, // K - go to next image + r() { + click('.js-rand'); + }, // R - go to random image + s() { + click('.js-source-link'); + }, // S - go to image source + l() { + click('.js-tag-sauce-toggle'); + }, // L - edit tags + o() { + openFullView(); + }, // O - open original + v() { + openFullViewNewTab(); + }, // V - open original in a new tab + f() { + // F - favourite image + click(getHover() ? `a.interaction--fave[data-image-id="${getHover()}"]` : '.block__header a.interaction--fave'); }, - 'u'() { // U - upvote image - click(getHover() ? `a.interaction--upvote[data-image-id="${getHover()}"]` - : '.block__header a.interaction--upvote'); + u() { + // U - upvote image + click(getHover() ? `a.interaction--upvote[data-image-id="${getHover()}"]` : '.block__header a.interaction--upvote'); }, }; diff --git a/assets/js/slider.ts b/assets/js/slider.ts index 92304488..95ca83fc 100644 --- a/assets/js/slider.ts +++ b/assets/js/slider.ts @@ -34,8 +34,7 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: function clampValue(value: number): number { if (cachedValue >= cachedLimit && value < cachedLimit) { return cachedLimit; - } - else if (cachedValue < cachedLimit && value >= cachedLimit) { + } else if (cachedValue < cachedLimit && value >= cachedLimit) { return cachedLimit - 1; // Offset by 1 to ensure stored value is less than limit. } @@ -55,7 +54,9 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: // Initializes cached variables. Should be used // when the pointer event begins. function initVars() { - if (!parent) { return; } + if (!parent) { + return; + } const rect = parent.getBoundingClientRect(); @@ -70,7 +71,9 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: // Called during pointer movement. function dragMove(e: PointerEvent) { - if (!dragging) { return; } + if (!dragging) { + return; + } e.preventDefault(); @@ -79,13 +82,7 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: // `lerp` cleverly clamps the value between min and max, // so no need for any explicit checks for that here, only // the crossover check is required. - curValue = clampValue( - lerp( - (desiredPos - minPos) / (maxPos - minPos), - cachedMin, - cachedMax - ) - ); + curValue = clampValue(lerp((desiredPos - minPos) / (maxPos - minPos), cachedMin, cachedMax)); // Same here, lerp clamps the value so it doesn't get out // of the slider boundary. @@ -99,7 +96,9 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: // Called when the pointer is let go of. function dragEnd(e: PointerEvent) { - if (!dragging) { return; } + if (!dragging) { + return; + } e.preventDefault(); @@ -111,7 +110,9 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: // Called when the slider head is clicked or tapped. function dragBegin(e: PointerEvent) { - if (!parent) { return; } + if (!parent) { + return; + } e.preventDefault(); initVars(); @@ -168,7 +169,7 @@ function setupSlider(el: HTMLInputElement) { // Sets up all sliders currently on the page. function setupSliders() { - $$('input[type="dualrange"]').forEach(el => { + $$('input[type="dualrange"]').forEach((el) => { setupSlider(el); }); } diff --git a/assets/js/sources.ts b/assets/js/sources.ts index b4bc51ec..0a0aab18 100644 --- a/assets/js/sources.ts +++ b/assets/js/sources.ts @@ -19,7 +19,7 @@ export function imageSourcesCreator() { if (target.matches('#source-form')) { const sourceSauce = assertNotNull($('.js-sourcesauce')); - detail.text().then(text => { + detail.text().then((text) => { sourceSauce.outerHTML = text; setupInputs(); }); diff --git a/assets/js/staffhider.ts b/assets/js/staffhider.ts index 86741d78..23aa3899 100644 --- a/assets/js/staffhider.ts +++ b/assets/js/staffhider.ts @@ -8,6 +8,6 @@ import { $$, hideEl } from './utils/dom'; export function hideStaffTools() { if (window.booru.hideStaffTools === 'true') { - $$('.js-staff-action').forEach(el => hideEl(el)); + $$('.js-staff-action').forEach((el) => hideEl(el)); } } diff --git a/assets/js/tags.ts b/assets/js/tags.ts index c3547f27..4e7f9191 100644 --- a/assets/js/tags.ts +++ b/assets/js/tags.ts @@ -19,44 +19,70 @@ function removeTag(tagId: number, list: number[]) { function createTagDropdown(tag: HTMLSpanElement) { const { userIsSignedIn, userCanEditFilter, watchedTagList, spoileredTagList, hiddenTagList } = window.booru; - const [ unwatch, watch, unspoiler, spoiler, unhide, hide, signIn, filter ] = $$('.tag__dropdown__link', tag); - const [ unwatched, watched, spoilered, hidden ] = $$('.tag__state', tag); + const [unwatch, watch, unspoiler, spoiler, unhide, hide, signIn, filter] = $$( + '.tag__dropdown__link', + tag, + ); + const [unwatched, watched, spoilered, hidden] = $$('.tag__state', tag); const tagId = parseInt(assertNotUndefined(tag.dataset.tagId), 10); const actions: TagDropdownActionList = { - unwatch() { hideEl(unwatch, watched); showEl(watch, unwatched); removeTag(tagId, watchedTagList); }, - watch() { hideEl(watch, unwatched); showEl(unwatch, watched); addTag(tagId, watchedTagList); }, + unwatch() { + hideEl(unwatch, watched); + showEl(watch, unwatched); + removeTag(tagId, watchedTagList); + }, + watch() { + hideEl(watch, unwatched); + showEl(unwatch, watched); + addTag(tagId, watchedTagList); + }, - unspoiler() { hideEl(unspoiler, spoilered); showEl(spoiler); removeTag(tagId, spoileredTagList); }, - spoiler() { hideEl(spoiler); showEl(unspoiler, spoilered); addTag(tagId, spoileredTagList); }, + unspoiler() { + hideEl(unspoiler, spoilered); + showEl(spoiler); + removeTag(tagId, spoileredTagList); + }, + spoiler() { + hideEl(spoiler); + showEl(unspoiler, spoilered); + addTag(tagId, spoileredTagList); + }, - unhide() { hideEl(unhide, hidden); showEl(hide); removeTag(tagId, hiddenTagList); }, - hide() { hideEl(hide); showEl(unhide, hidden); addTag(tagId, hiddenTagList); }, + unhide() { + hideEl(unhide, hidden); + showEl(hide); + removeTag(tagId, hiddenTagList); + }, + hide() { + hideEl(hide); + showEl(unhide, hidden); + addTag(tagId, hiddenTagList); + }, }; - const tagIsWatched = watchedTagList.includes(tagId); + const tagIsWatched = watchedTagList.includes(tagId); const tagIsSpoilered = spoileredTagList.includes(tagId); - const tagIsHidden = hiddenTagList.includes(tagId); + const tagIsHidden = hiddenTagList.includes(tagId); - const watchedLink = tagIsWatched ? unwatch : watch; - const spoilerLink = tagIsSpoilered ? unspoiler : spoiler; - const hiddenLink = tagIsHidden ? unhide : hide; + const watchedLink = tagIsWatched ? unwatch : watch; + const spoilerLink = tagIsSpoilered ? unspoiler : spoiler; + const hiddenLink = tagIsHidden ? unhide : hide; // State symbols (-, S, H, +) - if (tagIsWatched) showEl(watched); - if (tagIsSpoilered) showEl(spoilered); - if (tagIsHidden) showEl(hidden); - if (!tagIsWatched) showEl(unwatched); + if (tagIsWatched) showEl(watched); + if (tagIsSpoilered) showEl(spoilered); + if (tagIsHidden) showEl(hidden); + if (!tagIsWatched) showEl(unwatched); // Dropdown links - if (userIsSignedIn) showEl(watchedLink); + if (userIsSignedIn) showEl(watchedLink); if (userCanEditFilter) showEl(spoilerLink); if (userCanEditFilter) showEl(hiddenLink); - if (!userIsSignedIn) showEl(signIn); - if (userIsSignedIn && - !userCanEditFilter) showEl(filter); + if (!userIsSignedIn) showEl(signIn); + if (userIsSignedIn && !userCanEditFilter) showEl(filter); - tag.addEventListener('fetchcomplete', event => { + tag.addEventListener('fetchcomplete', (event) => { const act = assertNotUndefined(event.target.dataset.tagAction); actions[act](); }); diff --git a/assets/js/tagsinput.js b/assets/js/tagsinput.js index 1f6963ac..10e9fd61 100644 --- a/assets/js/tagsinput.js +++ b/assets/js/tagsinput.js @@ -5,7 +5,7 @@ import { $, $$, clearEl, removeEl, showEl, hideEl, escapeCss, escapeHtml } from './utils/dom'; function setupTagsInput(tagBlock) { - const [ textarea, container ] = $$('.js-taginput', tagBlock); + const [textarea, container] = $$('.js-taginput', tagBlock); const setup = $('.js-tag-block ~ button', tagBlock.parentNode); const inputField = $('input', container); @@ -42,7 +42,6 @@ function setupTagsInput(tagBlock) { importTags(); } - function handleAutocomplete(event) { insertTag(event.detail.value); inputField.focus(); @@ -82,10 +81,9 @@ function setupTagsInput(tagBlock) { // enter or comma if (keyCode === 13 || (keyCode === 188 && !shiftKey)) { event.preventDefault(); - inputField.value.split(',').forEach(t => insertTag(t)); + inputField.value.split(',').forEach((t) => insertTag(t)); inputField.value = ''; } - } function handleCtrlEnter(event) { @@ -131,19 +129,21 @@ function setupTagsInput(tagBlock) { container.appendChild(inputField); tags = []; - textarea.value.split(',').forEach(t => insertTag(t)); + textarea.value.split(',').forEach((t) => insertTag(t)); textarea.value = tags.join(', '); } } function fancyEditorRequested(tagBlock) { // Check whether the user made the fancy editor the default for each type of tag block. - return window.booru.fancyTagUpload && tagBlock.classList.contains('fancy-tag-upload') || - window.booru.fancyTagEdit && tagBlock.classList.contains('fancy-tag-edit'); + return ( + (window.booru.fancyTagUpload && tagBlock.classList.contains('fancy-tag-upload')) || + (window.booru.fancyTagEdit && tagBlock.classList.contains('fancy-tag-edit')) + ); } function setupTagListener() { - document.addEventListener('addtag', event => { + document.addEventListener('addtag', (event) => { if (event.target.value) event.target.value += ', '; event.target.value += event.detail.name; }); diff --git a/assets/js/tagsmisc.ts b/assets/js/tagsmisc.ts index 09366386..acf65702 100644 --- a/assets/js/tagsmisc.ts +++ b/assets/js/tagsmisc.ts @@ -30,7 +30,7 @@ function tagInputButtons(event: MouseEvent) { }, }; - for (const [ name, action ] of Object.entries(actions)) { + for (const [name, action] of Object.entries(actions)) { if (target && target.matches(`#tagsinput-${name}`)) { action(assertNotNull($('#image_tag_input'))); } @@ -38,7 +38,7 @@ function tagInputButtons(event: MouseEvent) { } function setupTags() { - $$('.js-tag-block').forEach(el => { + $$('.js-tag-block').forEach((el) => { setupTagsInput(el); el.classList.remove('js-tag-block'); }); @@ -48,7 +48,7 @@ function updateTagSauce({ target, detail }: FetchcompleteEvent) { if (target.matches('#tags-form')) { const tagSauce = assertNotNull($('.js-tagsauce')); - detail.text().then(text => { + detail.text().then((text) => { tagSauce.outerHTML = text; setupTags(); initTagDropdown(); diff --git a/assets/js/timeago.ts b/assets/js/timeago.ts index 12f39bd9..3eb8ec6a 100644 --- a/assets/js/timeago.ts +++ b/assets/js/timeago.ts @@ -36,24 +36,24 @@ function setTimeAgo(el: HTMLTimeElement) { const distMillis = distance(date); const seconds = Math.abs(distMillis) / 1000, - minutes = seconds / 60, - hours = minutes / 60, - days = hours / 24, - months = days / 30, - years = days / 365; + minutes = seconds / 60, + hours = minutes / 60, + days = hours / 24, + months = days / 30, + years = days / 365; const words = - seconds < 45 && substitute('seconds', seconds) || - seconds < 90 && substitute('minute', 1) || - minutes < 45 && substitute('minutes', minutes) || - minutes < 90 && substitute('hour', 1) || - hours < 24 && substitute('hours', hours) || - hours < 42 && substitute('day', 1) || - days < 30 && substitute('days', days) || - days < 45 && substitute('month', 1) || - days < 365 && substitute('months', months) || - years < 1.5 && substitute('year', 1) || - substitute('years', years); + (seconds < 45 && substitute('seconds', seconds)) || + (seconds < 90 && substitute('minute', 1)) || + (minutes < 45 && substitute('minutes', minutes)) || + (minutes < 90 && substitute('hour', 1)) || + (hours < 24 && substitute('hours', hours)) || + (hours < 42 && substitute('day', 1)) || + (days < 30 && substitute('days', days)) || + (days < 45 && substitute('month', 1)) || + (days < 365 && substitute('months', months)) || + (years < 1.5 && substitute('year', 1)) || + substitute('years', years); if (!el.getAttribute('title')) { el.setAttribute('title', assertNotNull(el.textContent)); diff --git a/assets/js/ujs.ts b/assets/js/ujs.ts index 413bc6cb..626f8c99 100644 --- a/assets/js/ujs.ts +++ b/assets/js/ujs.ts @@ -4,7 +4,7 @@ import { fire, delegate, leftClick } from './utils/events'; const headers = () => ({ 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'XMLHttpRequest' + 'x-requested-with': 'XMLHttpRequest', }); function confirm(event: Event, target: HTMLElement) { @@ -25,8 +25,7 @@ function disable(event: Event, target: HTMLAnchorElement | HTMLButtonElement | H if (label) { target.dataset.enableWith = assertNotNull(label.nodeValue); label.nodeValue = ` ${target.dataset.disableWith}`; - } - else { + } else { target.dataset.enableWith = target.innerHTML; target.innerHTML = assertNotUndefined(target.dataset.disableWith); } @@ -39,8 +38,8 @@ function disable(event: Event, target: HTMLAnchorElement | HTMLButtonElement | H function linkMethod(event: Event, target: HTMLAnchorElement) { event.preventDefault(); - const form = makeEl('form', { action: target.href, method: 'POST' }); - const csrf = makeEl('input', { type: 'hidden', name: '_csrf_token', value: window.booru.csrfToken }); + const form = makeEl('form', { action: target.href, method: 'POST' }); + const csrf = makeEl('input', { type: 'hidden', name: '_csrf_token', value: window.booru.csrfToken }); const method = makeEl('input', { type: 'hidden', name: '_method', value: target.dataset.method }); document.body.appendChild(form); @@ -57,8 +56,8 @@ function formRemote(event: Event, target: HTMLFormElement) { credentials: 'same-origin', method: (target.dataset.method || target.method).toUpperCase(), headers: headers(), - body: new FormData(target) - }).then(response => { + body: new FormData(target), + }).then((response) => { fire(target, 'fetchcomplete', response); if (response && response.status === 300) { window.location.reload(); @@ -67,12 +66,11 @@ function formRemote(event: Event, target: HTMLFormElement) { } function formReset(_event: Event | null, target: HTMLElement) { - $$('[disabled][data-disable-with][data-enable-with]', target).forEach(input => { + $$('[disabled][data-disable-with][data-enable-with]', target).forEach((input) => { const label = findFirstTextNode(input); if (label) { label.nodeValue = ` ${input.dataset.enableWith}`; - } - else { + } else { input.innerHTML = assertNotUndefined(input.dataset.enableWith); } delete input.dataset.enableWith; @@ -86,10 +84,8 @@ function linkRemote(event: Event, target: HTMLAnchorElement) { fetch(target.href, { credentials: 'same-origin', method: (target.dataset.method || 'get').toUpperCase(), - headers: headers() - }).then(response => - fire(target, 'fetchcomplete', response) - ); + headers: headers(), + }).then((response) => fire(target, 'fetchcomplete', response)); } delegate(document, 'click', { @@ -100,11 +96,11 @@ delegate(document, 'click', { }); delegate(document, 'submit', { - 'form[data-remote]': formRemote + 'form[data-remote]': formRemote, }); delegate(document, 'reset', { - form: formReset + form: formReset, }); window.addEventListener('pageshow', () => { diff --git a/assets/js/upload.js b/assets/js/upload.js index 62f749fb..6d0e52b5 100644 --- a/assets/js/upload.js +++ b/assets/js/upload.js @@ -11,7 +11,7 @@ const MATROSKA_MAGIC = 0x1a45dfa3; function scrapeUrl(url) { return fetchJson('POST', '/images/scrape', { url }) .then(handleError) - .then(response => response.json()); + .then((response) => response.json()); } function elementForEmbeddedImage({ camo_url, type }) { @@ -34,7 +34,7 @@ function setupImageUpload() { const [fileField, remoteUrl, scraperError] = $$('.js-scraper', form); const descrEl = $('.js-image-descr-input', form); const tagsEl = $('.js-image-tags-input', form); - const sourceEl = $$('.js-source-url', form).find(input => input.value === ''); + const sourceEl = $$('.js-source-url', form).find((input) => input.value === ''); const fetchButton = $('#js-scraper-preview'); if (!fetchButton) return; @@ -68,17 +68,25 @@ function setupImageUpload() { showEl(scraperError); enableFetch(); } - function hideError() { hideEl(scraperError); } - function disableFetch() { fetchButton.setAttribute('disabled', ''); } - function enableFetch() { fetchButton.removeAttribute('disabled'); } + function hideError() { + hideEl(scraperError); + } + function disableFetch() { + fetchButton.setAttribute('disabled', ''); + } + function enableFetch() { + fetchButton.removeAttribute('disabled'); + } const reader = new FileReader(); - reader.addEventListener('load', event => { - showImages([{ - camo_url: event.target.result, - type: fileField.files[0].type - }]); + reader.addEventListener('load', (event) => { + showImages([ + { + camo_url: event.target.result, + type: fileField.files[0].type, + }, + ]); // Clear any currently cached data, because the file field // has higher priority than the scraper: @@ -88,7 +96,9 @@ function setupImageUpload() { }); // Watch for files added to the form - fileField.addEventListener('change', () => { fileField.files.length && reader.readAsArrayBuffer(fileField.files[0]); }); + fileField.addEventListener('change', () => { + fileField.files.length && reader.readAsArrayBuffer(fileField.files[0]); + }); // Watch for [Fetch] clicks fetchButton.addEventListener('click', () => { @@ -96,37 +106,39 @@ function setupImageUpload() { disableFetch(); - scrapeUrl(remoteUrl.value).then(data => { - if (data === null) { - scraperError.innerText = 'No image found at that address.'; - showError(); - return; - } - else if (data.errors && data.errors.length > 0) { - scraperError.innerText = data.errors.join(' '); - showError(); - return; - } + scrapeUrl(remoteUrl.value) + .then((data) => { + if (data === null) { + scraperError.innerText = 'No image found at that address.'; + showError(); + return; + } else if (data.errors && data.errors.length > 0) { + scraperError.innerText = data.errors.join(' '); + showError(); + return; + } - hideError(); + hideError(); - // Set source - if (sourceEl) sourceEl.value = sourceEl.value || data.source_url || ''; - // Set description - if (descrEl) descrEl.value = descrEl.value || data.description || ''; - // Add author - if (tagsEl && data.author_name) addTag(tagsEl, `artist:${data.author_name.toLowerCase()}`); - // Clear selected file, if any - fileField.value = ''; - showImages(data.images); + // Set source + if (sourceEl) sourceEl.value = sourceEl.value || data.source_url || ''; + // Set description + if (descrEl) descrEl.value = descrEl.value || data.description || ''; + // Add author + if (tagsEl && data.author_name) addTag(tagsEl, `artist:${data.author_name.toLowerCase()}`); + // Clear selected file, if any + fileField.value = ''; + showImages(data.images); - enableFetch(); - }).catch(showError); + enableFetch(); + }) + .catch(showError); }); // Fetch on "enter" in url field - remoteUrl.addEventListener('keydown', event => { - if (event.keyCode === 13) { // Hit enter + remoteUrl.addEventListener('keydown', (event) => { + if (event.keyCode === 13) { + // Hit enter fetchButton.click(); } }); @@ -135,8 +147,7 @@ function setupImageUpload() { function setFetchEnabled() { if (remoteUrl.value.length > 0) { enableFetch(); - } - else { + } else { disableFetch(); } } diff --git a/assets/js/utils/__tests__/array.spec.ts b/assets/js/utils/__tests__/array.spec.ts index 4b1a9501..89e8f76d 100644 --- a/assets/js/utils/__tests__/array.spec.ts +++ b/assets/js/utils/__tests__/array.spec.ts @@ -84,15 +84,17 @@ describe('Array Utilities', () => { // Mixed parameters const mockObject = { value: Math.random() }; - expect(arraysEqual( - ['', null, false, uniqueValue, mockObject, Infinity, undefined], - ['', null, false, uniqueValue, mockObject, Infinity, undefined] - )).toBe(true); + expect( + arraysEqual( + ['', null, false, uniqueValue, mockObject, Infinity, undefined], + ['', null, false, uniqueValue, mockObject, Infinity, undefined], + ), + ).toBe(true); }); }); describe('negative cases', () => { - it('should NOT return true for matching only up to the first array\'s length', () => { + it("should NOT return true for matching only up to the first array's length", () => { // Numbers expect(arraysEqual([0], [0, 1])).toBe(false); expect(arraysEqual([0, 1], [0, 1, 2])).toBe(false); @@ -108,26 +110,15 @@ describe('Array Utilities', () => { // Mixed parameters const mockObject = { value: Math.random() }; - expect(arraysEqual( - [''], - ['', null, false, mockObject, Infinity, undefined] - )).toBe(false); - expect(arraysEqual( - ['', null], - ['', null, false, mockObject, Infinity, undefined] - )).toBe(false); - expect(arraysEqual( - ['', null, false], - ['', null, false, mockObject, Infinity, undefined] - )).toBe(false); - expect(arraysEqual( - ['', null, false, mockObject], - ['', null, false, mockObject, Infinity, undefined] - )).toBe(false); - expect(arraysEqual( - ['', null, false, mockObject, Infinity], - ['', null, false, mockObject, Infinity, undefined] - )).toBe(false); + expect(arraysEqual([''], ['', null, false, mockObject, Infinity, undefined])).toBe(false); + expect(arraysEqual(['', null], ['', null, false, mockObject, Infinity, undefined])).toBe(false); + expect(arraysEqual(['', null, false], ['', null, false, mockObject, Infinity, undefined])).toBe(false); + expect(arraysEqual(['', null, false, mockObject], ['', null, false, mockObject, Infinity, undefined])).toBe( + false, + ); + expect( + arraysEqual(['', null, false, mockObject, Infinity], ['', null, false, mockObject, Infinity, undefined]), + ).toBe(false); }); it('should return false for arrays of different length', () => { @@ -151,7 +142,7 @@ describe('Array Utilities', () => { expect(arraysEqual([mockObject], [mockObject, mockObject])).toBe(false); }); - it('should return false if items up to the first array\'s length differ', () => { + it("should return false if items up to the first array's length differ", () => { // Numbers expect(arraysEqual([0], [1])).toBe(false); expect(arraysEqual([0, 1], [1, 2])).toBe(false); @@ -168,22 +159,12 @@ describe('Array Utilities', () => { expect(arraysEqual([mockObject1], [mockObject2])).toBe(false); // Mixed parameters - expect(arraysEqual( - ['a'], - ['b', null, false, mockObject2, Infinity] - )).toBe(false); - expect(arraysEqual( - ['a', null, true], - ['b', null, false, mockObject2, Infinity] - )).toBe(false); - expect(arraysEqual( - ['a', null, true, mockObject1], - ['b', null, false, mockObject2, Infinity] - )).toBe(false); - expect(arraysEqual( - ['a', null, true, mockObject1, -Infinity], - ['b', null, false, mockObject2, Infinity] - )).toBe(false); + expect(arraysEqual(['a'], ['b', null, false, mockObject2, Infinity])).toBe(false); + expect(arraysEqual(['a', null, true], ['b', null, false, mockObject2, Infinity])).toBe(false); + expect(arraysEqual(['a', null, true, mockObject1], ['b', null, false, mockObject2, Infinity])).toBe(false); + expect(arraysEqual(['a', null, true, mockObject1, -Infinity], ['b', null, false, mockObject2, Infinity])).toBe( + false, + ); }); }); }); diff --git a/assets/js/utils/__tests__/dom.spec.ts b/assets/js/utils/__tests__/dom.spec.ts index a1bb03eb..dfd0faa2 100644 --- a/assets/js/utils/__tests__/dom.spec.ts +++ b/assets/js/utils/__tests__/dom.spec.ts @@ -87,11 +87,7 @@ describe('DOM Utilities', () => { }); it(`should remove the ${hiddenClass} class from all provided elements`, () => { - const mockElements = [ - createHiddenElement('div'), - createHiddenElement('a'), - createHiddenElement('strong'), - ]; + const mockElements = [createHiddenElement('div'), createHiddenElement('a'), createHiddenElement('strong')]; showEl(mockElements); expect(mockElements[0]).not.toHaveClass(hiddenClass); expect(mockElements[1]).not.toHaveClass(hiddenClass); @@ -99,14 +95,8 @@ describe('DOM Utilities', () => { }); it(`should remove the ${hiddenClass} class from elements provided in multiple arrays`, () => { - const mockElements1 = [ - createHiddenElement('div'), - createHiddenElement('a'), - ]; - const mockElements2 = [ - createHiddenElement('strong'), - createHiddenElement('em'), - ]; + const mockElements1 = [createHiddenElement('div'), createHiddenElement('a')]; + const mockElements2 = [createHiddenElement('strong'), createHiddenElement('em')]; showEl(mockElements1, mockElements2); expect(mockElements1[0]).not.toHaveClass(hiddenClass); expect(mockElements1[1]).not.toHaveClass(hiddenClass); @@ -135,14 +125,8 @@ describe('DOM Utilities', () => { }); it(`should add the ${hiddenClass} class to elements provided in multiple arrays`, () => { - const mockElements1 = [ - document.createElement('div'), - document.createElement('a'), - ]; - const mockElements2 = [ - document.createElement('strong'), - document.createElement('em'), - ]; + const mockElements1 = [document.createElement('div'), document.createElement('a')]; + const mockElements2 = [document.createElement('strong'), document.createElement('em')]; hideEl(mockElements1, mockElements2); expect(mockElements1[0]).toHaveClass(hiddenClass); expect(mockElements1[1]).toHaveClass(hiddenClass); @@ -159,24 +143,15 @@ describe('DOM Utilities', () => { }); it('should set the disabled attribute to true on all provided elements', () => { - const mockElements = [ - document.createElement('input'), - document.createElement('button'), - ]; + const mockElements = [document.createElement('input'), document.createElement('button')]; disableEl(mockElements); expect(mockElements[0]).toBeDisabled(); expect(mockElements[1]).toBeDisabled(); }); it('should set the disabled attribute to true on elements provided in multiple arrays', () => { - const mockElements1 = [ - document.createElement('input'), - document.createElement('button'), - ]; - const mockElements2 = [ - document.createElement('textarea'), - document.createElement('button'), - ]; + const mockElements1 = [document.createElement('input'), document.createElement('button')]; + const mockElements2 = [document.createElement('textarea'), document.createElement('button')]; disableEl(mockElements1, mockElements2); expect(mockElements1[0]).toBeDisabled(); expect(mockElements1[1]).toBeDisabled(); @@ -193,24 +168,15 @@ describe('DOM Utilities', () => { }); it('should set the disabled attribute to false on all provided elements', () => { - const mockElements = [ - document.createElement('input'), - document.createElement('button'), - ]; + const mockElements = [document.createElement('input'), document.createElement('button')]; enableEl(mockElements); expect(mockElements[0]).toBeEnabled(); expect(mockElements[1]).toBeEnabled(); }); it('should set the disabled attribute to false on elements provided in multiple arrays', () => { - const mockElements1 = [ - document.createElement('input'), - document.createElement('button'), - ]; - const mockElements2 = [ - document.createElement('textarea'), - document.createElement('button'), - ]; + const mockElements1 = [document.createElement('input'), document.createElement('button')]; + const mockElements2 = [document.createElement('textarea'), document.createElement('button')]; enableEl(mockElements1, mockElements2); expect(mockElements1[0]).toBeEnabled(); expect(mockElements1[1]).toBeEnabled(); @@ -245,14 +211,8 @@ describe('DOM Utilities', () => { }); it(`should toggle the ${hiddenClass} class on elements provided in multiple arrays`, () => { - const mockElements1 = [ - createHiddenElement('div'), - document.createElement('a'), - ]; - const mockElements2 = [ - createHiddenElement('strong'), - document.createElement('em'), - ]; + const mockElements1 = [createHiddenElement('div'), document.createElement('a')]; + const mockElements2 = [createHiddenElement('strong'), document.createElement('em')]; toggleEl(mockElements1, mockElements2); expect(mockElements1[0]).not.toHaveClass(hiddenClass); expect(mockElements1[1]).toHaveClass(hiddenClass); @@ -430,8 +390,7 @@ describe('DOM Utilities', () => { try { whenReady(mockCallback); expect(mockCallback).toHaveBeenCalledTimes(1); - } - finally { + } finally { readyStateSpy.mockRestore(); } }); @@ -446,8 +405,7 @@ describe('DOM Utilities', () => { expect(addEventListenerSpy).toHaveBeenCalledTimes(1); expect(addEventListenerSpy).toHaveBeenNthCalledWith(1, 'DOMContentLoaded', mockCallback); expect(mockCallback).not.toHaveBeenCalled(); - } - finally { + } finally { readyStateSpy.mockRestore(); addEventListenerSpy.mockRestore(); } @@ -456,7 +414,9 @@ describe('DOM Utilities', () => { describe('escapeHtml', () => { it('should replace only the expected characters with their HTML entity equivalents', () => { - expect(escapeHtml('')).toBe('<script src="http://example.com/?a=1&b=2"></script>'); + expect(escapeHtml('')).toBe( + '<script src="http://example.com/?a=1&b=2"></script>', + ); }); }); diff --git a/assets/js/utils/__tests__/draggable.spec.ts b/assets/js/utils/__tests__/draggable.spec.ts index cda0598a..d5415c23 100644 --- a/assets/js/utils/__tests__/draggable.spec.ts +++ b/assets/js/utils/__tests__/draggable.spec.ts @@ -14,7 +14,7 @@ describe('Draggable Utilities', () => { items: items as unknown as DataTransferItemList, setData(format: string, data: string) { items.push({ type: format, getAsString: (callback: FunctionStringCallback) => callback(data) }); - } + }, } as unknown as DataTransfer; } Object.assign(mockEvent, { dataTransfer }); @@ -44,7 +44,6 @@ describe('Draggable Utilities', () => { mockDraggable = createDraggableElement(); mockDragContainer.appendChild(mockDraggable); - // Redirect all document event listeners to this element for easier cleanup documentEventListenerSpy = vi.spyOn(document, 'addEventListener').mockImplementation((...params) => { mockDragContainer.addEventListener(...params); @@ -67,7 +66,7 @@ describe('Draggable Utilities', () => { expect(mockDraggable).toHaveClass(draggingClass); }); - it('should add dummy data to the dragstart event if it\'s empty', () => { + it("should add dummy data to the dragstart event if it's empty", () => { initDraggables(); const mockEvent = createDragEvent('dragstart'); @@ -81,13 +80,13 @@ describe('Draggable Utilities', () => { expect(dataTransferItem.type).toEqual('text/plain'); let stringValue: string | undefined; - dataTransferItem.getAsString(value => { + dataTransferItem.getAsString((value) => { stringValue = value; }); expect(stringValue).toEqual(''); }); - it('should keep data in the dragstart event if it\'s present', () => { + it("should keep data in the dragstart event if it's present", () => { initDraggables(); const mockTransferItemType = getRandomArrayItem(['text/javascript', 'image/jpg', 'application/json']); @@ -95,7 +94,9 @@ describe('Draggable Utilities', () => { type: mockTransferItemType, } as unknown as DataTransferItem; - const mockEvent = createDragEvent('dragstart', { dataTransfer: { items: [mockDataTransferItem] as unknown as DataTransferItemList } } as DragEventInit); + const mockEvent = createDragEvent('dragstart', { + dataTransfer: { items: [mockDataTransferItem] as unknown as DataTransferItemList }, + } as DragEventInit); expect(mockEvent.dataTransfer?.items).toHaveLength(1); fireEvent(mockDraggable, mockEvent); @@ -203,8 +204,7 @@ describe('Draggable Utilities', () => { expect(mockDropEvent.defaultPrevented).toBe(true); expect(mockSecondDraggable).not.toHaveClass(draggingClass); expect(mockSecondDraggable.nextElementSibling).toBe(mockDraggable); - } - finally { + } finally { boundingBoxSpy.mockRestore(); } }); @@ -232,8 +232,7 @@ describe('Draggable Utilities', () => { expect(mockDropEvent.defaultPrevented).toBe(true); expect(mockSecondDraggable).not.toHaveClass(draggingClass); expect(mockDraggable.nextElementSibling).toBe(mockSecondDraggable); - } - finally { + } finally { boundingBoxSpy.mockRestore(); } }); @@ -254,7 +253,7 @@ describe('Draggable Utilities', () => { }); describe('dragEnd', () => { - it('should remove dragging class from source and over class from target\'s descendants', () => { + it("should remove dragging class from source and over class from target's descendants", () => { initDraggables(); const mockStartEvent = createDragEvent('dragstart'); @@ -298,8 +297,7 @@ describe('Draggable Utilities', () => { fireEvent(mockDraggable, mockEvent); expect(mockEvent.dataTransfer?.effectAllowed).toBeFalsy(); - } - finally { + } finally { draggableClosestSpy.mockRestore(); } }); diff --git a/assets/js/utils/__tests__/events.spec.ts b/assets/js/utils/__tests__/events.spec.ts index 575883b7..8c068e5b 100644 --- a/assets/js/utils/__tests__/events.spec.ts +++ b/assets/js/utils/__tests__/events.spec.ts @@ -60,7 +60,7 @@ describe('Event utils', () => { const mockButton = document.createElement('button'); const mockHandler = vi.fn(); - mockButton.addEventListener('click', e => leftClick(mockHandler)(e, mockButton)); + mockButton.addEventListener('click', (e) => leftClick(mockHandler)(e, mockButton)); fireEvent.click(mockButton, { button: 0 }); @@ -72,7 +72,7 @@ describe('Event utils', () => { const mockHandler = vi.fn(); const mockButtonNumber = getRandomArrayItem([1, 2, 3, 4, 5]); - mockButton.addEventListener('click', e => leftClick(mockHandler)(e, mockButton)); + mockButton.addEventListener('click', (e) => leftClick(mockHandler)(e, mockButton)); fireEvent.click(mockButton, { button: mockButtonNumber }); diff --git a/assets/js/utils/__tests__/image.spec.ts b/assets/js/utils/__tests__/image.spec.ts index f27a1cd1..8c3286f5 100644 --- a/assets/js/utils/__tests__/image.spec.ts +++ b/assets/js/utils/__tests__/image.spec.ts @@ -92,7 +92,7 @@ describe('Image utils', () => { extension: string; videoClasses?: string[]; imgClasses?: string[]; - } + }; const createMockElements = ({ videoClasses, imgClasses, extension }: CreateMockElementsOptions) => { const mockElement = document.createElement('div'); @@ -101,7 +101,7 @@ describe('Image utils', () => { const mockImage = new Image(); mockImage.src = mockImageUri; if (imgClasses) { - imgClasses.forEach(videoClass => { + imgClasses.forEach((videoClass) => { mockImage.classList.add(videoClass); }); } @@ -109,7 +109,7 @@ describe('Image utils', () => { const mockVideo = document.createElement('video'); if (videoClasses) { - videoClasses.forEach(videoClass => { + videoClasses.forEach((videoClass) => { mockVideo.classList.add(videoClass); }); } @@ -131,18 +131,11 @@ describe('Image utils', () => { }; it('should hide the img element and show the video instead if no picture element is present', () => { - const { - mockElement, - mockImage, - playSpy, - mockVideo, - mockSize, - mockSizeUrls, - mockSpoilerOverlay, - } = createMockElements({ - extension: 'webm', - videoClasses: ['hidden'], - }); + const { mockElement, mockImage, playSpy, mockVideo, mockSize, mockSizeUrls, mockSpoilerOverlay } = + createMockElements({ + extension: 'webm', + videoClasses: ['hidden'], + }); const result = showThumb(mockElement); @@ -168,7 +161,7 @@ describe('Image utils', () => { expect(result).toBe(true); }); - ['data-size', 'data-uris'].forEach(missingAttributeName => { + ['data-size', 'data-uris'].forEach((missingAttributeName) => { it(`should return early if the ${missingAttributeName} attribute is missing`, () => { const { mockElement } = createMockElements({ extension: 'webm', @@ -181,8 +174,7 @@ describe('Image utils', () => { const result = showThumb(mockElement); expect(result).toBe(false); expect(jsonParseSpy).not.toHaveBeenCalled(); - } - finally { + } finally { jsonParseSpy.mockRestore(); } }); @@ -226,13 +218,8 @@ describe('Image utils', () => { }); it('should show the correct thumbnail image for jpg extension', () => { - const { - mockElement, - mockSizeImage, - mockSizeUrls, - mockSize, - mockSpoilerOverlay, - } = createMockElementWithPicture('jpg'); + const { mockElement, mockSizeImage, mockSizeUrls, mockSize, mockSpoilerOverlay } = + createMockElementWithPicture('jpg'); const result = showThumb(mockElement); expect(mockSizeImage.src).toBe(mockSizeUrls[mockSize]); @@ -243,13 +230,8 @@ describe('Image utils', () => { }); it('should show the correct thumbnail image for gif extension', () => { - const { - mockElement, - mockSizeImage, - mockSizeUrls, - mockSize, - mockSpoilerOverlay, - } = createMockElementWithPicture('gif'); + const { mockElement, mockSizeImage, mockSizeUrls, mockSize, mockSpoilerOverlay } = + createMockElementWithPicture('gif'); const result = showThumb(mockElement); expect(mockSizeImage.src).toBe(mockSizeUrls[mockSize]); @@ -260,13 +242,8 @@ describe('Image utils', () => { }); it('should show the correct thumbnail image for webm extension', () => { - const { - mockElement, - mockSpoilerOverlay, - mockSizeImage, - mockSizeUrls, - mockSize, - } = createMockElementWithPicture('webm'); + const { mockElement, mockSpoilerOverlay, mockSizeImage, mockSizeUrls, mockSize } = + createMockElementWithPicture('webm'); const result = showThumb(mockElement); expect(mockSizeImage.src).toBe(mockSizeUrls[mockSize].replace('webm', 'gif')); @@ -284,12 +261,10 @@ describe('Image utils', () => { }); const checkSrcsetAttribute = (size: ImageSize, x2size: ImageSize) => { - const { - mockElement, - mockSizeImage, - mockSizeUrls, - mockSpoilerOverlay, - } = createMockElementWithPicture('jpg', size); + const { mockElement, mockSizeImage, mockSizeUrls, mockSpoilerOverlay } = createMockElementWithPicture( + 'jpg', + size, + ); const result = showThumb(mockElement); expect(mockSizeImage.src).toBe(mockSizeUrls[size]); @@ -312,12 +287,10 @@ describe('Image utils', () => { it('should NOT set srcset on img if thumbUri is a gif at small size', () => { const mockSize = 'small'; - const { - mockElement, - mockSizeImage, - mockSizeUrls, - mockSpoilerOverlay, - } = createMockElementWithPicture('gif', mockSize); + const { mockElement, mockSizeImage, mockSizeUrls, mockSpoilerOverlay } = createMockElementWithPicture( + 'gif', + mockSize, + ); const result = showThumb(mockElement); expect(mockSizeImage.src).toBe(mockSizeUrls[mockSize]); @@ -336,12 +309,7 @@ describe('Image utils', () => { }); it('should return false if img source already matches thumbUri', () => { - const { - mockElement, - mockSizeImage, - mockSizeUrls, - mockSize, - } = createMockElementWithPicture('jpg'); + const { mockElement, mockSizeImage, mockSizeUrls, mockSize } = createMockElementWithPicture('jpg'); mockSizeImage.src = mockSizeUrls[mockSize]; const result = showThumb(mockElement); expect(result).toBe(false); @@ -408,8 +376,7 @@ describe('Image utils', () => { expect(querySelectorSpy).toHaveBeenCalledTimes(2); expect(querySelectorSpy).toHaveBeenNthCalledWith(1, 'picture'); expect(querySelectorSpy).toHaveBeenNthCalledWith(2, 'video'); - } - finally { + } finally { querySelectorSpy.mockRestore(); } }); @@ -430,8 +397,7 @@ describe('Image utils', () => { expect(querySelectorSpy).toHaveBeenNthCalledWith(3, 'img'); expect(querySelectorSpy).toHaveBeenNthCalledWith(4, `.${spoilerOverlayClass}`); expect(mockVideo).not.toHaveClass(hiddenClass); - } - finally { + } finally { querySelectorSpy.mockRestore(); pauseSpy.mockRestore(); } @@ -458,8 +424,7 @@ describe('Image utils', () => { expect(mockVideo).toBeEmptyDOMElement(); expect(mockVideo).toHaveClass(hiddenClass); expect(pauseSpy).toHaveBeenCalled(); - } - finally { + } finally { pauseSpy.mockRestore(); } }); @@ -482,8 +447,7 @@ describe('Image utils', () => { expect(imgQuerySelectorSpy).toHaveBeenNthCalledWith(1, 'picture'); expect(pictureQuerySelectorSpy).toHaveBeenNthCalledWith(1, 'img'); expect(imgQuerySelectorSpy).toHaveBeenNthCalledWith(2, `.${spoilerOverlayClass}`); - } - finally { + } finally { imgQuerySelectorSpy.mockRestore(); pictureQuerySelectorSpy.mockRestore(); } diff --git a/assets/js/utils/__tests__/local-autocompleter.spec.ts b/assets/js/utils/__tests__/local-autocompleter.spec.ts index 182e1308..2310c92d 100644 --- a/assets/js/utils/__tests__/local-autocompleter.spec.ts +++ b/assets/js/utils/__tests__/local-autocompleter.spec.ts @@ -7,7 +7,7 @@ describe('Local Autocompleter', () => { let mockData: ArrayBuffer; const defaultK = 5; - beforeAll(async() => { + beforeAll(async () => { const mockDataPath = join(__dirname, 'autocomplete-compiled-v2.bin'); /** * Read pre-generated binary autocomplete data @@ -78,9 +78,7 @@ describe('Local Autocompleter', () => { it('should return namespaced suggestions without including namespace', () => { const result = localAc.topK('test', defaultK); - expect(result).toEqual([ - expect.objectContaining({ name: 'artist:test', imageCount: 1 }), - ]); + expect(result).toEqual([expect.objectContaining({ name: 'artist:test', imageCount: 1 })]); }); it('should return only the required number of suggestions', () => { diff --git a/assets/js/utils/__tests__/requests.spec.ts b/assets/js/utils/__tests__/requests.spec.ts index fd1af19e..db8395b9 100644 --- a/assets/js/utils/__tests__/requests.spec.ts +++ b/assets/js/utils/__tests__/requests.spec.ts @@ -29,7 +29,7 @@ describe('Request utils', () => { headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'xmlhttprequest' + 'x-requested-with': 'xmlhttprequest', }, }); }); @@ -46,12 +46,12 @@ describe('Request utils', () => { headers: { 'Content-Type': 'application/json', 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'xmlhttprequest' + 'x-requested-with': 'xmlhttprequest', }, body: JSON.stringify({ ...mockBody, - _method: mockVerb - }) + _method: mockVerb, + }), }); }); }); @@ -64,7 +64,7 @@ describe('Request utils', () => { credentials: 'same-origin', headers: { 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'xmlhttprequest' + 'x-requested-with': 'xmlhttprequest', }, }); }); diff --git a/assets/js/utils/__tests__/store.spec.ts b/assets/js/utils/__tests__/store.spec.ts index b99745bb..bb8d0168 100644 --- a/assets/js/utils/__tests__/store.spec.ts +++ b/assets/js/utils/__tests__/store.spec.ts @@ -60,9 +60,11 @@ describe('Store utilities', () => { }, }; const initialValueKeys = Object.keys(initialValues) as (keyof typeof initialValues)[]; - setStorageValue(initialValueKeys.reduce((acc, key) => { - return { ...acc, [key]: JSON.stringify(initialValues[key]) }; - }, {})); + setStorageValue( + initialValueKeys.reduce((acc, key) => { + return { ...acc, [key]: JSON.stringify(initialValues[key]) }; + }, {}), + ); initialValueKeys.forEach((key, i) => { const result = store.get(key); @@ -166,7 +168,11 @@ describe('Store utilities', () => { expect(setItemSpy).toHaveBeenCalledTimes(2); expect(setItemSpy).toHaveBeenNthCalledWith(1, mockKey, JSON.stringify(mockValue)); - expect(setItemSpy).toHaveBeenNthCalledWith(2, mockKey + lastUpdatedSuffix, JSON.stringify(initialDateNow + mockMaxAge)); + expect(setItemSpy).toHaveBeenNthCalledWith( + 2, + mockKey + lastUpdatedSuffix, + JSON.stringify(initialDateNow + mockMaxAge), + ); }); }); diff --git a/assets/js/utils/__tests__/tag.spec.ts b/assets/js/utils/__tests__/tag.spec.ts index 61a196b8..3598ee57 100644 --- a/assets/js/utils/__tests__/tag.spec.ts +++ b/assets/js/utils/__tests__/tag.spec.ts @@ -57,7 +57,7 @@ describe('Tag utilities', () => { }); describe('getHiddenTags', () => { - it('should get a single hidden tag\'s information', () => { + it("should get a single hidden tag's information", () => { window.booru.hiddenTagList = [1, 1]; const result = getHiddenTags(); @@ -72,12 +72,7 @@ describe('Tag utilities', () => { const result = getHiddenTags(); expect(result).toHaveLength(4); - expect(result).toEqual([ - mockTagInfo[3], - mockTagInfo[2], - mockTagInfo[1], - mockTagInfo[4], - ]); + expect(result).toEqual([mockTagInfo[3], mockTagInfo[2], mockTagInfo[1], mockTagInfo[4]]); }); }); @@ -91,7 +86,7 @@ describe('Tag utilities', () => { expect(result).toHaveLength(0); }); - it('should get a single spoilered tag\'s information', () => { + it("should get a single spoilered tag's information", () => { window.booru.spoileredTagList = [1, 1]; window.booru.ignoredTagList = []; window.booru.spoilerType = getEnabledSpoilerType(); @@ -110,12 +105,7 @@ describe('Tag utilities', () => { const result = getSpoileredTags(); expect(result).toHaveLength(4); - expect(result).toEqual([ - mockTagInfo[2], - mockTagInfo[3], - mockTagInfo[1], - mockTagInfo[4], - ]); + expect(result).toEqual([mockTagInfo[2], mockTagInfo[3], mockTagInfo[1], mockTagInfo[4]]); }); it('should omit ignored tags from the list', () => { @@ -125,10 +115,7 @@ describe('Tag utilities', () => { const result = getSpoileredTags(); expect(result).toHaveLength(2); - expect(result).toEqual([ - mockTagInfo[1], - mockTagInfo[4], - ]); + expect(result).toEqual([mockTagInfo[1], mockTagInfo[4]]); }); }); @@ -140,10 +127,7 @@ describe('Tag utilities', () => { const result = imageHitsTags(mockImage, [mockTagInfo[1], mockTagInfo[2], mockTagInfo[3], mockTagInfo[4]]); expect(result).toHaveLength(mockImageTags.length); - expect(result).toEqual([ - mockTagInfo[1], - mockTagInfo[4], - ]); + expect(result).toEqual([mockTagInfo[1], mockTagInfo[4]]); }); it('should return empty array if data attribute is missing', () => { @@ -174,12 +158,16 @@ describe('Tag utilities', () => { it('should return the correct value for two tags', () => { const result = displayTags([mockTagInfo[1], mockTagInfo[4]]); - expect(result).toEqual(`${mockTagInfo[1].name}, ${mockTagInfo[4].name}`); + expect(result).toEqual( + `${mockTagInfo[1].name}, ${mockTagInfo[4].name}`, + ); }); it('should return the correct value for three tags', () => { const result = displayTags([mockTagInfo[1], mockTagInfo[4], mockTagInfo[3]]); - expect(result).toEqual(`${mockTagInfo[1].name}, ${mockTagInfo[4].name}, ${mockTagInfo[3].name}`); + expect(result).toEqual( + `${mockTagInfo[1].name}, ${mockTagInfo[4].name}, ${mockTagInfo[3].name}`, + ); }); it('should escape HTML in the tag name', () => { diff --git a/assets/js/utils/dom.ts b/assets/js/utils/dom.ts index 07705cbe..12114b9b 100644 --- a/assets/js/utils/dom.ts +++ b/assets/js/utils/dom.ts @@ -5,54 +5,63 @@ type PhilomenaInputElements = HTMLTextAreaElement | HTMLInputElement | HTMLButto /** * Get the first matching element */ -export function $(selector: string, context: Pick = document): E | null { +export function $( + selector: string, + context: Pick = document, +): E | null { return context.querySelector(selector); } /** * Get every matching element as an array */ -export function $$(selector: string, context: Pick = document): E[] { +export function $$( + selector: string, + context: Pick = document, +): E[] { const elements = context.querySelectorAll(selector); return [...elements]; } export function showEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => el.classList.remove('hidden')); + ([] as E[]).concat(...elements).forEach((el) => el.classList.remove('hidden')); } export function hideEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => el.classList.add('hidden')); + ([] as E[]).concat(...elements).forEach((el) => el.classList.add('hidden')); } export function toggleEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => el.classList.toggle('hidden')); + ([] as E[]).concat(...elements).forEach((el) => el.classList.toggle('hidden')); } export function clearEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => { + ([] as E[]).concat(...elements).forEach((el) => { while (el.firstChild) el.removeChild(el.firstChild); }); } export function disableEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => { + ([] as E[]).concat(...elements).forEach((el) => { el.disabled = true; }); } export function enableEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => { + ([] as E[]).concat(...elements).forEach((el) => { el.disabled = false; }); } export function removeEl(...elements: E[] | ConcatArray[]) { - ([] as E[]).concat(...elements).forEach(el => el.parentNode?.removeChild(el)); + ([] as E[]).concat(...elements).forEach((el) => el.parentNode?.removeChild(el)); } -export function makeEl(tag: Tag, attr?: Partial): HTMLElementTagNameMap[Tag] { +export function makeEl( + tag: Tag, + attr?: Partial, +): HTMLElementTagNameMap[Tag] { const el = document.createElement(tag); if (attr) { for (const prop in attr) { @@ -65,8 +74,11 @@ export function makeEl(tag: Tag, attr?: return el; } -export function onLeftClick(callback: (e: MouseEvent) => boolean | void, context: Pick = document): VoidFunction { - const handler: typeof callback = event => { +export function onLeftClick( + callback: (e: MouseEvent) => boolean | void, + context: Pick = document, +): VoidFunction { + const handler: typeof callback = (event) => { if (event.button === 0) callback(event); }; context.addEventListener('click', handler); @@ -80,24 +92,19 @@ export function onLeftClick(callback: (e: MouseEvent) => boolean | void, context export function whenReady(callback: VoidFunction): void { if (document.readyState !== 'loading') { callback(); - } - else { + } else { document.addEventListener('DOMContentLoaded', callback); } } export function escapeHtml(html: string): string { - return html.replace(/&/g, '&') - .replace(/>/g, '>') - .replace(//g, '>').replace(/(of: Node): N { - return Array.prototype.filter.call(of.childNodes, el => el.nodeType === Node.TEXT_NODE)[0]; + return Array.prototype.filter.call(of.childNodes, (el) => el.nodeType === Node.TEXT_NODE)[0]; } diff --git a/assets/js/utils/draggable.ts b/assets/js/utils/draggable.ts index 71d0f8bf..69c5a9d3 100644 --- a/assets/js/utils/draggable.ts +++ b/assets/js/utils/draggable.ts @@ -47,8 +47,7 @@ function drop(event: DragEvent, target: HTMLElement) { if (event.clientX < detX) { target.insertAdjacentElement('beforebegin', dragSrcEl); - } - else { + } else { target.insertAdjacentElement('afterend', dragSrcEl); } } @@ -57,18 +56,18 @@ function dragEnd(event: DragEvent, target: HTMLElement) { clearDragSource(); if (target.parentNode) { - $$('.over', target.parentNode).forEach(t => t.classList.remove('over')); + $$('.over', target.parentNode).forEach((t) => t.classList.remove('over')); } } export function initDraggables() { const draggableSelector = '.drag-container [draggable]'; - delegate(document, 'dragstart', { [draggableSelector]: dragStart}); - delegate(document, 'dragover', { [draggableSelector]: dragOver}); - delegate(document, 'dragenter', { [draggableSelector]: dragEnter}); - delegate(document, 'dragleave', { [draggableSelector]: dragLeave}); - delegate(document, 'dragend', { [draggableSelector]: dragEnd}); - delegate(document, 'drop', { [draggableSelector]: drop}); + delegate(document, 'dragstart', { [draggableSelector]: dragStart }); + delegate(document, 'dragover', { [draggableSelector]: dragOver }); + delegate(document, 'dragenter', { [draggableSelector]: dragEnter }); + delegate(document, 'dragleave', { [draggableSelector]: dragLeave }); + delegate(document, 'dragend', { [draggableSelector]: dragEnd }); + delegate(document, 'drop', { [draggableSelector]: drop }); } export function clearDragSource() { diff --git a/assets/js/utils/events.ts b/assets/js/utils/events.ts index e92b0109..06733721 100644 --- a/assets/js/utils/events.ts +++ b/assets/js/utils/events.ts @@ -3,16 +3,16 @@ import '../../types/ujs'; export interface PhilomenaAvailableEventsMap { - dragstart: DragEvent, - dragover: DragEvent, - dragenter: DragEvent, - dragleave: DragEvent, - dragend: DragEvent, - drop: DragEvent, - click: MouseEvent, - submit: Event, - reset: Event, - fetchcomplete: FetchcompleteEvent + dragstart: DragEvent; + dragover: DragEvent; + dragenter: DragEvent; + dragleave: DragEvent; + dragend: DragEvent; + drop: DragEvent; + click: MouseEvent; + submit: Event; + reset: Event; + fetchcomplete: FetchcompleteEvent; } export interface PhilomenaEventElement { @@ -20,7 +20,7 @@ export interface PhilomenaEventElement { type: K, // eslint-disable-next-line @typescript-eslint/no-explicit-any listener: (this: Document | HTMLElement, ev: PhilomenaAvailableEventsMap[K]) => any, - options?: boolean | AddEventListenerOptions | undefined + options?: boolean | AddEventListenerOptions | undefined, ): void; } @@ -30,21 +30,25 @@ export function fire(el: El, event: string, detail: D) { export function on( node: PhilomenaEventElement, - event: K, selector: string, func: ((e: PhilomenaAvailableEventsMap[K], target: Element) => boolean) + event: K, + selector: string, + func: (e: PhilomenaAvailableEventsMap[K], target: Element) => boolean, ) { delegate(node, event, { [selector]: func }); } export function leftClick(func: (e: E, t: Target) => void) { - return (event: E, target: Target) => { if (event.button === 0) return func(event, target); }; + return (event: E, target: Target) => { + if (event.button === 0) return func(event, target); + }; } export function delegate( node: PhilomenaEventElement, event: K, - selectors: Record void | boolean)> + selectors: Record void | boolean>, ) { - node.addEventListener(event, e => { + node.addEventListener(event, (e) => { for (const selector in selectors) { const evtTarget = e.target as EventTarget | Target | null; if (evtTarget && 'closest' in evtTarget && typeof evtTarget.closest === 'function') { diff --git a/assets/js/utils/image.ts b/assets/js/utils/image.ts index 99ab2722..cbdbb20e 100644 --- a/assets/js/utils/image.ts +++ b/assets/js/utils/image.ts @@ -53,8 +53,7 @@ export function showThumb(img: HTMLDivElement) { if (uris[size].indexOf('.webm') !== -1) { overlay.classList.remove('hidden'); overlay.innerHTML = 'WebM'; - } - else { + } else { overlay.classList.add('hidden'); } @@ -118,7 +117,9 @@ export function spoilerThumb(img: HTMLDivElement, spoilerUri: string, reason: st switch (window.booru.spoilerType) { case 'click': - img.addEventListener('click', event => { if (showThumb(img)) event.preventDefault(); }); + img.addEventListener('click', (event) => { + if (showThumb(img)) event.preventDefault(); + }); img.addEventListener('mouseleave', () => hideThumb(img, spoilerUri, reason)); break; case 'hover': diff --git a/assets/js/utils/lerp.ts b/assets/js/utils/lerp.ts index 41170284..42d58c2e 100644 --- a/assets/js/utils/lerp.ts +++ b/assets/js/utils/lerp.ts @@ -5,8 +5,11 @@ // clamp the value between min and max, depending on whether // the delta >= 1 or <= 0. export function lerp(delta: number, from: number, to: number): number { - if (delta >= 1) { return to; } - else if (delta <= 0) { return from; } + if (delta >= 1) { + return to; + } else if (delta <= 0) { + return from; + } return from + (to - from) * delta; } diff --git a/assets/js/utils/local-autocompleter.ts b/assets/js/utils/local-autocompleter.ts index 73d88f92..f27f8e2d 100644 --- a/assets/js/utils/local-autocompleter.ts +++ b/assets/js/utils/local-autocompleter.ts @@ -70,7 +70,7 @@ export class LocalAutocompleter { associations.push(this.view.getUint32(location + 1 + nameLength + 1 + i * 4, true)); } - return [ name, associations ]; + return [name, associations]; } /** @@ -79,14 +79,14 @@ export class LocalAutocompleter { getResultAt(i: number): [string, Result] { const nameLocation = this.view.getUint32(this.referenceStart + i * 8, true); const imageCount = this.view.getInt32(this.referenceStart + i * 8 + 4, true); - const [ name, associations ] = this.getTagFromLocation(nameLocation); + const [name, associations] = this.getTagFromLocation(nameLocation); if (imageCount < 0) { // This is actually an alias, so follow it - return [ name, this.getResultAt(-imageCount - 1)[1] ]; + return [name, this.getResultAt(-imageCount - 1)[1]]; } - return [ name, { name, imageCount, associations } ]; + return [name, { name, imageCount, associations }]; } /** @@ -100,7 +100,11 @@ export class LocalAutocompleter { /** * Perform a binary search to fetch all results matching a condition. */ - scanResults(getResult: (i: number) => [string, Result], compare: (name: string) => number, results: Record) { + scanResults( + getResult: (i: number) => [string, Result], + compare: (name: string) => number, + results: Record, + ) { const unfilter = store.get('unfilter_tag_suggestions'); let min = 0; @@ -109,14 +113,13 @@ export class LocalAutocompleter { const hiddenTags = window.booru.hiddenTagList; while (min < max - 1) { - const med = min + (max - min) / 2 | 0; + const med = (min + (max - min) / 2) | 0; const sortKey = getResult(med)[0]; if (compare(sortKey) >= 0) { // too large, go left max = med; - } - else { + } else { // too small, go right min = med; } @@ -124,13 +127,13 @@ export class LocalAutocompleter { // Scan forward until no more matches occur while (min < this.numTags - 1) { - const [ sortKey, result ] = getResult(++min); + const [sortKey, result] = getResult(++min); if (compare(sortKey) !== 0) { break; } // Add if not filtering or no associations are filtered - if (unfilter || hiddenTags.findIndex(ht => result.associations.includes(ht)) === -1) { + if (unfilter || hiddenTags.findIndex((ht) => result.associations.includes(ht)) === -1) { results[result.name] = result; } } diff --git a/assets/js/utils/requests.ts b/assets/js/utils/requests.ts index e71a7662..33a045a4 100644 --- a/assets/js/utils/requests.ts +++ b/assets/js/utils/requests.ts @@ -9,7 +9,7 @@ export function fetchJson(verb: HttpMethod, endpoint: string, body?: Record { credentials: 'same-origin', headers: { 'x-csrf-token': window.booru.csrfToken, - 'x-requested-with': 'xmlhttprequest' + 'x-requested-with': 'xmlhttprequest', }, }); } diff --git a/assets/js/utils/store.ts b/assets/js/utils/store.ts index a71d4256..d4b8579d 100644 --- a/assets/js/utils/store.ts +++ b/assets/js/utils/store.ts @@ -5,13 +5,11 @@ export const lastUpdatedSuffix = '__lastUpdated'; export default { - set(key: string, value: unknown) { try { localStorage.setItem(key, JSON.stringify(value)); return true; - } - catch { + } catch { return false; } }, @@ -21,8 +19,7 @@ export default { if (value === null) return null; try { return JSON.parse(value); - } - catch { + } catch { return value as unknown as Value; } }, @@ -31,8 +28,7 @@ export default { try { localStorage.removeItem(key); return true; - } - catch { + } catch { return false; } }, @@ -61,5 +57,4 @@ export default { return lastUpdatedTime === null || Date.now() > lastUpdatedTime; }, - }; diff --git a/assets/js/utils/tag.ts b/assets/js/utils/tag.ts index 26c6bdf9..7132efcb 100644 --- a/assets/js/utils/tag.ts +++ b/assets/js/utils/tag.ts @@ -30,7 +30,7 @@ function sortTags(hidden: boolean, a: TagData, b: TagData): number { export function getHiddenTags(): TagData[] { return unique(window.booru.hiddenTagList) - .map(tagId => getTag(tagId)) + .map((tagId) => getTag(tagId)) .sort(sortTags.bind(null, true)); } @@ -38,8 +38,8 @@ export function getSpoileredTags(): TagData[] { if (window.booru.spoilerType === 'off') return []; return unique(window.booru.spoileredTagList) - .filter(tagId => window.booru.ignoredTagList.indexOf(tagId) === -1) - .map(tagId => getTag(tagId)) + .filter((tagId) => window.booru.ignoredTagList.indexOf(tagId) === -1) + .map((tagId) => getTag(tagId)) .sort(sortTags.bind(null, false)); } @@ -49,7 +49,7 @@ export function imageHitsTags(img: HTMLElement, matchTags: TagData[]): TagData[] return []; } const imageTags = JSON.parse(imageTagsString); - return matchTags.filter(t => imageTags.indexOf(t.id) !== -1); + return matchTags.filter((t) => imageTags.indexOf(t.id) !== -1); } export function imageHitsComplex(img: HTMLElement, matchComplex: AstMatcher) { @@ -57,11 +57,13 @@ export function imageHitsComplex(img: HTMLElement, matchComplex: AstMatcher) { } export function displayTags(tags: TagData[]): string { - const mainTag = tags[0], otherTags = tags.slice(1); - let list = escapeHtml(mainTag.name), extras; + 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(', '); + extras = otherTags.map((tag) => escapeHtml(tag.name)).join(', '); list += `, ${extras}`; } diff --git a/assets/js/when-ready.ts b/assets/js/when-ready.ts index 07462efb..08d34edb 100644 --- a/assets/js/when-ready.ts +++ b/assets/js/when-ready.ts @@ -2,41 +2,40 @@ * Functions to execute when the DOM is ready */ -import { whenReady } from './utils/dom'; +import { whenReady } from './utils/dom'; -import { listenAutocomplete } from './autocomplete'; -import { loadBooruData } from './booru'; -import { registerEvents } from './boorujs'; -import { setupBurgerMenu } from './burger'; -import { bindCaptchaLinks } from './captcha'; -import { setupComments } from './comment'; -import { setupDupeReports } from './duplicate_reports'; -import { setSesCookie } from './fp'; -import { setupGalleryEditing } from './galleries'; +import { listenAutocomplete } from './autocomplete'; +import { loadBooruData } from './booru'; +import { registerEvents } from './boorujs'; +import { setupBurgerMenu } from './burger'; +import { bindCaptchaLinks } from './captcha'; +import { setupComments } from './comment'; +import { setupDupeReports } from './duplicate_reports'; +import { setSesCookie } from './fp'; +import { setupGalleryEditing } from './galleries'; import { initImagesClientside } from './imagesclientside'; -import { bindImageTarget } from './image_expansion'; -import { setupEvents } from './misc'; -import { setupNotifications } from './notifications'; -import { setupPreviews } from './preview'; -import { setupQuickTag } from './quick-tag'; -import { setupSettings } from './settings'; -import { listenForKeys } from './shortcuts'; -import { initTagDropdown } from './tags'; -import { setupTagListener } from './tagsinput'; -import { setupTagEvents } from './tagsmisc'; -import { setupTimestamps } from './timeago'; -import { setupImageUpload } from './upload'; -import { setupSearch } from './search'; -import { setupToolbar } from './markdowntoolbar'; -import { hideStaffTools } from './staffhider'; -import { pollOptionCreator } from './poll'; -import { warnAboutPMs } from './pmwarning'; -import { imageSourcesCreator } from './sources'; -import { sizeGraphs } from './graph'; -import { setupSliders } from './slider'; +import { bindImageTarget } from './image_expansion'; +import { setupEvents } from './misc'; +import { setupNotifications } from './notifications'; +import { setupPreviews } from './preview'; +import { setupQuickTag } from './quick-tag'; +import { setupSettings } from './settings'; +import { listenForKeys } from './shortcuts'; +import { initTagDropdown } from './tags'; +import { setupTagListener } from './tagsinput'; +import { setupTagEvents } from './tagsmisc'; +import { setupTimestamps } from './timeago'; +import { setupImageUpload } from './upload'; +import { setupSearch } from './search'; +import { setupToolbar } from './markdowntoolbar'; +import { hideStaffTools } from './staffhider'; +import { pollOptionCreator } from './poll'; +import { warnAboutPMs } from './pmwarning'; +import { imageSourcesCreator } from './sources'; +import { sizeGraphs } from './graph'; +import { setupSliders } from './slider'; whenReady(() => { - loadBooruData(); listenAutocomplete(); registerEvents(); @@ -67,5 +66,4 @@ whenReady(() => { imageSourcesCreator(); setupSliders(); sizeGraphs(); - }); diff --git a/assets/package-lock.json b/assets/package-lock.json index 95dcdbf5..a16baf7c 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -11,13 +11,10 @@ "@types/web": "^0.0.148", "autoprefixer": "^10.4.19", "cross-env": "^7.0.3", - "eslint": "^9.4.0", - "jest-environment-jsdom": "^29.7.0", "normalize.css": "^8.0.1", "postcss-mixins": "^10.0.1", "postcss-simple-vars": "^7.0.1", "typescript": "^5.4", - "typescript-eslint": "8.0.0-alpha.39", "vite": "^5.2" }, "devDependencies": { @@ -26,12 +23,17 @@ "@types/chai-dom": "^1.11.3", "@vitest/coverage-v8": "^1.6.0", "chai": "^5", + "eslint": "^9.4.0", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-vitest": "^0.5.4", + "jest-environment-jsdom": "^29.7.0", "jsdom": "^24.1.0", "prettier": "^3.3.2", "stylelint": "^16.6.1", "stylelint-config-standard": "^36.0.0", "stylelint-prettier": "^5.0.0", + "typescript-eslint": "8.0.0-alpha.39", "vitest": "^1.6.0", "vitest-fetch-mock": "^0.2.2" } @@ -59,6 +61,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, "dependencies": { "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" @@ -80,6 +83,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, "engines": { "node": ">=6.9.0" } @@ -88,6 +92,7 @@ "version": "7.24.7", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", @@ -102,6 +107,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "dependencies": { "color-convert": "^1.9.0" }, @@ -113,6 +119,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -126,6 +133,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "dependencies": { "color-name": "1.1.3" } @@ -133,12 +141,14 @@ "node_modules/@babel/highlight/node_modules/color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "node_modules/@babel/highlight/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, "engines": { "node": ">=0.8.0" } @@ -147,6 +157,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -155,6 +166,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -788,6 +800,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, "dependencies": { "eslint-visitor-keys": "^3.3.0" }, @@ -802,6 +815,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -813,6 +827,7 @@ "version": "4.11.0", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } @@ -821,6 +836,7 @@ "version": "0.17.0", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "dev": true, "dependencies": { "@eslint/object-schema": "^2.1.4", "debug": "^4.3.1", @@ -834,6 +850,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -856,6 +873,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -864,6 +882,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -881,6 +900,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, "engines": { "node": ">=12.22" }, @@ -893,6 +913,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, "engines": { "node": ">=18.18" }, @@ -914,6 +935,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", @@ -928,6 +950,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", @@ -944,6 +967,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -955,6 +979,7 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -1047,6 +1072,18 @@ "node": ">= 8" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.18.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", @@ -1242,12 +1279,14 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true }, "node_modules/@sinonjs/commons": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, "dependencies": { "type-detect": "4.0.8" } @@ -1256,6 +1295,7 @@ "version": "10.3.0", "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, "dependencies": { "@sinonjs/commons": "^3.0.0" } @@ -1347,6 +1387,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, "engines": { "node": ">= 10" } @@ -1380,12 +1421,14 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -1394,6 +1437,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } @@ -1402,6 +1446,7 @@ "version": "20.0.1", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", @@ -1412,6 +1457,7 @@ "version": "20.14.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "devOptional": true, "dependencies": { "undici-types": "~5.26.4" } @@ -1427,12 +1473,14 @@ "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true }, "node_modules/@types/tough-cookie": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==" + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true }, "node_modules/@types/web": { "version": "0.0.148", @@ -1443,6 +1491,7 @@ "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, "dependencies": { "@types/yargs-parser": "*" } @@ -1450,12 +1499,14 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.39.tgz", "integrity": "sha512-ILv1vDA8M9ah1vzYpnOs4UOLRdB63Ki/rsxedVikjMLq68hFfpsDR25bdMZ4RyUkzLJwOhcg3Jujm/C1nupXKA==", + "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.0.0-alpha.39", @@ -1488,6 +1539,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0-alpha.39.tgz", "integrity": "sha512-5k+pwV91plJojHgZkWlq4/TQdOrnEaeSvt48V0m8iEwdMJqX/63BXYxy8BUOSghWcjp05s73vy9HJjovAKmHkQ==", + "dev": true, "dependencies": { "@typescript-eslint/scope-manager": "8.0.0-alpha.39", "@typescript-eslint/types": "8.0.0-alpha.39", @@ -1515,6 +1567,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.39.tgz", "integrity": "sha512-HCBlKQROY+JIgWolucdFMj1W3VUnnIQTdxAhxJTAj3ix2nASmvKIFgrdo5KQMrXxQj6tC4l3zva10L+s0dUIIw==", + "dev": true, "dependencies": { "@typescript-eslint/types": "8.0.0-alpha.39", "@typescript-eslint/visitor-keys": "8.0.0-alpha.39" @@ -1531,6 +1584,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.39.tgz", "integrity": "sha512-alO13fRU6yVeJbwl9ESI3AYhq5dQdz3Dpd0I5B4uezs2lvgYp44dZsj5hWyPz/kL7JFEsjbn+4b/CZA0OQJzjA==", + "dev": true, "dependencies": { "@typescript-eslint/typescript-estree": "8.0.0-alpha.39", "@typescript-eslint/utils": "8.0.0-alpha.39", @@ -1554,6 +1608,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.39.tgz", "integrity": "sha512-yINN7j0/+S1VGSp0IgH52oQvUx49vkOug6xbrDA/9o+U55yCAQKSvYWvzYjNa+SZE3hXI0zwvYtMVsIAAMmKIQ==", + "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -1566,6 +1621,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.39.tgz", "integrity": "sha512-S8gREuP8r8PCxGegeojeXntx0P50ul9YH7c7JYpbLIIsEPNr5f7UHlm+I1NUbL04CBin4kvZ60TG4eWr/KKN9A==", + "dev": true, "dependencies": { "@typescript-eslint/types": "8.0.0-alpha.39", "@typescript-eslint/visitor-keys": "8.0.0-alpha.39", @@ -1593,6 +1649,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -1601,6 +1658,7 @@ "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1615,6 +1673,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.39.tgz", "integrity": "sha512-Nr2PrlfNhrNQTlFHlD7XJdTGw/Vt8qY44irk6bfjn9LxGdSG5e4c1R2UN6kvGMhhx20DBPbM7q3Z3r+huzmL1w==", + "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@typescript-eslint/scope-manager": "8.0.0-alpha.39", @@ -1636,6 +1695,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.39.tgz", "integrity": "sha512-DVJ0UdhucZy+/1GlIy7FX2+CFhCeNAi4VwaEAe7u2UDenQr9/kGqvzx00UlpWibmEVDw4KsPOI7Aqa1+2Vqfmw==", + "dev": true, "dependencies": { "@typescript-eslint/types": "8.0.0-alpha.39", "eslint-visitor-keys": "^3.4.3" @@ -1652,6 +1712,7 @@ "version": "3.4.3", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, @@ -1928,12 +1989,14 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead" + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true }, "node_modules/acorn": { "version": "8.12.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -1945,6 +2008,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, "dependencies": { "acorn": "^8.1.0", "acorn-walk": "^8.0.2" @@ -1954,6 +2018,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -1962,6 +2027,7 @@ "version": "8.3.3", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.3.tgz", "integrity": "sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==", + "dev": true, "dependencies": { "acorn": "^8.11.0" }, @@ -1985,6 +2051,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2000,6 +2067,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -2008,6 +2076,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -2021,7 +2090,8 @@ "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true }, "node_modules/aria-query": { "version": "5.3.0", @@ -2036,6 +2106,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, "engines": { "node": ">=8" } @@ -2061,7 +2132,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/autoprefixer": { "version": "10.4.19", @@ -2102,12 +2174,14 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2168,6 +2242,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "engines": { "node": ">=6" } @@ -2219,6 +2294,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2243,6 +2319,7 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, "funding": [ { "type": "github", @@ -2257,6 +2334,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -2267,7 +2345,8 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "node_modules/colord": { "version": "2.9.3", @@ -2279,6 +2358,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2289,7 +2369,8 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/confbox": { "version": "0.1.7", @@ -2405,7 +2486,8 @@ "node_modules/cssom": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==" + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true }, "node_modules/cssstyle": { "version": "4.0.1", @@ -2442,6 +2524,7 @@ "version": "4.3.5", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -2457,7 +2540,8 @@ "node_modules/decimal.js": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true }, "node_modules/deep-eql": { "version": "5.0.2", @@ -2471,12 +2555,14 @@ "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -2503,6 +2589,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, "dependencies": { "path-type": "^4.0.0" }, @@ -2521,6 +2608,7 @@ "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", "deprecated": "Use your platform's native DOMException instead", + "dev": true, "dependencies": { "webidl-conversions": "^7.0.0" }, @@ -2543,6 +2631,7 @@ "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, "engines": { "node": ">=0.12" }, @@ -2617,6 +2706,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { "node": ">=10" }, @@ -2628,6 +2718,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", @@ -2648,6 +2739,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", + "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2694,6 +2786,48 @@ "url": "https://eslint.org/donate" } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", + "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.8.6" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-plugin-vitest": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/eslint-plugin-vitest/-/eslint-plugin-vitest-0.5.4.tgz", @@ -2855,6 +2989,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "dev": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -2870,6 +3005,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -2881,6 +3017,7 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", "integrity": "sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==", + "dev": true, "dependencies": { "acorn": "^8.12.0", "acorn-jsx": "^5.3.2", @@ -2897,6 +3034,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -2909,6 +3047,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, "dependencies": { "estraverse": "^5.1.0" }, @@ -2920,6 +3059,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "dependencies": { "estraverse": "^5.2.0" }, @@ -2931,6 +3071,7 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { "node": ">=4.0" } @@ -2948,6 +3089,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2978,7 +3120,8 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true }, "node_modules/fast-diff": { "version": "1.3.0", @@ -3015,12 +3158,14 @@ "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "node_modules/fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -3043,6 +3188,7 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "dependencies": { "flat-cache": "^4.0.0" }, @@ -3065,6 +3211,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" @@ -3080,6 +3227,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" @@ -3091,12 +3239,14 @@ "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==" + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3183,6 +3333,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "dependencies": { "is-glob": "^4.0.3" }, @@ -3232,6 +3383,7 @@ "version": "14.0.0", "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, "engines": { "node": ">=18" }, @@ -3243,6 +3395,7 @@ "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", @@ -3267,17 +3420,20 @@ "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -3351,6 +3507,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -3362,6 +3519,7 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "dev": true, "engines": { "node": ">= 4" } @@ -3370,6 +3528,7 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -3385,6 +3544,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "engines": { "node": ">=0.8.19" } @@ -3467,6 +3627,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, "engines": { "node": ">=8" } @@ -3483,7 +3644,8 @@ "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==" + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true }, "node_modules/is-stream": { "version": "3.0.0", @@ -3556,6 +3718,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -3582,6 +3745,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "dependencies": { "debug": "4" }, @@ -3593,6 +3757,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, "dependencies": { "cssom": "~0.3.6" }, @@ -3603,12 +3768,14 @@ "node_modules/jest-environment-jsdom/node_modules/cssstyle/node_modules/cssom": { "version": "0.3.8", "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true }, "node_modules/jest-environment-jsdom/node_modules/data-urls": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, "dependencies": { "abab": "^2.0.6", "whatwg-mimetype": "^3.0.0", @@ -3622,6 +3789,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, "dependencies": { "whatwg-encoding": "^2.0.0" }, @@ -3633,6 +3801,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, "dependencies": { "@tootallnate/once": "2", "agent-base": "6", @@ -3646,6 +3815,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -3658,6 +3828,7 @@ "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, "dependencies": { "abab": "^2.0.6", "acorn": "^8.8.1", @@ -3702,6 +3873,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, "dependencies": { "punycode": "^2.1.1" }, @@ -3713,6 +3885,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, "dependencies": { "xml-name-validator": "^4.0.0" }, @@ -3724,6 +3897,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, "dependencies": { "iconv-lite": "0.6.3" }, @@ -3735,6 +3909,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, "engines": { "node": ">=12" } @@ -3743,6 +3918,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, "dependencies": { "tr46": "^3.0.0", "webidl-conversions": "^7.0.0" @@ -3755,6 +3931,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, "engines": { "node": ">=12" } @@ -3763,6 +3940,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -3782,6 +3960,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, "engines": { "node": ">=10" }, @@ -3793,6 +3972,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -3805,12 +3985,14 @@ "node_modules/jest-message-util/node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -3824,6 +4006,7 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -3839,12 +4022,14 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, "dependencies": { "argparse": "^2.0.1" }, @@ -3895,7 +4080,8 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -3906,17 +4092,20 @@ "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "dependencies": { "json-buffer": "3.0.1" } @@ -3940,6 +4129,7 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" @@ -3974,6 +4164,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, "dependencies": { "p-locate": "^5.0.0" }, @@ -3993,7 +4184,8 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true }, "node_modules/lodash.truncate": { "version": "4.4.2", @@ -4112,6 +4304,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "engines": { "node": ">= 0.6" } @@ -4120,6 +4313,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -4152,6 +4346,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4174,7 +4369,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/nanoid": { "version": "3.3.7", @@ -4196,7 +4392,8 @@ "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, "node_modules/node-fetch": { "version": "2.7.0", @@ -4297,7 +4494,8 @@ "node_modules/nwsapi": { "version": "2.2.10", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz", - "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==" + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==", + "dev": true }, "node_modules/once": { "version": "1.4.0", @@ -4327,6 +4525,7 @@ "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", @@ -4343,6 +4542,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, "dependencies": { "yocto-queue": "^0.1.0" }, @@ -4357,6 +4557,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, "dependencies": { "p-limit": "^3.0.2" }, @@ -4371,6 +4572,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "dependencies": { "callsites": "^3.0.0" }, @@ -4400,6 +4602,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, "dependencies": { "entities": "^4.4.0" }, @@ -4411,6 +4614,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, "engines": { "node": ">=8" } @@ -4436,6 +4640,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, "engines": { "node": ">=8" } @@ -4623,6 +4828,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, "engines": { "node": ">= 0.8.0" } @@ -4683,12 +4889,14 @@ "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "engines": { "node": ">=6" } @@ -4696,7 +4904,8 @@ "node_modules/querystringify": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true }, "node_modules/queue-microtask": { "version": "1.2.3", @@ -4754,12 +4963,14 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "engines": { "node": ">=4" } @@ -4838,12 +5049,14 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, "dependencies": { "xmlchars": "^2.2.0" }, @@ -4855,6 +5068,7 @@ "version": "7.6.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -4903,6 +5117,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, "engines": { "node": ">=8" } @@ -4928,6 +5143,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, "optional": true, "engines": { "node": ">=0.10.0" @@ -4945,6 +5161,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -4956,6 +5173,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, "engines": { "node": ">=8" } @@ -4990,6 +5208,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5025,6 +5244,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, "engines": { "node": ">=8" }, @@ -5262,6 +5482,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -5291,7 +5512,24 @@ "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "node_modules/synckit": { + "version": "0.8.8", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", + "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } }, "node_modules/table": { "version": "6.8.2", @@ -5348,7 +5586,8 @@ "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, "node_modules/tinybench": { "version": "2.8.0", @@ -5398,6 +5637,7 @@ "version": "4.1.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, "dependencies": { "psl": "^1.1.33", "punycode": "^2.1.1", @@ -5424,6 +5664,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "dev": true, "engines": { "node": ">=16" }, @@ -5431,10 +5672,17 @@ "typescript": ">=4.2.0" } }, + "node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "dependencies": { "prelude-ls": "^1.2.1" }, @@ -5446,6 +5694,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, "engines": { "node": ">=4" } @@ -5466,6 +5715,7 @@ "version": "8.0.0-alpha.39", "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.0-alpha.39.tgz", "integrity": "sha512-bsuR1BVJfHr7sBh7Cca962VPIcP+5UWaIa/+6PpnFZ+qtASjGTxKWIF5dG2o73BX9NsyqQfvRWujb3M9CIoRXA==", + "dev": true, "dependencies": { "@typescript-eslint/eslint-plugin": "8.0.0-alpha.39", "@typescript-eslint/parser": "8.0.0-alpha.39", @@ -5493,12 +5743,14 @@ "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "devOptional": true }, "node_modules/universalify": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, "engines": { "node": ">= 4.0.0" } @@ -5536,6 +5788,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "dependencies": { "punycode": "^2.1.0" } @@ -5544,6 +5797,7 @@ "version": "1.5.10", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -5796,6 +6050,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, "engines": { "node": ">=12" } @@ -5868,6 +6123,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -5895,6 +6151,7 @@ "version": "8.18.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, "engines": { "node": ">=10.0.0" }, @@ -5923,12 +6180,14 @@ "node_modules/xmlchars": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, "engines": { "node": ">=10" }, diff --git a/assets/package.json b/assets/package.json index 8fe6f207..1dbf4399 100644 --- a/assets/package.json +++ b/assets/package.json @@ -14,11 +14,8 @@ "@fortawesome/fontawesome-free": "^6.5.2", "@types/postcss-mixins": "^9.0.5", "@types/web": "^0.0.148", - "typescript-eslint": "8.0.0-alpha.39", "autoprefixer": "^10.4.19", "cross-env": "^7.0.3", - "eslint": "^9.4.0", - "jest-environment-jsdom": "^29.7.0", "normalize.css": "^8.0.1", "postcss-mixins": "^10.0.1", "postcss-simple-vars": "^7.0.1", @@ -31,12 +28,16 @@ "@types/chai-dom": "^1.11.3", "@vitest/coverage-v8": "^1.6.0", "chai": "^5", + "eslint": "^9.4.0", + "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-vitest": "^0.5.4", + "jest-environment-jsdom": "^29.7.0", "jsdom": "^24.1.0", "prettier": "^3.3.2", "stylelint": "^16.6.1", "stylelint-config-standard": "^36.0.0", "stylelint-prettier": "^5.0.0", + "typescript-eslint": "8.0.0-alpha.39", "vitest": "^1.6.0", "vitest-fetch-mock": "^0.2.2" } diff --git a/assets/test/fix-event-listeners.ts b/assets/test/fix-event-listeners.ts index d4e0a8bf..7a4b07e5 100644 --- a/assets/test/fix-event-listeners.ts +++ b/assets/test/fix-event-listeners.ts @@ -8,7 +8,7 @@ export function fixEventListeners(t: EventTarget) { eventListeners = {}; const oldAddEventListener = t.addEventListener; - t.addEventListener = function(type: string, listener: any, options: any): void { + t.addEventListener = (type: string, listener: any, options: any): void => { eventListeners[type] = eventListeners[type] || []; eventListeners[type].push(listener); return oldAddEventListener(type, listener, options); diff --git a/assets/test/mock-storage.ts b/assets/test/mock-storage.ts index d7b337a9..c05bcda6 100644 --- a/assets/test/mock-storage.ts +++ b/assets/test/mock-storage.ts @@ -2,7 +2,9 @@ import { MockInstance } from 'vitest'; type MockStorageKeys = 'getItem' | 'setItem' | 'removeItem'; -export function mockStorage(options: Pick): { [k in `${Keys}Spy`]: MockInstance } { +export function mockStorage( + options: Pick, +): { [k in `${Keys}Spy`]: MockInstance } { const getItemSpy = 'getItem' in options ? vi.spyOn(Storage.prototype, 'getItem') : undefined; const setItemSpy = 'setItem' in options ? vi.spyOn(Storage.prototype, 'setItem') : undefined; const removeItemSpy = 'removeItem' in options ? vi.spyOn(Storage.prototype, 'removeItem') : undefined; @@ -33,18 +35,18 @@ type MockStorageImplApi = { [k in `${MockStorageKeys}Spy`]: MockInstance } & { * Forces the mock storage back to its default (empty) state * @param value */ - clearStorage: VoidFunction, + clearStorage: VoidFunction; /** * Forces the mock storage to be in the specific state provided as the parameter * @param value */ - setStorageValue: (value: Record) => void, + setStorageValue: (value: Record) => void; /** * Forces the mock storage to throw an error for the duration of the provided function's execution, * or in case a promise is returned by the function, until that promise is resolved. */ - forceStorageError: (func: (...args: Args[]) => Return | Promise) => void -} + forceStorageError: (func: (...args: Args[]) => Return | Promise) => void; +}; export function mockStorageImpl(): MockStorageImplApi { let shouldThrow = false; @@ -66,7 +68,7 @@ export function mockStorageImpl(): MockStorageImplApi { delete tempStorage[key]; }, }); - const forceStorageError: MockStorageImplApi['forceStorageError'] = func => { + const forceStorageError: MockStorageImplApi['forceStorageError'] = (func) => { shouldThrow = true; const value = func(); if (!(value instanceof Promise)) { @@ -78,7 +80,7 @@ export function mockStorageImpl(): MockStorageImplApi { shouldThrow = false; }); }; - const setStorageValue: MockStorageImplApi['setStorageValue'] = value => { + const setStorageValue: MockStorageImplApi['setStorageValue'] = (value) => { tempStorage = value; }; const clearStorage = () => setStorageValue({}); diff --git a/assets/test/vitest-setup.ts b/assets/test/vitest-setup.ts index f6de62b6..ae3a62f8 100644 --- a/assets/test/vitest-setup.ts +++ b/assets/test/vitest-setup.ts @@ -21,7 +21,7 @@ window.booru = { spoileredFilter: matchNone(), interactions: [], tagsVersion: 5, - galleryImages: [] + galleryImages: [], }; // https://github.com/jsdom/jsdom/issues/1721#issuecomment-1484202038 @@ -31,6 +31,7 @@ Object.assign(globalThis, { URL, Blob }); // Prevents an error when calling `form.submit()` directly in // the code that is being tested +// eslint-disable-next-line prettier/prettier HTMLFormElement.prototype.submit = function() { fireEvent.submit(this); }; diff --git a/assets/types/ujs.ts b/assets/types/ujs.ts index f9cb88f5..5853216d 100644 --- a/assets/types/ujs.ts +++ b/assets/types/ujs.ts @@ -2,7 +2,7 @@ export {}; declare global { interface FetchcompleteEvent extends CustomEvent { - target: HTMLElement, + target: HTMLElement; } interface GlobalEventHandlersEventMap { diff --git a/assets/vite.config.ts b/assets/vite.config.ts index 9342c889..2eb4607d 100644 --- a/assets/vite.config.ts +++ b/assets/vite.config.ts @@ -11,18 +11,16 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { const isDev = command !== 'build' && mode !== 'test'; const targets = new Map(); - fs.readdirSync(path.resolve(__dirname, 'css/themes/')).forEach(name => { + fs.readdirSync(path.resolve(__dirname, 'css/themes/')).forEach((name) => { const m = name.match(/([-a-z]+).css/); - if (m) - targets.set(`css/${m[1]}`, `./css/themes/${m[1]}.css`); + if (m) targets.set(`css/${m[1]}`, `./css/themes/${m[1]}.css`); }); - fs.readdirSync(path.resolve(__dirname, 'css/options/')).forEach(name => { + fs.readdirSync(path.resolve(__dirname, 'css/options/')).forEach((name) => { const m = name.match(/([-a-z]+).css/); - if (m) - targets.set(`css/options/${m[1]}`, `./css/options/${m[1]}.css`); + if (m) targets.set(`css/options/${m[1]}`, `./css/options/${m[1]}.css`); }); return { @@ -37,8 +35,8 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { common: path.resolve(__dirname, 'css/common/'), views: path.resolve(__dirname, 'css/views/'), elements: path.resolve(__dirname, 'css/elements/'), - themes: path.resolve(__dirname, 'css/themes/') - } + themes: path.resolve(__dirname, 'css/themes/'), + }, }, build: { target: ['es2016', 'chrome67', 'firefox62', 'edge18', 'safari12'], @@ -51,19 +49,19 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { input: { 'js/app': './js/app.ts', 'css/application': './css/application.css', - ...Object.fromEntries(targets) + ...Object.fromEntries(targets), }, output: { entryFileNames: '[name].js', chunkFileNames: '[name].js', - assetFileNames: '[name][extname]' - } - } + assetFileNames: '[name][extname]', + }, + }, }, css: { - postcss: { - plugins: [postcssMixins(), postcssSimpleVars(), postcssRelativeColor(), autoprefixer] - } + postcss: { + plugins: [postcssMixins(), postcssSimpleVars(), postcssRelativeColor(), autoprefixer], + }, }, test: { globals: true, @@ -74,11 +72,7 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { coverage: { reporter: ['text', 'html'], include: ['js/**/*.{js,ts}'], - exclude: [ - 'node_modules/', - '.*\\.test\\.ts$', - '.*\\.d\\.ts$', - ], + exclude: ['node_modules/', '.*\\.test\\.ts$', '.*\\.d\\.ts$'], thresholds: { statements: 0, branches: 0, @@ -90,8 +84,8 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => { functions: 100, lines: 100, }, - } - } - } + }, + }, + }, }; });