├── README.md ├── index.js ├── manifest.json ├── settings.html ├── style.css └── toolbar.html /README.md: -------------------------------------------------------------------------------- 1 | # SillyTavern 输入助手 2 | 3 | *这是一个简单的SillyTavern输入助手插件,用于提高文本输入效率。* 4 | 5 | ## 功能 6 | 7 | * **插入特殊符号**:一键在输入框插入双引号、星号等成对符号,光标自动定位在中间 8 | * **快速换行**:一键移动到当前行末尾并插入换行符,提高排版效率 9 | * **插入标记**:快速插入 `{{User}}` 和 `{{Char}}` 标记,用于引用用户和角色名称 10 | * **自定义工具栏**:可以自由选择需要显示的按钮,定制个性化工具栏 11 | * **快捷键支持**:为每个功能设置键盘快捷键,提高操作效率 12 | * **按钮排序**:通过拖拽调整按钮显示顺序,使用更灵活 13 | * **自定义符号**:添加自己需要的特殊符号和光标位置 14 | 15 | ## 安装和使用 16 | 17 | ### 安装 18 | 19 | 1. 打开SillyTavern 20 | 2. 进入设置页面 21 | 3. 点击"扩展"选项卡 22 | 4. 使用扩展安装器,输入 `https://github.com/Mooooooon/st-input-helper` 进行安装 23 | 24 | ### 使用 25 | 26 | 1. 安装完成后,在设置页面的"扩展"选项卡中找到"输入助手"面板 27 | 2. 勾选"启用输入助手"选项,启用工具栏 28 | 3. 可以选择需要显示的按钮,取消勾选不需要的按钮 29 | 4. 可以拖动按钮左侧的排序图标调整按钮在工具栏中的显示顺序 30 | 5. 可以为每个功能设置快捷键,点击对应的输入框并按下快捷键组合 31 | 6. 可以添加自定义符号,点击"添加自定义符号"按钮并填写相关信息 32 | 33 | ### 自定义符号设置 34 | 35 | 1. 点击"添加自定义符号"按钮 36 | 2. 在弹出的对话框中填写: 37 | - 名称:按钮的悬停提示文本 38 | - 符号:插入到文本中的实际符号 39 | - 显示文本:按钮上显示的文本(默认与符号相同) 40 | - 光标位置:插入符号后光标的位置(开始、中间、结尾或自定义位置) 41 | 3. 点击"保存"即可创建新的按钮 42 | 4. 可以编辑或删除已创建的自定义符号 43 | 5. 也可以为自定义符号设置快捷键,操作方式与内置按钮相同 44 | 45 | ### 功能列表 46 | 47 | - 插入双星号 (**): 用于强调文本,光标置于中间 48 | - 插入双引号 (""): 用于引用文本,光标置于中间 49 | - 插入圆括号 (()): 用于分组信息,光标置于中间 50 | - 插入直角引号「」: 用于引用文本,光标置于中间 51 | - 插入直角引号『』: 用于引用文本,光标置于中间 52 | - 插入书名号《》: 用于书名、作品名,光标置于中间 53 | - 插入换行 (⏎): 移动到当前行末尾并插入换行符 54 | - 插入用户标记 ({{U}}): 插入 `{{User}}` 变量 55 | - 插入角色标记 ({{C}}): 插入 `{{Char}}` 变量 56 | - 自定义符号: 用户自行添加的符号 57 | 58 | ## 快捷键设置 59 | 60 | 1. 在设置面板中找到"快捷键设置"部分 61 | 2. 点击要设置的功能对应的输入框 62 | 3. 按下所需的键盘组合(例如:Ctrl+Alt+Q) 63 | 4. 设置会自动保存 64 | 5. 按ESC或点击"清除"按钮可以移除快捷键 65 | 66 | 注意:快捷键仅在聊天输入框获得焦点时有效。 67 | 68 | ## 兼容性 69 | 70 | * 需要SillyTavern v1.9.0或更高版本 71 | 72 | ## 支持和贡献 73 | 74 | 如有问题或建议,请在GitHub仓库提交issue或联系作者。 75 | 76 | 欢迎提交Pull Request来改进这个插件。 77 | 78 | ## 许可证 79 | 80 | MIT License 81 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // The main script for the extension 2 | // The following are examples of some basic extension functionality 3 | 4 | //You'll likely need to import extension_settings, getContext, and loadExtensionSettings from extensions.js 5 | import { extension_settings, getContext, loadExtensionSettings } from "../../../extensions.js"; 6 | 7 | //You'll likely need to import some other functions from the main script 8 | import { saveSettingsDebounced } from "../../../../script.js"; 9 | 10 | // 设置插件名称和路径 11 | const extensionName = "st-input-helper"; 12 | const extensionFolderPath = `scripts/extensions/third-party/${extensionName}`; 13 | const defaultSettings = { 14 | enabled: true, 15 | buttons: { 16 | asterisk: true, 17 | quotes: true, 18 | parentheses: true, 19 | bookQuotes1: true, 20 | bookQuotes2: true, 21 | bookQuotes3: true, // 新增《》按钮设置 22 | newline: true, 23 | user: true, 24 | char: true 25 | }, 26 | shortcuts: { 27 | asterisk: "", 28 | quotes: "", 29 | parentheses: "", 30 | bookQuotes1: "", 31 | bookQuotes2: "", 32 | bookQuotes3: "", 33 | newline: "", 34 | user: "", 35 | char: "" 36 | }, 37 | // 添加默认的按钮顺序 38 | buttonOrder: [ 39 | 'asterisk', 40 | 'quotes', 41 | 'parentheses', 42 | 'bookQuotes1', 43 | 'bookQuotes2', 44 | 'bookQuotes3', 45 | 'newline', 46 | 'user', 47 | 'char' 48 | ], 49 | // 添加自定义符号设置 50 | customSymbols: [] 51 | }; 52 | 53 | // 快捷键映射表 54 | const shortcutFunctionMap = { 55 | 'asterisk': insertAsterisk, 56 | 'quotes': insertQuotes, 57 | 'parentheses': insertParentheses, 58 | 'bookQuotes1': insertBookQuotes1, 59 | 'bookQuotes2': insertBookQuotes2, 60 | 'bookQuotes3': insertBookQuotes3, 61 | 'newline': insertNewLine, 62 | 'user': insertUserTag, 63 | 'char': insertCharTag 64 | }; 65 | 66 | // 加载插件设置 67 | async function loadSettings() { 68 | extension_settings[extensionName] = extension_settings[extensionName] || {}; 69 | if (Object.keys(extension_settings[extensionName]).length === 0) { 70 | Object.assign(extension_settings[extensionName], defaultSettings); 71 | } 72 | 73 | // 兼容旧版本设置 74 | if (!extension_settings[extensionName].buttons) { 75 | extension_settings[extensionName].buttons = defaultSettings.buttons; 76 | } 77 | 78 | // 兼容旧版本设置 - 快捷键 79 | if (!extension_settings[extensionName].shortcuts) { 80 | extension_settings[extensionName].shortcuts = defaultSettings.shortcuts; 81 | } 82 | 83 | // 兼容旧版本设置 - 按钮顺序 84 | if (!extension_settings[extensionName].buttonOrder) { 85 | extension_settings[extensionName].buttonOrder = defaultSettings.buttonOrder; 86 | } 87 | 88 | // 兼容旧版本设置 - 自定义符号 89 | if (!extension_settings[extensionName].customSymbols) { 90 | extension_settings[extensionName].customSymbols = []; 91 | } 92 | 93 | // 更新UI中的设置 94 | $("#enable_input_helper").prop("checked", extension_settings[extensionName].enabled); 95 | 96 | // 更新按钮显示设置 97 | const buttons = extension_settings[extensionName].buttons; 98 | $("#enable_asterisk_btn").prop("checked", buttons.asterisk !== false); 99 | $("#enable_quotes_btn").prop("checked", buttons.quotes !== false); 100 | $("#enable_parentheses_btn").prop("checked", buttons.parentheses !== false); 101 | $("#enable_book_quotes1_btn").prop("checked", buttons.bookQuotes1 !== false); 102 | $("#enable_book_quotes2_btn").prop("checked", buttons.bookQuotes2 !== false); 103 | $("#enable_book_quotes3_btn").prop("checked", buttons.bookQuotes3 !== false); // 新增书名号按钮设置 104 | $("#enable_newline_btn").prop("checked", buttons.newline !== false); 105 | $("#enable_user_btn").prop("checked", buttons.user !== false); 106 | $("#enable_char_btn").prop("checked", buttons.char !== false); 107 | 108 | // 更新快捷键设置 109 | const shortcuts = extension_settings[extensionName].shortcuts; 110 | for (const key in shortcuts) { 111 | $(`#shortcut_${key}`).val(shortcuts[key] || ""); 112 | } 113 | 114 | // 更新按钮顺序 115 | updateButtonsOrder(); 116 | 117 | updateButtonVisibility(); 118 | 119 | // 加载自定义符号按钮 120 | loadCustomSymbolButtons(); 121 | } 122 | 123 | // 更新设置面板中的按钮顺序 124 | function updateButtonsOrder() { 125 | const buttonOrder = extension_settings[extensionName].buttonOrder; 126 | if (!buttonOrder || buttonOrder.length === 0) return; 127 | 128 | // 根据保存的顺序重新排列设置面板中的按钮 129 | const container = $("#integrated_button_settings"); 130 | 131 | buttonOrder.forEach(key => { 132 | const buttonRow = $(`.integrated-button-row[data-button-key="${key}"]`); 133 | if (buttonRow.length) { 134 | container.append(buttonRow); 135 | } 136 | }); 137 | } 138 | 139 | // 初始化按钮排序 140 | function initSortable() { 141 | try { 142 | if ($("#integrated_button_settings").sortable) { 143 | $("#integrated_button_settings").sortable({ 144 | handle: ".drag-handle", 145 | axis: "y", 146 | delay: 150, 147 | stop: function() { 148 | // 获取新的排序 149 | const newOrder = []; 150 | $("#integrated_button_settings .integrated-button-row").each(function() { 151 | const buttonKey = $(this).attr("data-button-key"); 152 | newOrder.push(buttonKey); 153 | }); 154 | 155 | // 保存新排序到设置 156 | extension_settings[extensionName].buttonOrder = newOrder; 157 | saveSettingsDebounced(); 158 | 159 | // 更新工具栏按钮顺序 160 | updateToolbarButtonOrder(); 161 | } 162 | }); 163 | } else { 164 | console.warn("jQuery UI Sortable 不可用,无法启用拖拽排序功能"); 165 | } 166 | } catch (error) { 167 | console.error("初始化按钮排序功能失败:", error); 168 | } 169 | } 170 | 171 | // 更新工具栏按钮顺序 172 | function updateToolbarButtonOrder() { 173 | const buttonOrder = extension_settings[extensionName].buttonOrder || []; 174 | if (buttonOrder.length === 0) return; 175 | 176 | const toolbar = $("#input_helper_toolbar"); 177 | if (toolbar.length === 0) return; 178 | 179 | // 按照保存的顺序重新排列工具栏按钮 180 | buttonOrder.forEach(key => { 181 | // 防止空按钮ID 182 | const buttonId = getButtonIdFromKey(key); 183 | if (!buttonId) return; 184 | 185 | const button = $(`#${buttonId}`); 186 | if (button.length && extension_settings[extensionName].buttons[key] !== false) { 187 | toolbar.append(button); 188 | } 189 | }); 190 | } 191 | 192 | // 从按钮键名获取按钮ID 193 | function getButtonIdFromKey(key) { 194 | // 检查是否是自定义按钮 195 | if (key.startsWith('custom_')) { 196 | // 直接返回自定义按钮的ID 197 | const index = key.replace('custom_', ''); 198 | return `input_custom_${index}_btn`; 199 | } 200 | 201 | // 预定义按钮的映射 202 | const keyToId = { 203 | 'asterisk': 'input_asterisk_btn', 204 | 'quotes': 'input_quotes_btn', 205 | 'parentheses': 'input_parentheses_btn', 206 | 'bookQuotes1': 'input_book_quotes1_btn', 207 | 'bookQuotes2': 'input_book_quotes2_btn', 208 | 'bookQuotes3': 'input_book_quotes3_btn', 209 | 'newline': 'input_newline_btn', 210 | 'user': 'input_user_btn', 211 | 'char': 'input_char_btn' 212 | }; 213 | 214 | return keyToId[key] || ''; 215 | } 216 | 217 | // 更新按钮可见性 218 | function updateButtonVisibility() { 219 | const buttons = extension_settings[extensionName].buttons; 220 | 221 | // 根据设置显示/隐藏按钮 222 | $("#input_asterisk_btn").toggle(buttons.asterisk !== false); 223 | $("#input_quotes_btn").toggle(buttons.quotes !== false); 224 | $("#input_parentheses_btn").toggle(buttons.parentheses !== false); 225 | $("#input_book_quotes1_btn").toggle(buttons.bookQuotes1 !== false); 226 | $("#input_book_quotes2_btn").toggle(buttons.bookQuotes2 !== false); 227 | $("#input_book_quotes3_btn").toggle(buttons.bookQuotes3 !== false); // 新增书名号按钮 228 | $("#input_newline_btn").toggle(buttons.newline !== false); 229 | $("#input_user_btn").toggle(buttons.user !== false); 230 | $("#input_char_btn").toggle(buttons.char !== false); 231 | 232 | // 更新自定义按钮的显示/隐藏 233 | const customSymbols = extension_settings[extensionName].customSymbols || []; 234 | customSymbols.forEach((symbol, index) => { 235 | const buttonKey = `custom_${index}`; 236 | $(`#input_custom_${index}_btn`).toggle(buttons[buttonKey] !== false); 237 | }); 238 | 239 | // 检查所有按钮是否都被隐藏,如果是则隐藏整个工具栏 240 | const allHidden = Object.values(buttons).every(v => v === false); 241 | if (allHidden) { 242 | $("#input_helper_toolbar").hide(); 243 | } else if (extension_settings[extensionName].enabled) { 244 | $("#input_helper_toolbar").show(); 245 | 246 | // 更新按钮顺序 247 | updateToolbarButtonOrder(); 248 | } 249 | } 250 | 251 | // 开关设置变更响应 252 | function onEnableInputChange() { 253 | const value = $("#enable_input_helper").prop("checked"); 254 | extension_settings[extensionName].enabled = value; 255 | saveSettingsDebounced(); 256 | 257 | // 根据复选框状态显示或隐藏工具栏 258 | if (value) { 259 | updateButtonVisibility(); 260 | } else { 261 | $("#input_helper_toolbar").hide(); 262 | } 263 | } 264 | 265 | // 按钮显示设置变更响应 266 | function onButtonVisibilityChange(buttonKey) { 267 | return function() { 268 | const checked = $(this).prop("checked"); 269 | extension_settings[extensionName].buttons[buttonKey] = checked; 270 | saveSettingsDebounced(); 271 | updateButtonVisibility(); 272 | }; 273 | } 274 | 275 | // 获取输入框元素 276 | function getMessageInput() { 277 | return $("#send_textarea, #prompt_textarea").first(); 278 | } 279 | 280 | // 插入引号功能 281 | function insertQuotes() { 282 | if (!extension_settings[extensionName].enabled) return; 283 | 284 | const textarea = getMessageInput(); 285 | const startPos = textarea.prop("selectionStart"); 286 | const endPos = textarea.prop("selectionEnd"); 287 | const text = textarea.val(); 288 | 289 | const beforeText = text.substring(0, startPos); 290 | const selectedText = text.substring(startPos, endPos); 291 | const afterText = text.substring(endPos); 292 | 293 | // 插入双引号并将光标放在中间 294 | const newText = beforeText + "\"\"" + afterText; 295 | textarea.val(newText); 296 | 297 | // 设置光标位置在双引号中间 298 | setTimeout(() => { 299 | textarea.prop("selectionStart", startPos + 1); 300 | textarea.prop("selectionEnd", startPos + 1); 301 | textarea.focus(); 302 | }, 0); 303 | } 304 | 305 | // 插入换行功能 306 | function insertNewLine() { 307 | if (!extension_settings[extensionName].enabled) return; 308 | 309 | const textarea = getMessageInput(); 310 | const text = textarea.val(); 311 | const cursorPos = textarea.prop("selectionStart"); 312 | 313 | // 查找当前行的末尾位置 314 | let lineEnd = text.indexOf("\n", cursorPos); 315 | if (lineEnd === -1) { 316 | // 如果没有找到换行符,说明光标在最后一行,使用文本长度作为行末 317 | lineEnd = text.length; 318 | } 319 | 320 | // 在行末插入换行符 321 | const newText = text.substring(0, lineEnd) + "\n" + text.substring(lineEnd); 322 | textarea.val(newText); 323 | 324 | // 设置光标位置在新插入的换行符之后 325 | setTimeout(() => { 326 | textarea.prop("selectionStart", lineEnd + 1); 327 | textarea.prop("selectionEnd", lineEnd + 1); 328 | textarea.focus(); 329 | }, 0); 330 | } 331 | 332 | // 插入星号功能 333 | function insertAsterisk() { 334 | if (!extension_settings[extensionName].enabled) return; 335 | 336 | const textarea = getMessageInput(); 337 | const startPos = textarea.prop("selectionStart"); 338 | const endPos = textarea.prop("selectionEnd"); 339 | const text = textarea.val(); 340 | 341 | const beforeText = text.substring(0, startPos); 342 | const selectedText = text.substring(startPos, endPos); 343 | const afterText = text.substring(endPos); 344 | 345 | // 插入两个星号并将光标放在中间 346 | const newText = beforeText + "**" + afterText; 347 | textarea.val(newText); 348 | 349 | // 设置光标位置在星号中间 350 | setTimeout(() => { 351 | textarea.prop("selectionStart", startPos + 1); 352 | textarea.prop("selectionEnd", startPos + 1); 353 | textarea.focus(); 354 | }, 0); 355 | } 356 | 357 | // 插入用户标记功能 358 | function insertUserTag() { 359 | if (!extension_settings[extensionName].enabled) return; 360 | 361 | const textarea = getMessageInput(); 362 | const startPos = textarea.prop("selectionStart"); 363 | const endPos = textarea.prop("selectionEnd"); 364 | const text = textarea.val(); 365 | 366 | const beforeText = text.substring(0, startPos); 367 | const selectedText = text.substring(startPos, endPos); 368 | const afterText = text.substring(endPos); 369 | 370 | // 插入用户标记 371 | const newText = beforeText + "{{User}}" + afterText; 372 | textarea.val(newText); 373 | 374 | // 设置光标位置在标记之后 375 | setTimeout(() => { 376 | textarea.prop("selectionStart", startPos + 8); // "{{User}}".length = 8 377 | textarea.prop("selectionEnd", startPos + 8); 378 | textarea.focus(); 379 | }, 0); 380 | } 381 | 382 | // 插入角色标记功能 383 | function insertCharTag() { 384 | if (!extension_settings[extensionName].enabled) return; 385 | 386 | const textarea = getMessageInput(); 387 | const startPos = textarea.prop("selectionStart"); 388 | const endPos = textarea.prop("selectionEnd"); 389 | const text = textarea.val(); 390 | 391 | const beforeText = text.substring(0, startPos); 392 | const selectedText = text.substring(startPos, endPos); 393 | const afterText = text.substring(endPos); 394 | 395 | // 插入角色标记 396 | const newText = beforeText + "{{Char}}" + afterText; 397 | textarea.val(newText); 398 | 399 | // 设置光标位置在标记之后 400 | setTimeout(() => { 401 | textarea.prop("selectionStart", startPos + 8); // "{{Char}}".length = 8 402 | textarea.prop("selectionEnd", startPos + 8); 403 | textarea.focus(); 404 | }, 0); 405 | } 406 | 407 | // 插入圆括号功能 408 | function insertParentheses() { 409 | if (!extension_settings[extensionName].enabled) return; 410 | 411 | const textarea = getMessageInput(); 412 | const startPos = textarea.prop("selectionStart"); 413 | const endPos = textarea.prop("selectionEnd"); 414 | const text = textarea.val(); 415 | 416 | const beforeText = text.substring(0, startPos); 417 | const selectedText = text.substring(startPos, endPos); 418 | const afterText = text.substring(endPos); 419 | 420 | // 插入圆括号并将光标放在中间 421 | const newText = beforeText + "()" + afterText; 422 | textarea.val(newText); 423 | 424 | // 设置光标位置在括号中间 425 | setTimeout(() => { 426 | textarea.prop("selectionStart", startPos + 1); 427 | textarea.prop("selectionEnd", startPos + 1); 428 | textarea.focus(); 429 | }, 0); 430 | } 431 | 432 | // 插入书名号「」功能 433 | function insertBookQuotes1() { 434 | if (!extension_settings[extensionName].enabled) return; 435 | 436 | const textarea = getMessageInput(); 437 | const startPos = textarea.prop("selectionStart"); 438 | const endPos = textarea.prop("selectionEnd"); 439 | const text = textarea.val(); 440 | 441 | const beforeText = text.substring(0, startPos); 442 | const selectedText = text.substring(startPos, endPos); 443 | const afterText = text.substring(endPos); 444 | 445 | // 插入书名号并将光标放在中间 446 | const newText = beforeText + "「」" + afterText; 447 | textarea.val(newText); 448 | 449 | // 设置光标位置在书名号中间 450 | setTimeout(() => { 451 | textarea.prop("selectionStart", startPos + 1); 452 | textarea.prop("selectionEnd", startPos + 1); 453 | textarea.focus(); 454 | }, 0); 455 | } 456 | 457 | // 插入书名号『』功能 458 | function insertBookQuotes2() { 459 | if (!extension_settings[extensionName].enabled) return; 460 | 461 | const textarea = getMessageInput(); 462 | const startPos = textarea.prop("selectionStart"); 463 | const endPos = textarea.prop("selectionEnd"); 464 | const text = textarea.val(); 465 | 466 | const beforeText = text.substring(0, startPos); 467 | const selectedText = text.substring(startPos, endPos); 468 | const afterText = text.substring(endPos); 469 | 470 | // 插入书名号并将光标放在中间 471 | const newText = beforeText + "『』" + afterText; 472 | textarea.val(newText); 473 | 474 | // 设置光标位置在书名号中间 475 | setTimeout(() => { 476 | textarea.prop("selectionStart", startPos + 1); 477 | textarea.prop("selectionEnd", startPos + 1); 478 | textarea.focus(); 479 | }, 0); 480 | } 481 | 482 | // 插入书名号《》功能 483 | function insertBookQuotes3() { 484 | if (!extension_settings[extensionName].enabled) return; 485 | 486 | const textarea = getMessageInput(); 487 | const startPos = textarea.prop("selectionStart"); 488 | const endPos = textarea.prop("selectionEnd"); 489 | const text = textarea.val(); 490 | 491 | const beforeText = text.substring(0, startPos); 492 | const selectedText = text.substring(startPos, endPos); 493 | const afterText = text.substring(endPos); 494 | 495 | // 插入书名号并将光标放在中间 496 | const newText = beforeText + "《》" + afterText; 497 | textarea.val(newText); 498 | 499 | // 设置光标位置在书名号中间 500 | setTimeout(() => { 501 | textarea.prop("selectionStart", startPos + 1); 502 | textarea.prop("selectionEnd", startPos + 1); 503 | textarea.focus(); 504 | }, 0); 505 | } 506 | 507 | // 处理快捷键设置 508 | function setupShortcutInputs() { 509 | // 处理快捷键输入 510 | $(".shortcut-input").on("keydown", function(e) { 511 | e.preventDefault(); 512 | 513 | // 获取按键组合 514 | let keys = []; 515 | if (e.ctrlKey) keys.push("Ctrl"); 516 | if (e.altKey) keys.push("Alt"); 517 | if (e.shiftKey) keys.push("Shift"); 518 | 519 | // 添加主键(如果不是修饰键) 520 | if ( 521 | e.key !== "Control" && 522 | e.key !== "Alt" && 523 | e.key !== "Shift" && 524 | e.key !== "Meta" && 525 | e.key !== "Escape" 526 | ) { 527 | // 修复: 确保e.key存在并且有length属性 528 | const keyName = e.key && typeof e.key === 'string' && e.key.length === 1 529 | ? e.key.toUpperCase() 530 | : (e.key || "Unknown"); 531 | keys.push(keyName); 532 | } 533 | 534 | // 如果只按了Escape键,清除快捷键 535 | if (e.key === "Escape") { 536 | $(this).val(""); 537 | const shortcutKey = $(this).attr("id").replace("shortcut_", ""); 538 | extension_settings[extensionName].shortcuts[shortcutKey] = ""; 539 | saveSettingsDebounced(); 540 | return; 541 | } 542 | 543 | // 如果没有按键组合或只有修饰键,不设置 544 | if (keys.length === 0 || (keys.length === 1 && ["Ctrl", "Alt", "Shift"].includes(keys[0]))) { 545 | return; 546 | } 547 | 548 | // 设置快捷键 549 | const shortcutString = keys.join("+"); 550 | $(this).val(shortcutString); 551 | 552 | // 保存到设置 553 | const shortcutKey = $(this).attr("id").replace("shortcut_", ""); 554 | extension_settings[extensionName].shortcuts[shortcutKey] = shortcutString; 555 | saveSettingsDebounced(); 556 | }); 557 | 558 | // 处理清除按钮 559 | $(".shortcut-clear-btn").on("click", function() { 560 | const targetId = $(this).data("target"); 561 | $(`#${targetId}`).val(""); 562 | 563 | // 保存到设置 564 | const shortcutKey = targetId.replace("shortcut_", ""); 565 | extension_settings[extensionName].shortcuts[shortcutKey] = ""; 566 | saveSettingsDebounced(); 567 | }); 568 | } 569 | 570 | // 全局快捷键处理函数 571 | function handleGlobalShortcuts(e) { 572 | // 如果插件未启用或正在编辑快捷键,不处理 573 | if (!extension_settings[extensionName].enabled || $(document.activeElement).hasClass("shortcut-input")) { 574 | return; 575 | } 576 | 577 | // 如果当前焦点不在文本区域,不处理 578 | const messageInput = getMessageInput()[0]; 579 | if (document.activeElement !== messageInput) { 580 | return; 581 | } 582 | 583 | // 获取当前按键组合 584 | let keys = []; 585 | if (e.ctrlKey) keys.push("Ctrl"); 586 | if (e.altKey) keys.push("Alt"); 587 | if (e.shiftKey) keys.push("Shift"); 588 | 589 | // 添加主键(如果不是修饰键) 590 | if ( 591 | e.key !== "Control" && 592 | e.key !== "Alt" && 593 | e.key !== "Shift" && 594 | e.key !== "Meta" 595 | ) { 596 | // 修复: 确保e.key存在并且有length属性 597 | const keyName = e.key && typeof e.key === 'string' && e.key.length === 1 598 | ? e.key.toUpperCase() 599 | : (e.key || "Unknown"); 600 | keys.push(keyName); 601 | } 602 | 603 | // 如果没有有效的按键组合,不处理 604 | if (keys.length <= 1) { 605 | return; 606 | } 607 | 608 | const shortcutString = keys.join("+"); 609 | const shortcuts = extension_settings[extensionName].shortcuts; 610 | 611 | // 查找匹配的快捷键 612 | for (const key in shortcuts) { 613 | if (shortcuts[key] === shortcutString) { 614 | e.preventDefault(); 615 | 616 | // 检查是否是自定义按钮的快捷键 617 | if (key.startsWith('custom_')) { 618 | const index = parseInt(key.replace('custom_', '')); 619 | const customSymbols = extension_settings[extensionName].customSymbols || []; 620 | if (index >= 0 && index < customSymbols.length) { 621 | insertCustomSymbol(customSymbols[index]); 622 | return; 623 | } 624 | } 625 | // 执行对应的功能 626 | else if (shortcutFunctionMap[key]) { 627 | shortcutFunctionMap[key](); 628 | return; 629 | } 630 | } 631 | } 632 | } 633 | 634 | // 加载自定义符号按钮 635 | function loadCustomSymbolButtons() { 636 | const customSymbols = extension_settings[extensionName].customSymbols || []; 637 | 638 | // 清除现有的自定义按钮 639 | $(".custom-symbol-button").remove(); 640 | $(".integrated-button-row[data-custom='true']").remove(); 641 | 642 | // 为每个自定义符号创建按钮和设置项 643 | customSymbols.forEach((symbol, index) => { 644 | const buttonKey = `custom_${index}`; 645 | 646 | // 为工具栏创建按钮 647 | createCustomSymbolButton(symbol, index); 648 | 649 | // 为设置面板创建行 650 | createCustomSymbolSetting(symbol, index); 651 | 652 | // 更新按钮顺序 653 | if (!extension_settings[extensionName].buttonOrder.includes(buttonKey)) { 654 | extension_settings[extensionName].buttonOrder.push(buttonKey); 655 | } 656 | 657 | // 确保该按钮有显示设置 658 | if (extension_settings[extensionName].buttons[buttonKey] === undefined) { 659 | extension_settings[extensionName].buttons[buttonKey] = true; 660 | } 661 | 662 | // 确保该按钮有快捷键设置 663 | if (extension_settings[extensionName].shortcuts[buttonKey] === undefined) { 664 | extension_settings[extensionName].shortcuts[buttonKey] = ""; 665 | } 666 | 667 | // 更新快捷键映射 668 | shortcutFunctionMap[buttonKey] = function() { 669 | insertCustomSymbol(customSymbols[index]); 670 | }; 671 | }); 672 | 673 | // 更新按钮顺序 674 | updateButtonsOrder(); 675 | updateToolbarButtonOrder(); 676 | 677 | // 重新绑定快捷键输入框事件 678 | setupShortcutInputs(); 679 | } 680 | 681 | // 创建自定义符号按钮 682 | function createCustomSymbolButton(symbol, index) { 683 | const buttonId = `input_custom_${index}_btn`; 684 | const buttonKey = `custom_${index}`; 685 | 686 | // 先检查是否已存在,如果存在则移除 687 | $(`#${buttonId}`).remove(); 688 | 689 | // 创建按钮并添加到工具栏 690 | const button = $(``); 691 | $("#input_helper_toolbar").append(button); 692 | 693 | // 添加点击事件 694 | bindCustomSymbolEvent(button, symbol); 695 | } 696 | 697 | // 为自定义符号按钮绑定事件 698 | function bindCustomSymbolEvent(button, symbol) { 699 | // 检查是否是移动设备 700 | const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 701 | 702 | if (isMobile) { 703 | button.on("touchstart", function(e) { 704 | e.preventDefault(); 705 | insertCustomSymbol(symbol); 706 | 707 | // 确保输入框保持焦点状态 708 | setTimeout(() => { 709 | getMessageInput().focus(); 710 | }, 10); 711 | 712 | return false; 713 | }); 714 | } else { 715 | button.on("click", function() { 716 | insertCustomSymbol(symbol); 717 | }); 718 | } 719 | } 720 | 721 | // 创建自定义符号设置项 722 | function createCustomSymbolSetting(symbol, index) { 723 | const buttonKey = `custom_${index}`; 724 | 725 | // 先检查是否已存在,如果存在则移除 726 | $(`.integrated-button-row[data-button-key="${buttonKey}"]`).remove(); 727 | 728 | // 创建设置行 - 修改编辑和删除按钮位置 729 | const row = $(` 730 |
731 | 732 | 733 |
${symbol.display}
734 | 735 | 736 | 737 | 738 | 739 |
740 | `); 741 | 742 | // 添加到设置面板 743 | $("#integrated_button_settings").append(row); 744 | 745 | // 添加事件监听 746 | row.find(`#enable_${buttonKey}_btn`).on("input", onButtonVisibilityChange(buttonKey)); 747 | row.find(".custom-edit-btn").on("click", function() { 748 | const index = $(this).data("index"); 749 | editCustomSymbol(index); 750 | }); 751 | row.find(".custom-delete-btn").on("click", function() { 752 | const index = $(this).data("index"); 753 | deleteCustomSymbol(index); 754 | }); 755 | } 756 | 757 | // 插入自定义符号 758 | function insertCustomSymbol(symbol) { 759 | if (!extension_settings[extensionName].enabled) return; 760 | 761 | const textarea = getMessageInput(); 762 | const startPos = textarea.prop("selectionStart"); 763 | const endPos = textarea.prop("selectionEnd"); 764 | const text = textarea.val(); 765 | 766 | const beforeText = text.substring(0, startPos); 767 | const selectedText = text.substring(startPos, endPos); 768 | const afterText = text.substring(endPos); 769 | 770 | // 插入符号 771 | const newText = beforeText + symbol.symbol + afterText; 772 | textarea.val(newText); 773 | 774 | // 设置光标位置 775 | setTimeout(() => { 776 | // 计算光标位置 777 | let cursorPos = startPos; 778 | 779 | if (symbol.cursorPos === "start") { 780 | cursorPos = startPos; 781 | } else if (symbol.cursorPos === "end") { 782 | cursorPos = startPos + symbol.symbol.length; 783 | } else if (symbol.cursorPos === "middle") { 784 | cursorPos = startPos + Math.floor(symbol.symbol.length / 2); 785 | } else { 786 | // 具体位置 787 | cursorPos = startPos + parseInt(symbol.cursorPos) || startPos; 788 | } 789 | 790 | textarea.prop("selectionStart", cursorPos); 791 | textarea.prop("selectionEnd", cursorPos); 792 | textarea.focus(); 793 | }, 0); 794 | } 795 | 796 | // 编辑自定义符号 797 | function editCustomSymbol(index) { 798 | const symbols = extension_settings[extensionName].customSymbols; 799 | const symbol = symbols[index]; 800 | 801 | // 显示编辑对话框 802 | showCustomSymbolDialog(symbol, index); 803 | } 804 | 805 | // 删除自定义符号 806 | function deleteCustomSymbol(index) { 807 | if (confirm("确定要删除这个自定义符号吗?")) { 808 | const symbols = extension_settings[extensionName].customSymbols; 809 | const buttonKey = `custom_${index}`; 810 | 811 | // 从设置中删除 812 | symbols.splice(index, 1); 813 | 814 | // 从按钮顺序中删除 815 | const orderIndex = extension_settings[extensionName].buttonOrder.indexOf(buttonKey); 816 | if (orderIndex > -1) { 817 | extension_settings[extensionName].buttonOrder.splice(orderIndex, 1); 818 | } 819 | 820 | // 从按钮显示设置中删除 821 | delete extension_settings[extensionName].buttons[buttonKey]; 822 | 823 | // 从按钮快捷键设置中删除 824 | delete extension_settings[extensionName].shortcuts[buttonKey]; 825 | 826 | // 从工具栏中删除 827 | $(`#input_custom_${index}_btn`).remove(); 828 | 829 | // 从快捷键映射中删除 830 | delete shortcutFunctionMap[buttonKey]; 831 | 832 | // 保存设置 833 | saveSettingsDebounced(); 834 | 835 | // 移动设备监听器需要重新绑定 836 | rebindMobileEventListeners(); 837 | 838 | // 重新加载自定义按钮 - 这会导致索引重排 839 | loadCustomSymbolButtons(); 840 | 841 | // 更新工具栏 842 | updateButtonVisibility(); 843 | } 844 | } 845 | 846 | // 重新绑定移动设备事件监听器 847 | function rebindMobileEventListeners() { 848 | if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) { 849 | return; // 非移动设备不需要重新绑定 850 | } 851 | 852 | // 移除之前的监听器 853 | $("#input_helper_toolbar button").off("touchstart"); 854 | 855 | // 重新绑定监听器 856 | $("#input_helper_toolbar button").on("touchstart", function(e) { 857 | e.preventDefault(); 858 | const btnId = $(this).attr("id"); 859 | 860 | // 基于按钮ID调用相应的函数 861 | if (btnId === "input_asterisk_btn") insertAsterisk(); 862 | else if (btnId === "input_quotes_btn") insertQuotes(); 863 | else if (btnId === "input_parentheses_btn") insertParentheses(); 864 | else if (btnId === "input_book_quotes1_btn") insertBookQuotes1(); 865 | else if (btnId === "input_book_quotes2_btn") insertBookQuotes2(); 866 | else if (btnId === "input_book_quotes3_btn") insertBookQuotes3(); 867 | else if (btnId === "input_newline_btn") insertNewLine(); 868 | else if (btnId === "input_user_btn") insertUserTag(); 869 | else if (btnId === "input_char_btn") insertCharTag(); 870 | else if (btnId.startsWith("input_custom_")) { 871 | // 处理自定义按钮 872 | const index = parseInt(btnId.replace("input_custom_", "").replace("_btn", "")); 873 | const customSymbols = extension_settings[extensionName].customSymbols || []; 874 | if (index >= 0 && index < customSymbols.length) { 875 | insertCustomSymbol(customSymbols[index]); 876 | } 877 | } 878 | 879 | // 确保输入框保持焦点状态 880 | setTimeout(() => { 881 | getMessageInput().focus(); 882 | }, 10); 883 | 884 | return false; 885 | }); 886 | } 887 | 888 | // 显示自定义符号对话框 889 | function showCustomSymbolDialog(existingSymbol = null, editIndex = -1) { 890 | // 创建对话框 - 修改样式以正确应用主题颜色 891 | const dialog = $(` 892 |
893 |
894 |

