Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d8190ea07 | ||
|
|
9e2f174bcf | ||
|
|
199ee05f12 | ||
|
|
06f0157635 | ||
|
|
f96fac44a2 | ||
|
|
729574cf3b | ||
|
|
e4decf948b | ||
|
|
24dd6901ed | ||
|
|
aa192f51f0 | ||
|
|
300ee91cc1 | ||
|
|
3f3561a02b | ||
|
|
c3822bc4f8 | ||
|
|
d480c586d0 | ||
|
|
e0ce4beb21 | ||
|
|
f8cbd27c5c | ||
|
|
17bad4da60 | ||
|
|
9d12afb48c | ||
|
|
091ec15b2a | ||
|
|
c87d92d7f8 | ||
|
|
5d5da100e1 | ||
|
|
85b86c0618 | ||
|
|
1f93ca0259 | ||
|
|
e0486313a6 | ||
|
|
11b4eb48f0 | ||
|
|
156d569614 | ||
|
|
4fdef7f436 | ||
|
|
8189b2b75d | ||
|
|
5a76096c7e | ||
|
|
b6d4fb5590 |
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## English • [日本語](docs/README_jp.md)
|
## English • [日本語](docs/README_jp.md)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -43,6 +43,7 @@ When you type in a text input area, tags that partially match the text are displ
|
|||||||
- Tags that have already been entered are displayed grayed out.
|
- Tags that have already been entered are displayed grayed out.
|
||||||
- You can display Danbooru and e621 tags at the same time. You can also change the priority from the settings.
|
- You can display Danbooru and e621 tags at the same time. You can also change the priority from the settings.
|
||||||
- Supports autocomplete for Lora and Embedding inputs. You can enable/disable this feature in the settings.
|
- Supports autocomplete for Lora and Embedding inputs. You can enable/disable this feature in the settings.
|
||||||
|
- Clicking the 📖 icon opens the tag's Wiki page. If a tag is selected via keyboard, you can open it with the `F1` key.
|
||||||
|
|
||||||
## Related Tags
|
## Related Tags
|
||||||
|
|
||||||
@@ -53,6 +54,7 @@ When you select any tag in a text input area, a list of highly related tags is d
|
|||||||
- The display position is primarily at the bottom of the text area and automatically adjusts vertically based on available space.
|
- The display position is primarily at the bottom of the text area and automatically adjusts vertically based on available space.
|
||||||
- You can switch between vertical and horizontal display positions using the "↕️|↔️" button in the header.
|
- You can switch between vertical and horizontal display positions using the "↕️|↔️" button in the header.
|
||||||
- You can toggle the pinned state of the displayed related tags using the "📌|🎯" button in the header. To close the UI when pinned, press the Esc key.
|
- You can toggle the pinned state of the displayed related tags using the "📌|🎯" button in the header. To close the UI when pinned, press the Esc key.
|
||||||
|
- Clicking the tag in the header opens the tag's Wiki page.
|
||||||
- Tags that have already been entered are displayed grayed out. If you try to insert a grayed-out tag, the already entered tag will instead be selected.
|
- Tags that have already been entered are displayed grayed out. If you try to insert a grayed-out tag, the already entered tag will instead be selected.
|
||||||
- You can display related tags for the cursor position by pressing `Ctrl+Shift+Space`.
|
- You can display related tags for the cursor position by pressing `Ctrl+Shift+Space`.
|
||||||
|
|
||||||
@@ -187,6 +189,8 @@ For example, by preparing the following CSV, you can quickly insert correspondin
|
|||||||
- **Auto Format Trigger**: Choose when formatting is applied.
|
- **Auto Format Trigger**: Choose when formatting is applied.
|
||||||
- **Auto**: Format automatically when leaving text field
|
- **Auto**: Format automatically when leaving text field
|
||||||
- **Manual**: Format only via keyboard shortcut (default: `Alt+Shift+F`)
|
- **Manual**: Format only via keyboard shortcut (default: `Alt+Shift+F`)
|
||||||
|
- **Use Trailing Comma**: When enabled, ensures all lines end with a trailing comma when formatting. If disabled, removes trailing commas.
|
||||||
|
- **Trim Surrounding Spaces**: When enabled, trim any blank lines or spaces from the beginning and end of the prompt.
|
||||||
|
|
||||||
## Advanced Settings
|
## Advanced Settings
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,6 +1,6 @@
|
|||||||
# ComfyUI-Autocomplete-Plus
|
# ComfyUI-Autocomplete-Plus
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
## 概要
|
## 概要
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
- 入力済みのタグはグレーアウトで表示されます
|
- 入力済みのタグはグレーアウトで表示されます
|
||||||
- Danbooruとe621のタグを同時に表示出来ます。設定から優先順位を変更できます
|
- Danbooruとe621のタグを同時に表示出来ます。設定から優先順位を変更できます
|
||||||
- LoraとEmbeddingの入力補完に対応しています。設定から有効・無効を切り替えられます
|
- LoraとEmbeddingの入力補完に対応しています。設定から有効・無効を切り替えられます
|
||||||
|
- 「📖」アイコンをクリックするとタグのWikiページを開きます。キーボードで選択中の場合は `F1` キーで開くことが出来ます
|
||||||
|
|
||||||
## 関連タグ
|
## 関連タグ
|
||||||
|
|
||||||
@@ -51,6 +52,7 @@
|
|||||||
- 表示位置は、テキストエリアの下部を基本とし、空きスペースに応じて上下に自動調整されます
|
- 表示位置は、テキストエリアの下部を基本とし、空きスペースに応じて上下に自動調整されます
|
||||||
- ヘッダーの「↕️|↔️」ボタンで上下と左右の表示位置に切り替えられます
|
- ヘッダーの「↕️|↔️」ボタンで上下と左右の表示位置に切り替えられます
|
||||||
- ヘッダーの「📌|🎯」ボタンで表示する関連タグの固定状態を切り替えられます。固定状態で閉じたい場合はEscキーを押します
|
- ヘッダーの「📌|🎯」ボタンで表示する関連タグの固定状態を切り替えられます。固定状態で閉じたい場合はEscキーを押します
|
||||||
|
- ヘッダーのタグをクリックするとタグのWikiページを開きます
|
||||||
- 入力済みのタグはグレーアウトで表示されます。グレーアウトしたタグを挿入しようとした場合、代わりに入力済みのタグを選択状態にします
|
- 入力済みのタグはグレーアウトで表示されます。グレーアウトしたタグを挿入しようとした場合、代わりに入力済みのタグを選択状態にします
|
||||||
- `Ctrl+Shift+Space` キーでカーソル位置の関連タグを表示できます
|
- `Ctrl+Shift+Space` キーでカーソル位置の関連タグを表示できます
|
||||||
|
|
||||||
@@ -185,6 +187,8 @@ worst_quality,5,9999999,
|
|||||||
- **Auto Format Trigger**: フォーマットを適用するタイミングを選択します
|
- **Auto Format Trigger**: フォーマットを適用するタイミングを選択します
|
||||||
- **自動**: テキスト欄からフォーカスが外れた際に自動でフォーマットします
|
- **自動**: テキスト欄からフォーカスが外れた際に自動でフォーマットします
|
||||||
- **手動**: キーボードショートカットでのみフォーマットします(デフォルト: `Alt+Shift+F`)
|
- **手動**: キーボードショートカットでのみフォーマットします(デフォルト: `Alt+Shift+F`)
|
||||||
|
- **行末にカンマを使用**: 有効時、フォーマット時にすべての行末がカンマで終わるようになります。\n無効にすると行末のカンマが削除されます
|
||||||
|
- **先頭・末尾の空白を削除**: 有効時、プロンプトの先頭と末尾にある空行やスペースをすべて削除します
|
||||||
|
|
||||||
## 上級者向け設定
|
## 上級者向け設定
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,15 @@
|
|||||||
"manual": "Manual"
|
"manual": "Manual"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_UseTrailingComma": {
|
||||||
|
"name": "Use Trailing Comma",
|
||||||
|
"tooltip": "When enabled, ensures all lines end with a trailing comma.\nWhen disabled, removes trailing commas."
|
||||||
|
},
|
||||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||||
"name": "Enable Auto Format"
|
"name": "Enable Auto Format"
|
||||||
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||||
|
"name": "Trim Surrounding Spaces",
|
||||||
|
"tooltip": "When enabled, trim any blank lines from the beginning and end of the prompt."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,15 @@
|
|||||||
"manual": "手動"
|
"manual": "手動"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_UseTrailingComma": {
|
||||||
|
"name": "行末にカンマを使用",
|
||||||
|
"tooltip": "有効にすると、すべての行末がカンマで終わるようになります。\n無効にすると行末のカンマが削除されます。"
|
||||||
|
},
|
||||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||||
"name": "自動フォーマット機能の有効化"
|
"name": "自動フォーマット機能の有効化"
|
||||||
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||||
|
"name": "先頭・末尾の空白を削除",
|
||||||
|
"tooltip": "有効時、プロンプトの先頭と末尾にある空行やスペースをすべて削除します。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,15 @@
|
|||||||
"manual": "手動"
|
"manual": "手動"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_UseTrailingComma": {
|
||||||
|
"name": "在行末添加逗號",
|
||||||
|
"tooltip": "啟用時,確保所有行都以逗號結尾。\n停用時,移除結尾逗號。"
|
||||||
|
},
|
||||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||||
"name": "啟用自動格式化"
|
"name": "啟用自動格式化"
|
||||||
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||||
|
"name": "去除首尾空白",
|
||||||
|
"tooltip": "啟用時,清除提示詞開頭和結尾的所有空行和空格。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -79,7 +79,15 @@
|
|||||||
"manual": "手动"
|
"manual": "手动"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_UseTrailingComma": {
|
||||||
|
"name": "在行末添加逗号",
|
||||||
|
"tooltip": "启用时,确保所有行都以逗号结尾。\n禁用时,移除结尾逗号。"
|
||||||
|
},
|
||||||
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
"AutocompletePlus_AutoFormatter_EnableAutoFormat": {
|
||||||
"name": "启用自动格式化"
|
"name": "启用自动格式化"
|
||||||
|
},
|
||||||
|
"AutocompletePlus_AutoFormatter_TrimSurroundingSpaces": {
|
||||||
|
"name": "去除首尾空白",
|
||||||
|
"tooltip": "启用时,清除提示词开头和结尾的所有空行和空格。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Generated
+16
-15
@@ -4,6 +4,7 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
|
"name": "ComfyUI-Autocomplete-Plus",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.27.1",
|
"@babel/core": "^7.27.1",
|
||||||
"@babel/preset-env": "^7.27.2",
|
"@babel/preset-env": "^7.27.2",
|
||||||
@@ -2005,9 +2006,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
"node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.2",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -2266,9 +2267,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@jest/reporters/node_modules/glob": {
|
"node_modules/@jest/reporters/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4790,9 +4791,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-config/node_modules/glob": {
|
"node_modules/jest-config/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5130,9 +5131,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jest-runtime/node_modules/glob": {
|
"node_modules/jest-runtime/node_modules/glob": {
|
||||||
"version": "10.4.5",
|
"version": "10.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -5335,9 +5336,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
+1
-1
@@ -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.8.0"
|
version = "1.10.0"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
dependencies = ["",]
|
dependencies = ["",]
|
||||||
|
|
||||||
|
|||||||
+204
-23
@@ -2,6 +2,7 @@ import {
|
|||||||
formatPromptText,
|
formatPromptText,
|
||||||
__test__
|
__test__
|
||||||
} from "../../web/js/auto-formatter.js";
|
} from "../../web/js/auto-formatter.js";
|
||||||
|
import { settingValues } from "../../web/js/settings.js";
|
||||||
|
|
||||||
const {
|
const {
|
||||||
shouldAutoFormat
|
shouldAutoFormat
|
||||||
@@ -15,6 +16,14 @@ describe('AutoFormatter Functions', () => {
|
|||||||
inputName
|
inputName
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Store original setting value to restore after tests
|
||||||
|
const originalTrimSurroundingSpaces = settingValues.trimSurroundingSpaces;
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original setting after each test
|
||||||
|
settingValues.trimSurroundingSpaces = originalTrimSurroundingSpaces;
|
||||||
|
});
|
||||||
|
|
||||||
test('should return false for empty text', () => {
|
test('should return false for empty text', () => {
|
||||||
expect(shouldAutoFormat('', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
expect(shouldAutoFormat('', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
expect(shouldAutoFormat(' ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
expect(shouldAutoFormat(' ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
@@ -41,40 +50,212 @@ describe('AutoFormatter Functions', () => {
|
|||||||
expect(shouldAutoFormat('hello world', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
expect(shouldAutoFormat('hello world', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
expect(shouldAutoFormat('tag1 tag2', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
expect(shouldAutoFormat('tag1 tag2', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('blocklist priority with trimSurroundingSpaces', () => {
|
||||||
|
test('should prioritize blocklist over trimSurroundingSpaces when disabled', () => {
|
||||||
|
settingValues.trimSurroundingSpaces = false;
|
||||||
|
|
||||||
|
// Blocklisted nodes should return false even with surrounding spaces
|
||||||
|
expect(shouldAutoFormat(' some code ', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat('\nsome code\n', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat(' 0,0,0,1,1,1 ', mockNodeInfo('LoraLoaderBlockWeight //Inspire', 'block_vector'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should prioritize blocklist over trimSurroundingSpaces when enabled', () => {
|
||||||
|
settingValues.trimSurroundingSpaces = true;
|
||||||
|
|
||||||
|
// Blocklisted nodes should return false even with trimSurroundingSpaces enabled and surrounding spaces
|
||||||
|
expect(shouldAutoFormat(' some code ', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat('\nsome code\n', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat(' function() { } ', mockNodeInfo('Power Puter (rgthree)', 'code'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat(' 0,0,0,1,1,1 ', mockNodeInfo('LoraLoaderBlockWeight //Inspire', 'block_vector'))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('trimSurroundingSpaces with non-comma-separated text', () => {
|
||||||
|
test('should return false for non-comma-separated text when trimSurroundingSpaces is disabled', () => {
|
||||||
|
settingValues.trimSurroundingSpaces = false;
|
||||||
|
|
||||||
|
// Text without word+comma pattern should return false
|
||||||
|
expect(shouldAutoFormat(' hello world ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat('\nhello world\n', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat(' tag1 tag2 ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return true for non-comma-separated text with surrounding spaces when trimSurroundingSpaces is enabled', () => {
|
||||||
|
settingValues.trimSurroundingSpaces = true;
|
||||||
|
|
||||||
|
// Text with surrounding spaces should return true to format (trim them)
|
||||||
|
expect(shouldAutoFormat(' hello world ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||||
|
expect(shouldAutoFormat('\nhello world\n', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||||
|
expect(shouldAutoFormat(' tag1 tag2 ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||||
|
expect(shouldAutoFormat(' \nsome text\n ', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return false for text without surrounding spaces even when trimSurroundingSpaces is enabled', () => {
|
||||||
|
settingValues.trimSurroundingSpaces = true;
|
||||||
|
|
||||||
|
// Text without word+comma pattern and without surrounding spaces should return false
|
||||||
|
expect(shouldAutoFormat('hello world', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
|
expect(shouldAutoFormat('tag1 tag2', mockNodeInfo('CLIPTextEncode', 'text'))).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('formatPromptText', () => {
|
describe('formatPromptText', () => {
|
||||||
test('should format text by adding comma and space after tags', () => {
|
// Store original setting value to restore after tests
|
||||||
const input = 'tag1,tag2,tag3';
|
const originalUseTrailingComma = settingValues.useTrailingComma;
|
||||||
const expected = 'tag1, tag2, tag3, ';
|
const originalTrimSurroundingSpaces = settingValues.trimSurroundingSpaces;
|
||||||
expect(formatPromptText(input)).toBe(expected);
|
|
||||||
|
afterEach(() => {
|
||||||
|
// Restore original setting after each test
|
||||||
|
settingValues.useTrailingComma = originalUseTrailingComma;
|
||||||
|
settingValues.trimSurroundingSpaces = originalTrimSurroundingSpaces;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should remove extra spaces around tags', () => {
|
describe('with useTrailingComma enabled', () => {
|
||||||
const input = ' tag1 , tag2 ';
|
beforeEach(() => {
|
||||||
const expected = 'tag1, tag2, ';
|
settingValues.useTrailingComma = true;
|
||||||
expect(formatPromptText(input)).toBe(expected);
|
settingValues.trimSurroundingSpaces = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should format text by adding comma and space after tags', () => {
|
||||||
|
const input = 'tag1,tag2,tag3';
|
||||||
|
const expected = 'tag1, tag2, tag3, ';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove extra spaces around tags', () => {
|
||||||
|
const input = ' tag1 , tag2 ';
|
||||||
|
const expected = 'tag1, tag2, ';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should preserve special syntax like weights', () => {
|
||||||
|
const input = '(tag1:1.2), [tag2]';
|
||||||
|
// Note: The current implementation splits by comma.
|
||||||
|
// If the input is "(tag1:1.2), [tag2]", it splits into "(tag1:1.2)" and "[tag2]".
|
||||||
|
// Then joins with ", ".
|
||||||
|
const expected = '(tag1:1.2), [tag2], ';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle multiple lines', () => {
|
||||||
|
const input = 'tag1, tag2\ntag3, tag4';
|
||||||
|
const expected = 'tag1, tag2, \ntag3, tag4, ';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should keep empty lines unchanged', () => {
|
||||||
|
const input = 'tag1, tag2\n\ntag3, tag4';
|
||||||
|
const expected = 'tag1, tag2, \n\ntag3, tag4, ';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should not modify text that already has trailing comma', () => {
|
||||||
|
const input = 'tag1, tag2, ';
|
||||||
|
const expected = 'tag1, tag2, ';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should preserve special syntax like weights', () => {
|
describe('with useTrailingComma disabled', () => {
|
||||||
const input = '(tag1:1.2), [tag2]';
|
beforeEach(() => {
|
||||||
// Note: The current implementation splits by comma.
|
settingValues.useTrailingComma = false;
|
||||||
// If the input is "(tag1:1.2), [tag2]", it splits into "(tag1:1.2)" and "[tag2]".
|
settingValues.trimSurroundingSpaces = false;
|
||||||
// Then joins with ", ".
|
});
|
||||||
const expected = '(tag1:1.2), [tag2], ';
|
|
||||||
expect(formatPromptText(input)).toBe(expected);
|
test('should format text by adding comma and space after tags without trailing comma', () => {
|
||||||
|
const input = 'tag1,tag2,tag3';
|
||||||
|
const expected = 'tag1, tag2, tag3';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove extra spaces around tags without trailing comma', () => {
|
||||||
|
const input = ' tag1 , tag2 ';
|
||||||
|
const expected = 'tag1, tag2';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should preserve special syntax like weights without trailing comma', () => {
|
||||||
|
const input = '(tag1:1.2), [tag2]';
|
||||||
|
// Note: The current implementation splits by comma.
|
||||||
|
// If the input is "(tag1:1.2), [tag2]", it splits into "(tag1:1.2)" and "[tag2]".
|
||||||
|
// Then joins with ", ".
|
||||||
|
const expected = '(tag1:1.2), [tag2]';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle multiple lines without trailing comma', () => {
|
||||||
|
const input = 'tag1, tag2\ntag3, tag4';
|
||||||
|
const expected = 'tag1, tag2\ntag3, tag4';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should keep empty lines unchanged without trailing comma', () => {
|
||||||
|
const input = 'tag1, tag2\n\ntag3, tag4';
|
||||||
|
const expected = 'tag1, tag2\n\ntag3, tag4';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove existing trailing comma when disabled', () => {
|
||||||
|
const input = 'tag1, tag2, ';
|
||||||
|
const expected = 'tag1, tag2';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle multiple lines', () => {
|
describe('with trimSurroundingSpaces enabled', () => {
|
||||||
const input = 'tag1, tag2\ntag3, tag4';
|
beforeEach(() => {
|
||||||
const expected = 'tag1, tag2, \ntag3, tag4, ';
|
settingValues.trimSurroundingSpaces = true;
|
||||||
expect(formatPromptText(input)).toBe(expected);
|
settingValues.useTrailingComma = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove trailing newlines and spaces', () => {
|
||||||
|
const input = 'tag1, tag2\n\n ';
|
||||||
|
const expected = 'tag1, tag2';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove leading newlines and spaces', () => {
|
||||||
|
const input = ' \n\ntag1, tag2';
|
||||||
|
const expected = 'tag1, tag2';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should remove both leading and trailing whitespaces but keep middle empty lines', () => {
|
||||||
|
const input = ' \n\ntag1\n\ntag2\n\n ';
|
||||||
|
const expected = 'tag1\n\ntag2';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return empty string if input is only whitespace', () => {
|
||||||
|
const input = ' \n ';
|
||||||
|
expect(formatPromptText(input)).toBe('');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should keep empty lines unchanged', () => {
|
describe('with trimSurroundingSpaces disabled', () => {
|
||||||
const input = 'tag1, tag2\n\ntag3, tag4';
|
beforeEach(() => {
|
||||||
const expected = 'tag1, tag2, \n\ntag3, tag4, ';
|
settingValues.trimSurroundingSpaces = false;
|
||||||
expect(formatPromptText(input)).toBe(expected);
|
settingValues.useTrailingComma = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should preserve trailing newlines', () => {
|
||||||
|
const input = 'tag1, tag2\n\n';
|
||||||
|
const expected = 'tag1, tag2\n\n';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should preserve leading spaces (as first line content)', () => {
|
||||||
|
const input = ' \ntag1';
|
||||||
|
const expected = '\ntag1';
|
||||||
|
expect(formatPromptText(input)).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should handle input with only spaces', () => {
|
||||||
|
expect(formatPromptText(' ')).toBe(' ');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should handle empty input', () => {
|
test('should handle empty input', () => {
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
/* Light Theme */
|
/* Light Theme */
|
||||||
body {
|
body {
|
||||||
--autocomplete-plus-text-color-cat-blue: var(--p-blue-600);
|
--autocomplete-plus-text-color-cat-blue: var(--p-blue-700);
|
||||||
--autocomplete-plus-text-color-cat-red: var(--p-red-600);
|
--autocomplete-plus-text-color-cat-red: var(--p-red-600);
|
||||||
--autocomplete-plus-text-color-cat-purple: var(--p-purple-600);
|
--autocomplete-plus-text-color-cat-purple: var(--p-purple-600);
|
||||||
--autocomplete-plus-text-color-cat-green: var(--p-green-600);
|
--autocomplete-plus-text-color-cat-green: var(--p-green-700);
|
||||||
--autocomplete-plus-text-color-cat-yellow: var(--p-yellow-600);
|
--autocomplete-plus-text-color-cat-yellow: var(--p-yellow-600);
|
||||||
--autocomplete-plus-text-color-cat-gray: var(--p-gray-600);
|
--autocomplete-plus-text-color-cat-gray: var(--p-gray-600);
|
||||||
--autocomplete-plus-text-color-cat-sky: var(--p-sky-500);
|
--autocomplete-plus-text-color-cat-sky: var(--p-sky-600);
|
||||||
--autocomplete-plus-text-color-cat-orange: var(--p-orange-600);
|
--autocomplete-plus-text-color-cat-orange: var(--p-orange-600);
|
||||||
--autocomplete-plus-text-color-cat-white: var(--p-neutral-700);
|
--autocomplete-plus-text-color-cat-white: var(--p-neutral-700);
|
||||||
--autocomplete-plus-text-color-disabled: var(--p-gray-400);
|
--autocomplete-plus-text-color-disabled: var(--p-gray-400);
|
||||||
|
--autocomplete-plus-list-color-even: var(--p-neutral-300);
|
||||||
|
--autocomplete-plus-list-color-odd: var(--p-neutral-400);
|
||||||
|
--wiki-icon-text-color: var(--input-text);
|
||||||
|
--wiki-icon-opacity-normal: 0.7;
|
||||||
|
--wiki-icon-opacity-hover: 1.0;
|
||||||
|
--wiki-icon-opacity-disabled: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dark Theme */
|
/* Dark Theme */
|
||||||
@@ -24,6 +30,12 @@ body.dark-theme {
|
|||||||
--autocomplete-plus-text-color-cat-orange: var(--p-orange-400);
|
--autocomplete-plus-text-color-cat-orange: var(--p-orange-400);
|
||||||
--autocomplete-plus-text-color-cat-white: var(--p-neutral-200);
|
--autocomplete-plus-text-color-cat-white: var(--p-neutral-200);
|
||||||
--autocomplete-plus-text-color-disabled: var(--p-gray-500);
|
--autocomplete-plus-text-color-disabled: var(--p-gray-500);
|
||||||
|
--autocomplete-plus-list-color-even: var(--p-neutral-800);
|
||||||
|
--autocomplete-plus-list-color-odd: var(--p-neutral-900);
|
||||||
|
--wiki-icon-text-color: var(--input-text);
|
||||||
|
--wiki-icon-opacity-normal: 0.5;
|
||||||
|
--wiki-icon-opacity-hover: 0.9;
|
||||||
|
--wiki-icon-opacity-disabled: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#autocomplete-plus-root {
|
#autocomplete-plus-root {
|
||||||
@@ -97,16 +109,17 @@ 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 max-content 1fr auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#autocomplete-plus-list.no-alias {
|
#autocomplete-plus-list.no-alias {
|
||||||
grid-template-columns: max-content auto;
|
grid-template-columns: max-content max-content auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-plus-item {
|
.autocomplete-plus-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
grid-template-columns: subgrid;
|
grid-template-columns: subgrid;
|
||||||
@@ -115,23 +128,22 @@ body.dark-theme {
|
|||||||
.autocomplete-plus-item span {
|
.autocomplete-plus-item span {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Alternating row colors */
|
/* Alternating row colors */
|
||||||
.autocomplete-plus-item:nth-child(even) span {
|
.autocomplete-plus-item:nth-child(even) {
|
||||||
background-color: var(--comfy-menu-bg);
|
background-color: var(--autocomplete-plus-list-color-even);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-plus-item:nth-child(odd) span {
|
.autocomplete-plus-item:nth-child(odd) {
|
||||||
background-color: var(--comfy-menu-secondary-bg);
|
background-color: var(--autocomplete-plus-list-color-odd);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-plus-item.selected span {
|
.autocomplete-plus-item.selected {
|
||||||
background-color: var(--border-color);
|
background-color: var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autocomplete-plus-item:hover span {
|
.autocomplete-plus-item:hover {
|
||||||
background-color: var(--comfy-hover-bg);
|
background-color: var(--comfy-hover-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +172,28 @@ body.dark-theme {
|
|||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.autocomplete-plus-item .autocomplete-plus-wiki-icon {
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--wiki-icon-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2em;
|
||||||
|
opacity: var(--wiki-icon-opacity-normal);
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.autocomplete-plus-item .autocomplete-plus-wiki-icon:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
opacity: var(--wiki-icon-opacity-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide wiki icon for tags without wiki pages */
|
||||||
|
.autocomplete-plus-item .autocomplete-plus-wiki-icon.disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: var(--wiki-icon-opacity-disabled);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Related Tags UI Styles */
|
/* Related Tags UI Styles */
|
||||||
#related-tags-root {
|
#related-tags-root {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -236,7 +270,7 @@ body.dark-theme {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
border-bottom: 1px solid var(--border-color);
|
border-bottom: 1px solid var(--border-color);
|
||||||
background-color: var(--comfy-menu-bg);
|
background-color: var(--autocomplete-plus-list-color-even);
|
||||||
color: var(--descrip-text);
|
color: var(--descrip-text);
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
@@ -255,10 +289,16 @@ body.dark-theme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.related-tags-header-tag-name {
|
.related-tags-header-tag-name {
|
||||||
|
cursor: pointer;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.related-tags-header-tag-name.disabled {
|
||||||
|
cursor: default;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
.related-tags-header-tag-name .autocomplete-plus-tag-icon-svg {
|
.related-tags-header-tag-name .autocomplete-plus-tag-icon-svg {
|
||||||
width: 1em;
|
width: 1em;
|
||||||
height: 1em;
|
height: 1em;
|
||||||
@@ -296,16 +336,17 @@ 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 max-content 1fr auto;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#related-tags-list.no-alias {
|
#related-tags-list.no-alias {
|
||||||
grid-template-columns: max-content auto;
|
grid-template-columns: max-content max-content auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-tag-item {
|
.related-tag-item {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
grid-template-columns: subgrid;
|
grid-template-columns: subgrid;
|
||||||
@@ -315,22 +356,21 @@ body.dark-theme {
|
|||||||
.related-tag-item span {
|
.related-tag-item span {
|
||||||
align-content: center;
|
align-content: center;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-tag-item:nth-child(even) span {
|
.related-tag-item:nth-child(even) {
|
||||||
background-color: var(--comfy-menu-bg);
|
background-color: var(--autocomplete-plus-list-color-even);
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-tag-item:nth-child(odd) span {
|
.related-tag-item:nth-child(odd) {
|
||||||
background-color: var(--comfy-menu-secondary-bg);
|
background-color: var(--autocomplete-plus-list-color-odd);
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-tag-item:hover span {
|
.related-tag-item:hover {
|
||||||
background-color: var(--p-form-field-filled-hover-background);
|
background-color: var(--p-form-field-filled-hover-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
.related-tag-item.selected span {
|
.related-tag-item.selected {
|
||||||
background-color: var(--border-color);
|
background-color: var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,6 +395,29 @@ body.dark-theme {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.related-tag-item .related-tag-wiki-icon {
|
||||||
|
padding: 0 8px;
|
||||||
|
color: var(--wiki-icon-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1.2em;
|
||||||
|
opacity: var(--wiki-icon-opacity-normal);
|
||||||
|
text-align: center;
|
||||||
|
transition: opacity 0.1s ease;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.related-tag-item .related-tag-wiki-icon:hover {
|
||||||
|
filter: brightness(1.1);
|
||||||
|
opacity: var(--wiki-icon-opacity-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide wiki icon for tags without wiki pages */
|
||||||
|
.related-tag-item .related-tag-wiki-icon.disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: var(--wiki-icon-opacity-disabled);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
.related-tags-message {
|
.related-tags-message {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|||||||
+42
-16
@@ -7,15 +7,17 @@ import { NodeInfo } from './node-info.js';
|
|||||||
*
|
*
|
||||||
* Format conditions:
|
* Format conditions:
|
||||||
* 1. Skip formatting if node is in blocklist
|
* 1. Skip formatting if node is in blocklist
|
||||||
* 2. Skip formatting if text contains only numbers or single letters (separated by commas)
|
* 2. If text is empty after trimming, format only if trimSurroundingSpaces is enabled, otherwise don't format
|
||||||
* 3. Format if text contains "word + comma" pattern at least twice
|
* 3. Skip formatting if text contains only numbers or single letters (separated by commas)
|
||||||
* 4. Otherwise, don't format
|
* 4. Format if text contains "word + comma" pattern
|
||||||
|
* 5. Format if trimSurroundingSpaces is enabled and there are surrounding spaces or line breaks
|
||||||
|
* 6. Otherwise, don't format
|
||||||
*
|
*
|
||||||
* @param {NodeInfo} nodeInfo - The node information.
|
* @param {NodeInfo} nodeInfo - The node information.
|
||||||
* @returns {boolean} - True if the text should be formatted, false otherwise.
|
* @returns {boolean} - True if the text should be formatted, false otherwise.
|
||||||
*/
|
*/
|
||||||
function shouldAutoFormat(text, nodeInfo) {
|
function shouldAutoFormat(text, nodeInfo) {
|
||||||
if (!text || text.trim().length === 0) return false;
|
if (!text) return false;
|
||||||
|
|
||||||
// 1. Check if the node name is in the blocklist
|
// 1. Check if the node name is in the blocklist
|
||||||
const blocklist = [
|
const blocklist = [
|
||||||
@@ -34,10 +36,14 @@ function shouldAutoFormat(text, nodeInfo) {
|
|||||||
// console.debug(`[Autocomplete-Plus] auto-formatter on blur => nodeType: ${nodeInfo.nodeType}, inputName: ${nodeInfo.inputName}`);
|
// console.debug(`[Autocomplete-Plus] auto-formatter on blur => nodeType: ${nodeInfo.nodeType}, inputName: ${nodeInfo.inputName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const trimmedText = text.trim();
|
const trimmedText = text.trim();
|
||||||
|
|
||||||
// 2. Check if the text is purely numeric data or single-letter placeholders with commas
|
// 2. Check if the text is completely empty after trimming
|
||||||
|
if (trimmedText.length === 0) {
|
||||||
|
return settingValues.trimSurroundingSpaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Check if the text is purely numeric data or single-letter placeholders with commas
|
||||||
// (e.g., "0,0,0,1,1,1" or "0.5, -1.2, 0.8" or "A,B,R" for LoRA Block Weight)
|
// (e.g., "0,0,0,1,1,1" or "0.5, -1.2, 0.8" or "A,B,R" for LoRA Block Weight)
|
||||||
const elements = trimmedText.split(',').map(el => el.trim());
|
const elements = trimmedText.split(',').map(el => el.trim());
|
||||||
const isSingleLetterOrNumeric = elements.every(el => {
|
const isSingleLetterOrNumeric = elements.every(el => {
|
||||||
@@ -50,15 +56,18 @@ function shouldAutoFormat(text, nodeInfo) {
|
|||||||
return false; // Don't format numeric data or single-letter template patterns
|
return false; // Don't format numeric data or single-letter template patterns
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Check if the text contains the pattern "word + comma"
|
// 4. If text contains "word + comma" pattern, format it
|
||||||
const wordCommaPattern = /\w+\s*,/g;
|
const wordCommaPattern = /\w+\s*,/g;
|
||||||
const matches = trimmedText.match(wordCommaPattern);
|
if (trimmedText.match(wordCommaPattern)) {
|
||||||
|
return true;
|
||||||
if (matches == null) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true; // Text should be formatted
|
// 5. If trimSurroundingSpaces is enabled and there are surrounding spaces, format to trim them
|
||||||
|
if (settingValues.trimSurroundingSpaces && text !== trimmedText) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false; // Otherwise, don't format
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -68,10 +77,27 @@ function shouldAutoFormat(text, nodeInfo) {
|
|||||||
* @returns {string} - The formatted text.
|
* @returns {string} - The formatted text.
|
||||||
*/
|
*/
|
||||||
export function formatPromptText(text) {
|
export function formatPromptText(text) {
|
||||||
if (!text || text.trim().length === 0) return text;
|
// Handle null or undefined input
|
||||||
|
if (text == null) return text;
|
||||||
|
|
||||||
|
// If the text is empty or contains only spaces...
|
||||||
|
if (text.trim().length === 0) {
|
||||||
|
if (settingValues.trimSurroundingSpaces) {
|
||||||
|
// ...And trimSurroundingSpaces is enabled, return an empty string
|
||||||
|
return '';
|
||||||
|
} else {
|
||||||
|
// ...Otherwise, return the original text
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let processedText = text;
|
||||||
|
if (settingValues.trimSurroundingSpaces) {
|
||||||
|
processedText = text.trim();
|
||||||
|
}
|
||||||
|
|
||||||
// Split text into individual lines for processing
|
// Split text into individual lines for processing
|
||||||
const lines = text.split('\n');
|
const lines = processedText.split('\n');
|
||||||
const formattedLines = [];
|
const formattedLines = [];
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
@@ -100,8 +126,8 @@ export function formatPromptText(text) {
|
|||||||
// Rejoin cleaned tags with ", "
|
// Rejoin cleaned tags with ", "
|
||||||
let formattedLine = cleanedTags.join(', ');
|
let formattedLine = cleanedTags.join(', ');
|
||||||
|
|
||||||
// Add a trailing comma and space if the line contains valid tags
|
// Add a trailing comma and space if the setting is enabled and line contains valid tags
|
||||||
if (formattedLine.length > 0) {
|
if (formattedLine.length > 0 && settingValues.useTrailingComma) {
|
||||||
formattedLine += ', ';
|
formattedLine += ', ';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+42
-12
@@ -1,6 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
ModelTagSource,
|
ModelTagSource,
|
||||||
TagCategory,
|
|
||||||
TagData,
|
TagData,
|
||||||
autoCompleteData,
|
autoCompleteData,
|
||||||
getEnabledTagSourceInPriorityOrder
|
getEnabledTagSourceInPriorityOrder
|
||||||
@@ -19,7 +18,8 @@ import {
|
|||||||
getViewportMargin,
|
getViewportMargin,
|
||||||
getScrollbarWidth,
|
getScrollbarWidth,
|
||||||
IconSvgHtmlString,
|
IconSvgHtmlString,
|
||||||
addWeightToLora
|
addWeightToLora,
|
||||||
|
openTagWikiUrl
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
import { settingValues } from './settings.js';
|
import { settingValues } from './settings.js';
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ function getCurrentPartialTag(inputElement) {
|
|||||||
if (!inputElement) {
|
if (!inputElement) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = inputElement.value;
|
const text = inputElement.value;
|
||||||
const cursorPos = inputElement.selectionStart;
|
const cursorPos = inputElement.selectionStart;
|
||||||
|
|
||||||
@@ -312,13 +312,13 @@ function insertTagToTextArea(inputElement, tagDataToInsert) {
|
|||||||
if (!inputElement || !tagDataToInsert) {
|
if (!inputElement || !tagDataToInsert) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = inputElement.value;
|
const text = inputElement.value;
|
||||||
const cursorPos = inputElement.selectionStart;
|
const cursorPos = inputElement.selectionStart;
|
||||||
|
|
||||||
const tagRange = getCurrentTagRange(text, cursorPos);
|
const tagRange = getCurrentTagRange(text, cursorPos);
|
||||||
let tagStart, tagEnd, currentTag;
|
let tagStart, tagEnd, currentTag;
|
||||||
|
|
||||||
if (!tagRange) {
|
if (!tagRange) {
|
||||||
// Fallback: insert at cursor position
|
// Fallback: insert at cursor position
|
||||||
tagStart = cursorPos;
|
tagStart = cursorPos;
|
||||||
@@ -327,7 +327,7 @@ function insertTagToTextArea(inputElement, tagDataToInsert) {
|
|||||||
} else {
|
} else {
|
||||||
({ start: tagStart, end: tagEnd, tag: currentTag } = tagRange);
|
({ start: tagStart, end: tagEnd, tag: currentTag } = tagRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceStart = Math.min(cursorPos, tagStart);
|
const replaceStart = Math.min(cursorPos, tagStart);
|
||||||
let replaceEnd = cursorPos;
|
let replaceEnd = cursorPos;
|
||||||
|
|
||||||
@@ -407,6 +407,16 @@ class AutocompleteUI {
|
|||||||
|
|
||||||
// Add event listener for clicks on items
|
// Add event listener for clicks on items
|
||||||
this.tagsList.addEventListener('mousedown', (e) => {
|
this.tagsList.addEventListener('mousedown', (e) => {
|
||||||
|
// Check if wiki icon was clicked first
|
||||||
|
const wikiIcon = e.target.closest('.autocomplete-plus-wiki-icon');
|
||||||
|
if (wikiIcon && !wikiIcon.classList.contains('disabled')) {
|
||||||
|
openTagWikiUrl(wikiIcon.dataset.tagSource, wikiIcon.dataset.tagName);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if row was clicked (existing behavior)
|
||||||
const row = e.target.closest('.autocomplete-plus-item');
|
const row = e.target.closest('.autocomplete-plus-item');
|
||||||
if (row && row.dataset.index !== undefined) {
|
if (row && row.dataset.index !== undefined) {
|
||||||
const tagData = this.candidates[parseInt(row.dataset.index, 10)];
|
const tagData = this.candidates[parseInt(row.dataset.index, 10)];
|
||||||
@@ -480,7 +490,7 @@ class AutocompleteUI {
|
|||||||
/** Selects the currently highlighted item
|
/** Selects the currently highlighted item
|
||||||
* @returns {TagData|null} The selected tag data.
|
* @returns {TagData|null} The selected tag data.
|
||||||
*/
|
*/
|
||||||
getSelectedTag() {
|
getSelectedTagData() {
|
||||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.candidates.length) {
|
if (this.selectedIndex >= 0 && this.selectedIndex < this.candidates.length) {
|
||||||
return this.candidates[this.selectedIndex];
|
return this.candidates[this.selectedIndex];
|
||||||
}
|
}
|
||||||
@@ -517,7 +527,7 @@ class AutocompleteUI {
|
|||||||
* @param {boolean} isExisting
|
* @param {boolean} isExisting
|
||||||
*/
|
*/
|
||||||
#createTagElement(tagData, tagDataIndex, isExisting) {
|
#createTagElement(tagData, tagDataIndex, isExisting) {
|
||||||
const categoryText = TagCategory[tagData.source][tagData.category] || "unknown";
|
const categoryText = tagData.categoryText;
|
||||||
const aliasText = tagData.alias.join(', ');
|
const aliasText = tagData.alias.join(', ');
|
||||||
|
|
||||||
const tagRow = document.createElement('div');
|
const tagRow = document.createElement('div');
|
||||||
@@ -543,6 +553,18 @@ class AutocompleteUI {
|
|||||||
tagName.classList.add('autocomplete-plus-already-exists');
|
tagName.classList.add('autocomplete-plus-already-exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wiki icon
|
||||||
|
const wikiIcon = document.createElement('span');
|
||||||
|
wikiIcon.className = 'autocomplete-plus-wiki-icon';
|
||||||
|
if (tagData.hasWikiPage) {
|
||||||
|
wikiIcon.dataset.tagName = tagData.tag;
|
||||||
|
wikiIcon.dataset.tagSource = tagData.source;
|
||||||
|
wikiIcon.textContent = '📖'
|
||||||
|
wikiIcon.title = 'Open wiki page';
|
||||||
|
} else {
|
||||||
|
wikiIcon.classList.add('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
// Alias
|
// Alias
|
||||||
const alias = document.createElement('span');
|
const alias = document.createElement('span');
|
||||||
alias.className = 'autocomplete-plus-alias';
|
alias.className = 'autocomplete-plus-alias';
|
||||||
@@ -558,7 +580,7 @@ class AutocompleteUI {
|
|||||||
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
|
// Create tooltip with more info
|
||||||
let tooltipText = `Count: ${tagData.count}\nCategory: ${categoryText}`;
|
let tooltipText = `Count: ${tagData.count}\nCategory: ${categoryText}`;
|
||||||
if (aliasText.length > 0) {
|
if (aliasText.length > 0) {
|
||||||
tooltipText += `\nAlias: ${aliasText}`;
|
tooltipText += `\nAlias: ${aliasText}`;
|
||||||
@@ -566,6 +588,7 @@ class AutocompleteUI {
|
|||||||
tagRow.title = tooltipText;
|
tagRow.title = tooltipText;
|
||||||
|
|
||||||
tagRow.appendChild(tagName);
|
tagRow.appendChild(tagName);
|
||||||
|
tagRow.appendChild(wikiIcon);
|
||||||
|
|
||||||
if (!settingValues.hideAlias) {
|
if (!settingValues.hideAlias) {
|
||||||
tagRow.appendChild(alias);
|
tagRow.appendChild(alias);
|
||||||
@@ -672,7 +695,7 @@ class AutocompleteUI {
|
|||||||
|
|
||||||
/** Highlights the item (row) at the given index */
|
/** Highlights the item (row) at the given index */
|
||||||
#highlightItem() {
|
#highlightItem() {
|
||||||
if (!this.getSelectedTag()) return; // No valid selection
|
if (!this.getSelectedTagData()) return; // No valid selection
|
||||||
|
|
||||||
const items = this.tagsList.children; // Get rows from tbody
|
const items = this.tagsList.children; // Get rows from tbody
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
@@ -973,12 +996,19 @@ export class AutocompleteEventHandler {
|
|||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
const modifierKeyPressed = event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
|
const modifierKeyPressed = event.shiftKey || event.ctrlKey || event.altKey || event.metaKey;
|
||||||
if (!modifierKeyPressed && this.autocompleteUI.getSelectedTag() !== null) {
|
if (!modifierKeyPressed && this.autocompleteUI.getSelectedTagData() !== null) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
insertTagToTextArea(event.target, this.autocompleteUI.getSelectedTag());
|
insertTagToTextArea(event.target, this.autocompleteUI.getSelectedTagData());
|
||||||
}
|
}
|
||||||
this.autocompleteUI.hide();
|
this.autocompleteUI.hide();
|
||||||
break;
|
break;
|
||||||
|
case 'F1':
|
||||||
|
event.preventDefault();
|
||||||
|
const tagData = this.autocompleteUI.getSelectedTagData();
|
||||||
|
if (tagData && tagData.hasWikiPage) {
|
||||||
|
openTagWikiUrl(tagData.source, tagData.tag);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.autocompleteUI.hide();
|
this.autocompleteUI.hide();
|
||||||
|
|||||||
@@ -72,6 +72,23 @@ export class TagData {
|
|||||||
|
|
||||||
this.source = source;
|
this.source = source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the category text
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
get categoryText() {
|
||||||
|
return TagCategory[this.source][this.category] || "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the tag has a wiki page
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
get hasWikiPage() {
|
||||||
|
return Object.values(TagSource).includes(this.source)
|
||||||
|
&& ['general', 'artist', 'copyright', 'character', 'species', 'lore'].includes(this.categoryText);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AutocompleteData {
|
class AutocompleteData {
|
||||||
|
|||||||
@@ -490,6 +490,28 @@ app.registerExtension({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// --- Auto format settings ---
|
// --- Auto format settings ---
|
||||||
|
{
|
||||||
|
id: id + '.AutoFormatter.TrimSurroundingSpaces',
|
||||||
|
name: 'Trim Surrounding Spaces',
|
||||||
|
tooltip: 'When enabled, trim any blank lines from the beginning and end of the prompt.',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
category: [name, 'AutoFormatter', 'Trim Surrounding Spaces'],
|
||||||
|
onChange: (newVal, oldVal) => {
|
||||||
|
settingValues.trimSurroundingSpaces = newVal;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: id + '.AutoFormatter.UseTrailingComma',
|
||||||
|
name: 'Use Trailing Comma',
|
||||||
|
tooltip: 'When enabled, ensures all lines end with a trailing comma.\nWhen disabled, removes trailing commas.',
|
||||||
|
type: 'boolean',
|
||||||
|
defaultValue: false,
|
||||||
|
category: [name, 'AutoFormatter', 'Use Trailing Comma'],
|
||||||
|
onChange: (newVal, oldVal) => {
|
||||||
|
settingValues.useTrailingComma = newVal;
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: id + '.AutoFormatter.Trigger',
|
id: id + '.AutoFormatter.Trigger',
|
||||||
name: 'Auto Format Trigger',
|
name: 'Auto Format Trigger',
|
||||||
|
|||||||
+88
-25
@@ -1,4 +1,4 @@
|
|||||||
import { TagCategory, TagData, TagSource, autoCompleteData } from './data.js';
|
import { TagCategory, TagData, TagSource, autoCompleteData, getEnabledTagSourceInPriorityOrder } from './data.js';
|
||||||
import { settingValues } from './settings.js';
|
import { settingValues } from './settings.js';
|
||||||
import {
|
import {
|
||||||
extractTagsFromTextArea,
|
extractTagsFromTextArea,
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
isValidTag,
|
isValidTag,
|
||||||
normalizeTagToInsert,
|
normalizeTagToInsert,
|
||||||
normalizeTagToSearch,
|
normalizeTagToSearch,
|
||||||
|
openTagWikiUrl
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
|
||||||
// --- RelatedTags Logic ---
|
// --- RelatedTags Logic ---
|
||||||
@@ -95,6 +96,8 @@ function searchRelatedTags(tag) {
|
|||||||
category: tagData.category,
|
category: tagData.category,
|
||||||
source: tagData.source,
|
source: tagData.source,
|
||||||
count: tagData.count,
|
count: tagData.count,
|
||||||
|
categoryText: tagData.categoryText,
|
||||||
|
hasWikiPage: tagData.hasWikiPage
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -278,11 +281,31 @@ class RelatedTagsUI {
|
|||||||
// Timer ID for auto-refresh
|
// Timer ID for auto-refresh
|
||||||
this.autoRefreshTimerId = null;
|
this.autoRefreshTimerId = null;
|
||||||
|
|
||||||
|
// Add click handler for wiki link in header tag name
|
||||||
|
this.headerText.addEventListener('mousedown', (e) => {
|
||||||
|
const tagNameEl = e.target.closest('.related-tags-header-tag-name');
|
||||||
|
if (tagNameEl && !tagNameEl.classList.contains('disabled')) {
|
||||||
|
openTagWikiUrl(tagNameEl.dataset.tagSource, tagNameEl.dataset.tagName);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Add click handler for tag selection
|
// Add click handler for tag selection
|
||||||
this.tagsContainer.addEventListener('mousedown', (e) => {
|
this.tagsContainer.addEventListener('mousedown', (e) => {
|
||||||
|
// Check if wiki icon was clicked first
|
||||||
|
const wikiIcon = e.target.closest('.related-tag-wiki-icon');
|
||||||
|
if (wikiIcon && !wikiIcon.classList.contains('disabled')) {
|
||||||
|
openTagWikiUrl(wikiIcon.dataset.tagSource, wikiIcon.dataset.tagName);
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const row = e.target.closest('.related-tag-item');
|
const row = e.target.closest('.related-tag-item');
|
||||||
if (row && row.dataset.tag) {
|
if (row && row.dataset.tagName) {
|
||||||
this.#insertTag(row.dataset.tag);
|
this.#insertTag(row.dataset.tagName);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
@@ -322,9 +345,6 @@ class RelatedTagsUI {
|
|||||||
this.target = textareaElement;
|
this.target = textareaElement;
|
||||||
|
|
||||||
this.relatedTags = searchRelatedTags(this.currentTag);
|
this.relatedTags = searchRelatedTags(this.currentTag);
|
||||||
if (this.selectedIndex == -1) {
|
|
||||||
this.selectedIndex = 0; // Reset selection to the first item
|
|
||||||
}
|
|
||||||
|
|
||||||
this.#updateHeader();
|
this.#updateHeader();
|
||||||
this.#updateContent();
|
this.#updateContent();
|
||||||
@@ -366,10 +386,18 @@ class RelatedTagsUI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Moves the selection up or down */
|
/** Moves the selection up or down
|
||||||
|
* @param {direction} 1 for down, -1 for up
|
||||||
|
*/
|
||||||
navigate(direction) {
|
navigate(direction) {
|
||||||
if (this.relatedTags.length === 0) return;
|
if (this.relatedTags.length === 0) return;
|
||||||
this.selectedIndex += direction;
|
|
||||||
|
if (this.selectedIndex == -1) {
|
||||||
|
// Initialize selection based on navigation direction
|
||||||
|
this.selectedIndex = direction == 1 ? 0 : this.relatedTags.length - 1;
|
||||||
|
} else {
|
||||||
|
this.selectedIndex += direction;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.selectedIndex < 0) {
|
if (this.selectedIndex < 0) {
|
||||||
this.selectedIndex = this.relatedTags.length - 1; // Wrap around to bottom
|
this.selectedIndex = this.relatedTags.length - 1; // Wrap around to bottom
|
||||||
@@ -379,10 +407,27 @@ class RelatedTagsUI {
|
|||||||
this.#highlightItem();
|
this.#highlightItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Selects the currently highlighted item */
|
/**
|
||||||
getSelectedTag() {
|
* Get TagData of the current tag
|
||||||
|
* @returns {TagData|null}
|
||||||
|
*/
|
||||||
|
getCurrentTagData() {
|
||||||
|
for (const source of getEnabledTagSourceInPriorityOrder()) {
|
||||||
|
if (source in autoCompleteData && autoCompleteData[source].tagMap.has(this.currentTag)) {
|
||||||
|
return autoCompleteData[source].tagMap.get(this.currentTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Selects the currently highlighted item
|
||||||
|
* @return {TagData|null}
|
||||||
|
*/
|
||||||
|
getSelectedTagData() {
|
||||||
if (this.selectedIndex >= 0 && this.selectedIndex < this.relatedTags.length) {
|
if (this.selectedIndex >= 0 && this.selectedIndex < this.relatedTags.length) {
|
||||||
return this.relatedTags[this.selectedIndex].tag;
|
return this.relatedTags[this.selectedIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
return null; // No valid selection
|
return null; // No valid selection
|
||||||
@@ -401,14 +446,7 @@ class RelatedTagsUI {
|
|||||||
* Updates header content
|
* Updates header content
|
||||||
*/
|
*/
|
||||||
#updateHeader() {
|
#updateHeader() {
|
||||||
// Find the tag data for the current tag
|
let tagData = this.getCurrentTagData();
|
||||||
let tagData = Object.values(TagSource)
|
|
||||||
.map((source) => {
|
|
||||||
if (source in autoCompleteData && autoCompleteData[source].tagMap.has(this.currentTag)) {
|
|
||||||
return autoCompleteData[source].tagMap.get(this.currentTag);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.find((tagData) => tagData !== undefined);
|
|
||||||
|
|
||||||
if (!tagData) {
|
if (!tagData) {
|
||||||
// Create a dummy TagData if not found
|
// Create a dummy TagData if not found
|
||||||
@@ -426,6 +464,8 @@ class RelatedTagsUI {
|
|||||||
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}\nAlias: ${aliasText}`;
|
||||||
tagName.dataset.tagCategory = categoryText;
|
tagName.dataset.tagCategory = categoryText;
|
||||||
|
tagName.dataset.tagSource = tagData.source;
|
||||||
|
tagName.dataset.tagName = tagData.tag;
|
||||||
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'
|
||||||
@@ -435,6 +475,9 @@ class RelatedTagsUI {
|
|||||||
tagName.textContent += tagData.tag;
|
tagName.textContent += tagData.tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!tagData.hasWikiPage) {
|
||||||
|
tagName.classList.add('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
this.headerText.appendChild(tagName);
|
this.headerText.appendChild(tagName);
|
||||||
|
|
||||||
@@ -501,12 +544,12 @@ class RelatedTagsUI {
|
|||||||
* @returns {HTMLTableRowElement} The tag row element
|
* @returns {HTMLTableRowElement} The tag row element
|
||||||
*/
|
*/
|
||||||
#createTagElement(tagData, isExisting) {
|
#createTagElement(tagData, isExisting) {
|
||||||
const categoryText = TagCategory[tagData.source][tagData.category] || "unknown";
|
const categoryText = tagData.categoryText;
|
||||||
const aliasText = tagData.alias.join(', ');
|
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);
|
||||||
tagRow.dataset.tag = tagData.tag;
|
tagRow.dataset.tagName = tagData.tag;
|
||||||
tagRow.dataset.tagCategory = categoryText;
|
tagRow.dataset.tagCategory = categoryText;
|
||||||
|
|
||||||
// Tag name
|
// Tag name
|
||||||
@@ -519,6 +562,18 @@ class RelatedTagsUI {
|
|||||||
tagName.classList.add('related-tag-already-exists');
|
tagName.classList.add('related-tag-already-exists');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wiki icon
|
||||||
|
const wikiIcon = document.createElement('span');
|
||||||
|
wikiIcon.className = 'related-tag-wiki-icon';
|
||||||
|
if (tagData.hasWikiPage) {
|
||||||
|
wikiIcon.dataset.tagName = tagData.tag;
|
||||||
|
wikiIcon.dataset.tagSource = tagData.source;
|
||||||
|
wikiIcon.textContent = '📖'
|
||||||
|
wikiIcon.title = 'Open wiki page';
|
||||||
|
} else {
|
||||||
|
wikiIcon.classList.add('disabled');
|
||||||
|
}
|
||||||
|
|
||||||
// Alias
|
// Alias
|
||||||
const alias = document.createElement('span');
|
const alias = document.createElement('span');
|
||||||
alias.className = 'related-tag-alias';
|
alias.className = 'related-tag-alias';
|
||||||
@@ -543,6 +598,7 @@ class RelatedTagsUI {
|
|||||||
|
|
||||||
// Add cells to row
|
// Add cells to row
|
||||||
tagRow.appendChild(tagName);
|
tagRow.appendChild(tagName);
|
||||||
|
tagRow.appendChild(wikiIcon);
|
||||||
|
|
||||||
if (!settingValues.hideAlias) {
|
if (!settingValues.hideAlias) {
|
||||||
tagRow.appendChild(alias);
|
tagRow.appendChild(alias);
|
||||||
@@ -588,7 +644,7 @@ class RelatedTagsUI {
|
|||||||
|
|
||||||
/** Highlights the item (row) at the given index */
|
/** Highlights the item (row) at the given index */
|
||||||
#highlightItem() {
|
#highlightItem() {
|
||||||
if (this.getSelectedTag() === null) return; // No valid selection
|
if (this.getSelectedTagData() === null) return; // No valid selection
|
||||||
|
|
||||||
const items = this.tagsContainer.children; // Get rows
|
const items = this.tagsContainer.children; // Get rows
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
@@ -621,9 +677,9 @@ class RelatedTagsUI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertSelectedTag() {
|
insertSelectedTag() {
|
||||||
const selectedTag = this.getSelectedTag();
|
const selectedTag = this.getSelectedTagData();
|
||||||
if (selectedTag) {
|
if (selectedTag) {
|
||||||
this.#insertTag(selectedTag);
|
this.#insertTag(selectedTag.tag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,13 +825,20 @@ export class RelatedTagsEventHandler {
|
|||||||
break;
|
break;
|
||||||
case 'Enter':
|
case 'Enter':
|
||||||
case 'Tab':
|
case 'Tab':
|
||||||
if (this.relatedTagsUI.getSelectedTag() !== null) {
|
if (this.relatedTagsUI.getSelectedTagData() !== null) {
|
||||||
event.preventDefault(); // Prevent Tab from changing focus
|
event.preventDefault(); // Prevent Tab from changing focus
|
||||||
this.relatedTagsUI.insertSelectedTag();
|
this.relatedTagsUI.insertSelectedTag();
|
||||||
} else if (!this.relatedTagsUI.isPinned) { // If nothing selected and not pinned, hide the panel
|
} else if (!this.relatedTagsUI.isPinned) { // If nothing selected and not pinned, hide the panel
|
||||||
this.relatedTagsUI.hide();
|
this.relatedTagsUI.hide();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'F1':
|
||||||
|
event.preventDefault();
|
||||||
|
const tagData = this.relatedTagsUI.getSelectedTagData() || this.relatedTagsUI.getCurrentTagData();
|
||||||
|
if (tagData && tagData.hasWikiPage) {
|
||||||
|
openTagWikiUrl(tagData.source, tagData.tag);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case 'Escape':
|
case 'Escape':
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.relatedTagsUI.hide();
|
this.relatedTagsUI.hide();
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ export const settingValues = {
|
|||||||
// Auto format settings
|
// Auto format settings
|
||||||
enableAutoFormat: true,
|
enableAutoFormat: true,
|
||||||
autoFormatTrigger: 'auto', // Options: 'auto' (format on blur + shortcut), 'manual' (shortcut only)
|
autoFormatTrigger: 'auto', // Options: 'auto' (format on blur + shortcut), 'manual' (shortcut only)
|
||||||
|
useTrailingComma: false, // Whether to add comma at the end of each line
|
||||||
|
trimSurroundingSpaces: false, // Trim spaces around each tag
|
||||||
|
|
||||||
|
|
||||||
// Internal logic settings
|
// Internal logic settings
|
||||||
|
|||||||
@@ -654,3 +654,47 @@ export function getScrollbarWidth() {
|
|||||||
|
|
||||||
return _cachedScrollbarWidth;
|
return _cachedScrollbarWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Opens a wiki URL in a new browser tab for the given tag.
|
||||||
|
* @param {string} tagSource - The source of the tag ('danbooru', 'e621', 'embeddings', 'lora')
|
||||||
|
* @param {string} tagName - The name of the tag
|
||||||
|
* @returns {boolean} - True if URL was opened, false if not applicable
|
||||||
|
*/
|
||||||
|
export function openTagWikiUrl(tagSource, tagName) {
|
||||||
|
if (!tagSource || !tagName) {
|
||||||
|
console.warn('[Autocomplete-Plus] Cannot open wiki: missing tagSource or tagName');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let wikiUrl = null;
|
||||||
|
|
||||||
|
switch (tagSource) {
|
||||||
|
case 'danbooru':
|
||||||
|
const danbooruTag = encodeURIComponent(tagName.replace(/ /g, '_'));
|
||||||
|
wikiUrl = `https://danbooru.donmai.us/wiki_pages/${danbooruTag}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'e621':
|
||||||
|
const e621Tag = encodeURIComponent(tagName.replace(/ /g, '_'));
|
||||||
|
wikiUrl = `https://e621.net/wiki_pages/${e621Tag}`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'embeddings':
|
||||||
|
case 'lora':
|
||||||
|
// console.debug('[Autocomplete-Plus] Model tags do not have wiki pages');
|
||||||
|
return false;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.warn(`[Autocomplete-Plus] Unknown tag source: ${tagSource}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wikiUrl) {
|
||||||
|
// console.debug(`[Autocomplete-Plus] Opening wiki URL: ${wikiUrl}`);
|
||||||
|
window.open(wikiUrl, '_blank', 'noopener,noreferrer');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user