Skip to content

Commit 85b86c0

Browse files
feat: Add wiki icon functionality and related methods for tag information (#45)
1 parent 1f93ca0 commit 85b86c0

5 files changed

Lines changed: 229 additions & 32 deletions

File tree

web/css/autocomplete-plus.css

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@
1111
--autocomplete-plus-text-color-cat-white: var(--p-neutral-700);
1212
--autocomplete-plus-text-color-disabled: var(--p-gray-400);
1313
--autocomplete-plus-list-color-even: var(--p-neutral-300);
14-
--autocomplete-plus-list-color-odd: var(--p-neutral-400)
14+
--autocomplete-plus-list-color-odd: var(--p-neutral-400);
15+
--wiki-icon-text-color: var(--input-text);
16+
--wiki-icon-opacity-normal: 0.7;
17+
--wiki-icon-opacity-hover: 1.0;
18+
--wiki-icon-opacity-disabled: 0;
1519
}
1620

1721
/* Dark Theme */
@@ -27,8 +31,11 @@ body.dark-theme {
2731
--autocomplete-plus-text-color-cat-white: var(--p-neutral-200);
2832
--autocomplete-plus-text-color-disabled: var(--p-gray-500);
2933
--autocomplete-plus-list-color-even: var(--p-neutral-800);
30-
--autocomplete-plus-list-color-odd: var(--p-neutral-900)
31-
34+
--autocomplete-plus-list-color-odd: var(--p-neutral-900);
35+
--wiki-icon-text-color: var(--input-text);
36+
--wiki-icon-opacity-normal: 0.5;
37+
--wiki-icon-opacity-hover: 0.9;
38+
--wiki-icon-opacity-disabled: 0;
3239
}
3340

3441
#autocomplete-plus-root {
@@ -102,16 +109,17 @@ body.dark-theme {
102109
display: grid;
103110
box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
104111
grid-auto-rows: auto;
105-
grid-template-columns: max-content 1fr auto;
112+
grid-template-columns: max-content max-content 1fr auto;
106113
overflow-y: auto;
107114
}
108115

109116
#autocomplete-plus-list.no-alias {
110-
grid-template-columns: max-content auto;
117+
grid-template-columns: max-content max-content auto;
111118
}
112119

