mirror of
https://github.com/koloml/furbooru-tagging-assistant.git
synced 2026-03-25 07:12:58 +00:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 94733c9ff3 | |||
| d11cc2a9c5 | |||
| f4e30c60ad | |||
| 9031055ec9 | |||
| 8194a84ef7 | |||
| 2829ac022f | |||
| 5aac85dcaa | |||
| 9a14a5568d | |||
| a2ab0d4e7c | |||
| 5123b57320 | |||
| 2bdb789777 |
@@ -174,6 +174,15 @@ export async function buildScriptsAndStyles(buildOptions) {
|
||||
}
|
||||
});
|
||||
|
||||
const tantabusSwapPlugin = SwapDefinedVariablesPlugin({
|
||||
envVariable: 'SITE',
|
||||
expectedValue: 'tantabus',
|
||||
define: {
|
||||
__CURRENT_SITE__: JSON.stringify('tantabus'),
|
||||
__CURRENT_SITE_NAME__: JSON.stringify('Tantabus'),
|
||||
}
|
||||
});
|
||||
|
||||
// Building all scripts together with AMD loader in mind
|
||||
await build({
|
||||
configFile: false,
|
||||
@@ -209,6 +218,7 @@ export async function buildScriptsAndStyles(buildOptions) {
|
||||
?.push(...dependencies);
|
||||
}),
|
||||
derpibooruSwapPlugin,
|
||||
tantabusSwapPlugin,
|
||||
],
|
||||
define: defineConstants,
|
||||
});
|
||||
@@ -235,6 +245,7 @@ export async function buildScriptsAndStyles(buildOptions) {
|
||||
wrapScriptIntoIIFE(),
|
||||
ScssViteReadEnvVariableFunctionPlugin(),
|
||||
derpibooruSwapPlugin,
|
||||
tantabusSwapPlugin,
|
||||
],
|
||||
define: defineConstants,
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Furbooru Tagging Assistant",
|
||||
"description": "Small experimental extension for slightly quicker tagging experience. Furbooru Edition.",
|
||||
"version": "0.6.0.1",
|
||||
"version": "0.6.1",
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "furbooru-tagging-assistant@thecore.city",
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "furbooru-tagging-assistant",
|
||||
"version": "0.6.0.1",
|
||||
"version": "0.6.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "furbooru-tagging-assistant",
|
||||
"version": "0.6.0.1",
|
||||
"version": "0.6.1",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^7.2.0",
|
||||
"@sveltejs/adapter-static": "^3.0.10",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "furbooru-tagging-assistant",
|
||||
"version": "0.6.0.1",
|
||||
"version": "0.6.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -34,6 +34,11 @@ export const namespaceCategories: Map<string, string> = new Map([
|
||||
['series', 'content-fanmade'],
|
||||
['spoiler', 'spoiler'],
|
||||
['video', 'content-fanmade'],
|
||||
...(__CURRENT_SITE__ === 'tantabus' ? <const> [
|
||||
["prompter", "origin"],
|
||||
["creator", "origin"],
|
||||
["generator", "origin"]
|
||||
] : [])
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import TagSettings from "$lib/extension/settings/TagSettings";
|
||||
import { getComponent } from "$content/components/base/component-utils";
|
||||
import { decodeTagNameFromLink, resolveTagCategoryFromTagName } from "$lib/booru/tag-utils";
|
||||
import { resolveTagNameFromLink, resolveTagCategoryFromTagName } from "$lib/booru/tag-utils";
|
||||
|
||||
export class BlockCommunication extends BaseComponent {
|
||||
#contentSection: HTMLElement | null = null;
|
||||
#tagLinks: HTMLAnchorElement[] = [];
|
||||
|
||||
#tagLinksReplaced: boolean | null = null;
|
||||
#linkTextReplaced: boolean | null = null;
|
||||
|
||||
protected build() {
|
||||
this.#contentSection = this.container.querySelector('.communication__content');
|
||||
@@ -15,14 +16,30 @@ export class BlockCommunication extends BaseComponent {
|
||||
}
|
||||
|
||||
protected init() {
|
||||
BlockCommunication.#tagSettings.resolveReplaceLinks().then(this.#onReplaceLinkSettingResolved.bind(this));
|
||||
Promise.all([
|
||||
BlockCommunication.#tagSettings.resolveReplaceLinks(),
|
||||
BlockCommunication.#tagSettings.resolveReplaceLinkText(),
|
||||
]).then(([replaceLinks, replaceLinkText]) => {
|
||||
this.#onReplaceLinkSettingResolved(
|
||||
replaceLinks,
|
||||
replaceLinkText
|
||||
);
|
||||
});
|
||||
|
||||
BlockCommunication.#tagSettings.subscribe(settings => {
|
||||
this.#onReplaceLinkSettingResolved(settings.replaceLinks ?? false);
|
||||
this.#onReplaceLinkSettingResolved(
|
||||
settings.replaceLinks ?? false,
|
||||
settings.replaceLinkText ?? true
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#onReplaceLinkSettingResolved(haveToReplaceLinks: boolean) {
|
||||
if (!this.#tagLinks.length || this.#tagLinksReplaced === haveToReplaceLinks) {
|
||||
#onReplaceLinkSettingResolved(haveToReplaceLinks: boolean, shouldReplaceLinkText: boolean) {
|
||||
if (
|
||||
!this.#tagLinks.length
|
||||
|| this.#tagLinksReplaced === haveToReplaceLinks
|
||||
&& this.#linkTextReplaced === shouldReplaceLinkText
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -34,25 +51,76 @@ export class BlockCommunication extends BaseComponent {
|
||||
linkElement.textContent = linkElement.children[0].textContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolved tag name. It should be stored for the text replacement.
|
||||
*/
|
||||
let tagName: string | undefined;
|
||||
|
||||
if (haveToReplaceLinks) {
|
||||
const maybeDecodedTagName = decodeTagNameFromLink(linkElement.pathname) ?? '';
|
||||
linkElement.dataset.tagCategory = resolveTagCategoryFromTagName(maybeDecodedTagName) ?? '';
|
||||
tagName = resolveTagNameFromLink(new URL(linkElement.href)) ?? '';
|
||||
linkElement.dataset.tagCategory = resolveTagCategoryFromTagName(tagName) ?? '';
|
||||
} else {
|
||||
linkElement.dataset.tagCategory = '';
|
||||
}
|
||||
|
||||
this.#toggleTagLinkText(
|
||||
linkElement,
|
||||
haveToReplaceLinks && shouldReplaceLinkText,
|
||||
tagName,
|
||||
);
|
||||
}
|
||||
|
||||
this.#tagLinksReplaced = haveToReplaceLinks;
|
||||
this.#linkTextReplaced = shouldReplaceLinkText;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the link text with the tag name or restore it back to original content. This function will only perform
|
||||
* replacement on links without any additional tags inside. This will ensure link won't break original content.
|
||||
* @param linkElement Element to swap the text on.
|
||||
* @param shouldSwapToTagName Should we swap the text to tag name or retore it back from memory.
|
||||
* @param tagName Tag name to swap the text to. If not provided, text will be swapped back.
|
||||
* @private
|
||||
*/
|
||||
#toggleTagLinkText(linkElement: HTMLElement, shouldSwapToTagName: boolean, tagName?: string) {
|
||||
if (linkElement.childElementCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure we save the original text to memory.
|
||||
if (!BlockCommunication.#originalTagLinkTexts.has(linkElement)) {
|
||||
BlockCommunication.#originalTagLinkTexts.set(linkElement, linkElement.textContent);
|
||||
}
|
||||
|
||||
if (shouldSwapToTagName && tagName) {
|
||||
linkElement.textContent = tagName;
|
||||
} else {
|
||||
linkElement.textContent = BlockCommunication.#originalTagLinkTexts.get(linkElement) ?? linkElement.textContent;
|
||||
}
|
||||
}
|
||||
|
||||
#findAllTagLinks(): HTMLAnchorElement[] {
|
||||
return Array
|
||||
.from(this.#contentSection?.querySelectorAll('a') || [])
|
||||
.filter(link => link.pathname.startsWith('/tags/'))
|
||||
.filter(
|
||||
link =>
|
||||
// Support links pointing to the tag page.
|
||||
link.pathname.startsWith('/tags/')
|
||||
// Also capture link which point to the search results with single tag.
|
||||
|| link.pathname.startsWith('/search')
|
||||
&& link.search.includes('q=')
|
||||
);
|
||||
}
|
||||
|
||||
static #tagSettings = new TagSettings();
|
||||
|
||||
/**
|
||||
* Map of links to their original texts. These texts need to be stored here to make them restorable. Keys is a link
|
||||
* element and value is a text.
|
||||
* @private
|
||||
*/
|
||||
static #originalTagLinkTexts: WeakMap<HTMLElement, string> = new WeakMap();
|
||||
|
||||
static findAndInitializeAll() {
|
||||
for (const container of document.querySelectorAll<HTMLElement>('.block.communication')) {
|
||||
if (getComponent(container)) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { namespaceCategories } from "$config/tags";
|
||||
import { QueryLexer, QuotedTermToken, TermToken } from "$lib/booru/search/QueryLexer";
|
||||
|
||||
/**
|
||||
* Build the map containing both real tags and their aliases.
|
||||
@@ -51,16 +52,48 @@ const slugEncodedCharacters: Map<string, string> = new Map([
|
||||
]);
|
||||
|
||||
/**
|
||||
* Decode the tag name from its link path.
|
||||
* Try to parse the tag name from the search query URL. It uses the same tokenizer as the booru. It only returns the
|
||||
* tag name if query contains only one single tag without any additional conditions.
|
||||
*
|
||||
* @param tagLink Full or partial link to the tag.
|
||||
* @param searchLink Link with search query.
|
||||
*
|
||||
* @return Tag name or NULL if function is failed to recognize the link as tag-related link.
|
||||
* @return Tag name or NULL if query contains more than 1 tag or doesn't have any tags at all.
|
||||
*/
|
||||
export function decodeTagNameFromLink(tagLink: string): string | null {
|
||||
function parseTagNameFromSearchQuery(searchLink: URL): string | null {
|
||||
const lexer = new QueryLexer(searchLink.searchParams.get('q') || '');
|
||||
const parsedQuery = lexer.parse();
|
||||
|
||||
if (parsedQuery.length !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [token] = parsedQuery;
|
||||
|
||||
switch (true) {
|
||||
case token instanceof TermToken:
|
||||
return token.value;
|
||||
case token instanceof QuotedTermToken:
|
||||
return token.decodedValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the tag name from the following link.
|
||||
*
|
||||
* @param tagLink Search link or link to the tag to parse the tag name from.
|
||||
*
|
||||
* @return Tag name or NULL if function is failed to parse the name of the tag.
|
||||
*/
|
||||
export function resolveTagNameFromLink(tagLink: URL): string | null {
|
||||
if (tagLink.pathname.startsWith('/search') && tagLink.searchParams.has('q')) {
|
||||
return parseTagNameFromSearchQuery(tagLink);
|
||||
}
|
||||
|
||||
tagLinkRegExp.lastIndex = 0;
|
||||
|
||||
const result = tagLinkRegExp.exec(tagLink);
|
||||
const result = tagLinkRegExp.exec(tagLink.pathname);
|
||||
const encodedTagName = result?.groups?.encodedTagName;
|
||||
|
||||
if (!encodedTagName) {
|
||||
@@ -69,7 +102,8 @@ export function decodeTagNameFromLink(tagLink: string): string | null {
|
||||
|
||||
return decodeURIComponent(encodedTagName)
|
||||
.replaceAll(/-[a-z]+-/gi, match => slugEncodedCharacters.get(match) ?? match)
|
||||
.replaceAll('-', ' ');
|
||||
.replaceAll('-', ' ')
|
||||
.replaceAll('+', ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,6 +3,7 @@ import CacheableSettings from "$lib/extension/base/CacheableSettings";
|
||||
interface TagSettingsFields {
|
||||
groupSeparation: boolean;
|
||||
replaceLinks: boolean;
|
||||
replaceLinkText: boolean;
|
||||
}
|
||||
|
||||
export default class TagSettings extends CacheableSettings<TagSettingsFields> {
|
||||
@@ -18,6 +19,10 @@ export default class TagSettings extends CacheableSettings<TagSettingsFields> {
|
||||
return this._resolveSetting("replaceLinks", false);
|
||||
}
|
||||
|
||||
async resolveReplaceLinkText() {
|
||||
return this._resolveSetting("replaceLinkText", true);
|
||||
}
|
||||
|
||||
async setGroupSeparation(value: boolean) {
|
||||
return this._writeSetting("groupSeparation", Boolean(value));
|
||||
}
|
||||
@@ -25,4 +30,8 @@ export default class TagSettings extends CacheableSettings<TagSettingsFields> {
|
||||
async setReplaceLinks(value: boolean) {
|
||||
return this._writeSetting("replaceLinks", Boolean(value));
|
||||
}
|
||||
|
||||
async setReplaceLinkText(value: boolean) {
|
||||
return this._writeSetting("replaceLinkText", Boolean(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
import Menu from "$components/ui/menu/Menu.svelte";
|
||||
import MenuItem from "$components/ui/menu/MenuItem.svelte";
|
||||
import { stripBlacklistedTagsEnabled } from "$stores/preferences/maintenance";
|
||||
import { shouldReplaceLinksOnForumPosts, shouldSeparateTagGroups } from "$stores/preferences/tag";
|
||||
import {
|
||||
shouldReplaceLinksOnForumPosts,
|
||||
shouldReplaceTextOfTagLinks,
|
||||
shouldSeparateTagGroups
|
||||
} from "$stores/preferences/tag";
|
||||
import { popupTitle } from "$stores/popup";
|
||||
|
||||
$popupTitle = 'Tagging Preferences';
|
||||
@@ -31,4 +35,11 @@
|
||||
Find and replace links to the tags in the forum posts
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
{#if $shouldReplaceLinksOnForumPosts}
|
||||
<FormControl>
|
||||
<CheckboxField bind:checked={$shouldReplaceTextOfTagLinks}>
|
||||
Try to replace text on links pointing to tags in forum posts
|
||||
</CheckboxField>
|
||||
</FormControl>
|
||||
{/if}
|
||||
</FormContainer>
|
||||
|
||||
@@ -5,11 +5,13 @@ const tagSettings = new TagSettings();
|
||||
|
||||
export const shouldSeparateTagGroups = writable(false);
|
||||
export const shouldReplaceLinksOnForumPosts = writable(false);
|
||||
export const shouldReplaceTextOfTagLinks = writable(true);
|
||||
|
||||
Promise
|
||||
.allSettled([
|
||||
tagSettings.resolveGroupSeparation().then(value => shouldSeparateTagGroups.set(value)),
|
||||
tagSettings.resolveReplaceLinks().then(value => shouldReplaceLinksOnForumPosts.set(value)),
|
||||
tagSettings.resolveReplaceLinkText().then(value => shouldReplaceTextOfTagLinks.set(value)),
|
||||
])
|
||||
.then(() => {
|
||||
shouldSeparateTagGroups.subscribe(value => {
|
||||
@@ -20,8 +22,13 @@ Promise
|
||||
void tagSettings.setReplaceLinks(value);
|
||||
});
|
||||
|
||||
shouldReplaceTextOfTagLinks.subscribe(value => {
|
||||
void tagSettings.setReplaceLinkText(value);
|
||||
});
|
||||
|
||||
tagSettings.subscribe(settings => {
|
||||
shouldSeparateTagGroups.set(Boolean(settings.groupSeparation));
|
||||
shouldReplaceLinksOnForumPosts.set(Boolean(settings.replaceLinks));
|
||||
shouldReplaceTextOfTagLinks.set(Boolean(settings.replaceLinkText));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user