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

f81e14a2e8ce335254e83bf52ec82f4436bbe8e1

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
4 files changed, +26 -15Showing whitespace changes
public/index.html+1 -1
@@ -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>
public/locales/ru-ru.json+2 -2
@@ -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}",
public/locales/zh-tw.json+2 -2
@@ -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}",
public/scripts/world-info.js+21 -10
@@ -17,7 +17,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
17import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';17import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
18import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';18import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
19import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';19import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
20import { callGenericPopup, Popup, POPUP_TYPE } from './popup.js';20import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
21import { StructuredCloneMap } from './util/StructuredCloneMap.js';21import { StructuredCloneMap } from './util/StructuredCloneMap.js';
22import { renderTemplateAsync } from './templates.js';22import { renderTemplateAsync } from './templates.js';
23import { t } from './i18n.js';23import { 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 });
32563262
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 */
5427export async function moveWorldInfoEntry(sourceName, targetName, uid) {5435export 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) {
54665474
5467 const entryToMove = structuredClone(sourceData.entries[entryUidString]);5475 const entryToMove = structuredClone(sourceData.entries[entryUidString]);
54685476
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) {
54805487
5481 targetData.entries[newUid] = entryToMove;5488 targetData.entries[newUid] = entryToMove;
54825489
5490 if (deleteOriginal) {
5483 delete sourceData.entries[entryUidString];5491 delete sourceData.entries[entryUidString];
5484 // Remove from originalData if it exists5492 // Remove from originalData if it exists
5485 deleteWIOriginalDataValue(sourceData, entryUidString);5493 deleteWIOriginalDataValue(sourceData, entryUidString);
5486 // TODO: setWIOriginalDataValue5494 // TODO: setWIOriginalDataValue
5487 console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`);5495 console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`);
54885496 }
54895497
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}'.`);
54945502
54955503 console.log(`[WI Move] ${entryToMove.comment} ${deleteOriginal ? 'moved' : 'copied'} successfully to '${targetName}'.`);
5496 console.log(`[WI Move] ${entryToMove.comment} moved successfully to '${targetName}'.`);
54975504
5498 // Check if the currently viewed book in the editor is the source or target and reload it5505 // 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 }
55065513
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}`);