Add copy option to move WI entry (#4335) * Add copy option to move WI entry Closes #4334 * Update moveWorldInfoEntry log to indicate copy or move action
Signed| @@ -6624,7 +6624,7 @@ | ||
| 6624 | 6624 | </div> |
| 6625 | 6625 | </div> |
| 6626 | 6626 | </div> |
| 6627 | 6627 | <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move/Copy Entry to Another Lorebook" data-i18n="[title]Move/Copy Entry to Another Lorebook"></i> |
| 6628 | 6628 | <i class="menu_button duplicate_entry_button fa-solid fa-paste" title="Duplicate world info entry" data-i18n="[title]Duplicate world info entry" type="submit" value=""></i> |
| 6629 | 6629 | <i class="menu_button delete_entry_button fa-solid fa-trash-can" title="Delete world info entry" data-i18n="[title]Delete world info entry" type="submit" value=""></i> |
| 6630 | 6630 | </div> |
| @@ -2336,10 +2336,10 @@ | ||
| 2336 | 2336 | "Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)", |
| 2337 | 2337 | "( None )": "(Отсутствует)", |
| 2338 | 2338 | "Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами", |
| 2339 | 2339 | "Move/Copy Entry to Another Lorebook": "Переместить/Скопировать запись в другой лорбук", |
| 2340 | 2340 | "There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.", |
| 2341 | 2341 | "Select Target Lorebook": "Выберите куда переместить", |
| 2342 | 2342 | "Move/Copy '${0}' to:": "Переместить/Скопировать '${0}' в:", |
| 2343 | 2343 | "Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.", |
| 2344 | 2344 | "Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной", |
| 2345 | 2345 | "Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}", |
| @@ -2535,7 +2535,7 @@ | ||
| 2535 | 2535 | "Delete Persona": "刪除使用者角色", |
| 2536 | 2536 | "Duplicate Persona": "複製使用者角色", |
| 2537 | 2537 | "Expand and zoom": "展開並縮放", |
| 2538 | 2538 | "Move/Copy Entry to Another Lorebook": "將條目移動到其他知識書將條目移動/複製到其他知識書", |
| 2539 | 2539 | "Persona is locked to the current character": "使用者角色已綁定至目前角色", |
| 2540 | 2540 | "Persona is locked to the current chat": "使用者角色已綁定至目前聊天", |
| 2541 | 2541 | "Rename a prompt": "重新命名提示詞", |
| @@ -2595,7 +2595,7 @@ | ||
| 2595 | 2595 | "Tie this entry to specific characters or characters with specific tags": "將此項綁定至特定角色或標籤", |
| 2596 | 2596 | "There are no other lorebooks to move to.": "暫無其他知識書可供移動。", |
| 2597 | 2597 | "Select Target Lorebook": "選擇目標知識書", |
| 2598 | 2598 | "Move/Copy '${0}' to:": "將「${0}」移動至移動/複製至:", |
| 2599 | 2599 | "Please select a target lorebook.": "請選擇目標知識書。", |
| 2600 | 2600 | "Scan depth cannot be negative": "掃描深度不能為負值", |
| 2601 | 2601 | "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}", |
| @@ -17,7 +17,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from ' | ||
| 17 | 17 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 18 | 18 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 19 | 19 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| 20 | 20 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 21 | 21 | import { StructuredCloneMap } from './util/StructuredCloneMap.js'; |
| 22 | 22 | import { renderTemplateAsync } from './templates.js'; |
| 23 | 23 | import { t } from './i18n.js'; |
| @@ -3232,7 +3232,7 @@ export async function getWorldEntry(name, data, entry) { | ||
| 3232 | 3232 | return; |
| 3233 | 3233 | } |
| 3234 | 3234 | const wrapper = document.createElement('div'); |
| 3235 | 3235 | wrapper.textContent = t`Move/Copy '${sourceName}' to:`; |
| 3236 | 3236 | const container = document.createElement('div'); |
| 3237 | 3237 | container.appendChild(wrapper); |
| 3238 | 3238 | container.appendChild(select); |
| @@ -3240,10 +3240,15 @@ export async function getWorldEntry(name, data, entry) { | ||
| 3240 | 3240 | select.addEventListener('change', function () { |
| 3241 | 3241 | selectedWorldIndex = this.value === '' ? -1 : Number(this.value); |
| 3242 | 3242 | }); |
| 3243 | 3243 | const popupConfirmpopup = awaitnew callGenericPopupPopup(container, POPUP_TYPE.CONFIRM, '', { |
| 3244 | - okButton: t`Move`, | |
| 3245 | 3244 | cancelButton: t`Cancel`, |
| 3245 | + customButtons: [ | |
| 3246 | + { text: t`Move`, result: POPUP_RESULT.CUSTOM1 }, | |
| 3247 | + { text: t`Copy`, result: POPUP_RESULT.CUSTOM2 }, | |
| 3248 | + ], | |
| 3246 | 3249 | }); |
| 3250 | + popup.okButton.style.display = 'none'; // Hide the default OK button | |
| 3251 | + const popupConfirm = await popup.show(); | |
| 3247 | 3252 | if (!popupConfirm) return; |
| 3248 | 3253 | if (selectedWorldIndex === -1) return; |
| 3249 | 3254 | const selectedValue = world_names[selectedWorldIndex]; |
| @@ -3251,7 +3256,8 @@ export async function getWorldEntry(name, data, entry) { | ||
| 3251 | 3256 | toastr.warning(t`Please select a target lorebook.`); |
| 3252 | 3257 | return; |
| 3253 | 3258 | } |
| 3254 | - await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid); | |
| 3259 | + const deleteOriginal = popupConfirm === POPUP_RESULT.CUSTOM1; | |
| 3260 | + await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid, { deleteOriginal }); | |
| 3255 | 3261 | }); |
| 3256 | 3262 | |
| 3257 | 3263 | let drawerInitialized = false; |
| @@ -5422,9 +5428,11 @@ export async function assignLorebookToChat(event) { | ||
| 5422 | 5428 | * @param {string} sourceName - The name of the source lorebook file. |
| 5423 | 5429 | * @param {string} targetName - The name of the target lorebook file. |
| 5424 | 5430 | * @param {string|number} uid - The UID of the entry to move from the source lorebook. |
| 5431 | + * @param {Object} options - Additional options for the move operation. | |
| 5432 | + * @param {boolean} [options.deleteOriginal=true] - Whether to delete the original entry from the source lorebook after moving it. | |
| 5425 | 5433 | * @returns {Promise<boolean>} True if the move was successful, false otherwise. |
| 5426 | 5434 | */ |
| 5427 | 5435 | export async function moveWorldInfoEntry(sourceName, targetName, uid, { deleteOriginal = true } = {}) { |
| 5428 | 5436 | if (sourceName === targetName) { |
| 5429 | 5437 | return false; |
| 5430 | 5438 | } |
| @@ -5466,7 +5474,6 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) { | ||
| 5466 | 5474 | |
| 5467 | 5475 | const entryToMove = structuredClone(sourceData.entries[entryUidString]); |
| 5468 | 5476 | |
| 5469 | - | |
| 5470 | 5477 | const newUid = getFreeWorldEntryUid(targetData); |
| 5471 | 5478 | if (newUid === null) { |
| 5472 | 5479 | console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); |
| @@ -5480,20 +5487,20 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) { | ||
| 5480 | 5487 | |
| 5481 | 5488 | targetData.entries[newUid] = entryToMove; |
| 5482 | 5489 | |
| 5483 | - delete sourceData.entries[entryUidString]; | |
| 5490 | + if (deleteOriginal) { | |
| 5484 | - // Remove from originalData if it exists | |
| 5491 | + delete sourceData.entries[entryUidString]; | |
| 5485 | - deleteWIOriginalDataValue(sourceData, entryUidString); | |
| 5492 | + // Remove from originalData if it exists | |
| 5486 | - // TODO: setWIOriginalDataValue | |
| 5493 | + deleteWIOriginalDataValue(sourceData, entryUidString); | |
| 5487 | - console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); | |
| 5494 | + // TODO: setWIOriginalDataValue | |
| 5488 | - | |
| 5495 | + console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); | |
| 5496 | + } | |
| 5489 | 5497 | |
| 5490 | 5498 | await saveWorldInfo(targetName, targetData, true); |
| 5491 | 5499 | console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); |
| 5492 | 5500 | await saveWorldInfo(sourceName, sourceData, true); |
| 5493 | 5501 | console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); |
| 5494 | 5502 | |
| 5495 | - | |
| 5503 | + console.log(`[WI Move] ${entryToMove.comment} ${deleteOriginal ? 'moved' : 'copied'} successfully to '${targetName}'.`); | |
| 5496 | - console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`); | |
| 5497 | 5504 | |
| 5498 | 5505 | // Check if the currently viewed book in the editor is the source or target and reload it |
| 5499 | 5506 | const currentEditorBookIndex = Number($('#world_editor_select').val()); |
| @@ -5504,6 +5511,10 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) { | ||
| 5504 | 5511 | } |
| 5505 | 5512 | } |
| 5506 | 5513 | |
| 5514 | + toastr.success(deleteOriginal | |
| 5515 | + ? t`Entry moved successfully from '${sourceName}' to '${targetName}'.` | |
| 5516 | + : t`Entry copied successfully to '${targetName}'.`); | |
| 5517 | + | |
| 5507 | 5518 | return true; |
| 5508 | 5519 | } catch (error) { |
| 5509 | 5520 | toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); |