diff --git a/.github/assets/fullscreen-viewer-icon.png b/.github/assets/fullscreen-viewer-icon.png new file mode 100644 index 0000000..1db420b Binary files /dev/null and b/.github/assets/fullscreen-viewer-icon.png differ diff --git a/.github/assets/fullscreen-viewer-showcase.png b/.github/assets/fullscreen-viewer-showcase.png new file mode 100644 index 0000000..5bb8bd6 Binary files /dev/null and b/.github/assets/fullscreen-viewer-showcase.png differ diff --git a/.github/assets/groups-showcase.png b/.github/assets/groups-showcase.png new file mode 100644 index 0000000..de43e21 Binary files /dev/null and b/.github/assets/groups-showcase.png differ diff --git a/.github/assets/profiles-showcase.png b/.github/assets/profiles-showcase.png new file mode 100644 index 0000000..c4d7660 Binary files /dev/null and b/.github/assets/profiles-showcase.png differ diff --git a/.vite/lib/content-scripts.js b/.vite/lib/content-scripts.js index f9385ca..a59da49 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. @@ -157,6 +159,19 @@ export async function buildScriptsAndStyles(buildOptions) { } const aliasesSettings = makeAliases(buildOptions.rootDir); + const defineConstants = { + __CURRENT_SITE__: JSON.stringify('furbooru'), + __CURRENT_SITE_NAME__: JSON.stringify('Furbooru'), + }; + + const derpibooruSwapPlugin = SwapDefinedVariablesPlugin({ + envVariable: 'SITE', + expectedValue: 'derpibooru', + define: { + __CURRENT_SITE__: JSON.stringify('derpibooru'), + __CURRENT_SITE_NAME__: JSON.stringify('Derpibooru'), + } + }); // Building all scripts together with AMD loader in mind await build({ @@ -192,7 +207,9 @@ export async function buildScriptsAndStyles(buildOptions) { .get(fileName) ?.push(...dependencies); }), - ] + derpibooruSwapPlugin, + ], + define: defineConstants, }); // Build styles separately because AMD converts styles to JS files. @@ -215,7 +232,10 @@ export async function buildScriptsAndStyles(buildOptions) { }, plugins: [ wrapScriptIntoIIFE(), - ] + ScssViteReadEnvVariableFunctionPlugin(), + derpibooruSwapPlugin, + ], + define: defineConstants, }); return pathsReplacement; diff --git a/.vite/lib/manifest.js b/.vite/lib/manifest.js index dffef10..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,13 +165,27 @@ 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[]} mathces + * @property {string[]} matches * @property {string[]|undefined} js * @property {string[]|undefined} css */ 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')); 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(''); + } + } + } +} 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 + */ diff --git a/README.md b/README.md index 869da41..09b5127 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,47 @@ -# Furbooru Tagging Assistant +# Philomena Tagging Assistant + +This is a browser extension written for the [Furbooru](https://furbooru.org) and [Derpibooru](https://derpibooru.org) +image-boards. It gives you the ability to manually go over the list of images and apply tags to them without opening +each individual image. + +## Installation + +This extension is available for both Chromium- and Firefox-based browsers. You can find the links to the extension pages +below. + +### Furbooru Tagging Assistant [![Get the Add-on on Firefox](.github/assets/firefox.png)](https://addons.mozilla.org/en-US/firefox/addon/furbooru-tagging-assistant/) [![Get the extension on Chrome](.github/assets/chrome.png)](https://chromewebstore.google.com/detail/kpgaphaooaaodgodmnkamhmoedjcnfkj) -This is a browser extension written for the [Furbooru](https://furbooru.org) image-board. It gives you the ability to -tag the images more easily and quickly. +### Derpibooru Tagging Assistant + +I wasn't able to release the extension for Derpibooru yet. Links will be available shortly. + +## Features + +### Tagging Profiles + +Select a set of tags and add/remove them from images without opening them. Just hover over image, click on tags and +you're done! + +![Tagging Profiles Showcase](.github/assets/profiles-showcase.png) + +### Custom Tag Groups + +Customize the list of tags with your own custom tag groups. Apply custom colors to different groups or even separate +them from each other with group titles. + +![Tag Groups Showcase](.github/assets/groups-showcase.png) + +### Fullscreen Viewer + +Open up the specific image or video in fullscreen mode by clicking 🔍 icon in the bottom left corner of the image. This +feature is opt-in and should be enabled in the settings first. + +![Fullscreen Viewer Icon](.github/assets/fullscreen-viewer-icon.png) + +![Fullscreen Viewer Showcase](.github/assets/fullscreen-viewer-showcase.png) ## Building @@ -19,11 +56,18 @@ npm install --save-dev ``` Second, you need to run the `build` command. It will first build the popup using SvelteKit and then build all the -content scripts/stylesheets and copy the manifest afterward. Simply run: +content scripts/stylesheets and copy the manifest afterward. + +Extension can currently be built for 2 different imageboards using one of the following commands: ```shell +# To build the extension for Furbooru, use: npm run build + +# To build the extension for Derpbooru, use: +npm run build:derpibooru ``` -When building is complete, resulting files can be found in the `/build` directory. These files can be either used -directly in Chrome (via loading the extension as unpacked extension) or manually compressed into `*.zip` file. +When build is complete, extension files can be found in the `/build` directory. These files can be either used +directly in Chrome (via loading the extension as unpacked extension) or manually compressed into `*.zip` file and loaded +into Firefox. diff --git a/manifest.json b/manifest.json index 0a2ca64..1fdb8c2 100644 --- a/manifest.json +++ b/manifest.json @@ -1,6 +1,6 @@ { "name": "Furbooru Tagging Assistant", - "description": "Experimental extension with a set of tools to make the tagging faster and easier. Made specifically for Furbooru.", + "description": "Small experimental extension for slightly quicker tagging experience. Furbooru Edition.", "version": "0.4.5", "browser_specific_settings": { "gecko": { 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..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", @@ -19,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", 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; 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
diff --git a/src/config/tags.ts b/src/config/tags.ts index 6cfea79..ccef91e 100644 --- a/src/config/tags.ts +++ b/src/config/tags.ts @@ -1,4 +1,4 @@ -export const tagsBlacklist: string[] = [ +export const tagsBlacklist: string[] = (__CURRENT_SITE__ === 'furbooru' ? [ "anthro art", "anthro artist", "anthro cute", @@ -63,4 +63,21 @@ export const tagsBlacklist: string[] = [ "tagme", "upvotes galore", "wall of faves" -]; +] : [ + "tagme", + "tag me", + "not tagged", + "no tag", + "notag", + "notags", + "upvotes galore", + "downvotes galore", + "wall of faves", + "drama in the comments", + "drama in comments", + "tag needed", + "paywall", + "cringeworthy", + "solo oc", + "tag your shit" +]); diff --git a/src/lib/extension/BulkEntitiesTransporter.ts b/src/lib/extension/BulkEntitiesTransporter.ts index fb2f565..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[] { @@ -45,6 +69,7 @@ export default class BulkEntitiesTransporter { exportToJSON(entities: StorageEntity[]): string { return JSON.stringify({ $type: 'list', + $site: __CURRENT_SITE__, elements: entities .map(entity => { switch (true) { @@ -77,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"; + } } 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"> { diff --git a/src/routes/about/+page.svelte b/src/routes/about/+page.svelte index 9f578eb..40f6382 100644 --- a/src/routes/about/+page.svelte +++ b/src/routes/about/+page.svelte @@ -1,6 +1,12 @@ @@ -8,18 +14,26 @@

- Furbooru Tagging Assistant + {__CURRENT_SITE_NAME__} Tagging Assistant

- This is a tool made to help tag images on Furbooru more efficiently. It is currently in development and is not yet - ready for use, but it still can provide some useful functionality. + This is a small tool to make tagging on {__CURRENT_SITE_NAME__} just a little bit more convenient. Group tags with + your own rules; add or remove tags from the images without opening them up; preview images and videos on click and + a little bit more. This extension is highly unstable and might break at any point, so be aware.


- - Visit Furbooru + + Visit {__CURRENT_SITE_NAME__} GitHub Repo
+ + 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 @@