From ce9a2b5f9bb8e72232f3092a7c69872ae2172085 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 05:59:23 +0400 Subject: [PATCH 01/18] Fixed typo in content script entry --- .vite/lib/manifest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vite/lib/manifest.js b/.vite/lib/manifest.js index dffef10..63b039c 100644 --- a/.vite/lib/manifest.js +++ b/.vite/lib/manifest.js @@ -124,7 +124,7 @@ export function loadManifest(filePath) { /** * @typedef {Object} ContentScriptsEntry - * @property {string[]} mathces + * @property {string[]} matches * @property {string[]|undefined} js * @property {string[]|undefined} css */ From b321c1049c4b0b50664ae6e0884cd341932f6231 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:36:19 +0400 Subject: [PATCH 02/18] Installed `cross-env` to support quickly adding ENV variables to builder --- package-lock.json | 26 ++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 27 insertions(+) diff --git a/package-lock.json b/package-lock.json index 93ce13b..c425cde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@types/node": "^22.15.29", "@vitest/coverage-v8": "^3.2.0", "cheerio": "^1.0.0", + "cross-env": "^10.0.0", "jsdom": "^26.1.0", "sass": "^1.89.1", "svelte": "^5.33.14", @@ -231,6 +232,13 @@ "node": ">=18" } }, + "node_modules/@epic-web/invariant": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz", + "integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==", + "dev": true, + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.1", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz", @@ -1646,6 +1654,24 @@ "node": ">= 0.6" } }, + "node_modules/cross-env": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.0.0.tgz", + "integrity": "sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@epic-web/invariant": "^1.0.0", + "cross-spawn": "^7.0.6" + }, + "bin": { + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", diff --git a/package.json b/package.json index 9c55a7c..c2a991f 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/node": "^22.15.29", "@vitest/coverage-v8": "^3.2.0", "cheerio": "^1.0.0", + "cross-env": "^10.0.0", "jsdom": "^26.1.0", "sass": "^1.89.1", "svelte": "^5.33.14", From efd65225323c598afb07082d78f74b265f08cb55 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:37:12 +0400 Subject: [PATCH 03/18] Implemented plugin for swaping defined build-time constants --- .vite/plugins/swap-defined-variables.js | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .vite/plugins/swap-defined-variables.js diff --git a/.vite/plugins/swap-defined-variables.js b/.vite/plugins/swap-defined-variables.js new file mode 100644 index 0000000..61aabfc --- /dev/null +++ b/.vite/plugins/swap-defined-variables.js @@ -0,0 +1,28 @@ +/** + * @param {SwapDefinedVariablesSettings} settings + * @return {import('vite').Plugin} + */ +export function SwapDefinedVariablesPlugin(settings) { + return { + name: 'koloml:swap-defined-variables', + enforce: 'post', + configResolved: (config) => { + if ( + config.define + && process.env.hasOwnProperty(settings.envVariable) + && process.env[settings.envVariable] === settings.expectedValue + ) { + for (const [key, value] of Object.entries(settings.define)) { + config.define[key] = value; + } + } + } + } +} + +/** + * @typedef {Object} SwapDefinedVariablesSettings + * @property {string} envVariable + * @property {string} expectedValue + * @property {Record} define + */ From 2eefbf96ca659d54eff3f9d580cac457c9bc4dd3 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:37:48 +0400 Subject: [PATCH 04/18] Implemented plugin to provide constants to SCSS through custom function --- .../scss-read-env-variable-function.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .vite/plugins/scss-read-env-variable-function.js diff --git a/.vite/plugins/scss-read-env-variable-function.js b/.vite/plugins/scss-read-env-variable-function.js new file mode 100644 index 0000000..9bf94f8 --- /dev/null +++ b/.vite/plugins/scss-read-env-variable-function.js @@ -0,0 +1,46 @@ +import { SassString, Value } from "sass"; + +/** + * @return {import('vite').Plugin} + */ +export function ScssViteReadEnvVariableFunctionPlugin() { + return { + name: 'koloml:scss-read-env-variable-function', + apply: 'build', + enforce: 'post', + + configResolved: config => { + config.css.preprocessorOptions ??= {}; + config.css.preprocessorOptions.scss ??= {}; + config.css.preprocessorOptions.scss.functions ??= {}; + + /** + * @param {Value[]} args + * @return {SassString} + */ + config.css.preprocessorOptions.scss.functions['vite-read-env-variable($constant-name)'] = (args) => { + const constName = args[0].assertString('constant-name').text; + + if (config.define && config.define.hasOwnProperty(constName)) { + let returnedValue = config.define[constName]; + + try { + returnedValue = JSON.parse(returnedValue); + } catch { + returnedValue = null; + } + + if (typeof returnedValue !== 'string') { + console.warn(`Attempting to read the constant with non-string type: ${constName}`); + return new SassString(''); + } + + return new SassString(returnedValue); + } + + console.warn(`Constant does not exist: ${constName}`); + return new SassString(''); + } + } + } +} From c37f680e9f421da78c3771e61f42f54bceef18d2 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:38:52 +0400 Subject: [PATCH 05/18] Provide current site name & identifier for build-time modifications --- .vite/lib/content-scripts.js | 17 ++++++++++++- package.json | 1 + vite.config.ts | 49 ++++++++++++++++++++++++------------ 3 files changed, 50 insertions(+), 17 deletions(-) diff --git a/.vite/lib/content-scripts.js b/.vite/lib/content-scripts.js index f9385ca..5a84761 100644 --- a/.vite/lib/content-scripts.js +++ b/.vite/lib/content-scripts.js @@ -2,6 +2,8 @@ import { build } from "vite"; import { createHash } from "crypto"; import path from "path"; import fs from "fs"; +import { SwapDefinedVariablesPlugin } from "../plugins/swap-defined-variables.js"; +import { ScssViteReadEnvVariableFunctionPlugin } from "../plugins/scss-read-env-variable-function.js"; /** * Create the result base file name for the file. @@ -192,6 +194,15 @@ export async function buildScriptsAndStyles(buildOptions) { .get(fileName) ?.push(...dependencies); }), + ScssViteReadEnvVariableFunctionPlugin(), + SwapDefinedVariablesPlugin({ + envVariable: 'SITE', + expectedValue: 'derpibooru', + define: { + __CURRENT_SITE__: JSON.stringify('derpibooru'), + __CURRENT_SITE_NAME__: JSON.stringify('Derpibooru'), + } + }), ] }); @@ -215,7 +226,11 @@ export async function buildScriptsAndStyles(buildOptions) { }, plugins: [ wrapScriptIntoIIFE(), - ] + ], + define: { + __CURRENT_SITE__: JSON.stringify('furbooru'), + __CURRENT_SITE_NAME__: JSON.stringify('Furbooru'), + } }); return pathsReplacement; diff --git a/package.json b/package.json index c2a991f..b7a9a94 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "build": "npm run build:popup && npm run build:extension", + "build:derpibooru": "cross-env SITE=derpibooru npm run build", "build:popup": "vite build", "build:extension": "node build-extension.js", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", diff --git a/vite.config.ts b/vite.config.ts index f8d70c5..b6d5b66 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,21 +1,38 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vitest/config'; +import { ScssViteReadEnvVariableFunctionPlugin } from "./.vite/plugins/scss-read-env-variable-function"; +import { SwapDefinedVariablesPlugin } from "./.vite/plugins/swap-defined-variables"; -export default defineConfig({ - build: { - // SVGs imported from the FA6 don't need to be inlined! - assetsInlineLimit: 0 - }, - plugins: [ - sveltekit(), - ], - test: { - globals: true, - environment: 'jsdom', - exclude: ['**/node_modules/**', '.*\\.d\\.ts$', '.*\\.spec\\.ts$'], - coverage: { - reporter: ['text', 'html'], - include: ['src/lib/**/*.{js,ts}'], +export default defineConfig(() => { + return { + build: { + // SVGs imported from the FA6 don't need to be inlined! + assetsInlineLimit: 0 + }, + plugins: [ + sveltekit(), + ScssViteReadEnvVariableFunctionPlugin(), + SwapDefinedVariablesPlugin({ + envVariable: 'SITE', + expectedValue: 'derpibooru', + define: { + __CURRENT_SITE__: JSON.stringify('derpibooru'), + __CURRENT_SITE_NAME__: JSON.stringify('Derpibooru'), + } + }), + ], + test: { + globals: true, + environment: 'jsdom', + exclude: ['**/node_modules/**', '.*\\.d\\.ts$', '.*\\.spec\\.ts$'], + coverage: { + reporter: ['text', 'html'], + include: ['src/lib/**/*.{js,ts}'], + } + }, + define: { + __CURRENT_SITE__: JSON.stringify('furbooru'), + __CURRENT_SITE_NAME__: JSON.stringify('Furbooru'), } - } + }; }); From 4837184d40a7ed94f293e60197907e5279b3b75a Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:39:30 +0400 Subject: [PATCH 06/18] Reading constants in SCSS, modifying colors for Derpibooru variant --- src/styles/colors.scss | 29 +++++++++++++++++++++++++++++ src/styles/environment.scss | 20 ++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/styles/environment.scss diff --git a/src/styles/colors.scss b/src/styles/colors.scss index 75ebcbf..7bdfe9e 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -1,4 +1,5 @@ @use 'sass:color'; +@use 'environment'; $background: #15121a; @@ -55,3 +56,31 @@ $error-background: #7a2725; $warning-background: #7d4825; $warning-border: #95562c; + +@if environment.$current-site == 'derpibooru' { + $background: #141a24; + + $text: #e0e0e0; + $text-gray: #90a1bb; + + $link: #478acc; + $link-hover: #b099dd; + + $header: #284371; + $header-toolbar: #1c3252; + $header-hover-background: #1d3153; + $header-mobile-link-hover: #546c99; + + $footer: #1d242f; + $footer-text: $text-gray; + + $block-header: #252d3c; + $block-border: #2d3649; + $block-background: #1d242f; + $block-background-alternate: #171d26; + + $media-box-border: #3d4657; + + $input-background: #282e39; + $input-border: #575e6b; +} diff --git a/src/styles/environment.scss b/src/styles/environment.scss new file mode 100644 index 0000000..3837505 --- /dev/null +++ b/src/styles/environment.scss @@ -0,0 +1,20 @@ +@use 'sass:meta'; +@use 'sass:string'; + +@function get-defined-constant($constant-name, $default-value: '') { + $resolved-value: $default-value; + + @if meta.function-exists('vite-read-env-variable') { + $candidate-value: meta.call(meta.get-function('vite-read-env-variable'), $constant-name); + + @if string.length($candidate-value) != 0 { + $resolved-value: $candidate-value + } + } + + @return $resolved-value; +} + +$current-site: get-defined-constant('__CURRENT_SITE__', 'furbooru'); + + From 234f80b99205b865e5197c4358dba18f87565ca6 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:39:48 +0400 Subject: [PATCH 07/18] Added defined constants to typedefinition for TypeScript --- src/app.d.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/app.d.ts b/src/app.d.ts index e0ceca7..ffec69b 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -4,6 +4,16 @@ import MaintenanceProfile from "$entities/MaintenanceProfile"; import type TagGroup from "$entities/TagGroup"; declare global { + /** + * Identifier of the current site this extension is built for. + */ + const __CURRENT_SITE__: string; + + /** + * Name of the site. + */ + const __CURRENT_SITE_NAME__: string; + // Helper type to not deal with differences between the setTimeout of @types/node and usual web browser's type. type Timeout = ReturnType; From c811c13b70fa64bd2925489573777a7c37ba9558 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:40:13 +0400 Subject: [PATCH 08/18] Use site name constant in header component --- src/components/layout/Header.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/layout/Header.svelte b/src/components/layout/Header.svelte index 9740212..40ab04f 100644 --- a/src/components/layout/Header.svelte +++ b/src/components/layout/Header.svelte @@ -1,5 +1,5 @@
- Furbooru Tagging Assistant + {__CURRENT_SITE_NAME__} Tagging Assistant
From ae3c77031fa646b76e31e1a07511dd23d51a3428 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:41:23 +0400 Subject: [PATCH 10/18] Added modifications to manifest for Derpibooru variant of the extension --- .vite/lib/manifest.js | 61 +++++++++++++++++++++++++++++++++++++++++ .vite/pack-extension.js | 9 ++++++ 2 files changed, 70 insertions(+) diff --git a/.vite/lib/manifest.js b/.vite/lib/manifest.js index 63b039c..54aa712 100644 --- a/.vite/lib/manifest.js +++ b/.vite/lib/manifest.js @@ -86,6 +86,53 @@ class ManifestProcessor { } } + /** + * Find all patterns in content scripts and host permissions and replace the hostname to the different one. + * + * @param {string|string[]} singleOrMultipleHostnames One or multiple hostnames to replace the original hostname with. + */ + replaceHostTo(singleOrMultipleHostnames) { + if (typeof singleOrMultipleHostnames === 'string') { + singleOrMultipleHostnames = [singleOrMultipleHostnames]; + } + + this.#manifestObject.host_permissions = singleOrMultipleHostnames.map(hostname => `*://*.${hostname}/`); + + this.#manifestObject.content_scripts?.forEach(entry => { + entry.matches = entry.matches.reduce((resultMatches, originalMatchPattern) => { + for (const updatedHostname of singleOrMultipleHostnames) { + resultMatches.push( + originalMatchPattern.replace( + /\*:\/\/\*\.[a-z]+\.[a-z]+\//, + `*://*.${updatedHostname}/` + ), + ); + } + + return resultMatches; + }, []); + }) + } + + /** + * Set different identifier for Gecko-based browsers (Firefox). + * + * @param {string} id ID of the extension to use. + */ + setGeckoIdentifier(id) { + this.#manifestObject.browser_specific_settings.gecko.id = id; + } + + /** + * Set the different extension name. + * + * @param {string} booruName + */ + replaceBooruNameWith(booruName) { + this.#manifestObject.name = this.#manifestObject.name.replaceAll('Furbooru', booruName); + this.#manifestObject.description = this.#manifestObject.description.replaceAll('Furbooru', booruName); + } + /** * Save the current state of the manifest into the selected file. * @@ -118,10 +165,24 @@ export function loadManifest(filePath) { /** * @typedef {Object} Manifest + * @property {string} name + * @property {string} description * @property {string} version + * @property {BrowserSpecificSettings} browser_specific_settings + * @property {string[]} host_permissions * @property {ContentScriptsEntry[]|undefined} content_scripts */ +/** + * @typedef {Object} BrowserSpecificSettings + * @property {GeckoSettings} gecko + */ + +/** + * @typedef {Object} GeckoSettings + * @property {string} id + */ + /** * @typedef {Object} ContentScriptsEntry * @property {string[]} matches diff --git a/.vite/pack-extension.js b/.vite/pack-extension.js index 7b4d0ea..d264e9f 100644 --- a/.vite/pack-extension.js +++ b/.vite/pack-extension.js @@ -67,6 +67,15 @@ export async function packExtension(settings) { return entry; }) + if (process.env.SITE === 'derpibooru') { + manifest.replaceHostTo([ + 'derpibooru.org', + 'trixiebooru.org' + ]); + manifest.replaceBooruNameWith('Derpibooru'); + manifest.setGeckoIdentifier('derpibooru-tagging-assistant@thecore.city'); + } + manifest.passVersionFromPackage(path.resolve(settings.rootDir, 'package.json')); manifest.saveTo(path.resolve(settings.exportDir, 'manifest.json')); From 83d27cc966f8e67b0ec67413e4e0b59456841a48 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 19:46:20 +0400 Subject: [PATCH 11/18] Add `$site` property to identify what site entity was created for --- src/lib/extension/BulkEntitiesTransporter.ts | 1 + src/lib/extension/transporting/exporters.ts | 2 ++ src/lib/extension/transporting/importables.ts | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/src/lib/extension/BulkEntitiesTransporter.ts b/src/lib/extension/BulkEntitiesTransporter.ts index fb2f565..7594b25 100644 --- a/src/lib/extension/BulkEntitiesTransporter.ts +++ b/src/lib/extension/BulkEntitiesTransporter.ts @@ -45,6 +45,7 @@ export default class BulkEntitiesTransporter { exportToJSON(entities: StorageEntity[]): string { return JSON.stringify({ $type: 'list', + $site: __CURRENT_SITE__, elements: entities .map(entity => { switch (true) { diff --git a/src/lib/extension/transporting/exporters.ts b/src/lib/extension/transporting/exporters.ts index fe6886b..a0aa6c7 100644 --- a/src/lib/extension/transporting/exporters.ts +++ b/src/lib/extension/transporting/exporters.ts @@ -11,6 +11,7 @@ const entitiesExporters: ExportersMap = { profiles: entity => { return { $type: "profiles", + $site: __CURRENT_SITE__, v: 2, id: entity.id, name: entity.settings.name, @@ -22,6 +23,7 @@ const entitiesExporters: ExportersMap = { groups: entity => { return { $type: "groups", + $site: __CURRENT_SITE__, v: 2, id: entity.id, name: entity.settings.name, diff --git a/src/lib/extension/transporting/importables.ts b/src/lib/extension/transporting/importables.ts index e60dca9..e1f4c10 100644 --- a/src/lib/extension/transporting/importables.ts +++ b/src/lib/extension/transporting/importables.ts @@ -5,6 +5,10 @@ export interface ImportableElement { * Type of importable. Should be unique to properly import everything. */ $type: Type; + /** + * Identifier of the site this element is built for. + */ + $site?: string; } export interface ImportableElementsList extends ImportableElement<"list"> { From 8822a2581bfccd9d4ddbf1a61af6bc087af2f3b0 Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 20:40:57 +0400 Subject: [PATCH 12/18] Store last same site status of the imported object --- src/lib/extension/BulkEntitiesTransporter.ts | 45 ++++++++++++++++++-- src/lib/extension/EntitiesTransporter.ts | 42 ++++++++++++++++++ 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/src/lib/extension/BulkEntitiesTransporter.ts b/src/lib/extension/BulkEntitiesTransporter.ts index 7594b25..b83fa49 100644 --- a/src/lib/extension/BulkEntitiesTransporter.ts +++ b/src/lib/extension/BulkEntitiesTransporter.ts @@ -1,7 +1,7 @@ import type StorageEntity from "$lib/extension/base/StorageEntity"; import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from "lz-string"; import type { ImportableElementsList, ImportableEntityObject } from "$lib/extension/transporting/importables"; -import EntitiesTransporter from "$lib/extension/EntitiesTransporter"; +import EntitiesTransporter, { type SameSiteStatus } from "$lib/extension/EntitiesTransporter"; import MaintenanceProfile from "$entities/MaintenanceProfile"; import TagGroup from "$entities/TagGroup"; @@ -10,9 +10,17 @@ type TransportersMapping = { } export default class BulkEntitiesTransporter { + #lastSameSiteStatus: SameSiteStatus = null; + + get lastImportSameSiteStatus() { + return this.#lastSameSiteStatus; + } + parseAndImportFromJSON(jsonString: string): StorageEntity[] { let parsedObject: any; + this.#lastSameSiteStatus = null; + try { parsedObject = JSON.parse(jsonString); } catch (e) { @@ -23,7 +31,11 @@ export default class BulkEntitiesTransporter { throw new TypeError('Invalid or unsupported object!'); } - return parsedObject.elements + this.#lastSameSiteStatus = EntitiesTransporter.checkIsSameSiteImportedObject(parsedObject); + + let hasDifferentStatuses = false; + + const resultEntities = parsedObject.elements .map(importableObject => { if (!(importableObject.$type in BulkEntitiesTransporter.#transporters)) { console.warn('Attempting to import unsupported entity: ' + importableObject.$type); @@ -31,9 +43,21 @@ export default class BulkEntitiesTransporter { } const transporter = BulkEntitiesTransporter.#transporters[importableObject.$type as keyof App.EntityNamesMap]; - return transporter.importFromObject(importableObject); + const resultEntity = transporter.importFromObject(importableObject); + + if (transporter.lastImportSameSiteStatus !== this.#lastSameSiteStatus) { + hasDifferentStatuses = true; + } + + return resultEntity; }) .filter(maybeEntity => !!maybeEntity); + + if (hasDifferentStatuses) { + this.#lastSameSiteStatus = 'unknown'; + } + + return resultEntities; } parseAndImportFromCompressedJSON(compressedJsonString: string): StorageEntity[] { @@ -78,4 +102,19 @@ export default class BulkEntitiesTransporter { profiles: new EntitiesTransporter(MaintenanceProfile), groups: new EntitiesTransporter(TagGroup), } + + /** + * Check if the imported object is created for the same site extension or not. + * @param importedObject Object to check. + * @private + */ + static #checkIsSameSiteImportedObject(importedObject: Record): SameSiteStatus { + if (!('$site' in importedObject)) { + return "unknown"; + } + + return importedObject.$site === __CURRENT_SITE__ + ? "same" + : "different"; + } } diff --git a/src/lib/extension/EntitiesTransporter.ts b/src/lib/extension/EntitiesTransporter.ts index ac29569..0b90cec 100644 --- a/src/lib/extension/EntitiesTransporter.ts +++ b/src/lib/extension/EntitiesTransporter.ts @@ -2,10 +2,33 @@ import { validateImportedEntity } from "$lib/extension/transporting/validators"; import { exportEntityToObject } from "$lib/extension/transporting/exporters"; import StorageEntity from "$lib/extension/base/StorageEntity"; import { compressToEncodedURIComponent, decompressFromEncodedURIComponent } from "lz-string"; +import type { ImportableElement } from "$lib/extension/transporting/importables"; + +/** + * Status of the last import. + * + * - `NULL` - no import was done yet or was unsuccessful. + * - `"unknown"` — imported object was created before v0.5, when extension started to be built for multiple sites. + * - `"same"` — imported object is marked as generated by the same type of extension. + * - `"different"` — imported object is marked as generated by some other type of extension. + */ +export type SameSiteStatus = null | "unknown" | "same" | "different"; export default class EntitiesTransporter { readonly #targetEntityConstructor: new (...any: any[]) => EntityType; + #lastSameSiteStatus: SameSiteStatus = null; + + /** + * Read the status of the last successful import. This flag could be used to determine if it was for the same site as + * the current extension or when it's generated before site identity was passed to the importable object. + * + * @see {SameSiteStatus} For the list of possible statuses. + */ + get lastImportSameSiteStatus() { + return this.#lastSameSiteStatus; + } + /** * Name of the entity, exported directly from the constructor. * @private @@ -37,6 +60,8 @@ export default class EntitiesTransporter { } importFromObject(importedObject: Record): EntityType { + this.#lastSameSiteStatus = null; + // TODO: There should be an auto-upgrader somewhere before the validation. So if even the older version of schema // was used, we still will will be able to pass the validation. For now we only have non-breaking changes. validateImportedEntity( @@ -44,6 +69,8 @@ export default class EntitiesTransporter { importedObject, ); + this.#lastSameSiteStatus = EntitiesTransporter.checkIsSameSiteImportedObject(importedObject); + return new this.#targetEntityConstructor( importedObject.id, importedObject @@ -54,6 +81,7 @@ export default class EntitiesTransporter { const importedObject = this.#tryParsingAsJSON(jsonString); if (!importedObject) { + this.#lastSameSiteStatus = null; throw new Error('Invalid JSON!'); } @@ -108,4 +136,18 @@ export default class EntitiesTransporter { return jsonObject } + + /** + * Check if the imported object is created for the same site extension or not. + * @param importedObject Object to check. + */ + static checkIsSameSiteImportedObject(importedObject: Record): SameSiteStatus { + if (!('$site' in importedObject)) { + return "unknown"; + } + + return importedObject.$site === __CURRENT_SITE__ + ? "same" + : "different"; + } } From 0deafb4a003ca01c9810d2fa407247af97f0c61d Mon Sep 17 00:00:00 2001 From: KoloMl Date: Fri, 8 Aug 2025 20:41:55 +0400 Subject: [PATCH 13/18] Warn user of potentially different/unknown site imports --- src/routes/transporting/import/+page.svelte | 31 +++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/routes/transporting/import/+page.svelte b/src/routes/transporting/import/+page.svelte index 8020297..ddc744d 100644 --- a/src/routes/transporting/import/+page.svelte +++ b/src/routes/transporting/import/+page.svelte @@ -13,6 +13,7 @@ import ProfileView from "$components/features/ProfileView.svelte"; import GroupView from "$components/features/GroupView.svelte"; import { goto } from "$app/navigation"; + import type { SameSiteStatus } from "$lib/extension/EntitiesTransporter"; let importedString = $state(''); let errorMessage = $state(''); @@ -50,6 +51,8 @@ const transporter = new BulkEntitiesTransporter(); + let lastImportStatus = $state(null); + function tryBulkImport() { importedProfiles = []; importedGroups = []; @@ -75,6 +78,8 @@ return; } + lastImportStatus = transporter.lastImportSameSiteStatus; + if (importedEntities.length) { for (const targetImportedEntity of importedEntities) { switch (targetImportedEntity.type) { @@ -180,7 +185,25 @@ {:else} Cancel Import -
+ {#if lastImportStatus !== 'same'} +
+ {/if} +
+ {#if lastImportStatus === "different"} +

+ Warning! + Looks like these entities were exported for the different extension! There are many differences between tagging + systems of Furobooru and Derpibooru, so make sure to check if these settings are correct before using them! +

+ {/if} + {#if lastImportStatus === 'unknown'} +

+ Warning! + We couldn't verify if these settings are meant for this site or not. There are many differences between tagging + systems of Furbooru and Derpibooru, so make sure to check if these settings are correct before using them. +

+ {/if} + {#if importedProfiles.length}
@@ -234,7 +257,7 @@ diff --git a/src/styles/colors.scss b/src/styles/colors.scss index 7bdfe9e..40e72bb 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -27,27 +27,38 @@ $media-box-border: #311e49; $tag-background: #1b3c21; $tag-count-background: #2d6236; $tag-text: #4aa158; +$tag-border: #2d6236; $tag-rating-text: #418dd9; $tag-rating-background: color.adjust($tag-rating-text, $lightness: -35%); +$tag-rating-border: color.adjust($tag-rating-text, $saturation: -10%, $lightness: -20%); $tag-spoiler-text: #d49b39; $tag-spoiler-background: color.adjust($tag-spoiler-text, $lightness: -34%); +$tag-spoiler-border: color.adjust($tag-spoiler-text, $lightness: -23%); $tag-origin-text: #6f66d6; $tag-origin-background: color.adjust($tag-origin-text, $lightness: -40%); +$tag-origin-border: color.adjust($tag-origin-text, $saturation: -28%, $lightness: -22%); $tag-oc-text: #b157b7; $tag-oc-background: color.adjust($tag-oc-text, $lightness: -33%); +$tag-oc-border: color.adjust($tag-oc-text, $lightness: -15%); $tag-error-text: #d45460; $tag-error-background: color.adjust($tag-error-text, $lightness: -38%, $saturation: -6%, $space: hsl); +$tag-error-border: color.adjust($tag-error-text, $lightness: -22%, $space: hsl); $tag-character-text: #4aaabf; $tag-character-background: color.adjust($tag-character-text, $lightness: -33%); +$tag-character-border: color.adjust($tag-character-text, $lightness: -20%); $tag-content-official-text: #b9b541; $tag-content-official-background: color.adjust($tag-content-official-text, $lightness: -29%, $saturation: -2%, $space: hsl); +$tag-content-official-border: color.adjust($tag-content-official-text, $lightness: -20%, $space: hsl); $tag-content-fanmade-text: #cc8eb5; $tag-content-fanmade-background: color.adjust($tag-content-fanmade-text, $lightness: -40%); +$tag-content-fanmade-border: color.adjust($tag-content-fanmade-text, $saturation: -10%, $lightness: -20%); $tag-species-text: #b16b50; $tag-species-background: color.adjust($tag-species-text, $lightness: -35%); +$tag-species-border: color.adjust($tag-species-text, $saturation: -10%, $lightness: -20%); $tag-body-type-text: #b8b8b8; -$tag-body-type-background: color.adjust($tag-body-type-text, $lightness: -35%, $saturation: -10%, $space: hsl); +$tag-body-type-background: color.adjust($tag-body-type-text, $lightness: -50%, $space: hsl); +$tag-body-type-border: color.adjust($tag-body-type-text, $lightness: -37%, $saturation: -10%, $space: hsl); $input-background: #26232d; $input-border: #5c5a61; diff --git a/src/styles/injectable/tag.scss b/src/styles/injectable/tag.scss index 68dd70c..bbec107 100644 --- a/src/styles/injectable/tag.scss +++ b/src/styles/injectable/tag.scss @@ -1,4 +1,5 @@ @use '../colors'; +@use '../environment'; .tag { background: colors.$tag-background; @@ -9,9 +10,13 @@ padding: 0 4px; display: flex; + @if environment.$current-site == 'derpibooru' { + border: 1px solid colors.$tag-border; + } + .remove { content: "x"; margin-left: 6px; cursor: pointer; } -} \ No newline at end of file +}