From bf91d9afc90fc3650be050886a85a86728cc984a Mon Sep 17 00:00:00 2001 From: Stardust <1441308506a@gmail.com> Date: Mon, 16 Mar 2026 02:14:32 +0800 Subject: [PATCH] Chat lorebook click unify (#5246) * Unify chat lorebook button click behavior with character lorebook button * Update locale strings for chat lorebook button * Unify chat lorebook button click behavior with character lorebook button * Update locale strings for chat lorebook button * Also unify character lorebook button to support Alt modifier * Update locale strings for character lorebook button * Update public/locales/fr-fr.json Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add long-press support for lorebook buttons on mobile * Update locale strings for lorebook button long-press * Fix long-press to use event delegation for dynamic elements --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- public/index.html | 20 +++++++++++++---- public/locales/fr-fr.json | 6 ++--- public/locales/ko-kr.json | 2 +- public/locales/ru-ru.json | 6 ++--- public/locales/zh-cn.json | 6 ++--- public/locales/zh-tw.json | 6 ++--- public/scripts/personas.js | 16 ++++++++++++-- public/scripts/utils.js | 43 ++++++++++++++++++++++++++++++++++++ public/scripts/world-info.js | 19 ++++++++++++---- 9 files changed, 101 insertions(+), 23 deletions(-) diff --git a/public/index.html b/public/index.html index 8b98ceb48..62f452854 100644 --- a/public/index.html +++ b/public/index.html @@ -5832,12 +5832,18 @@
- +
+

@@ -6003,8 +6009,8 @@ - - + + @@ -6155,8 +6161,14 @@
- +
+
diff --git a/public/locales/fr-fr.json b/public/locales/fr-fr.json index 537d46f2f..2abacb386 100644 --- a/public/locales/fr-fr.json +++ b/public/locales/fr-fr.json @@ -1518,10 +1518,10 @@ "Contain": "Contenir", "Stretch": "Étirer", "Center": "Centrer", - "Persona Lore Alt+Click to open the lorebook": "Lore de persona\nAlt+Click pour ouvrir le lorebook", + "persona_lore_button_title": "Lore de persona\n\nCliquer pour charger\nMajuscule/Alt-clic ou appui long pour ouvrir la fenêtre contextuelle « Lien vers le Lorebook de persona ».", "None (disabled)": "Aucun (désactivé)", - "world_button_title": "Lore de personnage\n\nCliquer pour charger\nMajuscule-clic pour ouvrir la fenêtre contextuelle « Lien vers le Wold Info ».", - "Chat Lore Alt+Click to open the lorebook": "Lore du chat\nAlt+Click pour ouvrir le lorebook", + "world_button_title": "Lore de personnage\n\nCliquer pour charger\nMajuscule/Alt-clic ou appui long pour ouvrir la fenêtre contextuelle « Lien vers le World Info ».", + "chat_lorebook_button_title": "Lore du chat\n\nCliquer pour charger\nMajuscule/Alt-clic ou appui long pour ouvrir la fenêtre contextuelle « Lien vers le Lorebook du chat ».", "Manual": "Manuel", "Duplicate persona": "Dupliquer la persona", "popup-button-crop": "Recadrer", diff --git a/public/locales/ko-kr.json b/public/locales/ko-kr.json index 747001ef5..4354c9220 100644 --- a/public/locales/ko-kr.json +++ b/public/locales/ko-kr.json @@ -1535,7 +1535,7 @@ "Only apply color as accent": "색상은 오직 강조로써만 적용됩니다", "qr--colorClear": "색상 지우기", "Color": "색상", - "world_button_title": "캐릭터 로어. 클릭하여 로드하세요. Shift를 클릭하면 '월드 인포 링크' 팝업이 열립니다.", + "world_button_title": "캐릭터 로어. 클릭하여 로드하세요. Shift/Alt+클릭 또는 길게 누르면 '월드 인포 링크' 팝업이 열립니다.", "Select TTS Provider": "TTS 공급자 선택", "tts_enabled": "활성화", "Narrate user messages": "사용자 메시지 나레이션", diff --git a/public/locales/ru-ru.json b/public/locales/ru-ru.json index 2a76de5ca..448c0b359 100644 --- a/public/locales/ru-ru.json +++ b/public/locales/ru-ru.json @@ -1904,7 +1904,7 @@ "Save": "Сохранить", "Chat Lorebook": "Лорбук для чата", "chat_world_template_txt": "Выбранный мир будет привязан к этому чату. Будет работать наряду с глобальным лорбуком и лором персонажа.", - "world_button_title": "Лор персонажа\n\nНажмите, чтобы загрузить\nShift + ЛКМ, чтобы открыть диалог привязки мира", + "world_button_title": "Лор персонажа\n\nНажмите, чтобы загрузить\nShift / Alt + ЛКМ или долгое нажатие, чтобы открыть диалог привязки мира", "No auxillary Lorebooks set. Click here to select.": "Вспомогательный лорбук не выбран. Нажмите, чтобы выбрать.", "ext_regex_user_input_desc": "Отправленные вами сообщения.", "ext_regex_ai_input_desc": "Полученные от API ответы.", @@ -2130,7 +2130,7 @@ "openai_reasoning_effort_low": "Поверхностные", "openai_reasoning_effort_medium": "Обычные", "openai_reasoning_effort_high": "Подробные", - "Persona Lore Alt+Click to open the lorebook": "Лорбук данной персоны\nAlt + ЛКМ чтобы открыть лорбук", + "persona_lore_button_title": "Лорбук данной персоны\n\nНажмите, чтобы открыть\nShift / Alt + ЛКМ или долгое нажатие, чтобы открыть диалог привязки лорбука", "Persona Lorebook for": "Лорбук для персоны", "persona_world_template_txt": "Выбранный мир будет привязан к этой персоне. Будет работать вместе с глобальным лорбуком и лорбуками персонажа и чата.", "Global list": "Глобальный список", @@ -2145,7 +2145,7 @@ "Enter a valid API URL": "Введите корректный адрес API", "No Ollama model selected.": "Не выбрана модель Ollama", "Background Fitting": "Способ подгонки фона под разрешение", - "Chat Lore Alt+Click to open the lorebook": "Лорбук данного чата\nAlt + ЛКМ чтобы открыть лорбук", + "chat_lorebook_button_title": "Лорбук данного чата\n\nНажмите, чтобы открыть\nShift / Alt + ЛКМ или долгое нажатие, чтобы открыть диалог привязки лорбука", "Token Counter": "Подсчитать токены", "Type / paste in the box below to see the number of tokens in the text.": "Введите или вставьте текст в окошко ниже, чтобы подсчитать количество токенов в нём.", "Selected tokenizer:": "Выбранный токенайзер:", diff --git a/public/locales/zh-cn.json b/public/locales/zh-cn.json index 5ca3ea96a..d04f09c07 100644 --- a/public/locales/zh-cn.json +++ b/public/locales/zh-cn.json @@ -1053,7 +1053,7 @@ "Current Persona": "当前人设", "Rename Persona": "重命名人设", "Click to set user name for all messages": "点击为所有消息设置用户名", - "Persona Lore Alt+Click to open the lorebook": "人设世界书\nAlt+单击 打开世界书", + "persona_lore_button_title": "人设世界书\n\n单击加载\nShift/Alt+单击或长按打开“链接到人设世界书”弹出窗口", "Change Persona Image": "更改人设图", "Duplicate Persona": "复制人设", "Delete Persona": "删除人设", @@ -1093,8 +1093,8 @@ "Click to select a new avatar for this character": "单击以为此角色选择新的头像", "Add to Favorites": "添加到收藏夹", "Advanced Definition": "高级定义", - "world_button_title": "角色世界书\n\n单击加载\nShift+单击打开“链接到世界书”弹出窗口", - "Chat Lore Alt+Click to open the lorebook": "聊天世界书\nAlt+单击打开世界书", + "world_button_title": "角色世界书\n\n单击加载\nShift/Alt+单击或长按打开“链接到世界书”弹出窗口", + "chat_lorebook_button_title": "聊天世界书\n\n单击加载\nShift/Alt+单击或长按打开“链接到聊天世界书”弹出窗口", "Connected Personas": "绑定的用户设定", "Export and Download": "导出并下载", "Duplicate Character": "复制角色", diff --git a/public/locales/zh-tw.json b/public/locales/zh-tw.json index abdb91293..b722abf6c 100644 --- a/public/locales/zh-tw.json +++ b/public/locales/zh-tw.json @@ -1543,7 +1543,7 @@ "When enabled, nodes that have swipes splitting off of them will appear subtly larger, in addition to having the double border.": "啟用後,具分支滑動的節點將顯示雙重邊框,還會略微放大。", "WI Entry Status:🔵 Constant🟢 Normal🔗 Vectorized": "世界資訊條目狀態:\\r🔵 恆定\\r🟢 正常\\r🔗 向量化", "Width of a node, in pixels at zoom level 1.0.": "縮放等級為 1.0 時,節點的像素寬度。", - "world_button_title": "角色背景設定\n「Shift+點選」可開啟「連結至世界資訊」彈窗", + "world_button_title": "角色背景設定\n「Shift/Alt+點選」或長按可開啟「連結至世界資訊」彈窗", "# of Beams": "# of Beams", "Additional Parameters": "其他參數", "All": "全部", @@ -2367,8 +2367,8 @@ "Contain": "自適應", "Stretch": "拉伸", "Center": "置中", - "Persona Lore Alt+Click to open the lorebook": "「Alt+點選」可開啟角色知識書", - "Chat Lore Alt+Click to open the lorebook": "「Alt+點選」可開啟聊天知識書", + "persona_lore_button_title": "人設知識書\n\n點選以載入\nShift/Alt+點選或長按開啟「連結人設知識書」彈窗", + "chat_lorebook_button_title": "聊天知識書\n\n點選以載入\nShift/Alt+點選或長按開啟「連結聊天知識書」彈窗", "Function Tool": "功能工具", "Functions in this category are for advanced users only. Don't click anything if you're not sure about the consequences.": "此類功能僅供高階使用者使用。若不確定後果,請勿點選任何內容。", "Are you sure you want to delete this user?": "確定要刪除該使用者嗎?", diff --git a/public/scripts/personas.js b/public/scripts/personas.js index 4fede9440..1e50dddc4 100644 --- a/public/scripts/personas.js +++ b/public/scripts/personas.js @@ -24,7 +24,7 @@ import { } from '../script.js'; import { persona_description_positions, power_user } from './power-user.js'; import { getTokenCountAsync } from './tokenizers.js'; -import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler } from './utils.js'; +import { PAGINATION_TEMPLATE, clearInfoBlock, debounce, delay, download, ensureImageFormatSupported, flashHighlight, getBase64Async, getCharIndex, isFalseBoolean, isTrueBoolean, onlyUnique, parseJsonFile, setInfoBlock, localizePagination, renderPaginationDropdown, paginationDropdownChangeHandler, addLongPressEvent } from './utils.js'; import { debounce_timeout } from './constants.js'; import { FILTER_TYPES, FilterHelper } from './filters.js'; import { groups, selected_group } from './group-chats.js'; @@ -1182,7 +1182,7 @@ async function onPersonaLoreButtonClick(event) { return; } - if (event.altKey && selectedLorebook) { + if (selectedLorebook && !event.shiftKey && !event.altKey) { openWorldInfoEditor(selectedLorebook); return; } @@ -2003,6 +2003,18 @@ export async function initPersonas() { $('#persona_depth_value').on('input', onPersonaDescriptionDepthValueInput); $('#persona_depth_role').on('input', onPersonaDescriptionDepthRoleInput); $('#persona_lore_button').on('click', onPersonaLoreButtonClick); + addLongPressEvent('#persona_lore_button', function () { + onPersonaLoreButtonClick({ shiftKey: true, altKey: false }); + }); + $('#persona-management-dropdown').on('change', async function () { + const target = $(this).find(':selected').attr('id'); + $(this).prop('selectedIndex', 0); + switch (target) { + case 'persona_lorebook_link': + await onPersonaLoreButtonClick({ shiftKey: true, altKey: false }); + break; + } + }); $('#personas_backup').on('click', onBackupPersonas); $('#personas_restore').on('click', () => $('#personas_restore_input').trigger('click')); $('#personas_restore_input').on('change', onPersonasRestoreInput); diff --git a/public/scripts/utils.js b/public/scripts/utils.js index bedcbba27..94dce24af 100644 --- a/public/scripts/utils.js +++ b/public/scripts/utils.js @@ -2938,3 +2938,46 @@ export function createTimeout(ms, errorMessage = '') { setTimeout(() => reject(new Error(errorMessage)), ms); }); } + +/** + * Registers a long-press (touch hold) event as an alternative to modifier+click. + * Supports event delegation for dynamically created elements. + * @param {string} selector CSS selector for target elements + * @param {function} callback Callback to invoke on long-press, `this` is the matched element + * @param {number} [delay=500] Long-press duration in ms + */ +export function addLongPressEvent(selector, callback, delay = 500) { + let timer = null; + let fired = false; + let target = null; + + document.addEventListener('touchstart', function (event) { + const el = event.target.closest(selector); + if (!el) return; + target = el; + fired = false; + timer = setTimeout(() => { + fired = true; + event.preventDefault(); + callback.call(el, event); + }, delay); + }, { passive: false }); + + document.addEventListener('touchend', cancelTimer); + document.addEventListener('touchmove', cancelTimer); + document.addEventListener('touchcancel', cancelTimer); + + document.addEventListener('click', function (event) { + if (fired && target && target.contains(event.target)) { + event.preventDefault(); + event.stopImmediatePropagation(); + fired = false; + target = null; + } + }, true); + + function cancelTimer() { + clearTimeout(timer); + timer = null; + } +} diff --git a/public/scripts/world-info.js b/public/scripts/world-info.js index 90e6ecf1d..3cb4881b3 100644 --- a/public/scripts/world-info.js +++ b/public/scripts/world-info.js @@ -1,7 +1,7 @@ import { Fuse } from '../lib.js'; import { saveSettings, substituteParams, getRequestHeaders, chat_metadata, this_chid, characters, saveCharacterDebounced, menu_type, eventSource, event_types, getExtensionPromptByName, saveMetadata, getCurrentChatId, extension_prompt_roles, create_save, createOrEditCharacter, name1 } from '../script.js'; -import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents, uuidv4, normalizeArray, getUniqueName, logSlashCommandWarn } from './utils.js'; +import { download, debounce, initScrollHeight, resetScrollHeight, parseJsonFile, extractDataFromPng, getFileBuffer, getCharaFilename, getSortableDelay, escapeRegex, PAGINATION_TEMPLATE, navigation_option, waitUntilCondition, isTrueBoolean, setValueByPath, flashHighlight, select2ModifyOptions, getSelect2OptionId, dynamicSelect2DataViaAjax, highlightRegex, select2ChoiceClickSubscribe, isFalseBoolean, getSanitizedFilename, checkOverwriteExistingData, getStringHash, parseStringArray, cancelDebounce, findChar, onlyUnique, equalsIgnoreCaseAndAccents, uuidv4, normalizeArray, getUniqueName, logSlashCommandWarn, addLongPressEvent } from './utils.js'; import { extension_settings, getContext } from './extensions.js'; import { NOTE_MODULE_NAME, metadata_keys, shouldWIAddPrompt } from './authors-note.js'; import { isMobile } from './RossAscends-mods.js'; @@ -5738,7 +5738,7 @@ export function openWorldInfoEditor(worldName) { export async function assignLorebookToChat(event) { const selectedName = chat_metadata[METADATA_KEY]; - if (selectedName && event.altKey) { + if (selectedName && !event.shiftKey && !event.altKey) { openWorldInfoEditor(selectedName); return; } @@ -6110,15 +6110,18 @@ export function initWorldInfo() { const worldName = characters[chid]?.data?.extensions?.world; const hasEmbed = checkEmbeddedWorld(chid); - if (worldName && world_names.includes(worldName) && !event.shiftKey) { + if (worldName && world_names.includes(worldName) && !event.shiftKey && !event.altKey) { openWorldInfoEditor(worldName); - } else if (hasEmbed && !event.shiftKey) { + } else if (hasEmbed && !event.shiftKey && !event.altKey) { await importEmbeddedWorldInfo(); saveCharacterDebounced(); } else { openSetWorldMenu(); } }); + addLongPressEvent('#world_button', function () { + $(this).trigger($.Event('click', { shiftKey: true })); + }); const debouncedWorldInfoSearch = debounce((searchQuery) => { worldInfoFilter.setFilterData(FILTER_TYPES.WORLD_INFO_SEARCH, searchQuery); @@ -6140,6 +6143,14 @@ export function initWorldInfo() { }); $(document).on('click', '.chat_lorebook_button', assignLorebookToChat); + addLongPressEvent('.chat_lorebook_button', function () { + assignLorebookToChat({ shiftKey: true, altKey: false }); + }); + + $('#group-chat-lorebook-dropdown').on('change', async function () { + $(this).prop('selectedIndex', 0); + await assignLorebookToChat({ shiftKey: true, altKey: false }); + }); // Not needed on mobile if (!isMobile()) {