mirror of
https://github.com/koloml/philomena-tagging-assistant.git
synced 2026-09-22 20:09:39 +00:00
Find and modify duplicate reports, show source url icons in the diff
This commit is contained in:
@@ -104,6 +104,17 @@
|
||||
"css": [
|
||||
"src/styles/content/tag-presets.scss"
|
||||
]
|
||||
},
|
||||
{
|
||||
"matches": [
|
||||
"*://*.furbooru.org/duplicate_reports"
|
||||
],
|
||||
"js": [
|
||||
"src/content/duplicate-reports.ts"
|
||||
],
|
||||
"css": [
|
||||
"src/styles/content/duplicate-reports.scss"
|
||||
]
|
||||
}
|
||||
],
|
||||
"action": {
|
||||
|
||||
@@ -24,4 +24,17 @@ export default class ImageContainer extends BaseComponent {
|
||||
|
||||
return JSON.parse(jsonUris);
|
||||
}
|
||||
|
||||
extractSources(): string[] {
|
||||
const jsonSourceUrls = this.container.dataset.sourceUrls;
|
||||
let sourceUrls: string[] | null = null;
|
||||
|
||||
try {
|
||||
sourceUrls = JSON.parse(jsonSourceUrls || '[]');
|
||||
} catch (e) {
|
||||
console.warn('Failed to parse source URLs for the image!', this, e);
|
||||
}
|
||||
|
||||
return sourceUrls || [];
|
||||
}
|
||||
}
|
||||
|
||||
86
src/content/components/philomena/dupe/DupeDiff.ts
Normal file
86
src/content/components/philomena/dupe/DupeDiff.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import { createFontAwesomeIcon } from "$lib/dom-utils";
|
||||
|
||||
const brandsSubtype = 'brands';
|
||||
|
||||
const blueskyIcon = ['bluesky', brandsSubtype];
|
||||
const twitterIcon = ['x-twitter', brandsSubtype];
|
||||
|
||||
export default class DupeDiff extends BaseComponent {
|
||||
#sourcesLine: HTMLElement | null = null;
|
||||
|
||||
protected init() {
|
||||
const {
|
||||
"6": sourcesLine,
|
||||
} = this.container.querySelectorAll<HTMLElement>('table tr > td')
|
||||
|
||||
this.#sourcesLine = sourcesLine;
|
||||
}
|
||||
|
||||
renderSources(leftSources: string[], rightSources: string[]): void {
|
||||
if (!this.#sourcesLine) {
|
||||
console.error("Can't render sources since no sources line in diff fonud!");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const childElement of this.#sourcesLine.children) {
|
||||
childElement.remove();
|
||||
}
|
||||
|
||||
const sourceIconsContainer = document.createElement('span');
|
||||
sourceIconsContainer.classList.add('source-icons');
|
||||
|
||||
sourceIconsContainer.append(
|
||||
this.#renderSourceIcons(leftSources),
|
||||
" vs ",
|
||||
this.#renderSourceIcons(rightSources),
|
||||
);
|
||||
|
||||
this.#sourcesLine.append(sourceIconsContainer);
|
||||
}
|
||||
|
||||
#renderSourceIcons(sourcesList: string[]): HTMLElement {
|
||||
const iconsContainer = document.createElement('span');
|
||||
iconsContainer.classList.add('source-icons__list');
|
||||
|
||||
for (const sourceUrl of sourcesList) {
|
||||
iconsContainer.append(this.#renderIcon(sourceUrl));
|
||||
}
|
||||
|
||||
if (!sourcesList.length) {
|
||||
iconsContainer.append('(none)');
|
||||
}
|
||||
|
||||
return iconsContainer;
|
||||
}
|
||||
|
||||
#renderIcon(url: string): HTMLElement {
|
||||
let iconSlug = 'globe';
|
||||
let maybeSubtype: string | undefined;
|
||||
|
||||
for (const [iconOrIconWithSubtype, singleOrMultiPattern] of DupeDiff.#iconsToPatternsMap) {
|
||||
if (
|
||||
typeof singleOrMultiPattern === 'string' && url.includes(singleOrMultiPattern)
|
||||
|| Array.isArray(singleOrMultiPattern) && singleOrMultiPattern.some(singlePattern => url.includes(singlePattern))
|
||||
) {
|
||||
if (Array.isArray(iconOrIconWithSubtype)) {
|
||||
[iconSlug, maybeSubtype] = iconOrIconWithSubtype;
|
||||
} else {
|
||||
iconSlug = iconOrIconWithSubtype;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceIcon = createFontAwesomeIcon(iconSlug, maybeSubtype);
|
||||
sourceIcon.title = url;
|
||||
|
||||
return sourceIcon;
|
||||
}
|
||||
|
||||
static #iconsToPatternsMap = new Map<string[] | string, string[] | string>([
|
||||
[blueskyIcon, 'bsky.app'],
|
||||
['paw', 'furaffinity.net'],
|
||||
[twitterIcon, ['x.com', 'twitter.com']],
|
||||
]);
|
||||
}
|
||||
19
src/content/components/philomena/dupe/DupeImage.ts
Normal file
19
src/content/components/philomena/dupe/DupeImage.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import ImageContainer from "$content/components/philomena/ImageContainer";
|
||||
|
||||
export default class DupeImage extends BaseComponent {
|
||||
readonly imageContainer: ImageContainer;
|
||||
|
||||
constructor(container: HTMLElement) {
|
||||
super(container);
|
||||
|
||||
const imageContainerElement = container.querySelector<HTMLElement>('.image-container');
|
||||
|
||||
if (!imageContainerElement) {
|
||||
throw new Error('Missing image container inside the dupe row!');
|
||||
}
|
||||
|
||||
this.imageContainer = new ImageContainer(imageContainerElement);
|
||||
this.imageContainer.initialize();
|
||||
}
|
||||
}
|
||||
34
src/content/components/philomena/dupe/DupeReportRow.ts
Normal file
34
src/content/components/philomena/dupe/DupeReportRow.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import DupeDiff from "$content/components/philomena/dupe/DupeDiff";
|
||||
import DupeImage from "$content/components/philomena/dupe/DupeImage";
|
||||
|
||||
export default class DupeReportRow extends BaseComponent {
|
||||
#leftImage: DupeImage;
|
||||
#rightImage: DupeImage;
|
||||
#difference: DupeDiff;
|
||||
|
||||
constructor(leftCell: HTMLElement, rightCell: HTMLElement, diffCell: HTMLElement, reportOptions: HTMLElement) {
|
||||
super(reportOptions);
|
||||
|
||||
this.#leftImage = new DupeImage(leftCell);
|
||||
this.#rightImage = new DupeImage(rightCell);
|
||||
this.#difference = new DupeDiff(diffCell);
|
||||
}
|
||||
|
||||
protected build() {
|
||||
this.#leftImage.initialize();
|
||||
this.#rightImage.initialize();
|
||||
this.#difference.initialize();
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.#extractAndRenderSourcesIcons();
|
||||
}
|
||||
|
||||
#extractAndRenderSourcesIcons() {
|
||||
this.#difference.renderSources(
|
||||
this.#leftImage.imageContainer.extractSources(),
|
||||
this.#rightImage.imageContainer.extractSources(),
|
||||
);
|
||||
}
|
||||
}
|
||||
49
src/content/components/philomena/dupe/GridDupeReportsList.ts
Normal file
49
src/content/components/philomena/dupe/GridDupeReportsList.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { BaseComponent } from "$content/components/base/BaseComponent";
|
||||
import DupeReportRow from "$content/components/philomena/dupe/DupeReportRow";
|
||||
|
||||
export default class GridDupeReportsList extends BaseComponent {
|
||||
readonly #reports: DupeReportRow[] = [];
|
||||
|
||||
protected build() {
|
||||
const childrenElements = this.container.children;
|
||||
|
||||
for (let cellIndex = 0; cellIndex < this.container.childElementCount; cellIndex += 4) {
|
||||
const startingCell = childrenElements.item(cellIndex);
|
||||
|
||||
// First 4 cells are actually table headers, skipping them.
|
||||
if (!(startingCell instanceof HTMLElement) || startingCell.tagName === 'P') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rightImageCell = startingCell.nextElementSibling;
|
||||
const diffCell = rightImageCell?.nextElementSibling || null;
|
||||
const reportOptionsCell = diffCell?.nextElementSibling || null;
|
||||
|
||||
if (!(rightImageCell instanceof HTMLElement) || !(diffCell instanceof HTMLElement) || !(reportOptionsCell instanceof HTMLElement)) {
|
||||
console.error(`Unable to capture duplicate report row from starting cell at index ${cellIndex}!`);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.#reports.push(
|
||||
new DupeReportRow(
|
||||
startingCell,
|
||||
rightImageCell,
|
||||
diffCell,
|
||||
reportOptionsCell,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected init() {
|
||||
for (const report of this.#reports) {
|
||||
report.initialize();
|
||||
}
|
||||
}
|
||||
|
||||
static findAndInitialize() {
|
||||
for (const container of document.querySelectorAll<HTMLElement>('.grid--dupe-report-list')) {
|
||||
new GridDupeReportsList(container).initialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
3
src/content/duplicate-reports.ts
Normal file
3
src/content/duplicate-reports.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import GridDupeReportsList from "$content/components/philomena/dupe/GridDupeReportsList";
|
||||
|
||||
GridDupeReportsList.findAndInitialize();
|
||||
10
src/styles/content/duplicate-reports.scss
Normal file
10
src/styles/content/duplicate-reports.scss
Normal file
@@ -0,0 +1,10 @@
|
||||
.dr__diff {
|
||||
.source-icons {
|
||||
margin-left: .5em;
|
||||
|
||||
&__list {
|
||||
display: inline-flex;
|
||||
gap: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user