Add tag tooltips
This commit is contained in:
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user