${existingSymbol ? '编辑符号' : '添加自定义符号'}

895 |
896 |
897 | 898 | 899 |
900 |
901 | 902 | 903 |
904 |
905 | 906 | 907 |
908 |
909 | 910 | 916 | 917 |
918 |
919 |
920 | 921 | 922 |
923 |
924 |
925 | `); 926 | 927 | // 添加到页面 928 | $("body").append(dialog); 929 | 930 | // 处理自定义光标位置选择 931 | $("#custom_symbol_cursor").on("change", function() { 932 | if ($(this).val() === "custom") { 933 | $("#custom_symbol_cursor_pos").show(); 934 | } else { 935 | $("#custom_symbol_cursor_pos").hide(); 936 | } 937 | }); 938 | 939 | // 取消按钮事件 940 | $("#custom_symbol_cancel").on("click", function() { 941 | dialog.remove(); 942 | }); 943 | 944 | // 保存按钮事件 945 | $("#custom_symbol_save").on("click", function() { 946 | const name = $("#custom_symbol_name").val().trim(); 947 | const symbol = $("#custom_symbol_symbol").val(); 948 | const display = $("#custom_symbol_display").val() || symbol; 949 | let cursorPos = $("#custom_symbol_cursor").val(); 950 | 951 | if (cursorPos === "custom") { 952 | cursorPos = $("#custom_symbol_cursor_pos").val(); 953 | } 954 | 955 | // 验证输入 956 | if (!name || !symbol) { 957 | alert("请输入名称和符号!"); 958 | return; 959 | } 960 | 961 | // 创建符号对象 962 | const symbolObj = { 963 | name: name, 964 | symbol: symbol, 965 | display: display, 966 | cursorPos: cursorPos 967 | }; 968 | 969 | // 保存到设置 970 | if (editIndex >= 0) { 971 | // 编辑现有符号 972 | extension_settings[extensionName].customSymbols[editIndex] = symbolObj; 973 | } else { 974 | // 添加新符号 975 | if (!extension_settings[extensionName].customSymbols) { 976 | extension_settings[extensionName].customSymbols = []; 977 | } 978 | extension_settings[extensionName].customSymbols.push(symbolObj); 979 | } 980 | 981 | // 保存设置 982 | saveSettingsDebounced(); 983 | 984 | // 重新加载自定义按钮 985 | loadCustomSymbolButtons(); 986 | 987 | // 关闭对话框 988 | dialog.remove(); 989 | }); 990 | } 991 | 992 | // 初始化插件 993 | jQuery(async () => { 994 | // 加载HTML 995 | const settingsHtml = await $.get(`${extensionFolderPath}/settings.html`); 996 | $("#extensions_settings2").append(settingsHtml); 997 | 998 | // 加载输入工具栏HTML 999 | const toolbarHtml = await $.get(`${extensionFolderPath}/toolbar.html`); 1000 | 1001 | // 将工具栏插入到 #qr--bar 下方,并确保正确的视觉顺序 1002 | const $qrBar = $("#qr--bar"); 1003 | console.log($qrBar.length) 1004 | if ($qrBar.length == 0) { 1005 | $("#send_form").append( 1006 | '
' 1007 | ); 1008 | } 1009 | $("#qr--bar").append(toolbarHtml); 1010 | 1011 | // 移动设备优化:防止按钮点击导致键盘消失和重新出现 1012 | const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); 1013 | if (isMobile) { 1014 | // 在移动设备上,阻止工具栏按钮的默认行为,避免输入框失焦 1015 | $("#input_helper_toolbar").on("mousedown touchstart", function(e) { 1016 | e.preventDefault(); // 阻止默认行为 1017 | // 不阻止冒泡,以便点击事件仍然被处理 1018 | }); 1019 | 1020 | // 初始绑定移动设备触摸事件 - 使用统一的函数便于重新绑定 1021 | rebindMobileEventListeners(); 1022 | } else { 1023 | // 桌面端使用原有的点击事件 1024 | $("#input_asterisk_btn").on("click", insertAsterisk); 1025 | $("#input_quotes_btn").on("click", insertQuotes); 1026 | $("#input_newline_btn").on("click", insertNewLine); 1027 | $("#input_user_btn").on("click", insertUserTag); 1028 | $("#input_char_btn").on("click", insertCharTag); 1029 | $("#input_parentheses_btn").on("click", insertParentheses); 1030 | $("#input_book_quotes1_btn").on("click", insertBookQuotes1); 1031 | $("#input_book_quotes2_btn").on("click", insertBookQuotes2); 1032 | $("#input_book_quotes3_btn").on("click", insertBookQuotes3); 1033 | // 动态添加的自定义按钮会在创建时绑定事件 1034 | } 1035 | 1036 | // 注册事件监听 1037 | $("#insert_quotes_button").on("click", insertQuotes); 1038 | $("#new_line_button").on("click", insertNewLine); 1039 | $("#insert_asterisk_button").on("click", insertAsterisk); 1040 | $("#enable_input_helper").on("input", onEnableInputChange); 1041 | 1042 | // 注册设置变更事件监听 1043 | $("#enable_input_helper").on("input", onEnableInputChange); 1044 | $("#enable_asterisk_btn").on("input", onButtonVisibilityChange("asterisk")); 1045 | $("#enable_quotes_btn").on("input", onButtonVisibilityChange("quotes")); 1046 | $("#enable_parentheses_btn").on("input", onButtonVisibilityChange("parentheses")); 1047 | $("#enable_book_quotes1_btn").on("input", onButtonVisibilityChange("bookQuotes1")); 1048 | $("#enable_book_quotes2_btn").on("input", onButtonVisibilityChange("bookQuotes2")); 1049 | $("#enable_book_quotes3_btn").on("input", onButtonVisibilityChange("bookQuotes3")); 1050 | $("#enable_newline_btn").on("input", onButtonVisibilityChange("newline")); 1051 | $("#enable_user_btn").on("input", onButtonVisibilityChange("user")); 1052 | $("#enable_char_btn").on("input", onButtonVisibilityChange("char")); 1053 | 1054 | // 加载设置 1055 | await loadSettings(); 1056 | 1057 | // 设置快捷键输入框 1058 | setupShortcutInputs(); 1059 | 1060 | // 初始化排序功能 1061 | initSortable(); 1062 | 1063 | // 注册全局快捷键事件 1064 | $(document).on("keydown", handleGlobalShortcuts); 1065 | 1066 | // 根据初始化设置显示或隐藏工具栏 1067 | if (!extension_settings[extensionName].enabled) { 1068 | $("#input_helper_toolbar").hide(); 1069 | } 1070 | 1071 | // 添加添加自定义符号按钮 1072 | $("#integrated_button_settings").after(` 1073 |
1074 | 1075 |
1076 | `); 1077 | 1078 | // 添加自定义符号按钮事件 1079 | $("#add_custom_symbol_btn").on("click", function() { 1080 | showCustomSymbolDialog(); 1081 | }); 1082 | 1083 | // 注册自定义符号对话框键盘事件处理 1084 | $(document).on("keydown", function(e) { 1085 | // 如果对话框正在显示且按下了Escape,关闭对话框 1086 | if ($("#custom_symbol_dialog").length && e.key === "Escape") { 1087 | $("#custom_symbol_dialog").remove(); 1088 | } 1089 | 1090 | // 如果对话框正在显示且按下了Enter,模拟点击保存按钮 1091 | if ($("#custom_symbol_dialog").length && e.key === "Enter" && !e.ctrlKey && !e.shiftKey && !e.altKey) { 1092 | if ($(document.activeElement).is("input") && !$(document.activeElement).is("textarea")) { 1093 | $("#custom_symbol_save").click(); 1094 | } 1095 | } 1096 | }); 1097 | 1098 | console.log("输入助手插件已加载"); 1099 | }); 1100 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "输入助手", 3 | "loading_order": 9, 4 | "requires": [], 5 | "optional": [], 6 | "js": "index.js", 7 | "css": "style.css", 8 | "author": "AI助手和Mooooooon", 9 | "version": "1.3.1", 10 | "homePage": "https://github.com/Mooooooon/st-input-helper" 11 | } 12 | -------------------------------------------------------------------------------- /settings.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 输入助手 6 |
7 |
8 |
9 |
10 | 11 | 12 |
13 | 14 |
15 | 按钮管理 16 |
管理按钮显示和快捷键,点击输入框并按下键盘组合可设置快捷键
17 |
拖动排序按钮调整工具栏中的显示顺序
18 |
19 | 20 |
21 | 22 |
23 | 24 | 25 |
**
26 | 27 | 28 | 29 |
30 | 31 | 32 |
33 | 34 | 35 |
""
36 | 37 | 38 | 39 |
40 | 41 | 42 |
43 | 44 | 45 |
()
46 | 47 | 48 | 49 |
50 | 51 | 52 |
53 | 54 | 55 |
「」
56 | 57 | 58 | 59 |
60 | 61 | 62 |
63 | 64 | 65 |
『』
66 | 67 | 68 | 69 |
70 | 71 | 72 |
73 | 74 | 75 |
《》
76 | 77 | 78 | 79 |
80 | 81 | 82 |
83 | 84 | 85 |
86 | 87 | 88 | 89 |
90 | 91 | 92 |
93 | 94 | 95 |
{{U}}
96 | 97 | 98 | 99 |
100 | 101 | 102 |
103 | 104 | 105 |
{{C}}
106 | 107 | 108 | 109 |
110 |
111 | 112 |
113 |
114 |
115 |
116 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* All style elements for your extension go here */ 2 | 3 | .example-extension_block { 4 | margin-bottom: 10px; 5 | } 6 | 7 | #insert_quotes_button, #new_line_button { 8 | width: 100%; 9 | margin-bottom: 5px; 10 | padding: 8px; 11 | cursor: pointer; 12 | transition: all 0.2s ease; 13 | } 14 | 15 | #insert_quotes_button:hover, #new_line_button:hover { 16 | background-color: rgba(100, 100, 240, 0.2); 17 | } 18 | 19 | .example-extension-settings .inline-drawer-header { 20 | cursor: pointer; 21 | } 22 | 23 | /* 发送消息按钮旁的工具栏样式 */ 24 | .input-helper-toolbar { 25 | display: flex; 26 | align-items: center; 27 | justify-content: center; /* 居中显示 */ 28 | margin: 5px auto; /* 减少间距,更紧凑 */ 29 | width: 100%; 30 | padding: 2px 10px; /* 左右添加一些内边距 */ 31 | position: relative; /* 设置定位 */ 32 | z-index: 9; /* 确保工具栏在QR Bar下方 */ 33 | background: transparent; /* 透明背景 */ 34 | border-radius: 4px; /* 圆角边框 */ 35 | order: 2; /* 确保顺序在 QR Bar 之后 */ 36 | opacity: 0.7; /* 匹配QR Bar的透明度 */ 37 | transition: 0.3s; /* 添加过渡效果 */ 38 | } 39 | 40 | .input-helper-toolbar:hover { 41 | opacity: 1; /* 悬停时提高透明度 */ 42 | } 43 | 44 | /* 当QR Bar存在时的特殊调整 */ 45 | #qr--bar { 46 | position: relative; /* 设置定位 */ 47 | z-index: 10; /* 确保QR Bar在工具栏上方 */ 48 | order: 1; /* 确保 QR Bar 在前 */ 49 | } 50 | 51 | #qr--bar + .input-helper-toolbar { 52 | margin-top: 3px; /* 稍微上移,使其视觉上更像是在QR Bar下方 */ 53 | margin-bottom: 5px; /* 增加与下方元素的间距 */ 54 | padding-top: 4px; /* 增加顶部内边距 */ 55 | border-top: none; /* 移除顶部边框 */ 56 | } 57 | 58 | /* 强制修复在某些主题下可能的层叠问题 */ 59 | #send_form { 60 | display: flex; 61 | flex-direction: column; 62 | } 63 | 64 | /* 按钮样式匹配menu_button和QR按钮 */ 65 | .input-helper-btn { 66 | color: var(--SmartThemeBodyColor); 67 | filter: grayscale(0.5); 68 | background-color: var(--SmartThemeBlurTintColor); 69 | border: 1px solid var(--SmartThemeBorderColor); 70 | border-radius: 5px; 71 | width: 50px; 72 | height: 30px; 73 | margin: 0 3px; 74 | font-size: 14px; 75 | cursor: pointer; 76 | display: flex; 77 | align-items: center; 78 | justify-content: center; 79 | transition: 0.3s; 80 | text-align: center; 81 | } 82 | 83 | .input-helper-btn:hover { 84 | filter: grayscale(0); 85 | background-color: var(--SmartThemeChatTintColor, var(--grey50)); 86 | } 87 | 88 | /* 添加星号按钮样式 */ 89 | #insert_asterisk_button, #insert_asterisk_button:hover { 90 | width: 100%; 91 | margin-bottom: 5px; 92 | padding: 8px; 93 | cursor: pointer; 94 | transition: all 0.2s ease; 95 | } 96 | 97 | #insert_asterisk_button:hover { 98 | background-color: rgba(100, 100, 240, 0.2); 99 | } 100 | 101 | /* 添加新的按钮样式 */ 102 | #input_parentheses_btn, 103 | #input_book_quotes1_btn, 104 | #input_book_quotes2_btn { 105 | font-family: var(--mainFontFamily); 106 | } 107 | 108 | /* 设置面板样式 */ 109 | .example-extension_block { 110 | margin-bottom: 10px; 111 | } 112 | 113 | .example-extension_block label { 114 | margin-left: 5px; 115 | } 116 | 117 | /* 快捷键设置样式 */ 118 | .shortcut-container { 119 | display: flex; 120 | align-items: center; 121 | margin-bottom: 8px; 122 | } 123 | 124 | .shortcut-input { 125 | margin: 0 8px; 126 | width: 120px; 127 | background-color: var(--SmartThemeBlurTintColor); 128 | color: var(--SmartThemeBodyColor); 129 | border: 1px solid var(--SmartThemeBorderColor); 130 | padding: 4px 8px; 131 | border-radius: 4px; 132 | text-align: center; 133 | } 134 | 135 | /* 整合的按钮管理样式 */ 136 | #integrated_button_settings { 137 | display: flex; 138 | flex-direction: column; 139 | gap: 5px; 140 | margin-top: 5px; 141 | } 142 | 143 | .integrated-button-row { 144 | display: flex; 145 | align-items: center; 146 | padding: 5px 8px; 147 | border: 1px solid var(--SmartThemeBorderColor); 148 | border-radius: 5px; 149 | background-color: rgba(30, 30, 30, 0.3); 150 | } 151 | 152 | .integrated-button-row input[type="checkbox"] { 153 | margin-right: 8px; 154 | } 155 | 156 | .button-preview { 157 | width: 40px; 158 | height: 25px; 159 | display: flex; 160 | align-items: center; 161 | justify-content: center; 162 | background-color: var(--SmartThemeBlurTintColor); 163 | border: 1px solid var(--SmartThemeBorderColor); 164 | border-radius: 5px; 165 | margin-right: 8px; 166 | filter: grayscale(0.5); 167 | font-family: var(--mainFontFamily); 168 | } 169 | 170 | .integrated-button-row label { 171 | flex-grow: 1; 172 | margin-right: 10px; 173 | } 174 | 175 | .shortcut-input { 176 | width: 120px; 177 | background-color: var(--SmartThemeBlurTintColor); 178 | color: var(--SmartThemeBodyColor); 179 | border: 1px solid var(--SmartThemeBorderColor); 180 | padding: 4px 8px; 181 | border-radius: 4px; 182 | text-align: center; 183 | margin-right: 5px; 184 | font-family: var(--monoFontFamily); 185 | font-size: 12px; 186 | } 187 | 188 | .shortcut-clear-btn { 189 | width: 24px; 190 | height: 24px; 191 | cursor: pointer; 192 | color: rgba(255, 100, 100, 0.8); 193 | background: none; 194 | border: none; 195 | font-size: 16px; 196 | padding: 0; 197 | display: flex; 198 | align-items: center; 199 | justify-content: center; 200 | } 201 | 202 | .shortcut-clear-btn:hover { 203 | color: rgba(255, 50, 50, 1); 204 | } 205 | 206 | .shortcut-note { 207 | font-size: 12px; 208 | color: var(--SmartThemeEmColor); 209 | margin-bottom: 8px; 210 | font-style: italic; 211 | } 212 | 213 | .shortcut-clear-btn { 214 | border: 1px solid var(--SmartThemeBorderColor); 215 | background-color: rgba(180, 0, 0, 0.2); 216 | color: var(--SmartThemeBodyColor); 217 | border-radius: 4px; 218 | padding: 2px 6px; 219 | cursor: pointer; 220 | font-size: 12px; 221 | } 222 | 223 | .shortcut-clear-btn:hover { 224 | background-color: rgba(220, 0, 0, 0.3); 225 | } 226 | 227 | .shortcut-note { 228 | font-size: 12px; 229 | color: var(--SmartThemeEmColor); 230 | margin-bottom: 8px; 231 | font-style: italic; 232 | } 233 | 234 | /* 拖拽排序相关样式 */ 235 | .drag-handle { 236 | cursor: grab; 237 | margin-right: 8px; 238 | color: var(--SmartThemeEmColor); 239 | opacity: 0.6; 240 | transition: opacity 0.3s; 241 | } 242 | 243 | .drag-handle:hover { 244 | opacity: 1; 245 | } 246 | 247 | .drag-handle:active { 248 | cursor: grabbing; 249 | } 250 | 251 | .integrated-button-row { 252 | cursor: default; 253 | transition: background-color 0.2s, transform 0.1s; 254 | } 255 | 256 | .integrated-button-row.ui-sortable-helper { 257 | background-color: rgba(50, 50, 80, 0.6); 258 | transform: scale(1.02); 259 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); 260 | } 261 | 262 | .integrated-button-row.ui-sortable-placeholder { 263 | visibility: visible !important; 264 | background-color: rgba(30, 30, 50, 0.3); 265 | border: 1px dashed var(--SmartThemeBorderColor); 266 | height: 38px; 267 | } 268 | 269 | /* 移动设备优化 */ 270 | @media (max-width: 768px) { 271 | .input-helper-toolbar { 272 | padding: 5px 5px; 273 | margin: 8px auto; 274 | /* 增加触摸区域大小,改善移动设备体验 */ 275 | touch-action: manipulation; 276 | } 277 | 278 | .input-helper-btn { 279 | width: 38px; /* 略微增大触摸区域 */ 280 | height: 38px; 281 | margin: 0 2px; 282 | font-size: 14px; 283 | /* 更好的触摸反馈 */ 284 | touch-action: manipulation; 285 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 286 | user-select: none; 287 | } 288 | 289 | /* 移动设备上禁用悬停效果,改用激活状态 */ 290 | .input-helper-btn:hover { 291 | filter: grayscale(0.5); 292 | background-color: var(--SmartThemeBlurTintColor); 293 | } 294 | 295 | .input-helper-btn:active { 296 | filter: grayscale(0); 297 | background-color: var(--SmartThemeChatTintColor, var(--grey50)); 298 | transform: scale(0.95); 299 | } 300 | } 301 | 302 | /* 防止移动设备上的按钮导致输入框失焦 */ 303 | .prevent-keyboard-hide { 304 | pointer-events: none; /* 让按钮容器不接收指针事件 */ 305 | } 306 | 307 | .prevent-keyboard-hide > button { 308 | pointer-events: auto; /* 让里面的按钮可以接收指针事件 */ 309 | } 310 | 311 | /* 自定义符号对话框样式 - 移除重复定义 */ 312 | .custom-symbol-dialog { 313 | position: fixed; 314 | top: 0; 315 | left: 0; 316 | width: 100%; 317 | height: 100%; 318 | min-height: 100vh; 319 | background-color: rgba(0, 0, 0, 0.7); 320 | display: flex; 321 | align-items: center; 322 | justify-content: center; 323 | z-index: 9999; 324 | } 325 | 326 | .custom-symbol-dialog-content { 327 | background-color: var(--SmartThemeBlurTintColor); 328 | color: var(--SmartThemeBodyColor); 329 | border: 1px solid var(--SmartThemeBorderColor); 330 | border-radius: 8px; 331 | padding: 20px; 332 | width: 350px; 333 | max-width: 90%; 334 | margin: 0 20px; 335 | max-height: 90vh ; 336 | overflow-y: auto; 337 | box-shadow: 0 4px 12px var(--SmartThemeShadowColor); 338 | font-family: "Noto Sans", sans-serif; 339 | -webkit-font-smoothing: antialiased; 340 | box-sizing: border-box; 341 | } 342 | 343 | .custom-symbol-form { 344 | margin: 15px 0; 345 | } 346 | 347 | .form-group { 348 | margin-bottom: 10px; 349 | display: flex; 350 | align-items: center; 351 | } 352 | 353 | .form-group label { 354 | width: 100px; 355 | display: inline-block; 356 | color: rgb(220, 220, 220); 357 | } 358 | 359 | .form-group input, .form-group select { 360 | flex: 1; 361 | padding: 5px; 362 | border: 1px solid rgba(90, 90, 110, 0.7); 363 | background-color: rgba(50, 55, 65, 0.9); 364 | color: rgb(220, 220, 220); 365 | border-radius: 4px; 366 | } 367 | 368 | .custom-symbol-buttons { 369 | display: flex; 370 | justify-content: flex-end; 371 | gap: 10px; 372 | margin-top: 15px; 373 | } 374 | 375 | .custom-symbol-buttons button { 376 | padding: 5px 15px; 377 | border: 1px solid rgba(90, 90, 110, 0.7); 378 | background-color: rgba(50, 55, 65, 0.9); 379 | color: rgb(220, 220, 220); 380 | border-radius: 4px; 381 | cursor: pointer; 382 | } 383 | 384 | .custom-symbol-buttons button:hover { 385 | background-color: rgba(60, 65, 75, 0.9); 386 | } 387 | 388 | /* 自定义符号设置行样式 */ 389 | .custom-edit-btn, .custom-delete-btn { 390 | background: none; 391 | border: none; 392 | font-size: 16px; 393 | cursor: pointer; 394 | padding: 0; 395 | width: 24px; 396 | height: 24px; 397 | display: flex; 398 | align-items: center; 399 | justify-content: center; 400 | margin-right: 5px; /* 修改为右边距,因为位置调整了 */ 401 | opacity: 0.7; 402 | transition: opacity 0.2s; 403 | } 404 | 405 | .custom-edit-btn:hover, .custom-delete-btn:hover { 406 | opacity: 1; 407 | } 408 | 409 | .custom-delete-btn { 410 | color: rgba(255, 80, 80, 0.9); 411 | } 412 | 413 | /* 调整自定义按钮行的布局 */ 414 | .integrated-button-row[data-custom="true"] label { 415 | flex-grow: 1; 416 | margin-right: 0; /* 减少右边距,为编辑/删除按钮腾出空间 */ 417 | } 418 | 419 | .integrated-button-row[data-custom="true"] .shortcut-input { 420 | margin-left: auto; /* 将快捷键输入框推到右侧 */ 421 | } 422 | 423 | /* 添加自定义符号按钮 */ 424 | #add_custom_symbol_btn { 425 | width: 100%; 426 | margin: 10px 0; 427 | padding: 8px; 428 | } -------------------------------------------------------------------------------- /toolbar.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | --------------------------------------------------------------------------------