diff --git a/manifest.json b/manifest.json index f61bc04..d04d455 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "name": "Furbooru Tagging Assistant", "description": "Small experimental extension for slightly quicker tagging experience. Furbooru Edition.", - "version": "0.5.0", + "version": "0.5.1", "browser_specific_settings": { "gecko": { "id": "furbooru-tagging-assistant@thecore.city" @@ -47,6 +47,9 @@ ], "js": [ "src/content/tags-editor.ts" + ], + "css": [ + "src/styles/content/tags-editor.scss" ] }, { diff --git a/package-lock.json b/package-lock.json index 2acab45..6e95786 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "furbooru-tagging-assistant", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { diff --git a/package.json b/package.json index ef887c7..35d2735 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "furbooru-tagging-assistant", - "version": "0.5.0", + "version": "0.5.1", "private": true, "scripts": { "build": "npm run build:popup && npm run build:extension", diff --git a/src/content/deps/amd.ts b/src/content/deps/amd.ts index 9f4f525..1ced3ee 100644 --- a/src/content/deps/amd.ts +++ b/src/content/deps/amd.ts @@ -2,21 +2,52 @@ import { amdLite } from "amd-lite"; const originalDefine = amdLite.define; +/** + * Set of already defined modules. Used for deduplication. + */ +const definedModules = new Set(); + +/** + * Throttle timer to make sure only one attempt at loading modules will run for a batch of loaded scripts. + */ +let throttledAutoRunTimer: NodeJS.Timeout | number | undefined; + +/** + * Schedule the automatic resolving of all waiting modules on the next available frame. + */ +function scheduleModulesAutoRun() { + clearTimeout(throttledAutoRunTimer); + + throttledAutoRunTimer = setTimeout(() => { + amdLite.resolveDependencies(Object.keys(amdLite.waitingModules)); + }); +} + amdLite.define = (name, dependencies, originalCallback) => { - return originalDefine(name, dependencies, function () { + // Chrome doesn't run the same content script multiple times, while Firefox does. Since each content script and their + // chunks are intended to be run only once, we should just ignore any attempts of running the same module more than + // once. Names of the modules are assumed to be unique. + if (definedModules.has(name)) { + return; + } + + definedModules.add(name); + + originalDefine(name, dependencies, function () { const callbackResult = originalCallback(...arguments); // Workaround for the entry script not returning anything causing AMD-Lite to send warning about modules not // being loaded/not existing. return typeof callbackResult !== 'undefined' ? callbackResult : {}; - }) + }); + + // Schedule the auto run on the next available frame. Firefox and Chromium have a lot of differences in how they + // decide to execute content scripts. For example, Firefox might decide to skip a frame before attempting to load + // different groups of them. Chromium on the other hand doesn't have that issue, but it doesn't allow us to, for + // example, schedule a microtask to run the modules. + scheduleModulesAutoRun(); } amdLite.init({ publicScope: window }); - -// We don't have anything asynchronous, so it's safe to execute everything on the next frame. -requestAnimationFrame(() => { - amdLite.resolveDependencies(Object.keys(amdLite.waitingModules)) -}); diff --git a/src/lib/components/TagDropdownWrapper.ts b/src/lib/components/TagDropdownWrapper.ts index 7340854..2b60171 100644 --- a/src/lib/components/TagDropdownWrapper.ts +++ b/src/lib/components/TagDropdownWrapper.ts @@ -148,7 +148,13 @@ export class TagDropdownWrapper extends BaseComponent { profileSpecificButtonText = `Remove from profile "${profileName}"`; } - this.#toggleOnExistingButton.innerText = profileSpecificButtonText; + // Derpibooru has icons in dropdown. Make sure to only edit text and keep the icon untouched. Also, add the space + // before the text to make space between text and icon. + if (__CURRENT_SITE__ === 'derpibooru' && this.#toggleOnExistingButton.lastChild instanceof Text) { + this.#toggleOnExistingButton.lastChild.textContent = ` ${profileSpecificButtonText}`; + } else { + this.#toggleOnExistingButton.textContent = profileSpecificButtonText; + } if (!this.#toggleOnExistingButton.isConnected) { this.#dropdownContainer?.append(this.#toggleOnExistingButton); @@ -243,9 +249,19 @@ export class TagDropdownWrapper extends BaseComponent { static #createDropdownLink(text: string, onClickHandler: (event: MouseEvent) => void): HTMLAnchorElement { const dropdownLink = document.createElement('a'); dropdownLink.href = '#'; - dropdownLink.innerText = text; dropdownLink.className = 'tag__dropdown__link'; + // Derpibooru has an icon in dropdown item. Create the icon and place the text with additional space in front of it. + if (__CURRENT_SITE__ === 'derpibooru') { + const dropdownLinkIcon = document.createElement('i'); + dropdownLinkIcon.classList.add('fa', 'fa-tags'); + + dropdownLink.textContent = ` ${text}`; + dropdownLink.insertAdjacentElement('afterbegin', dropdownLinkIcon); + } else { + dropdownLink.textContent = text; + } + dropdownLink.addEventListener('click', event => { event.preventDefault(); onClickHandler(event); diff --git a/src/lib/components/TagsListBlock.ts b/src/lib/components/TagsListBlock.ts index 01e9a78..d63edd5 100644 --- a/src/lib/components/TagsListBlock.ts +++ b/src/lib/components/TagsListBlock.ts @@ -134,6 +134,7 @@ export class TagsListBlock extends BaseComponent { heading.style.display = 'none'; heading.style.order = `var(${TagsListBlock.#orderCssVariableForGroup(group.id)}, 0)`; heading.style.flexBasis = '100%'; + heading.classList.add('tag-category-headline'); // We're inserting heading to the top just to make sure that heading is always in front of the tags related to // this category. diff --git a/src/styles/booru-vars.scss b/src/styles/booru-vars.scss index 1db638d..462d9c5 100644 --- a/src/styles/booru-vars.scss +++ b/src/styles/booru-vars.scss @@ -1,6 +1,9 @@ $background-color: var(--background-color); $media-border: var(--media-border); $media-box-color: var(--media-box-color); +$padding-small: var(--padding-small); +$padding-normal: var(--padding-normal); +$padding-large: var(--padding-large); // These variables are defined dynamically based on the category of the tag $resolved-tag-background: var(--tag-background); diff --git a/src/styles/content/tags-editor.scss b/src/styles/content/tags-editor.scss new file mode 100644 index 0000000..07a79a9 --- /dev/null +++ b/src/styles/content/tags-editor.scss @@ -0,0 +1,23 @@ +@use '$styles/booru-vars'; +@use '$styles/environment'; + +h2.tag-category-headline { + // Basic margin top and bottom values gathered from Chrome. + $base-margin-top: .83em; + $base-margin-bottom: .62em; + + // Tag List element was updated to use flex & gaps. This should be applied to Furbooru later, once updates will be + // applied from the base Philomena version. + @if environment.$current-site == 'derpibooru' { + margin: { + top: calc(#{$base-margin-top} - #{booru-vars.$padding-small}); + bottom: calc(#{$base-margin-bottom} - #{booru-vars.$padding-small}); + } + } + @else { + margin: { + top: $base-margin-top; + bottom: $base-margin-bottom; + } + } +} diff --git a/src/styles/injectable/tag.scss b/src/styles/injectable/tag.scss index bbec107..db105d4 100644 --- a/src/styles/injectable/tag.scss +++ b/src/styles/injectable/tag.scss @@ -3,7 +3,6 @@ .tag { background: colors.$tag-background; - line-height: 28px; color: colors.$tag-text; font-weight: 700; font-size: 14px; @@ -12,6 +11,10 @@ @if environment.$current-site == 'derpibooru' { border: 1px solid colors.$tag-border; + line-height: 24px; + } + @else { + line-height: 28px; } .remove {