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 @@
66246624 </div>
66256625 </div>
66266626 </div>
66276627 <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>
66286628 <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>
66296629 <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>
66306630 </div>
public/locales/ru-ru.json+2 -2
@@ -2336,10 +2336,10 @@
23362336 "Can be used to automatically activate Quick Replies": "Используется для автоматической активации быстрых ответов (Quick Replies)",
23372337 "( None )": "(Отсутствует)",
23382338 "Tie this entry to specific characters or characters with specific tags": "Привязать запись к опред. персонажам или персонажам с заданными тегами",
23392339 "Move/Copy Entry to Another Lorebook": "Переместить/Скопировать запись в другой лорбук",
23402340 "There are no other lorebooks to move to.": "Некуда перемещать: не найдено других лорбуков.",
23412341 "Select Target Lorebook": "Выберите куда переместить",
23422342 "Move/Copy '${0}' to:": "Переместить/Скопировать '${0}' в:",
23432343 "Please select a target lorebook.": "Выберите лорбук, в который будет перемещена запись.",
23442344 "Scan depth cannot be negative": "Глубина сканирования не может быть отрицательной",
23452345 "Scan depth cannot exceed ${0}": "Глубина сканирования не может превышать ${0}",
public/locales/zh-tw.json+2 -2
@@ -2535,7 +2535,7 @@
25352535 "Delete Persona": "刪除使用者角色",
25362536 "Duplicate Persona": "複製使用者角色",
25372537 "Expand and zoom": "展開並縮放",
25382538 "Move/Copy Entry to Another Lorebook": "將條目移動到其他知識書將條目移動/複製到其他知識書",
25392539 "Persona is locked to the current character": "使用者角色已綁定至目前角色",
25402540 "Persona is locked to the current chat": "使用者角色已綁定至目前聊天",
25412541 "Rename a prompt": "重新命名提示詞",
@@ -2595,7 +2595,7 @@
25952595 "Tie this entry to specific characters or characters with specific tags": "將此項綁定至特定角色或標籤",
25962596 "There are no other lorebooks to move to.": "暫無其他知識書可供移動。",
25972597 "Select Target Lorebook": "選擇目標知識書",
25982598 "Move/Copy '${0}' to:": "將「${0}」移動至移動/複製至:",
25992599 "Please select a target lorebook.": "請選擇目標知識書。",
26002600 "Scan depth cannot be negative": "掃描深度不能為負值",
26012601 "Scan depth cannot exceed ${0}": "掃描深度不能超過 ${0}",
public/scripts/world-info.js+21 -10
@@ -17,7 +17,7 @@ import { ARGUMENT_TYPE, SlashCommandArgument, SlashCommandNamedArgument } from '
1717import { SlashCommandEnumValue, enumTypes } from './slash-commands/SlashCommandEnumValue.js';
1818import { commonEnumProviders, enumIcons } from './slash-commands/SlashCommandCommonEnumsProvider.js';
1919import { SlashCommandClosure } from './slash-commands/SlashCommandClosure.js';
2020import { callGenericPopup, Popup, POPUP_RESULT, POPUP_TYPE } from './popup.js';
2121import { StructuredCloneMap } from './util/StructuredCloneMap.js';
2222import { renderTemplateAsync } from './templates.js';
2323import { t } from './i18n.js';
@@ -3232,7 +3232,7 @@ export async function getWorldEntry(name, data, entry) {
32323232 return;
32333233 }
32343234 const wrapper = document.createElement('div');
32353235 wrapper.textContent = t`Move/Copy '${sourceName}' to:`;
32363236 const container = document.createElement('div');
32373237 container.appendChild(wrapper);
32383238 container.appendChild(select);
@@ -3240,10 +3240,15 @@ export async function getWorldEntry(name, data, entry) {
32403240 select.addEventListener('change', function () {
32413241 selectedWorldIndex = this.value === '' ? -1 : Number(this.value);
32423242 });
32433243 const popupConfirmpopup = awaitnew callGenericPopupPopup(container, POPUP_TYPE.CONFIRM, '', {
3244- okButton: t`Move`,
32453244 cancelButton: t`Cancel`,
3245+ customButtons: [
3246+ { text: t`Move`, result: POPUP_RESULT.CUSTOM1 },
3247+ { text: t`Copy`, result: POPUP_RESULT.CUSTOM2 },
3248+ ],
32463249 });
3250+ popup.okButton.style.display = 'none'; // Hide the default OK button
3251+ const popupConfirm = await popup.show();
32473252 if (!popupConfirm) return;
32483253 if (selectedWorldIndex === -1) return;
32493254 const selectedValue = world_names[selectedWorldIndex];
@@ -3251,7 +3256,8 @@ export async function getWorldEntry(name, data, entry) {
32513256 toastr.warning(t`Please select a target lorebook.`);
32523257 return;
32533258 }
3254- await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid);
3259+ const deleteOriginal = popupConfirm === POPUP_RESULT.CUSTOM1;
3260+ await moveWorldInfoEntry(sourceWorld, selectedValue, sourceUid, { deleteOriginal });
32553261 });
32563262
32573263 let drawerInitialized = false;
@@ -5422,9 +5428,11 @@ export async function assignLorebookToChat(event) {
54225428 * @param {string} sourceName - The name of the source lorebook file.
54235429 * @param {string} targetName - The name of the target lorebook file.
54245430 * @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.
54255433 * @returns {Promise<boolean>} True if the move was successful, false otherwise.
54265434 */
54275435export async function moveWorldInfoEntry(sourceName, targetName, uid, { deleteOriginal = true } = {}) {
54285436 if (sourceName === targetName) {
54295437 return false;
54305438 }
@@ -5466,7 +5474,6 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) {
54665474
54675475 const entryToMove = structuredClone(sourceData.entries[entryUidString]);
54685476
5469-
54705477 const newUid = getFreeWorldEntryUid(targetData);
54715478 if (newUid === null) {
54725479 console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`);
@@ -5480,20 +5487,20 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) {
54805487
54815488 targetData.entries[newUid] = entryToMove;
54825489
5490+ if (deleteOriginal) {
54835491 delete sourceData.entries[entryUidString];
54845492 // Remove from originalData if it exists
54855493 deleteWIOriginalDataValue(sourceData, entryUidString);
54865494 // TODO: setWIOriginalDataValue
54875495 console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`);
5488-
5496+ }
54895497
54905498 await saveWorldInfo(targetName, targetData, true);
54915499 console.debug(`[WI Move] Saved target lorebook '${targetName}'.`);
54925500 await saveWorldInfo(sourceName, sourceData, true);
54935501 console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`);
54945502
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}'.`);
54975504
54985505 // Check if the currently viewed book in the editor is the source or target and reload it
54995506 const currentEditorBookIndex = Number($('#world_editor_select').val());
@@ -5504,6 +5511,10 @@ export async function moveWorldInfoEntry(sourceName, targetName, uid) {
55045511 }
55055512 }
55065513
5514+ toastr.success(deleteOriginal
5515+ ? t`Entry moved successfully from '${sourceName}' to '${targetName}'.`
5516+ : t`Entry copied successfully to '${targetName}'.`);
5517+
55075518 return true;
55085519 } catch (error) {
55095520 toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`);