Compare commits

..
9 changed files with 127 additions and 207 deletions
+19 -11
View File
@@ -235,7 +235,16 @@ class Downloader:
def _check_new_csv_from_hf_dataset(self, dataset_meta: dict, now_utc: datetime, force_check: bool = False): def _check_new_csv_from_hf_dataset(self, dataset_meta: dict, now_utc: datetime, force_check: bool = False):
"""Checks HuggingFace for file updates and updates metadata.""" """Checks HuggingFace for file updates and updates metadata."""
perform_hf_check = force_check perform_hf_check = True
if not force_check and dataset_meta.get("last_remote_check_timestamp"):
try:
last_check_dt = datetime.fromisoformat(dataset_meta["last_remote_check_timestamp"])
if now_utc - last_check_dt < timedelta(days=7):
perform_hf_check = False
except (ValueError, KeyError, TypeError):
print(
"[Autocomplete-Plus] Invalid or missing timestamp for last_remote_check_timestamp. Will perform remote check."
)
if perform_hf_check: if perform_hf_check:
huggingface_dataset_id = dataset_meta["hf_dataset_id"] huggingface_dataset_id = dataset_meta["hf_dataset_id"]
@@ -294,16 +303,15 @@ class Downloader:
return f"File {file_name} is missing or empty locally." return f"File {file_name} is missing or empty locally."
# Check if the last modified date on HuggingFace is newer than the last download date # Check if the last modified date on HuggingFace is newer than the last download date
# Temporarily comment out until changes to remote files can be detected try:
# try: last_download_dt = datetime.fromisoformat(file_meta_entry["last_download"])
# last_download_dt = datetime.fromisoformat(file_meta_entry["last_download"]) hf_modified_dt = datetime.fromisoformat(file_meta_entry["last_modified_on_hf"])
# hf_modified_dt = datetime.fromisoformat(file_meta_entry["last_modified_on_hf"]) if hf_modified_dt > last_download_dt:
# if hf_modified_dt > last_download_dt: return f"Remote file {file_name} is newer (HF: {last_download_dt}, Local Download: {hf_modified_dt})."
# return f"Remote file {file_name} is newer (HF: {hf_modified_dt}, Local Download: {last_download_dt})." except (ValueError, TypeError):
# except (ValueError, TypeError): file_meta_entry["last_download"] = None
# file_meta_entry["last_download"] = None file_meta_entry["last_modified_on_hf"] = None
# file_meta_entry["last_modified_on_hf"] = None return f"Invalid timestamp format for {file_name}. Forcing download to ensure integrity."
# return f"Invalid timestamp format for {file_name}. Forcing download to ensure integrity."
# If the file is missing or empty, but the last download timestamp exists, we need to retry. # If the file is missing or empty, but the last download timestamp exists, we need to retry.
if not check_file_valid(local_file_path) and file_meta_entry.get("last_download") is not None: if not check_file_valid(local_file_path) and file_meta_entry.get("last_download") is not None:
+1 -1
View File
@@ -1,7 +1,7 @@
[project] [project]
name = "comfyui-autocomplete-plus" name = "comfyui-autocomplete-plus"
description = "Autocomplete and Related Tag display for ComfyUI" description = "Autocomplete and Related Tag display for ComfyUI"
version = "1.5.0" version = "1.4.0"
license = {file = "LICENSE"} license = {file = "LICENSE"}
dependencies = ["",] dependencies = ["",]
+2 -10
View File
@@ -97,14 +97,10 @@ body.dark-theme {
display: grid; display: grid;
box-shadow: 0 2px 8px rgb(0 0 0 / 30%); box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
grid-auto-rows: auto; grid-auto-rows: auto;
grid-template-columns: max-content 1fr auto; grid-template-columns: max-content 1fr auto auto;
overflow-y: auto; overflow-y: auto;
} }
#autocomplete-plus-list.no-alias {
grid-template-columns: max-content auto;
}
.autocomplete-plus-item { .autocomplete-plus-item {
display: grid; display: grid;
cursor: pointer; cursor: pointer;
@@ -296,14 +292,10 @@ body.dark-theme {
display: grid; display: grid;
box-shadow: 0 2px 8px rgb(0 0 0 / 30%); box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
grid-auto-rows: auto; grid-auto-rows: auto;
grid-template-columns: max-content 1fr auto; grid-template-columns: max-content 1fr auto auto;
overflow-y: auto; overflow-y: auto;
} }
#related-tags-list.no-alias {
grid-template-columns: max-content auto;
}
.related-tag-item { .related-tag-item {
display: grid; display: grid;
cursor: pointer; cursor: pointer;
+26 -41
View File
@@ -17,7 +17,6 @@ import {
extractTagsFromTextArea, extractTagsFromTextArea,
getCurrentTagRange, getCurrentTagRange,
getViewportMargin, getViewportMargin,
getScrollbarWidth,
IconSvgHtmlString IconSvgHtmlString
} from './utils.js'; } from './utils.js';
import { settingValues } from './settings.js'; import { settingValues } from './settings.js';
@@ -312,10 +311,10 @@ function insertTagToTextArea(inputElement, tagDataToInsert) {
let replaceEnd = cursorPos; let replaceEnd = cursorPos;
let normalizedTag; let normalizedTag;
if (Object.values(ModelTagSource).includes(tagDataToInsert.source)) { if(Object.values(ModelTagSource).includes(tagDataToInsert.source)){
// If the tag is from a model tag source, don't want to normalize it // If the tag is from a model tag source, don't want to normalize it
normalizedTag = tagDataToInsert.tag; normalizedTag = tagDataToInsert.tag;
} else { }else{
normalizedTag = normalizeTagToInsert(tagDataToInsert.tag); normalizedTag = normalizeTagToInsert(tagDataToInsert.tag);
} }
@@ -472,8 +471,6 @@ class AutocompleteUI {
return; return;
} }
this.tagsList.classList.toggle('no-alias', settingValues.hideAlias);
const existingTags = extractTagsFromTextArea(this.target); const existingTags = extractTagsFromTextArea(this.target);
const currentTag = getCurrentPartialTag(this.target); const currentTag = getCurrentPartialTag(this.target);
@@ -491,7 +488,6 @@ class AutocompleteUI {
*/ */
#createTagElement(tagData, isExisting) { #createTagElement(tagData, isExisting) {
const categoryText = TagCategory[tagData.source][tagData.category] || "unknown"; const categoryText = TagCategory[tagData.source][tagData.category] || "unknown";
const aliasText = tagData.alias.join(', ');
const tagRow = document.createElement('div'); const tagRow = document.createElement('div');
tagRow.classList.add('autocomplete-plus-item', tagData.source); tagRow.classList.add('autocomplete-plus-item', tagData.source);
@@ -521,29 +517,26 @@ class AutocompleteUI {
alias.className = 'autocomplete-plus-alias'; alias.className = 'autocomplete-plus-alias';
// Display alias if available // Display alias if available
if (aliasText.length > 0) { if (tagData.alias && tagData.alias.length > 0) {
let aliasText = tagData.alias.join(', ');
alias.textContent = `${aliasText}`; alias.textContent = `${aliasText}`;
alias.title = aliasText; // Full alias on hover alias.title = tagData.alias.join(', '); // Full alias on hover
} }
// Category
const category = document.createElement('span');
category.className = `autocomplete-plus-category`;
category.textContent = `${categoryText.substring(0, 2)}`;
category.title = categoryText; // Full category on hover
// Count // Count
const tagCount = document.createElement('span'); const tagCount = document.createElement('span');
tagCount.className = `autocomplete-plus-tag-count`; tagCount.className = `autocomplete-plus-tag-count`;
tagCount.textContent = formatCountHumanReadable(tagData.count); tagCount.textContent = formatCountHumanReadable(tagData.count);
// Create tooltip with more info
let tooltipText = `Count: ${tagData.count}\nCategory: ${categoryText}`;
if (aliasText.length > 0) {
tooltipText += `\nAlias: ${aliasText}`;
}
tagRow.title = tooltipText;
tagRow.appendChild(tagName); tagRow.appendChild(tagName);
tagRow.appendChild(alias);
if (!settingValues.hideAlias) { tagRow.appendChild(category);
tagRow.appendChild(alias);
}
tagRow.appendChild(tagCount); tagRow.appendChild(tagCount);
this.tagsList.appendChild(tagRow); this.tagsList.appendChild(tagRow);
} }
@@ -581,14 +574,21 @@ class AutocompleteUI {
let topPosition = targetElmOffset.top + ((caretTop - targetElmOffset.top) + caretLineHeight) * scale; let topPosition = targetElmOffset.top + ((caretTop - targetElmOffset.top) + caretLineHeight) * scale;
let leftPosition = targetElmOffset.left + (caretLeft - targetElmOffset.left) * scale;; let leftPosition = targetElmOffset.left + (caretLeft - targetElmOffset.left) * scale;;
const maxWidth = Math.min(rootRect.width, viewportWidth / 2);
const naturalHeight = rootRect.height; const naturalHeight = rootRect.height;
//Horizontal Collision Detection and Adjustment
if (leftPosition + maxWidth > viewportWidth - margin.right) {
leftPosition = viewportWidth - maxWidth - margin.right;
}
if (leftPosition < margin.left) {
leftPosition = margin.left;
}
// Vertical Collision Detection and Adjustment // Vertical Collision Detection and Adjustment
const availableSpaceBelow = viewportHeight - topPosition - margin.bottom; const availableSpaceBelow = viewportHeight - topPosition - margin.bottom;
const availableSpaceAbove = caretTop - margin.top; const availableSpaceAbove = caretTop - margin.top;
let needsVerticalScroll = false;
let maxHeight = rootRect.height;
if (naturalHeight <= availableSpaceBelow) { if (naturalHeight <= availableSpaceBelow) {
// Fits perfectly below the caret // Fits perfectly below the caret
@@ -600,15 +600,14 @@ class AutocompleteUI {
topPosition = caretTop - naturalHeight - margin.top; topPosition = caretTop - naturalHeight - margin.top;
} else { } else {
// Doesn't fit perfectly either below or above, needs scrolling. // Doesn't fit perfectly either below or above, needs scrolling.
needsVerticalScroll = true;
// Choose the position (above or below) that offers more space. // Choose the position (above or below) that offers more space.
if (availableSpaceBelow >= availableSpaceAbove) { if (availableSpaceBelow >= availableSpaceAbove) {
// Scroll below: topPosition remains as initially calculated // Scroll below: topPosition remains as initially calculated
maxHeight = availableSpaceBelow; this.tagsList.style.maxHeight = `${availableSpaceBelow}px`;
} else { } else {
// Scroll above: Position near the top edge and set max-height // Scroll above: Position near the top edge and set max-height
topPosition = margin.top; topPosition = margin.top;
maxHeight = availableSpaceAbove; this.tagsList.style.maxHeight = `${availableSpaceAbove}px`;
} }
} }
} }
@@ -617,30 +616,16 @@ class AutocompleteUI {
if (topPosition < margin.top) { if (topPosition < margin.top) {
topPosition = margin.top; topPosition = margin.top;
// If pushed down, recalculate max-height if it was set based on top alignment // If pushed down, recalculate max-height if it was set based on top alignment
if (availableSpaceBelow < availableSpaceAbove) { if (this.tagsList.style.maxHeight && availableSpaceBelow < availableSpaceAbove) {
// Recalculate max-height based on space from the top margin // Recalculate max-height based on space from the top margin
maxHeight = viewportHeight - margin.top - margin.bottom; this.tagsList.style.maxHeight = `${viewportHeight - margin.top - margin.bottom}px`;
needsVerticalScroll = true;
} }
} }
// Calculate maxWidth considering scrollbar width if vertical scrolling is needed
const scrollbarWidth = needsVerticalScroll ? getScrollbarWidth() : 0;
const maxWidth = Math.min(rootRect.width + scrollbarWidth, viewportWidth / 2);
// Horizontal Collision Detection and Adjustment
if (leftPosition + maxWidth > viewportWidth - margin.right) {
leftPosition = viewportWidth - maxWidth - margin.right;
}
if (leftPosition < margin.left) {
leftPosition = margin.left;
}
// Apply the calculated position and display the element // Apply the calculated position and display the element
this.root.style.left = `${leftPosition}px`; this.root.style.left = `${leftPosition}px`;
this.root.style.top = `${topPosition}px`; this.root.style.top = `${topPosition}px`;
this.root.style.maxWidth = `${maxWidth}px`; this.root.style.maxWidth = `${maxWidth}px`;
this.tagsList.style.maxHeight = `${maxHeight}px`;
} }
/** Highlights the item (row) at the given index */ /** Highlights the item (row) at the given index */
+23 -26
View File
@@ -154,7 +154,6 @@ async function loadTags(csvUrl, siteName) {
} }
const csvText = await response.text(); const csvText = await response.text();
const lines = csvText.split('\n').filter(line => line.trim().length > 0); const lines = csvText.split('\n').filter(line => line.trim().length > 0);
const totalLines = lines.length;
const startIndex = lines[0].toLowerCase().startsWith(TAGS_CSV_HEADER) ? 1 : 0; const startIndex = lines[0].toLowerCase().startsWith(TAGS_CSV_HEADER) ? 1 : 0;
@@ -184,18 +183,8 @@ async function loadTags(csvUrl, siteName) {
updateMaxTagLength(tag.length); updateMaxTagLength(tag.length);
autoCompleteData[siteName].sortedTags.push(tagData); autoCompleteData[siteName].sortedTags.push(tagData);
} else {
console.warn(`[Autocomplete-Plus] Invalid CSV format in line ${i + 1} of ${csvUrl}: ${line}. Expected ${TAGS_CSV_HEADER_COLUMNS.length} columns, but got ${columns.length}.`);
continue;
}
}
// Sort by count in descending order // Set the tag and its alias in the maps
autoCompleteData[siteName].sortedTags.sort((a, b) => b.count - a.count);
// Build maps as before, but ensure not to overwrite if already processed from extra files
autoCompleteData[siteName].sortedTags.forEach(tagData => {
if (!autoCompleteData[siteName].tagMap.has(tagData.tag)) {
autoCompleteData[siteName].tagMap.set(tagData.tag, tagData); autoCompleteData[siteName].tagMap.set(tagData.tag, tagData);
if (tagData.alias && Array.isArray(tagData.alias)) { if (tagData.alias && Array.isArray(tagData.alias)) {
tagData.alias.forEach(alias => { tagData.alias.forEach(alias => {
@@ -204,8 +193,11 @@ async function loadTags(csvUrl, siteName) {
} }
}); });
} }
} else {
console.warn(`[Autocomplete-Plus] Invalid CSV format in line ${i + 1} of ${csvUrl}: ${line}. Expected ${TAGS_CSV_HEADER_COLUMNS.length} columns, but got ${columns.length}.`);
continue;
} }
}); }
} catch (error) { } catch (error) {
console.error(`[Autocomplete-Plus] Failed to fetch or process tags from ${csvUrl}:`, error); console.error(`[Autocomplete-Plus] Failed to fetch or process tags from ${csvUrl}:`, error);
@@ -276,17 +268,17 @@ async function loadCooccurrence(csvUrl, siteName) {
const startIndex = lines[0].startsWith('tag_a,tag_b,count') ? 1 : 0; const startIndex = lines[0].startsWith('tag_a,tag_b,count') ? 1 : 0;
await processInChunks(lines, startIndex, autoCompleteData[siteName].cooccurrenceMap, csvUrl, siteName); await processCooccurrenceInChunks(lines, startIndex, autoCompleteData[siteName].cooccurrenceMap, csvUrl, siteName);
} catch (error) { } catch (error) {
console.error(`[Autocomplete-Plus] Failed to fetch or process cooccurrence data from ${csvUrl}:`, error); console.error(`[Autocomplete-Plus] Failed to fetch or process cooccurrence data from ${csvUrl}:`, error);
} }
} }
/** /**
* Process CSV data in chunks to avoid blocking the UI. * Process Co-Occurrence CSV data in chunks to avoid blocking the UI.
* Modifies the targetMap directly. * Modifies the targetMap directly.
*/ */
function processInChunks(lines, startIndex, targetMap, csvUrl, siteName) { function processCooccurrenceInChunks(lines, startIndex, targetMap, csvUrl, siteName) {
return new Promise((resolve) => { return new Promise((resolve) => {
const CHUNK_SIZE = 10000; const CHUNK_SIZE = 10000;
let i = startIndex; let i = startIndex;
@@ -297,7 +289,7 @@ function processInChunks(lines, startIndex, targetMap, csvUrl, siteName) {
for (; i < endIndex; i++) { for (; i < endIndex; i++) {
const line = lines[i]; const line = lines[i];
const columns = parseCSVLine(line); const columns = line.split(",");
if (columns.length >= 3) { if (columns.length >= 3) {
const tagA = columns[0].trim(); const tagA = columns[0].trim();
@@ -307,17 +299,19 @@ function processInChunks(lines, startIndex, targetMap, csvUrl, siteName) {
if (!tagA || !tagB || isNaN(count)) continue; if (!tagA || !tagB || isNaN(count)) continue;
// Add tagA -> tagB relationship // Add tagA -> tagB relationship
if (!targetMap.has(tagA)) { let subMapA = targetMap.get(tagA);
targetMap.set(tagA, new Map()); if (!subMapA) {
subMapA = new Map();
targetMap.set(tagA, subMapA);
} }
targetMap.get(tagA).set(tagB, count); subMapA.set(tagB, count);
let subMapB = targetMap.get(tagB);
// Add tagB -> tagA relationship (bidirectional) if (!subMapB) {
if (!targetMap.has(tagB)) { subMapB = new Map();
targetMap.set(tagB, new Map()); targetMap.set(tagB, subMapB);
} }
targetMap.get(tagB).set(tagA, count); subMapB.set(tagA, count);
pairCount++; pairCount++;
} }
@@ -450,6 +444,9 @@ async function initializeDataFromCSV(csvListData, source) {
await Promise.all([ await Promise.all([
Promise.all(tagsLoadPromiseFactories.map(factory => factory())) Promise.all(tagsLoadPromiseFactories.map(factory => factory()))
.then(() => { .then(() => {
// Sort by count in descending order
autoCompleteData[source].sortedTags.sort((a, b) => b.count - a.count);
// Build FlexSearch index after tags are loaded // Build FlexSearch index after tags are loaded
return buildFlexSearchIndex(source); return buildFlexSearchIndex(source);
}) })
@@ -546,7 +543,7 @@ async function loadLoras() {
} }
/** /**
* Load all data sources asynchronously. * Load all data sources in parallel.
*/ */
export async function loadDataAsync() { export async function loadDataAsync() {
return Promise.all([ return Promise.all([
+33 -47
View File
@@ -190,41 +190,40 @@ async function addExtraSettings() {
} }
// Add extra setting for checking new CSV updates // Add extra setting for checking new CSV updates
// Note: Temporarily comment out until changes to remote files can be detected app.ui.settings.addSetting({
// app.ui.settings.addSetting({ id: id + ".check_new_csv",
// id: id + ".check_new_csv", defaultValue: null,
// defaultValue: null, name: "Check CSV updates",
// name: "Check CSV updates", category: [name, "Misc", "Check new CSV"],
// category: [name, "Misc", "Check new CSV"], type: () => {
// type: () => { const lastCheckSpan = $el("span", {
// const lastCheckSpan = $el("span", { textContent: lastCheckTimeText,
// textContent: lastCheckTimeText, className: "text-sm text-gray-500",
// className: "text-sm text-gray-500", style: {
// style: { marginRight: "16px"
// marginRight: "16px" }
// } });
// });
// const checkButton = $el("button", { const checkButton = $el("button", {
// textContent: "Check now", textContent: "Check now",
// className: "p-button p-component p-button-primary", className: "p-button p-component p-button-primary",
// onclick: async () => { onclick: async () => {
// await performUpdateCheck(checkButton, lastCheckSpan); await performUpdateCheck(checkButton, lastCheckSpan);
// } }
// }); });
// return $el("div", { return $el("div", {
// className: "flex-row items-center gap-2", className: "flex-row items-center gap-2",
// }, [ }, [
// $el("div", { $el("div", {
// className: "p-component", className: "p-component",
// }, [ }, [
// lastCheckSpan, lastCheckSpan,
// checkButton, checkButton,
// ]), ]),
// ]); ]);
// } }
// }); });
} }
/** /**
@@ -241,7 +240,7 @@ app.registerExtension({
let rootPath = import.meta.url.replace("js/main.js", ""); let rootPath = import.meta.url.replace("js/main.js", "");
loadCSS(rootPath + "css/autocomplete-plus.css"); // Load CSS for autocomplete loadCSS(rootPath + "css/autocomplete-plus.css"); // Load CSS for autocomplete
await loadDataAsync(); loadDataAsync();
}, },
// One the Settings Screen, displays reverse order in same category // One the Settings Screen, displays reverse order in same category
@@ -383,19 +382,6 @@ app.registerExtension({
onChange: (newVal, oldVal) => { onChange: (newVal, oldVal) => {
settingValues.enableRelatedTags = newVal; settingValues.enableRelatedTags = newVal;
} }
},
// --- Display settings ---
{
id: id + ".display_hide_alias",
name: "Hide Alias",
tooltip: "Hide alias in the autocomplete and related tags display",
type: "boolean",
defaultValue: false,
category: [name, "Display", "Hide Alias"],
onChange: (newVal, oldVal) => {
settingValues.hideAlias = newVal;
}
} }
] ]
}); });
+23 -33
View File
@@ -4,7 +4,6 @@ import {
extractTagsFromTextArea, extractTagsFromTextArea,
findAllTagPositions, findAllTagPositions,
getCurrentTagRange, getCurrentTagRange,
getScrollbarWidth,
getViewportMargin, getViewportMargin,
isLongText, isLongText,
isValidTag, isValidTag,
@@ -260,7 +259,7 @@ class RelatedTagsUI {
this.headerControls.appendChild(this.pinBtn); this.headerControls.appendChild(this.pinBtn);
this.header.appendChild(this.headerControls); this.header.appendChild(this.headerControls);
this.root.appendChild(this.header); this.root.appendChild(this.header);
// Create a tbody for the tags // Create a tbody for the tags
@@ -420,8 +419,8 @@ class RelatedTagsUI {
}); });
} }
const categoryText = TagCategory[tagData.source][tagData.category] || "unknown"; const tagText = this.currentTag;
const aliasText = tagData.alias.join(', '); const categoryText = TagCategory[tagData.source][tagData.category];
// Update header text with current tag // Update header text with current tag
this.headerText.innerHTML = ''; // Clear previous content this.headerText.innerHTML = ''; // Clear previous content
@@ -429,15 +428,15 @@ class RelatedTagsUI {
const tagName = document.createElement('span'); const tagName = document.createElement('span');
tagName.classList.add('related-tags-header-tag-name', tagData.source); tagName.classList.add('related-tags-header-tag-name', tagData.source);
tagName.title = `Count: ${tagData.count}\nCategory: ${categoryText}\nAlias: ${aliasText}`; tagName.title = `Count: ${tagData.count}\nCategory: ${categoryText}`;
tagName.dataset.tagCategory = categoryText; tagName.dataset.tagCategory = categoryText;
if (tagData.source && ['left', 'right'].includes(settingValues.tagSourceIconPosition)) { if (tagData.source && ['left', 'right'].includes(settingValues.tagSourceIconPosition)) {
const tagSourceIconHtml = `<svg class="autocomplete-plus-tag-icon-svg"><use xlink:href="#autocomplete-plus-icon-${tagData.source}"></use></svg>`; const tagSourceIconHtml = `<svg class="autocomplete-plus-tag-icon-svg"><use xlink:href="#autocomplete-plus-icon-${tagData.source}"></use></svg>`;
tagName.innerHTML = settingValues.tagSourceIconPosition == 'left' tagName.innerHTML = settingValues.tagSourceIconPosition == 'left'
? `${tagSourceIconHtml} ${tagData.tag}` ? `${tagSourceIconHtml} ${tagText}`
: `${tagData.tag} ${tagSourceIconHtml}`; : `${tagText} ${tagSourceIconHtml}`;
} else { } else {
tagName.textContent += tagData.tag; tagName.textContent += tagText;
} }
@@ -448,8 +447,8 @@ class RelatedTagsUI {
this.headerAlias.innerHTML = ''; this.headerAlias.innerHTML = '';
// Add alias if available // Add alias if available
if (aliasText.length > 0 && !settingValues.hideAlias) { if (tagData.alias?.length > 0) {
this.headerAlias.textContent = aliasText; this.headerAlias.textContent = tagData.alias.join(', ');
this.headerAlias.style.display = 'block'; this.headerAlias.style.display = 'block';
} }
@@ -486,9 +485,6 @@ class RelatedTagsUI {
return; return;
} }
// Toggle column class based on settings
this.tagsContainer.classList.toggle('no-alias', settingValues.hideAlias);
const existingTags = extractTagsFromTextArea(this.target); const existingTags = extractTagsFromTextArea(this.target);
// Create tag rows // Create tag rows
@@ -507,7 +503,6 @@ class RelatedTagsUI {
*/ */
#createTagElement(tagData, isExisting) { #createTagElement(tagData, isExisting) {
const categoryText = TagCategory[tagData.source][tagData.category] || "unknown"; const categoryText = TagCategory[tagData.source][tagData.category] || "unknown";
const aliasText = tagData.alias.join(', ');
const tagRow = document.createElement('div'); const tagRow = document.createElement('div');
tagRow.classList.add('related-tag-item', tagData.source); tagRow.classList.add('related-tag-item', tagData.source);
@@ -529,30 +524,33 @@ class RelatedTagsUI {
alias.className = 'related-tag-alias'; alias.className = 'related-tag-alias';
// Display alias if available // Display alias if available
if (aliasText.length > 0) { if (tagData.alias && tagData.alias.length > 0) {
let aliasText = tagData.alias.join(', ');
alias.textContent = `${aliasText}`; alias.textContent = `${aliasText}`;
alias.title = aliasText; // Full alias on hover alias.title = tagData.alias.join(', '); // Full alias on hover
} }
// Category
const category = document.createElement('span');
category.className = `related-tag-category`;
category.textContent = `${categoryText.substring(0, 2)}`;
// Similarity // Similarity
const similarity = document.createElement('span'); const similarity = document.createElement('span');
similarity.className = 'related-tag-similarity'; similarity.className = 'related-tag-similarity';
similarity.textContent = `${(tagData.similarity * 100).toFixed(2)}%`; similarity.textContent = `${(tagData.similarity * 100).toFixed(2)}%`;
// Create tooltip with more info // Create tooltip with more info
let tooltipText = `Similarity: ${(tagData.similarity * 100).toFixed(2)}%\nCount: ${tagData.count}\nCategory: ${categoryText}`; let tooltipText = `Tag: ${tagData.tag}\nSimilarity: ${(tagData.similarity * 100).toFixed(2)}%\nCount: ${tagData.count}`;
if (aliasText.length > 0) { if (tagData.alias && tagData.alias.length > 0) {
tooltipText += `\nAlias: ${aliasText}`; tooltipText += `\nAlias: ${tagData.alias.join(', ')}`;
} }
tagRow.title = tooltipText; tagRow.title = tooltipText;
// Add cells to row // Add cells to row
tagRow.appendChild(tagName); tagRow.appendChild(tagName);
tagRow.appendChild(alias);
if (!settingValues.hideAlias) { tagRow.appendChild(category);
tagRow.appendChild(alias);
}
tagRow.appendChild(similarity); tagRow.appendChild(similarity);
return tagRow; return tagRow;
@@ -582,7 +580,7 @@ class RelatedTagsUI {
const newHeaderRect = this.header.getBoundingClientRect(); const newHeaderRect = this.header.getBoundingClientRect();
if (this.relatedTags.length > 0) { if(this.relatedTags.length > 0){
this.tagsContainer.style.maxHeight = `${placementArea.height - newHeaderRect.height}px`; this.tagsContainer.style.maxHeight = `${placementArea.height - newHeaderRect.height}px`;
} }
@@ -671,10 +669,6 @@ class RelatedTagsUI {
area.y = targetRect.bottom; area.y = targetRect.bottom;
} }
// Calculate width considering scrollbar width if vertical scrolling is needed
const scrollbarWidth = area.height < elemHeight ? getScrollbarWidth() : 0;
area.width = Math.min(elemWidth + scrollbarWidth, maxWidth);
// Adjust x position to avoid overflow // Adjust x position to avoid overflow
area.x = Math.min(area.x, viewportWidth - area.width - margin.right); area.x = Math.min(area.x, viewportWidth - area.width - margin.right);
} else { } else {
@@ -691,10 +685,6 @@ class RelatedTagsUI {
area.x = targetRect.right; area.x = targetRect.right;
} }
// Calculate width considering scrollbar width if vertical scrolling is needed
const scrollbarWidth = area.height < elemHeight ? getScrollbarWidth() : 0;
area.width = Math.min(area.width + scrollbarWidth, viewportWidth - margin.left - margin.right);
// Adjust y position to avoid overflow // Adjust y position to avoid overflow
area.y = Math.min(area.y, viewportHeight - area.height - margin.bottom); area.y = Math.min(area.y, viewportHeight - area.height - margin.bottom);
} }
-3
View File
@@ -16,9 +16,6 @@ export const settingValues = {
relatedTagsDisplayPosition: 'horizontal', // 'horizontal' or 'vertical' relatedTagsDisplayPosition: 'horizontal', // 'horizontal' or 'vertical'
relatedTagsTriggerMode: 'click', // Options: 'click', 'ctrl+Click' relatedTagsTriggerMode: 'click', // Options: 'click', 'ctrl+Click'
// Display settings
hideAlias: false, // Hide alias in the autocomplete and related tags display
// Internal logic settings // Internal logic settings
_useFallbackAttachmentForEventListener: false, // Fallback to attach event listener when somthing goes wrong _useFallbackAttachmentForEventListener: false, // Fallback to attach event listener when somthing goes wrong
-35
View File
@@ -584,38 +584,3 @@ export function getViewportMargin() {
right: rightBarRect.width, right: rightBarRect.width,
}; };
} }
// Cache for scrollbar width measurement
let _cachedScrollbarWidth;
/**
* Measures and returns the scrollbar width for the current browser.
* Creates a temporary element to measure the scrollbar width accurately.
* @returns {number} The scrollbar width in pixels
*/
export function getScrollbarWidth() {
// Return cached value if available
if (_cachedScrollbarWidth !== undefined) {
return _cachedScrollbarWidth;
}
// Create a temporary div with scrollbar
const outer = document.createElement('div');
outer.style.visibility = 'hidden';
outer.style.position = 'absolute';
outer.style.overflow = 'scroll';
outer.style.top = '-9999px';
outer.style.width = '100px';
document.body.appendChild(outer);
const inner = document.createElement('div');
inner.style.width = '100%';
outer.appendChild(inner);
// Cache the result
_cachedScrollbarWidth = outer.offsetWidth - inner.offsetWidth;
document.body.removeChild(outer);
return _cachedScrollbarWidth;
}