diff --git a/.vite/lib/content-scripts.js b/.vite/lib/content-scripts.js index aad275e..55f886c 100644 --- a/.vite/lib/content-scripts.js +++ b/.vite/lib/content-scripts.js @@ -48,6 +48,7 @@ function wrapScriptIntoIIFE() { */ function makeAliases(rootDir) { return { + "$config": path.resolve(rootDir, 'src/config'), "$lib": path.resolve(rootDir, 'src/lib'), "$entities": path.resolve(rootDir, 'src/lib/extension/entities'), "$styles": path.resolve(rootDir, 'src/styles'), diff --git a/src/config/tags.ts b/src/config/tags.ts new file mode 100644 index 0000000..6cfea79 --- /dev/null +++ b/src/config/tags.ts @@ -0,0 +1,66 @@ +export const tagsBlacklist: string[] = [ + "anthro art", + "anthro artist", + "anthro cute", + "anthro furry", + "anthro nsfw", + "anthro oc", + "anthroart", + "anthroartist", + "anthrofurry", + "anthronsfw", + "anthrooc", + "art", + "artist", + "artwork", + "cringe", + "cringeworthy", + "cute art", + "cute artwork", + "cute furry", + "downvotes galore", + "drama in comments", + "drama in the comments", + "fandom", + "furries", + "furry anthro", + "furry art", + "furry artist", + "furry artwork", + "furry character", + "furry community", + "furry cute", + "furry fandom", + "furry nsfw", + "furry oc", + "furryanthro", + "furryart", + "furryartist", + "furryartwork", + "furrynsfw", + "furryoc", + "image", + "no tag", + "not tagged", + "notag", + "notags", + "nsfw anthro", + "nsfw art", + "nsfw artist", + "nsfw artwork", + "nsfw", + "nsfwanthro", + "nsfwart", + "nsfwartist", + "nsfwartwork", + "paywall", + "rcf community", + "sfw", + "solo oc", + "tag me", + "tag needed", + "tag your shit", + "tagme", + "upvotes galore", + "wall of faves" +]; diff --git a/src/lib/components/MaintenancePopup.js b/src/lib/components/MaintenancePopup.js index 0267304..ea3ab0f 100644 --- a/src/lib/components/MaintenancePopup.js +++ b/src/lib/components/MaintenancePopup.js @@ -3,6 +3,16 @@ import MaintenanceProfile from "$entities/MaintenanceProfile.ts"; import {BaseComponent} from "$lib/components/base/BaseComponent.js"; import {getComponent} from "$lib/components/base/ComponentUtils.js"; import ScrapedAPI from "$lib/booru/scraped/ScrapedAPI.js"; +import {tagsBlacklist} from "$config/tags.ts"; + +class BlackListedTagsEncounteredError extends Error { + /** + * @param {string} tagName + */ + constructor(tagName) { + super(`This tag is blacklisted and prevents submission: ${tagName}`); + } +} export class MaintenancePopup extends BaseComponent { /** @type {HTMLElement} */ @@ -11,6 +21,9 @@ export class MaintenancePopup extends BaseComponent { /** @type {HTMLElement[]} */ #tagsList = []; + /** @type {Map} */ + #suggestedInvalidTags = new Map(); + /** @type {MaintenanceProfile|null} */ #activeProfile = null; @@ -89,11 +102,16 @@ export class MaintenancePopup extends BaseComponent { /** @type {string[]} */ const activeProfileTagsList = this.#activeProfile?.settings.tags || []; - for (let tagElement of this.#tagsList) { + for (const tagElement of this.#tagsList) { + tagElement.remove(); + } + + for (const tagElement of this.#suggestedInvalidTags.values()) { tagElement.remove(); } this.#tagsList = new Array(activeProfileTagsList.length); + this.#suggestedInvalidTags.clear(); const currentPostTags = this.#mediaBoxTools.mediaBox.tagsAndAliases; @@ -109,6 +127,12 @@ export class MaintenancePopup extends BaseComponent { tagElement.classList.toggle('is-present', isPresent); tagElement.classList.toggle('is-missing', !isPresent); 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)) { + MaintenancePopup.#markTagAsInvalid(tagElement); + this.#suggestedInvalidTags.set(tagName, tagElement); + } }); } @@ -188,6 +212,8 @@ export class MaintenancePopup extends BaseComponent { let maybeTagsAndAliasesAfterUpdate; + const shouldAutoRemove = await MaintenancePopup.#maintenanceSettings.resolveStripBlacklistedTags(); + try { maybeTagsAndAliasesAfterUpdate = await MaintenancePopup.#scrapedAPI.updateImageTags( this.#mediaBoxTools.mediaBox.imageId, @@ -200,11 +226,27 @@ export class MaintenancePopup extends BaseComponent { tagsList.add(tagName); } + if (shouldAutoRemove) { + for (let tagName of tagsBlacklist) { + tagsList.delete(tagName); + } + } else { + for (let tagName of tagsList) { + if (tagsBlacklist.includes(tagName)) { + throw new BlackListedTagsEncounteredError(tagName); + } + } + } + return tagsList; } ); } catch (e) { - console.warn('Tags submission failed:', e); + if (e instanceof BlackListedTagsEncounteredError) { + this.#revealInvalidTags(); + } else { + console.warn('Tags submission failed:', e); + } MaintenancePopup.#notifyAboutPendingSubmission(false); this.emit('maintenance-state-change', 'failed'); @@ -228,6 +270,36 @@ export class MaintenancePopup extends BaseComponent { this.#isSubmitting = false; } + #revealInvalidTags() { + const tagsAndAliases = this.#mediaBoxTools.mediaBox.tagsAndAliases; + + if (!tagsAndAliases) { + return; + } + + const firstTagInList = this.#tagsList[0]; + + for (let tagName of tagsBlacklist) { + if (tagsAndAliases.has(tagName)) { + if (this.#suggestedInvalidTags.has(tagName)) { + continue; + } + + const tagElement = MaintenancePopup.#buildTagElement(tagName); + MaintenancePopup.#markTagAsInvalid(tagElement); + tagElement.classList.add('is-present'); + + this.#suggestedInvalidTags.set(tagName, tagElement); + + if (firstTagInList && firstTagInList.isConnected) { + this.#tagsListElement.insertBefore(tagElement, firstTagInList); + } else { + this.#tagsListElement.appendChild(tagElement); + } + } + } + } + /** * @return {boolean} */ @@ -248,6 +320,15 @@ export class MaintenancePopup extends BaseComponent { return tagElement; } + /** + * Marks the tag with red color. + * @param {HTMLElement} tagElement Element to mark. + */ + static #markTagAsInvalid(tagElement) { + tagElement.dataset.tagCategory = 'error'; + tagElement.setAttribute('data-tag-category', 'error'); + } + /** * Controller with maintenance settings. * @type {MaintenanceSettings} diff --git a/src/lib/extension/settings/MaintenanceSettings.ts b/src/lib/extension/settings/MaintenanceSettings.ts index 94b9b85..c8aeadd 100644 --- a/src/lib/extension/settings/MaintenanceSettings.ts +++ b/src/lib/extension/settings/MaintenanceSettings.ts @@ -3,6 +3,7 @@ import CacheableSettings from "$lib/extension/base/CacheableSettings.ts"; interface MaintenanceSettingsFields { activeProfile: string | null; + stripBlacklistedTags: boolean; } export default class MaintenanceSettings extends CacheableSettings { @@ -27,6 +28,10 @@ export default class MaintenanceSettings extends CacheableSettings profile.id === resolvedProfileId) || null; } + async resolveStripBlacklistedTags() { + return this._resolveSetting('stripBlacklistedTags', false); + } + /** * Set the active maintenance profile. * @@ -36,4 +41,8 @@ export default class MaintenanceSettings extends CacheableSettings { await this._writeSetting("activeProfile", profileId); } + + async setStripBlacklistedTags(isEnabled: boolean) { + await this._writeSetting('stripBlacklistedTags', isEnabled); + } } diff --git a/src/routes/preferences/+page.svelte b/src/routes/preferences/+page.svelte index bc1d369..f4f4ea3 100644 --- a/src/routes/preferences/+page.svelte +++ b/src/routes/preferences/+page.svelte @@ -6,6 +6,7 @@ Back
+ Tagging Search Misc & Tools
diff --git a/src/routes/preferences/tags/+page.svelte b/src/routes/preferences/tags/+page.svelte new file mode 100644 index 0000000..811329a --- /dev/null +++ b/src/routes/preferences/tags/+page.svelte @@ -0,0 +1,20 @@ + + + + Back +
+
+ + + + Automatically remove black-listed tags from the images + + + diff --git a/src/stores/maintenance-preferences.ts b/src/stores/maintenance-preferences.ts new file mode 100644 index 0000000..05954a0 --- /dev/null +++ b/src/stores/maintenance-preferences.ts @@ -0,0 +1,18 @@ +import {writable} from "svelte/store"; +import MaintenanceSettings from "$lib/extension/settings/MaintenanceSettings.ts"; + +export const stripBlacklistedTagsEnabled = writable(true); + +const maintenanceSettings = new MaintenanceSettings(); + +Promise + .all([ + maintenanceSettings.resolveStripBlacklistedTags().then(v => stripBlacklistedTagsEnabled.set(v ?? true)) + ]) + .then(() => { + maintenanceSettings.subscribe(settings => { + stripBlacklistedTagsEnabled.set(typeof settings.stripBlacklistedTags === 'boolean' ? settings.stripBlacklistedTags : true); + }); + + stripBlacklistedTagsEnabled.subscribe(v => maintenanceSettings.setStripBlacklistedTags(v)); + }); diff --git a/src/styles/colors.scss b/src/styles/colors.scss index b6beafa..37e5039 100644 --- a/src/styles/colors.scss +++ b/src/styles/colors.scss @@ -25,6 +25,27 @@ $tag-background: #1b3c21; $tag-count-background: #2d6236; $tag-text: #4aa158; +$tag-rating-text: #418dd9; +$tag-rating-background: darken($tag-rating-text, 35%); +$tag-spoiler-text: #d49b39; +$tag-spoiler-background: darken($tag-spoiler-text, 34%); +$tag-origin-text: #6f66d6; +$tag-origin-background: darken($tag-origin-text, 40%); +$tag-oc-text: #b157b7; +$tag-oc-background: darken($tag-oc-text, 33%); +$tag-error-text: #d45460; +$tag-error-background: desaturate(darken($tag-error-text, 38%), 6%); +$tag-character-text: #4aaabf; +$tag-character-background: darken($tag-character-text, 33%); +$tag-content-official-text: #b9b541; +$tag-content-official-background: desaturate(darken($tag-content-official-text, 29%),2%); +$tag-content-fanmade-text: #cc8eb5; +$tag-content-fanmade-background: darken($tag-content-fanmade-text, 40%); +$tag-species-text: #b16b50; +$tag-species-background: darken($tag-species-text, 35%); +$tag-body-type-text: #b8b8b8; +$tag-body-type-background: desaturate(darken($tag-body-type-text, 35%), 10%); + $input-background: #26232d; $input-border: #5c5a61; diff --git a/src/styles/content/listing.scss b/src/styles/content/listing.scss index 54b4496..37804f5 100644 --- a/src/styles/content/listing.scss +++ b/src/styles/content/listing.scss @@ -70,6 +70,11 @@ color: colors.$tag-background; } + &[data-tag-category=error]:hover { + background: colors.$tag-error-text; + color: colors.$tag-error-background; + } + &.is-missing:not(.is-added), &.is-present.is-removed { opacity: 0.5; diff --git a/svelte.config.js b/svelte.config.js index 5532039..e32e94c 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -14,6 +14,7 @@ const config = { name: Date.now().toString(36) }, alias: { + "$config": "./src/config", "$components": "./src/components", "$styles": "./src/styles", "$stores": "./src/stores",