| 5267 | 5331 | }); |
| 5268 | 5332 | }); |
| 5269 | 5333 | }); |
| 5334 | + |
| 5335 | +/** |
| 5336 | + * Moves a World Info entry from a source lorebook to a target lorebook. |
| 5337 | + * |
| 5338 | + * @param {string} sourceName - The name of the source lorebook file. |
| 5339 | + * @param {string} targetName - The name of the target lorebook file. |
| 5340 | + * @param {number|string} uid - The UID of the entry to move from the source lorebook. |
| 5341 | + * @returns {Promise<boolean>} True if the move was successful, false otherwise. |
| 5342 | + */ |
| 5343 | +export async function moveWorldInfoEntry(sourceName, targetName, uid) { |
| 5344 | + console.log(`[WI] Attempting to move entry UID ${uid} from '${sourceName}' to '${targetName}'`); |
| 5345 | + |
| 5346 | + if (!sourceName || !targetName || uid === undefined || uid === null) { |
| 5347 | + console.error('[WI Move] Missing required arguments.'); |
| 5348 | + return false; |
| 5349 | + } |
| 5350 | + |
| 5351 | + if (sourceName === targetName) { |
| 5352 | + toastr.warning(t`Source and target lorebooks cannot be the same.`); |
| 5353 | + return false; |
| 5354 | + } |
| 5355 | + |
| 5356 | + if (!world_names.includes(sourceName)) { |
| 5357 | + toastr.error(t`Source lorebook '${sourceName}' not found.`); |
| 5358 | + console.error(`[WI Move] Source lorebook '${sourceName}' does not exist.`); |
| 5359 | + return false; |
| 5360 | + } |
| 5361 | + |
| 5362 | + if (!world_names.includes(targetName)) { |
| 5363 | + toastr.error(t`Target lorebook '${targetName}' not found.`); |
| 5364 | + console.error(`[WI Move] Target lorebook '${targetName}' does not exist.`); |
| 5365 | + return false; |
| 5366 | + } |
| 5367 | + |
| 5368 | + const entryUidString = String(uid); |
| 5369 | + |
| 5370 | + try { |
| 5371 | + const sourceData = await loadWorldInfo(sourceName); |
| 5372 | + const targetData = await loadWorldInfo(targetName); |
| 5373 | + |
| 5374 | + if (!sourceData || !sourceData.entries) { |
| 5375 | + toastr.error(t`Failed to load data for source lorebook '${sourceName}'.`); |
| 5376 | + console.error(`[WI Move] Could not load source data for '${sourceName}'.`); |
| 5377 | + return false; |
| 5378 | + } |
| 5379 | + if (!targetData || !targetData.entries) { |
| 5380 | + toastr.error(t`Failed to load data for target lorebook '${targetName}'.`); |
| 5381 | + console.error(`[WI Move] Could not load target data for '${targetName}'.`); |
| 5382 | + return false; |
| 5383 | + } |
| 5384 | + |
| 5385 | + if (!sourceData.entries[entryUidString]) { |
| 5386 | + toastr.error(t`Entry not found in source lorebook '${sourceName}'.`); |
| 5387 | + console.error(`[WI Move] Entry UID ${entryUidString} not found in '${sourceName}'.`); |
| 5388 | + return false; |
| 5389 | + } |
| 5390 | + |
| 5391 | + const entryToMove = structuredClone(sourceData.entries[entryUidString]); |
| 5392 | + |
| 5393 | + |
| 5394 | + const newUid = getFreeWorldEntryUid(targetData); |
| 5395 | + if (newUid === null) { |
| 5396 | + console.error(`[WI Move] Failed to get a free UID in '${targetName}'.`); |
| 5397 | + return false; |
| 5398 | + } |
| 5399 | + |
| 5400 | + entryToMove.uid = newUid; |
| 5401 | + // Reset displayIndex or let it be recalculated based on target book's sorting? |
| 5402 | + // For simplicity, let's assign a high index initially, assuming it might be sorted later. |
| 5403 | + // Or maybe better, find the max displayIndex in target and add 1? |
| 5404 | + const maxDisplayIndex = Object.values(targetData.entries).reduce((max, entry) => Math.max(max, entry.displayIndex ?? -1), -1); |
| 5405 | + entryToMove.displayIndex = maxDisplayIndex + 1; |
| 5406 | + |
| 5407 | + targetData.entries[newUid] = entryToMove; |
| 5408 | + |
| 5409 | + delete sourceData.entries[entryUidString]; |
| 5410 | + // Remove from originalData if it exists, using the original UID |
| 5411 | + deleteWIOriginalDataValue(sourceData, entryUidString); |
| 5412 | + console.debug(`[WI Move] Removed entry UID ${entryUidString} from source '${sourceName}'.`); |
| 5413 | + |
| 5414 | + |
| 5415 | + // Save immediately to reduce chances of inconsistency if the browser is closed |
| 5416 | + // Note: This is not truly atomic. If one save fails, state could be inconsistent. |
| 5417 | + await saveWorldInfo(targetName, targetData, true); |
| 5418 | + console.debug(`[WI Move] Saved target lorebook '${targetName}'.`); |
| 5419 | + await saveWorldInfo(sourceName, sourceData, true); |
| 5420 | + console.debug(`[WI Move] Saved source lorebook '${sourceName}'.`); |
| 5421 | + |
| 5422 | + |
| 5423 | + toastr.success(t`${entryToMove.comment} moved successfully!`); |
| 5424 | + |
| 5425 | + // Check if the currently viewed book in the editor is the source or target and reload it |
| 5426 | + const currentEditorBookIndex = Number($('#world_editor_select').val()); |
| 5427 | + if (!isNaN(currentEditorBookIndex)) { |
| 5428 | + const currentEditorBookName = world_names[currentEditorBookIndex]; |
| 5429 | + if (currentEditorBookName === sourceName || currentEditorBookName === targetName) { |
| 5430 | + reloadEditor(currentEditorBookName); |
| 5431 | + } |
| 5432 | + } |
| 5433 | + |
| 5434 | + return true; |
| 5435 | + } catch (error) { |
| 5436 | + toastr.error(t`An unexpected error occurred while moving the entry: ${error.message}`); |
| 5437 | + console.error('[WI Move] Unexpected error:', error); |
| 5438 | + return false; |
| 5439 | + } |
| 5440 | +} |