Add tag tooltips

This commit is contained in:
Lucas Sköld
2025-02-05 22:23:06 +01:00
parent 80a458453f
commit 5b9afe68af
2 changed files with 186 additions and 1 deletions
+73
View File
@@ -0,0 +1,73 @@
export class BooruApi {
constructor() {
this.baseUrl = "https://danbooru.donmai.us";
this.wikiUrl = `${this.baseUrl}/wiki_pages`;
this.parser = new DOMParser();
this.fetchCache = {};
}
cleanTag(tag) {
return tag.replaceAll(" ", "_").trim();
}
async #fetch(url, options = {}) {
if (this.fetchCache[url]) {
return this.fetchCache[url];
}
const { responseType = "json" } = options;
try {
const response = await fetch(url);
if (!response.ok) {
return {
success: false,
error: {
status: response.status,
message: `HTTP error! status: ${response.status}`,
url: url,
},
};
}
let data;
if (responseType === "html") {
data = await response.text();
} else {
data = await response.json();
}
this.fetchCache[url] = {
success: true,
data: data,
};
return this.fetchCache[url];
} catch (error) {
console.error(`Failed to fetch ${url}:`, error);
return {
success: false,
error: {
message: error.message,
url: url,
original: error,
},
};
}
}
getElementById(html, id) {
const doc = this.parser.parseFromString(html, "text/html");
const element = doc.querySelector(`#${id}`);
return element;
}
async getTagDescription(tag) {
const url = `${this.wikiUrl}/${this.cleanTag(tag)}`;
const response = await this.#fetch(url, { responseType: "html" });
if (!response.success) {
return null;
}
const body = this.getElementById(response.data, "wiki-page-body");
return body.querySelector("p").textContent;
}
}
+113 -1
View File
@@ -1,5 +1,8 @@
import { settingsHelper, API_PREFIX } from "./settings.js";
import { hexToRgb } from "./util.js";
import { BooruApi } from "./booruTagApi.js";
const booruApi = new BooruApi();
// Track all enhanced textareas
const enhancedTextareas = new WeakSet();
@@ -44,7 +47,49 @@ function enhanceTextarea(textarea) {
overlayEl.className = "input-overlay";
textarea.parentNode.insertBefore(overlayEl, textarea);
let currentTooltipTag = null;
let tooltipTimeout = null;
textarea.style.background = "transparent";
textarea.addEventListener("mousemove", (e) => {
// Clear existing timeout
if (tooltipTimeout) {
clearTimeout(tooltipTimeout);
}
tooltipTimeout = setTimeout(() => {
// Temporarily hide overlay to get element below
textarea.style.pointerEvents = "none";
const elementBelow = document.elementFromPoint(
e.clientX,
e.clientY
);
textarea.style.pointerEvents = "auto";
// Check if hovering over tag span in overlay
if (elementBelow && elementBelow.closest(".tag-span")) {
const tagSpan = elementBelow.closest(".tag-span");
const tag = tagSpan.dataset.tag;
// Only update if different tag
if (currentTooltipTag !== tag) {
hideTagTooltip();
showTagTooltip(tagSpan, tag);
currentTooltipTag = tag;
}
} else if (currentTooltipTag) {
hideTagTooltip();
currentTooltipTag = null;
}
}, 50); // 50ms debounce
});
textarea.addEventListener("mouseout", () => {
if (tooltipTimeout) {
clearTimeout(tooltipTimeout);
}
hideTagTooltip();
currentTooltipTag = null;
});
// Setup the textarea and overlay
setOverlayPosition(textarea, overlayEl);
@@ -499,9 +544,76 @@ async function syncText(inputEl, overlayEl, tries = 1) {
}
});
highlightedText = highlightedText
.split(",")
.map((tag) => {
if (!tag.trim()) return tag;
return `<span class="tag-span" data-tag="${tag.trim()}">${tag}</span>`;
})
.join(",");
overlayEl.innerHTML = highlightedText;
}
async function showTagTooltip(element, tag) {
let tooltip = document.getElementById("tag-tooltip");
const description =
(await booruApi.getTagDescription(tag)) ?? "No description available.";
if (!tooltip) {
tooltip = document.createElement("div");
tooltip.id = "tag-tooltip";
document.body.appendChild(tooltip);
}
tooltip.style.cssText = `
position: fixed;
background: var(--comfy-menu-bg);
color: var(--comfy-menu-text);
padding: 12px;
border-radius: 4px;
font-size: 14px;
z-index: 1000;
pointer-events: none;
max-width: 300px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
`;
const rect = element.getBoundingClientRect();
tooltip.style.left = `${rect.left}px`;
tooltip.style.top = `${rect.bottom + 5}px`;
// Create tooltip content with title and description
const titleElement = document.createElement("div");
titleElement.style.cssText = `
font-weight: bold;
margin-bottom: ${description ? "8px" : "0"};
color: var(--comfy-menu-text);
`;
titleElement.textContent = tag;
tooltip.innerHTML = "";
tooltip.appendChild(titleElement);
const descElement = document.createElement("div");
descElement.style.cssText = `
font-size: 13px;
line-height: 1.4;
color: var(--comfy-menu-text);
opacity: 0.9;
`;
descElement.textContent = description;
tooltip.appendChild(descElement);
tooltip.style.display = "block";
}
function hideTagTooltip() {
const tooltips = document.querySelectorAll("#tag-tooltip");
tooltips.forEach((tooltip) => {
tooltip.remove();
});
}
function setOverlayPosition(inputEl, overlayEl) {
const textareaStyle = window.getComputedStyle(inputEl);
overlayEl.style.left = textareaStyle.left;
@@ -527,7 +639,7 @@ function setOverlayStyle(inputEl, overlayEl) {
overlayEl.style.padding = textareaStyle.padding;
overlayEl.style.boxSizing = textareaStyle.boxSizing;
overlayEl.style.zIndex = "1";
overlayEl.style.pointerEvents = "none";
overlayEl.style.pointerEvents = "auto";
overlayEl.style.color = "transparent";
overlayEl.style.overflowX = textareaStyle.overflowX;
overlayEl.style.overflowY = textareaStyle.overflowY;