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 | </div> | 6624 | </div> |
| 6625 | </div> | 6625 | </div> |
| 6626 | </div> | 6626 | </div> |
| 6627 | <i class="menu_button move_entry_button fa-solid fa-right-left" title="Move Entry to Another Lorebook" data-i18n="[title]Move Entry to Another Lorebook"></i> | 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 | <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> | 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 | <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> | 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 | </div> | 6630 | </div> |
| @@ -2336,10 +2336,10 @@ | |||
| 2336 | "Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)", | 2336 | "Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)", |
| 2337 | "( None )": "(Отсутствует)", | 2337 | "( None )": "(Отсутствует)", |
| 2338 | "Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами", | 2338 | "Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами", |
| 2339 | "Move Entry to Another Lorebook": "Переместить запись в другой лорбук", | 2339 | "Move/Copy Entry to Another Lorebook": "Переместить/Скопировать запись в другой лорбук", |
| 2340 | "There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.", | 2340 | "There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.", |
| 2341 | "Select Target Lorebook": "Выберите куда переместить", | 2341 | "Select Target Lorebook": "Выберите куда переместить", |
| 2342 | "Move '${0}' to:": "Переместить '${0}' в:", | 2342 | "Move/Copy '${0}' to:": "Переместить/Скопировать '${0}' в:", |
| 2343 | "Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.", | 2343 | "Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.", |
| 2344 | "Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной", | 2344 | "Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной", |
| 2345 | "Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}", | 2345 | "Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}", |
| @@ -2535,7 +2535,7 @@ | |||
| 2535 | "Delete Persona": "刪除使用者角色", | 2535 | "Delete Persona": "刪除使用者角色", |
| 2536 | "Duplicate Persona": "複製使用者角色", | 2536 | "Duplicate Persona": "複製使用者角色", |
| 2537 | "Expand and zoom": "展開並縮放", | 2537 | "Expand and zoom": "展開並縮放", |
| 2538 | "Move Entry to Another Lorebook": "將條目移動到其他知識書", | 2538 | "Move/Copy Entry to Another Lorebook": "將條目移動/複製到其他知識書", |
| 2539 | "Persona is locked to the current character": "使用者角色已綁定至目前角色", | 2539 | "Persona is locked to the current character": "使用者角色已綁定至目前角色", |
| 2540 | "Persona is locked to the current chat": "使用者角色已綁定至目前聊天", | 2540 | "Persona is locked to the current chat": "使用者角色已綁定至目前聊天", |
| 2541 | "Rename a prompt": "重新命名提示詞", | 2541 | "Rename a prompt": "重新命名提示詞", |
| @@ -2595,7 +2595,7 @@ | |||
| 2595 | "Tie this entry to specific characters or characters with specific tags": "將此項綁定至特定角色或標籤", | 2595 | "Tie this entry to specific characters or characters with specific tags": "將此項綁定至特定角色或標籤", |
| 2596 | "There are no other lorebooks to move to.": "暫無其他知識書可供移動。", | 2596 | "There are no other lorebooks to move to.": "暫無其他知識書可供移動。", |
| 2597 | "Select Target Lorebook": "選擇目標知識書", | 2597 | "Select Target Lorebook": "選擇目標知識書", |
| 2598 | "Move '${0}' to:": "將「${0}」移動至:", | 2598 | "Move/Copy '${0}' to:": "將「${0}」移動/複製至:", |
| 2599 | "Please select a target lorebook.": "請選擇目標知識書。", | 2599 | "Please select a target lorebook.": "請選擇目標知識書。", |
| 2600 | "Scan depth cannot be negative": "掃描深度不能為負值", | 2600 | "Scan depth cannot be negative": "掃描深度不能為負值", |
| 2601 | "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}", | 2601 | "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}", |
| @@ -17,7 +17,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from ' | |||
| 17 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; | 17 | import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js'; |
| 18 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; | 18 | import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js'; |
| 19 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; | 19 | import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js'; |
| 20 | import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js'; | 20 | import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js'; |
| 21 | import { StructuredCloneMap } from './util/StructuredCloneMap.js'; | 21 | import { StructuredCloneMap } from './util/StructuredCloneMap.js'; |
| 22 | import { renderTemplateAsync } from './templates.js'; | 22 | import { renderTemplateAsync } from './templates.js'; |
| 23 | import { t } from './i18n.js'; | 23 | import { t } from './i18n.js'; |
| @@ -3232,7 +3232,7 @@ export async function getWorldEntry(name, data, entry) { | |||
| 3232 | return; | 3232 | return; |
| 3233 | } | 3233 | } |
| 3234 | const wrapper = document.createElement('div'); | 3234 | const wrapper = document.createElement('div'); |
| 3235 | wrapper.textContent = t`Move '${sourceName}' to:`; | 3235 | wrapper.textContent = t`Move/Copy '${sourceName}' to:`; |
| 3236 | const container = document.createElement('div'); | 3236 | const container = document.createElement('div'); |
| 3237 | container.appendChild(wrapper); | 3237 | container.appendChild(wrapper); |
| 3238 | container.appendChild(select); | 3238 | container.appendChild(select); |
| @@ -3240,10 +3240,15 @@ export async function getWorldEntry(name, data, entry) { | |||
| 3240 | select.addEventListener('change', function () { | 3240 | select.addEventListener('change', function () { |
| 3241 | selectedWorldIndex = this.value === '' ? -1 : Number(this.value); | 3241 | selectedWorldIndex = this.value === '' ? -1 : Number(this.value); |
| 3242 | }); | 3242 | }); |
| 3243 | const popupConfirm = await callGenericPopup(container, POPUP_TYPE.CONFIRM, '', { | 3243 | const popup = new Popup(container, POPUP_TYPE.CONFIRM, '', { |
| 3244 | okButton: t`Move`, | ||
| 3245 | cancelButton: t`Cancel`, | 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 | if (!popupConfirm) return; | 3252 | if (!popupConfirm) return; |
| 3248 | if (selectedWorldIndex === -1) return; | 3253 | if (selectedWorldIndex === -1) return; |
| 3249 | const selectedValue = world_names[selectedWorldIndex]; | 3254 | const selectedValue = world_names[selectedWorldIndex]; |
| @@ -3251,7 +3256,8 @@ export async function getWorldEntry(name, data, entry) { | |||
| 3251 | toastr.warning(t`Please select a target lorebook.`); | 3256 | toastr.warning(t`Please select a target lorebook.`); |
| 3252 | return; | 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 | let drawerInitialized = false; | 3263 | let drawerInitialized = false; |
| @@ -5422,9 +5428,11 @@ export async function assignLorebookToChat(event) { | |||
| 5422 | * @param {string} sourceName - The name of the source lorebook file. | 5428 | * @param {string} sourceName - The name of the source lorebook file. |
| 5423 | * @param {string} targetName - The name of the target lorebook file. | 5429 | * @param {string} targetName - The name of the target lorebook file. |
| 5424 | * @param {string|number} uid - The UID of the entry to move from the source lorebook. | 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 | * @returns {Promise<boolean>} True if the move was successful, false otherwise. | 5433 | * @returns {Promise<boolean>} True if the move was successful, false otherwise. |
| 5426 | */ | 5434 | */ |
| 5427 | export async function moveWorldInfoEntry(sourceName, targetName, uid) { | 5435 | export async function moveWorldInfoEntry(sourceName, targetName, uid, { deleteOriginal = true } = {}) { |
| 5428 | if (sourceName === targetName) { | 5436 | if (sourceName === targetName) { |
| 5429 | return false; | 5437 | return false; |
| 5430 | } | 5438 | } |
| @@ -5466,7 +5474,6 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) { | |||
| 5466 | 5474 | ||
| 5467 | const entryToMove = structuredClone(sourceData.entries[entryUidString]); | 5475 | const entryToMove = structuredClone(sourceData.entries[entryUidString]); |
| 5468 | 5476 | ||
| 5469 | |||
| 5470 | const newUid = getFreeWorldEntryUid(targetData); | 5477 | const newUid = getFreeWorldEntryUid(targetData); |
| 5471 | if (newUid === null) { | 5478 | if (newUid === null) { |
| 5472 | console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); | 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 | targetData.entries[newUid] = entryToMove; | 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 | await saveWorldInfo(targetName, targetData, true); | 5498 | await saveWorldInfo(targetName, targetData, true); |
| 5491 | console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); | 5499 | console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); |
| 5492 | await saveWorldInfo(sourceName, sourceData, true); | 5500 | await saveWorldInfo(sourceName, sourceData, true); |
| 5493 | console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); | 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 | // Check if the currently viewed book in the editor is the source or target and reload it | 5505 | // Check if the currently viewed book in the editor is the source or target and reload it |
| 5499 | const currentEditorBookIndex = Number($('#world_editor_select').val()); | 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 | return true; | 5518 | return true; |
| 5508 | } catch (error) { | 5519 | } catch (error) { |
| 5509 | toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); | 5520 | toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); |