113120
.autocomplete-plus-item {
114121
display: grid;
122+
border-bottom: 1px solid var(--border-color);
115123
cursor: pointer;
116124
grid-column: 1 / -1;
117125
grid-template-columns: subgrid;
@@ -120,23 +128,22 @@ body.dark-theme {
120128
.autocomplete-plus-item span {
121129
align-content: center;
122130
padding: 4px 8px;
123-
border-bottom: 1px solid var(--border-color);
124131
}
125132

126133
/* Alternating row colors */
127-
.autocomplete-plus-item:nth-child(even) span {
134+
.autocomplete-plus-item:nth-child(even) {
128135
background-color: var(--autocomplete-plus-list-color-even);
129136
}
130137

131-
.autocomplete-plus-item:nth-child(odd) span {
138+
.autocomplete-plus-item:nth-child(odd) {
132139
background-color: var(--autocomplete-plus-list-color-odd);
133140
}
134141

135-
.autocomplete-plus-item.selected span {
142+
.autocomplete-plus-item.selected {
136143
background-color: var(--border-color);
137144
}
138145

139-
.autocomplete-plus-item:hover span {
146+
.autocomplete-plus-item:hover {
140147
background-color: var(--comfy-hover-bg);
141148
}
142149

@@ -165,6 +172,28 @@ body.dark-theme {
165172
text-align: right;
166173
}
167174

175+
.autocomplete-plus-item .autocomplete-plus-wiki-icon {
176+
padding: 0 8px;
177+
color: var(--wiki-icon-text-color);
178+
cursor: pointer;
179+
font-size: 1.2em;
180+
opacity: var(--wiki-icon-opacity-normal);
181+
text-align: center;
182+
vertical-align: middle;
183+
}
184+
185+
.autocomplete-plus-item .autocomplete-plus-wiki-icon:hover {
186+
filter: brightness(1.1);
187+
opacity: var(--wiki-icon-opacity-hover);
188+
}
189+
190+
/* Hide wiki icon for tags without wiki pages */
191+
.autocomplete-plus-item .autocomplete-plus-wiki-icon.disabled {
192+
cursor: default;
193+
opacity: var(--wiki-icon-opacity-disabled);
194+
pointer-events: none;
195+
}
196+
168197
/* Related Tags UI Styles */
169198
#related-tags-root {
170199
position: absolute;
@@ -260,10 +289,16 @@ body.dark-theme {
260289
}
261290

262291
.related-tags-header-tag-name {
292+
cursor: pointer;
263293
font-weight: bold;
264294
word-break: break-all;
265295
}
266296

297+
.related-tags-header-tag-name.disabled {
298+
cursor: default;
299+
text-decoration: none;
300+
}
301+
267302
.related-tags-header-tag-name .autocomplete-plus-tag-icon-svg {
268303
width: 1em;
269304
height: 1em;
@@ -301,16 +336,17 @@ body.dark-theme {
301336
display: grid;
302337
box-shadow: 0 2px 8px rgb(0 0 0 / 30%);
303338
grid-auto-rows: auto;
304-
grid-template-columns: max-content 1fr auto;
339+
grid-template-columns: max-content max-content 1fr auto;
305340
overflow-y: auto;
306341
}
307342

308343
#related-tags-list.no-alias {
309-
grid-template-columns: max-content auto;
344+
grid-template-columns: max-content max-content auto;
310345
}
311346

312347
.related-tag-item {
313348
display: grid;
349+
border-bottom: 1px solid var(--border-color);
314350
cursor: pointer;
315351
grid-column: 1 / -1;
316352
grid-template-columns: subgrid;
@@ -320,22 +356,21 @@ body.dark-theme {
320356
.related-tag-item span {
321357
align-content: center;
322358
padding: 4px 8px;
323-
border-bottom: 1px solid var(--border-color);
324359
}
325360

326-
.related-tag-item:nth-child(even) span {
361+
.related-tag-item:nth-child(even) {
327362
background-color: var(--autocomplete-plus-list-color-even);
328363
}
329364

330-
.related-tag-item:nth-child(odd) span {
331-
background-color: var(---autocomplete-plus-list-color-odd);
365+
.related-tag-item:nth-child(odd) {
366+
background-color: var(--autocomplete-plus-list-color-odd);
332367
}
333368

334-
.related-tag-item:hover span {
369+
.related-tag-item:hover {
335370
background-color: var(--p-form-field-filled-hover-background);
336371
}
337372

338-
.related-tag-item.selected span {
373+
.related-tag-item.selected {
339374
background-color: var(--border-color);
340375
}
341376

@@ -360,6 +395,29 @@ body.dark-theme {
360395
white-space: nowrap;
361396
}
362397

398+
.related-tag-item .related-tag-wiki-icon {
399+
padding: 0 8px;
400+
color: var(--wiki-icon-text-color);
401+
cursor: pointer;
402+
font-size: 1.2em;
403+
opacity: var(--wiki-icon-opacity-normal);
404+
text-align: center;
405+
transition: opacity 0.1s ease;
406+
vertical-align: middle;
407+
}
408+
409+
.related-tag-item .related-tag-wiki-icon:hover {
410+
filter: brightness(1.1);
411+
opacity: var(--wiki-icon-opacity-hover);
412+
}
413+
414+
/* Hide wiki icon for tags without wiki pages */
415+
.related-tag-item .related-tag-wiki-icon.disabled {
416+
cursor: default;
417+
opacity: var(--wiki-icon-opacity-disabled);
418+
pointer-events: none;
419+
}
420+
363421
.related-tags-message {
364422
width: 100%;
365423
padding: 12px;

web/js/autocomplete.js

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
ModelTagSource,
3-
TagCategory,
43
TagData,
54
autoCompleteData,
65
getEnabledTagSourceInPriorityOrder
@@ -19,7 +18,8 @@ import {
1918
getViewportMargin,
2019
getScrollbarWidth,
2120
IconSvgHtmlString,
22-
addWeightToLora
21+
addWeightToLora,
22+
openTagWikiUrl
2323
} from './utils.js';
2424
import { settingValues } from './settings.js';
2525

@@ -263,7 +263,7 @@ function getCurrentPartialTag(inputElement) {
263263
if (!inputElement) {
264264
return "";
265265
}
266-
266+
267267
const text = inputElement.value;
268268
const cursorPos = inputElement.selectionStart;
269269

@@ -312,13 +312,13 @@ function insertTagToTextArea(inputElement, tagDataToInsert) {
312312
if (!inputElement || !tagDataToInsert) {
313313
return;
314314
}
315-
315+
316316
const text = inputElement.value;
317317
const cursorPos = inputElement.selectionStart;
318318

319319
const tagRange = getCurrentTagRange(text, cursorPos);
320320
let tagStart, tagEnd, currentTag;
321-
321+
322322
if (!tagRange) {
323323
// Fallback: insert at cursor position
324324
tagStart = cursorPos;
@@ -327,7 +327,7 @@ function insertTagToTextArea(inputElement, tagDataToInsert) {
327327
} else {
328328
({ start: tagStart, end: tagEnd, tag: currentTag } = tagRange);
329329
}
330-
330+
331331
const replaceStart = Math.min(cursorPos, tagStart);
332332
let replaceEnd = cursorPos;
333333

@@ -407,6 +407,16 @@ class AutocompleteUI {
407407

408408
// Add event listener for clicks on items
409409
this.tagsList.addEventListener('mousedown', (e) => {
410+
// Check if wiki icon was clicked first
411+
const wikiIcon = e.target.closest('.autocomplete-plus-wiki-icon');
412+
if (wikiIcon && !wikiIcon.classList.contains('disabled')) {
413+
openTagWikiUrl(wikiIcon.dataset.tagSource, wikiIcon.dataset.tagName);
414+
e.preventDefault();
415+
e.stopPropagation();
416+
return;
417+
}
418+
419+
// Check if row was clicked (existing behavior)
410420
const row = e.target.closest('.autocomplete-plus-item');
411421
if (row && row.dataset.index !== undefined) {
412422
const tagData = this.candidates[parseInt(row.dataset.index, 10)];
@@ -517,7 +527,7 @@ class AutocompleteUI {
517527
* @param {boolean} isExisting
518528
*/
519529
#createTagElement(tagData, tagDataIndex, isExisting) {
520-
const categoryText = TagCategory[tagData.source][tagData.category] || "unknown";
530+
const categoryText = tagData.categoryText;
521531
const aliasText = tagData.alias.join(', ');
522532

523533
const tagRow = document.createElement('div');
@@ -543,6 +553,18 @@ class AutocompleteUI {
543553
tagName.classList.add('autocomplete-plus-already-exists');
544554
}
545555

556+
// Wiki icon
557+
const wikiIcon = document.createElement('span');
558+
wikiIcon.className = 'autocomplete-plus-wiki-icon';
559+
if (tagData.hasWikiPage) {
560+
wikiIcon.dataset.tagName = tagData.tag;
561+
wikiIcon.dataset.tagSource = tagData.source;
562+
wikiIcon.textContent = '📖'
563+
wikiIcon.title = 'Open wiki page';
564+
} else {
565+
wikiIcon.classList.add('disabled');
566+
}
567+
546568
// Alias
547569
const alias = document.createElement('span');
548570
alias.className = 'autocomplete-plus-alias';
@@ -558,14 +580,15 @@ class AutocompleteUI {
558580
tagCount.className = `autocomplete-plus-tag-count`;
559581
tagCount.textContent = formatCountHumanReadable(tagData.count);
560582

561-
// Create tooltip with more info
583+
// Create tooltip with more info
562584
let tooltipText = `Count: ${tagData.count}\nCategory: ${categoryText}`;
563585
if (aliasText.length > 0) {
564586
tooltipText += `\nAlias: ${aliasText}`;
565587
}
566588
tagRow.title = tooltipText;
567589

568590
tagRow.appendChild(tagName);
591+
tagRow.appendChild(wikiIcon);
569592

570593
if (!settingValues.hideAlias) {
571594
tagRow.appendChild(alias);
@@ -979,6 +1002,13 @@ export class AutocompleteEventHandler {
9791002
}
9801003
this.autocompleteUI.hide();
9811004
break;
1005+
case 'F1':
1006+
event.preventDefault();
1007+
const tagData = this.autocompleteUI.getSelectedTag();
1008+
if (tagData && tagData.hasWikiPage) {
1009+
openTagWikiUrl(tagData.source, tagData.tag);
1010+
}
1011+
break;
9821012
case 'Escape':
9831013
event.preventDefault();
9841014
this.autocompleteUI.hide();

web/js/data.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,23 @@ export class TagData {
7272

7373
this.source = source;
7474
}
75+
76+
/**
77+
* Get the category text
78+
* @returns {string}
79+
*/
80+
get categoryText() {
81+
return TagCategory[this.source][this.category] || "unknown";
82+
}
83+
84+
/**
85+
* Check if the tag has a wiki page
86+
* @returns {boolean}
87+
*/
88+
get hasWikiPage() {
89+
return Object.values(TagSource).includes(this.source)
90+
&& ['general', 'artist', 'copyright', 'character', 'species', 'lore'].includes(this.categoryText);
91+
}
7592
}
7693

7794
class AutocompleteData {

0 commit comments

Comments
 (0)