From 9586d121e4d440fcbc03ab46e21604474c1d30c0 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 25 Feb 2025 03:19:22 +0400 Subject: [PATCH 1/8] Moved storage definition to constructor for testability --- src/lib/extension/ConfigurationController.ts | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/lib/extension/ConfigurationController.ts b/src/lib/extension/ConfigurationController.ts index 860b852..06d2e7f 100644 --- a/src/lib/extension/ConfigurationController.ts +++ b/src/lib/extension/ConfigurationController.ts @@ -2,12 +2,16 @@ import StorageHelper, { type StorageChangeSubscriber } from "$lib/browser/Storag export default class ConfigurationController { readonly #configurationName: string; + readonly #storage: StorageHelper; /** * @param {string} configurationName Name of the configuration to work with. + * @param {StorageHelper} [storage] Selected storage where the settings are stored in. If not provided, local storage + * is used. */ - constructor(configurationName: string) { + constructor(configurationName: string, storage: StorageHelper = new StorageHelper(chrome.storage.local)) { this.#configurationName = configurationName; + this.#storage = storage; } /** @@ -19,7 +23,7 @@ export default class ConfigurationController { * @return The setting value or the default value if the setting does not exist. */ async readSetting(settingName: string, defaultValue: DefaultType | null = null): Promise { - const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); + const settings = await this.#storage.read(this.#configurationName, {}); return settings[settingName] ?? defaultValue; } @@ -32,11 +36,11 @@ export default class ConfigurationController { * @return {Promise} */ async writeSetting(settingName: string, value: any): Promise { - const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); + const settings = await this.#storage.read(this.#configurationName, {}); settings[settingName] = value; - ConfigurationController.#storageHelper.write(this.#configurationName, settings); + this.#storage.write(this.#configurationName, settings); } /** @@ -45,11 +49,11 @@ export default class ConfigurationController { * @param {string} settingName Setting name to delete. */ async deleteSetting(settingName: string): Promise { - const settings = await ConfigurationController.#storageHelper.read(this.#configurationName, {}); + const settings = await this.#storage.read(this.#configurationName, {}); delete settings[settingName]; - ConfigurationController.#storageHelper.write(this.#configurationName, settings); + this.#storage.write(this.#configurationName, settings); } /** @@ -69,10 +73,8 @@ export default class ConfigurationController { callback(changes[this.#configurationName].newValue); } - ConfigurationController.#storageHelper.subscribe(subscriber); + this.#storage.subscribe(subscriber); - return () => ConfigurationController.#storageHelper.unsubscribe(subscriber); + return () => this.#storage.unsubscribe(subscriber); } - - static #storageHelper = new StorageHelper(chrome.storage.local); } From ed263d2da4f5ed0f4615b9cc66eaa036c0dcbf58 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 25 Feb 2025 03:19:49 +0400 Subject: [PATCH 2/8] Installed types for NodeJS for testing --- package-lock.json | 18 ++++++++++++++++++ package.json | 1 + 2 files changed, 19 insertions(+) diff --git a/package-lock.json b/package-lock.json index 9332b49..354e124 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@sveltejs/kit": "^2.17.2", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@types/chrome": "^0.0.304", + "@types/node": "^22.13.5", "@vitest/coverage-v8": "^3.0.6", "cheerio": "^1.0.0", "jsdom": "^26.0.0", @@ -814,6 +815,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.13.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.5.tgz", + "integrity": "sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.20.0" + } + }, "node_modules/@vitest/coverage-v8": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.0.6.tgz", @@ -2972,6 +2983,13 @@ "node": ">=18.17" } }, + "node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true, + "license": "MIT" + }, "node_modules/vite": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/vite/-/vite-6.1.0.tgz", diff --git a/package.json b/package.json index 6f4ff32..409b3ed 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@sveltejs/kit": "^2.17.2", "@sveltejs/vite-plugin-svelte": "^5.0.3", "@types/chrome": "^0.0.304", + "@types/node": "^22.13.5", "@vitest/coverage-v8": "^3.0.6", "cheerio": "^1.0.0", "jsdom": "^26.0.0", From a9d53afdbe347885ffff4a0724ab6931ca4ebb22 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 25 Feb 2025 03:20:43 +0400 Subject: [PATCH 3/8] Mocked storage change events for mocked storage area --- tests/mocks/ChromeEvent.ts | 14 ++++++------- tests/mocks/ChromeLocalStorageArea.ts | 2 +- tests/mocks/ChromeStorageArea.ts | 27 ++++++++++++++++++++++--- tests/mocks/ChromeStorageChangeEvent.ts | 27 +++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 11 deletions(-) create mode 100644 tests/mocks/ChromeStorageChangeEvent.ts diff --git a/tests/mocks/ChromeEvent.ts b/tests/mocks/ChromeEvent.ts index 7c3029b..5b16ed9 100644 --- a/tests/mocks/ChromeEvent.ts +++ b/tests/mocks/ChromeEvent.ts @@ -1,9 +1,9 @@ export default class ChromeEvent implements chrome.events.Event { - addListener = vi.fn(); - getRules = vi.fn(); - hasListener = vi.fn(); - removeRules = vi.fn(); - addRules = vi.fn(); - removeListener = vi.fn(); - hasListeners = vi.fn(); + addListener = vi.fn(); + getRules = vi.fn(); + hasListener = vi.fn(); + removeRules = vi.fn(); + addRules = vi.fn(); + removeListener = vi.fn(); + hasListeners = vi.fn(); } diff --git a/tests/mocks/ChromeLocalStorageArea.ts b/tests/mocks/ChromeLocalStorageArea.ts index 8aa4f77..e14586e 100644 --- a/tests/mocks/ChromeLocalStorageArea.ts +++ b/tests/mocks/ChromeLocalStorageArea.ts @@ -1,5 +1,5 @@ import ChromeStorageArea from "$tests/mocks/ChromeStorageArea"; export class ChromeLocalStorageArea extends ChromeStorageArea implements chrome.storage.LocalStorageArea { - QUOTA_BYTES = 100000; + QUOTA_BYTES = 100000; } diff --git a/tests/mocks/ChromeStorageArea.ts b/tests/mocks/ChromeStorageArea.ts index 1372fd3..95609a6 100644 --- a/tests/mocks/ChromeStorageArea.ts +++ b/tests/mocks/ChromeStorageArea.ts @@ -1,4 +1,4 @@ -import ChromeEvent from "./ChromeEvent"; +import ChromeStorageChangeEvent from "$tests/mocks/ChromeStorageChangeEvent"; type ChangedEventCallback = (changes: chrome.storage.StorageChange) => void @@ -13,8 +13,20 @@ export default class ChromeStorageArea implements chrome.storage.StorageArea { }) }); set = vi.fn((...args: any[]): Promise => { - return new Promise((resolve, reject) => { + return new Promise((resolve) => { + const change: Record = {}; + const setter = args[0]; + + for (let targetKey of Object.keys(setter)) { + change[targetKey] = { + oldValue: this.#mockedData[targetKey] ?? undefined, + newValue: setter[targetKey], + }; + } + this.#mockedData = Object.assign(this.#mockedData, args[0]); + this.onChanged.mockEmitStorageChange(change); + resolve(); }) }); @@ -23,7 +35,16 @@ export default class ChromeStorageArea implements chrome.storage.StorageArea { const key = args[0]; if (typeof key === 'string') { + const change: chrome.storage.StorageChange = { + oldValue: this.#mockedData[key], + }; + delete this.#mockedData[key]; + + this.onChanged.mockEmitStorageChange({ + [key]: change + }); + resolve(); } @@ -58,7 +79,7 @@ export default class ChromeStorageArea implements chrome.storage.StorageArea { }); }); setAccessLevel = vi.fn(); - onChanged = new ChromeEvent(); + onChanged = new ChromeStorageChangeEvent(); getKeys = vi.fn(); insertMockedData(data: Record) { diff --git a/tests/mocks/ChromeStorageChangeEvent.ts b/tests/mocks/ChromeStorageChangeEvent.ts new file mode 100644 index 0000000..b4fb187 --- /dev/null +++ b/tests/mocks/ChromeStorageChangeEvent.ts @@ -0,0 +1,27 @@ +import ChromeEvent from "$tests/mocks/ChromeEvent"; +import { EventEmitter } from "node:events"; + +type MockedStorageChanges = Record; +type IncomingStorageChangeListener = (changes: MockedStorageChanges) => void; + +const storageChangeEvent = Symbol(); + +interface StorageChangeEventMap { + [storageChangeEvent]: [MockedStorageChanges]; +} + +export default class ChromeStorageChangeEvent extends ChromeEvent { + #emitter = new EventEmitter(); + + addListener = vi.fn((actualListener: IncomingStorageChangeListener) => { + this.#emitter.addListener(storageChangeEvent, actualListener); + }); + + removeListener = vi.fn((actualListener: IncomingStorageChangeListener) => { + this.#emitter.removeListener(storageChangeEvent, actualListener); + }); + + mockEmitStorageChange(changes: MockedStorageChanges) { + this.#emitter.emit(storageChangeEvent, changes); + } +} From 09edc44af846c340427afb3ef18c42f04e501a25 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 25 Feb 2025 03:38:49 +0400 Subject: [PATCH 4/8] Added tests for configuration controller --- .../extension/ConfigurationController.spec.ts | 186 ++++++++++++++++++ tests/utils.ts | 7 + 2 files changed, 193 insertions(+) create mode 100644 tests/lib/extension/ConfigurationController.spec.ts create mode 100644 tests/utils.ts diff --git a/tests/lib/extension/ConfigurationController.spec.ts b/tests/lib/extension/ConfigurationController.spec.ts new file mode 100644 index 0000000..20e4577 --- /dev/null +++ b/tests/lib/extension/ConfigurationController.spec.ts @@ -0,0 +1,186 @@ +import ConfigurationController from "$lib/extension/ConfigurationController"; +import ChromeStorageArea from "$tests/mocks/ChromeStorageArea"; +import StorageHelper from "$lib/browser/StorageHelper"; +import { randomString } from "$tests/utils"; + +describe('ConfigurationController', () => { + const mockedStorageArea = new ChromeStorageArea(); + const mockedStorageHelper = new StorageHelper(mockedStorageArea); + + beforeEach(() => { + mockedStorageArea.clear(); + }); + + it('should read setting from the field inside the configuration object', async () => { + const name = randomString(); + const field = randomString(); + const value = randomString(); + + mockedStorageArea.insertMockedData({ + [name]: { + [field]: value + } + }); + + const controller = new ConfigurationController(name, mockedStorageHelper); + const returnedValue = await controller.readSetting(field); + + expect(returnedValue).toBe(value); + }); + + it('should return fallback value if configuration field does not exist', async () => { + const controller = new ConfigurationController(randomString(), mockedStorageHelper); + const fallbackValue = randomString(); + const returnedValue = await controller.readSetting(randomString(), fallbackValue); + + expect(returnedValue).toBe(fallbackValue); + }); + + it('should treat existing falsy values as existing values', async () => { + const name = randomString(); + + const falsyValuesStorage = [0, false, ''].reduce((record, value) => { + record[randomString()] = value; + return record; + }, {} as Record); + + mockedStorageArea.insertMockedData({ + [name]: falsyValuesStorage + }); + + const controller = new ConfigurationController(name, mockedStorageHelper); + + for (const fieldName of Object.keys(falsyValuesStorage)) { + const returnedValue = await controller.readSetting(fieldName, randomString()); + + expect(returnedValue).toBe(falsyValuesStorage[fieldName]); + } + }); + + it('should write data to storage', async () => { + const name = randomString(); + const field = randomString(); + const value = randomString(); + + const controller = new ConfigurationController(name, mockedStorageHelper); + await controller.writeSetting(field, value); + + const expectedStructure = { + [name]: { + [field]: value, + } + }; + + expect(mockedStorageArea.mockedData).toEqual(expectedStructure); + }); + + it('should update existing object without touching other entries', async () => { + const name = randomString(); + const existingField = randomString(); + const existingValue = randomString(); + const addedField = randomString(); + const addedValue = randomString(); + + mockedStorageArea.insertMockedData({ + [name]: { + [existingField]: existingValue, + } + }); + + const controller = new ConfigurationController(name, mockedStorageHelper); + await controller.writeSetting(addedField, addedValue); + + const expectedStructure = { + [name]: { + [existingField]: existingValue, + [addedField]: addedValue, + } + } + + expect(mockedStorageArea.mockedData).toEqual(expectedStructure); + }); + + it('should delete setting from storage', async () => { + const name = randomString(); + const field = randomString(); + const value = randomString(); + + mockedStorageArea.insertMockedData({ + [name]: { + [field]: value + } + }); + + const controller = new ConfigurationController(name, mockedStorageHelper); + await controller.deleteSetting(field); + + expect(mockedStorageArea.mockedData).toEqual({ + [name]: {}, + }); + }); + + it('should return updated settings contents on changes', async () => { + const name = randomString(); + const initialField = randomString(); + const initialValue = randomString(); + + const addedField = randomString(); + const addedValue = randomString(); + + const updatedInitialValue = randomString(); + const receivedData: Record[] = []; + + mockedStorageArea.insertMockedData({ + [name]: { + [initialField]: initialValue, + } + }); + + const controller = new ConfigurationController(name, mockedStorageHelper); + const subscriber = vi.fn((storageState: Record) => { + receivedData.push(JSON.parse(JSON.stringify(storageState))); + }); + + controller.subscribeToChanges(subscriber); + + await controller.writeSetting(addedField, addedValue); + await controller.writeSetting(initialField, updatedInitialValue); + await controller.deleteSetting(initialField); + + expect(subscriber).toBeCalledTimes(3); + + const expectedData: Record[] = [ + // First, initial data and added field are present + { + [initialField]: initialValue, + [addedField]: addedValue, + }, + // Then we get new value on initial field + { + [initialField]: updatedInitialValue, + [addedField]: addedValue, + }, + // And then the initial value is dropped + { + [addedField]: addedValue, + } + ]; + + expect(receivedData).toEqual(expectedData); + }); + + it('should stop listening once unsubscribe called', async () => { + const controller = new ConfigurationController(randomString(), mockedStorageHelper); + const subscriber = vi.fn(); + + const unsubscribe = controller.subscribeToChanges(subscriber); + + await controller.writeSetting(randomString(), randomString()); + expect(subscriber).toBeCalledTimes(1); + + unsubscribe(); + subscriber.mockReset(); + await controller.writeSetting(randomString(), randomString()) + expect(subscriber).not.toBeCalled(); + }); +}); diff --git a/tests/utils.ts b/tests/utils.ts new file mode 100644 index 0000000..61ac9a7 --- /dev/null +++ b/tests/utils.ts @@ -0,0 +1,7 @@ +export function randomString(): string { + return crypto.randomUUID(); +} + +export function copyValue(object: T): T { + return JSON.parse(JSON.stringify(object)); +} From dc0a9f0aa832c34ac70e29f886eee243b0f1160a Mon Sep 17 00:00:00 2001 From: KoloMl Date: Tue, 25 Feb 2025 03:39:30 +0400 Subject: [PATCH 5/8] Imported utils function for random string --- tests/lib/components/base/BaseComponent.spec.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/lib/components/base/BaseComponent.spec.ts b/tests/lib/components/base/BaseComponent.spec.ts index d15392c..9a80330 100644 --- a/tests/lib/components/base/BaseComponent.spec.ts +++ b/tests/lib/components/base/BaseComponent.spec.ts @@ -1,9 +1,6 @@ import { BaseComponent } from "$lib/components/base/BaseComponent"; import { getComponent } from "$lib/components/base/component-utils"; - -function randomString() { - return crypto.randomUUID(); -} +import { randomString } from "$tests/utils"; describe('BaseComponent', () => { it('should bind the component to the element', () => { From d5ed86fb4005036d597bc90b860e21ca07d68205 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 27 Feb 2025 00:50:05 +0400 Subject: [PATCH 6/8] Exposing timer return type globally --- src/app.d.ts | 3 +++ src/lib/components/MaintenancePopup.ts | 2 +- src/lib/extension/CustomCategoriesResolver.ts | 6 ++++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/app.d.ts b/src/app.d.ts index 5a58731..e0ceca7 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -4,6 +4,9 @@ import MaintenanceProfile from "$entities/MaintenanceProfile"; import type TagGroup from "$entities/TagGroup"; declare global { + // Helper type to not deal with differences between the setTimeout of @types/node and usual web browser's type. + type Timeout = ReturnType; + namespace App { // interface Error {} // interface Locals {} diff --git a/src/lib/components/MaintenancePopup.ts b/src/lib/components/MaintenancePopup.ts index 718c6a8..fa65ce5 100644 --- a/src/lib/components/MaintenancePopup.ts +++ b/src/lib/components/MaintenancePopup.ts @@ -30,7 +30,7 @@ export class MaintenancePopup extends BaseComponent { #tagsToAdd: Set = new Set(); #isPlanningToSubmit: boolean = false; #isSubmitting: boolean = false; - #tagsSubmissionTimer: number | null = null; + #tagsSubmissionTimer: Timeout | null = null; #emitter = emitterAt(this); /** diff --git a/src/lib/extension/CustomCategoriesResolver.ts b/src/lib/extension/CustomCategoriesResolver.ts index 169833d..4381d53 100644 --- a/src/lib/extension/CustomCategoriesResolver.ts +++ b/src/lib/extension/CustomCategoriesResolver.ts @@ -6,7 +6,7 @@ export default class CustomCategoriesResolver { #tagCategories = new Map(); #compiledRegExps = new Map(); #tagDropdowns: TagDropdownWrapper[] = []; - #nextQueuedUpdate = -1; + #nextQueuedUpdate: Timeout | null = null; constructor() { TagGroup.subscribe(this.#onTagGroupsReceived.bind(this)); @@ -24,7 +24,9 @@ export default class CustomCategoriesResolver { } #queueUpdatingTags() { - clearTimeout(this.#nextQueuedUpdate); + if (this.#nextQueuedUpdate) { + clearTimeout(this.#nextQueuedUpdate); + } this.#nextQueuedUpdate = setTimeout( this.#updateUnprocessedTags.bind(this), From 76e7bf15427b7f3230c6b7f7092459a28b14f65b Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 27 Feb 2025 00:53:44 +0400 Subject: [PATCH 7/8] Fixed missing empty checks for required components --- src/lib/components/ImageShowFullscreenButton.ts | 2 +- src/lib/components/MaintenancePopup.ts | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/lib/components/ImageShowFullscreenButton.ts b/src/lib/components/ImageShowFullscreenButton.ts index 01a1eaf..f2c0993 100644 --- a/src/lib/components/ImageShowFullscreenButton.ts +++ b/src/lib/components/ImageShowFullscreenButton.ts @@ -47,7 +47,7 @@ export class ImageShowFullscreenButton extends BaseComponent { } #onButtonClicked() { - const imageLinks = this.#mediaBoxTools?.mediaBox.imageLinks; + const imageLinks = this.#mediaBoxTools?.mediaBox?.imageLinks; if (!imageLinks) { throw new Error('Failed to resolve image links from media box tools!'); diff --git a/src/lib/components/MaintenancePopup.ts b/src/lib/components/MaintenancePopup.ts index fa65ce5..8792022 100644 --- a/src/lib/components/MaintenancePopup.ts +++ b/src/lib/components/MaintenancePopup.ts @@ -70,6 +70,10 @@ export class MaintenancePopup extends BaseComponent { const mediaBox = this.#mediaBoxTools.mediaBox; + if (!mediaBox) { + throw new Error('Media box component not found!'); + } + mediaBox.on('mouseout', this.#onMouseLeftArea.bind(this)); mediaBox.on('mouseover', this.#onMouseEnteredArea.bind(this)); } @@ -83,7 +87,7 @@ export class MaintenancePopup extends BaseComponent { } #refreshTagsList() { - if (!this.#mediaBoxTools) { + if (!this.#mediaBoxTools?.mediaBox) { return; } @@ -109,11 +113,11 @@ export class MaintenancePopup extends BaseComponent { this.#tagsList[index] = tagElement; this.#tagsListElement.appendChild(tagElement); - const isPresent = currentPostTags.has(tagName); + const isPresent = currentPostTags?.has(tagName); tagElement.classList.toggle('is-present', isPresent); tagElement.classList.toggle('is-missing', !isPresent); - tagElement.classList.toggle('is-aliased', isPresent && currentPostTags.get(tagName) !== tagName); + tagElement.classList.toggle('is-aliased', isPresent && currentPostTags?.get(tagName) !== tagName); // Just to prevent duplication, we need to include this tag to the map of suggested invalid tags if (tagsBlacklist.includes(tagName)) { @@ -193,7 +197,7 @@ export class MaintenancePopup extends BaseComponent { } async #onSubmissionTimerPassed() { - if (!this.#isPlanningToSubmit || this.#isSubmitting || !this.#mediaBoxTools) { + if (!this.#isPlanningToSubmit || this.#isSubmitting || !this.#mediaBoxTools?.mediaBox) { return; } @@ -264,7 +268,7 @@ export class MaintenancePopup extends BaseComponent { } #revealInvalidTags() { - if (!this.#mediaBoxTools) { + if (!this.#mediaBoxTools?.mediaBox) { return; } From 8e843c2b19a118d663b816720ebc817541d8fae0 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Thu, 27 Feb 2025 00:54:00 +0400 Subject: [PATCH 8/8] Fixed element types not being set up for queries --- src/content/header.ts | 2 +- src/content/listing.ts | 3 +-- src/content/tags.ts | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/content/header.ts b/src/content/header.ts index 00ccd65..a306a89 100644 --- a/src/content/header.ts +++ b/src/content/header.ts @@ -1,6 +1,6 @@ import { initializeSiteHeader } from "$lib/components/SiteHeaderWrapper"; -const siteHeader = document.querySelector('.header'); +const siteHeader = document.querySelector('.header'); if (siteHeader) { initializeSiteHeader(siteHeader); diff --git a/src/content/listing.ts b/src/content/listing.ts index 985eac1..63a436b 100644 --- a/src/content/listing.ts +++ b/src/content/listing.ts @@ -4,8 +4,7 @@ import { calculateMediaBoxesPositions, initializeMediaBox } from "$lib/component import { createMaintenanceStatusIcon } from "$lib/components/MaintenanceStatusIcon"; import { createImageShowFullscreenButton } from "$lib/components/ImageShowFullscreenButton"; -/** @type {NodeListOf} */ -const mediaBoxes = document.querySelectorAll('.media-box'); +const mediaBoxes = document.querySelectorAll('.media-box'); mediaBoxes.forEach(mediaBoxElement => { initializeMediaBox(mediaBoxElement, [ diff --git a/src/content/tags.ts b/src/content/tags.ts index f8d685b..a4fa996 100644 --- a/src/content/tags.ts +++ b/src/content/tags.ts @@ -1,6 +1,6 @@ import { watchTagDropdownsInTagsEditor, wrapTagDropdown } from "$lib/components/TagDropdownWrapper"; -for (let tagDropdownElement of document.querySelectorAll('.tag.dropdown')) { +for (let tagDropdownElement of document.querySelectorAll('.tag.dropdown')) { wrapTagDropdown(tagDropdownElement); }