├── README └── wip-01.png ├── README.md ├── manifest.json ├── style.css └── index.js /README/wip-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LenAnderson/SillyTavern-WorldInfoPresets/HEAD/README/wip-01.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Silly Tavern - World Info Presets 2 | 3 | ![](README/wip-01.png) 4 | 5 | 6 | ## Slash Commands 7 | 8 | ```stscript 9 | /echo activate my preset | 10 | /wipreset my preset 11 | ``` 12 | 13 | ```stscript 14 | /echo deactivate preset | 15 | /wipreset 16 | ``` 17 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "display_name": "WorldInfo Presets", 3 | "loading_order": 100, 4 | "requires": [], 5 | "optional": [], 6 | "js": "index.js", 7 | "css": "style.css", 8 | "author": "LenAnderson", 9 | "version": "1.8.0", 10 | "homePage": "https://github.com/LenAnderson/SillyTavern-WorldInfoPresets" 11 | } 12 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | .stwip--container { 2 | flex: 1 1 auto; 3 | display: flex; 4 | flex-direction: row; 5 | gap: 1em; 6 | align-items: baseline; 7 | 8 | filter: saturate(0.5); 9 | margin-right: 1em; 10 | opacity: 0.25; 11 | transition: 200ms; 12 | 13 | &:hover { 14 | opacity: 1; 15 | filter: saturate(1.0); 16 | } 17 | 18 | > .stwip--actions { 19 | display: flex; 20 | flex-direction: row; 21 | gap: 0.25em; 22 | } 23 | } 24 | 25 | .shadow_popup:has(.stwip--transferModal), .popup:has(.stwip--transferModal) { 26 | .dialogue_popup_ok, .popup-button-ok { 27 | &:after { 28 | content: 'Transfer'; 29 | height: 0; 30 | overflow: hidden; 31 | font-weight: bold; 32 | } 33 | display: flex; 34 | align-items: center; 35 | flex-direction: column; 36 | white-space: pre; 37 | font-weight: normal; 38 | box-shadow: 0 0 0; 39 | transition: 200ms; 40 | } 41 | .stwip--copy { 42 | &:after { 43 | content: 'Copy'; 44 | height: 0; 45 | overflow: hidden; 46 | font-weight: bold; 47 | } 48 | display: flex; 49 | align-items: center; 50 | flex-direction: column; 51 | white-space: pre; 52 | font-weight: normal; 53 | box-shadow: 0 0 0; 54 | transition: 200ms; 55 | } 56 | &:has(.stwip--worldSelect:focus) { 57 | .dialogue_popup_ok, .popup-button-ok { 58 | font-weight: bold; 59 | box-shadow: 0 0 10px; 60 | } 61 | &.stwip--isCopy { 62 | .dialogue_popup_ok, .popup-button-ok { 63 | font-weight: normal; 64 | box-shadow: 0 0 0; 65 | } 66 | .stwip--copy { 67 | font-weight: bold; 68 | box-shadow: 0 0 10px; 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import { callPopup, eventSource, event_types, getRequestHeaders, saveSettingsDebounced } from '../../../../script.js'; 2 | import { extension_settings } from '../../../extensions.js'; 3 | import { POPUP_RESULT, POPUP_TYPE, Popup } from '../../../popup.js'; 4 | import { executeSlashCommands, registerSlashCommand } from '../../../slash-commands.js'; 5 | import { delay, navigation_option } from '../../../utils.js'; 6 | import { createWorldInfoEntry, deleteWIOriginalDataValue, deleteWorldInfoEntry, importWorldInfo, loadWorldInfo, saveWorldInfo, world_info } from '../../../world-info.js'; 7 | 8 | 9 | 10 | 11 | export class Settings { 12 | static from(props) { 13 | props.presetList = props.presetList?.map(it=>Preset.from(it)) ?? []; 14 | const instance = Object.assign(new this(), props); 15 | extension_settings.worldInfoPresets = instance; 16 | return instance; 17 | } 18 | /**@type {String}*/ presetName; 19 | /**@type {Preset[]}*/ presetList = []; 20 | get preset() { 21 | return this.presetList.find(it=>it.name == this.presetName); 22 | } 23 | } 24 | export class Preset { 25 | static from(props) { 26 | const instance = Object.assign(new this(), props); 27 | return instance; 28 | } 29 | /**@type {String}*/ name; 30 | /**@type {String[]}*/ worldList = []; 31 | 32 | toJSON() { 33 | return { 34 | name: this.name, 35 | worldList: this.worldList, 36 | }; 37 | } 38 | } 39 | /**@type {Settings}*/ 40 | export const settings = Settings.from(extension_settings.worldInfoPresets ?? {}); 41 | 42 | /**@type {HTMLSelectElement}*/ 43 | let presetSelect; 44 | 45 | const activatePresetByName = async(name)=>{ 46 | await activatePreset(settings.presetList.find(it=>it.name.toLowerCase() == name.toLowerCase())); 47 | }; 48 | export const activatePreset = async(preset)=>{ 49 | //TODO use delta instead of brute force 50 | await executeSlashCommands('/world silent=true {{newline}}'); 51 | settings.presetName = preset?.name ?? ''; 52 | updateSelect(); 53 | if (preset) { 54 | for (const world of settings.presetList.find(it=>it.name == settings.presetName).worldList) { 55 | await executeSlashCommands(`/world silent=true ${world}`); 56 | } 57 | } 58 | }; 59 | 60 | const updateSelect = ()=>{ 61 | /**@type {HTMLOptionElement[]}*/ 62 | // @ts-ignore 63 | const opts = Array.from(presetSelect.children); 64 | 65 | const added = []; 66 | const removed = []; 67 | const updated = []; 68 | for (const preset of settings.presetList) { 69 | const opt = opts.find(opt=>opt.value.toLowerCase() == preset.name.toLowerCase()); 70 | if (opt) { 71 | if (opt.value != preset.name) { 72 | updated.push({ preset, opt }); 73 | } 74 | } else { 75 | added.push(preset); 76 | } 77 | } 78 | for (const opt of opts) { 79 | if (opt.value == '') continue; 80 | if (settings.presetList.find(preset=>opt.value.toLowerCase() == preset.name.toLowerCase())) continue; 81 | removed.push(opt); 82 | } 83 | for (const opt of removed) { 84 | opt.remove(); 85 | opts.splice(opts.indexOf(opt), 1); 86 | } 87 | for (const update of updated) { 88 | update.opt.value = update.preset.name; 89 | update.opt.textContent = update.preset.name; 90 | } 91 | const sortedOpts = opts.toSorted((a,b)=>a.value.toLowerCase().localeCompare(b.value.toLowerCase())); 92 | sortedOpts.forEach((opt, idx)=>{ 93 | if (presetSelect.children[idx] != opt) { 94 | presetSelect.children[idx].insertAdjacentElement('beforebegin', opt); 95 | } 96 | }); 97 | for (const preset of added) { 98 | const opt = document.createElement('option'); { 99 | opt.value = preset.name; 100 | opt.textContent = preset.name; 101 | const before = Array.from(presetSelect.children).find(it=>it.value.toLowerCase().localeCompare(preset.name.toLowerCase()) == 1); 102 | if (before) before.insertAdjacentElement('beforebegin', opt); 103 | else presetSelect.append(opt); 104 | } 105 | } 106 | presetSelect.value = settings.presetName; 107 | }; 108 | 109 | const loadBook = async(name)=>{ 110 | const result = await fetch('/api/worldinfo/get', { 111 | method: 'POST', 112 | headers: getRequestHeaders(), 113 | body: JSON.stringify({ name }), 114 | }); 115 | if (result.ok) { 116 | const data = await result.json(); 117 | data.entries = Object.keys(data.entries).map(it=>{ 118 | data.entries[it].book = name; 119 | return data.entries[it]; 120 | }); 121 | data.book = name; 122 | return data; 123 | } else { 124 | toastr.warning(`Failed to load World Info book: ${name}`); 125 | } 126 | }; 127 | 128 | 129 | const importBooks = async(data)=>{ 130 | if (data.books && Object.keys(data.books).length > 0) { 131 | const doImport = await callPopup(`

The preset contains World Info books. Import the books?

`, 'confirm'); 132 | if (doImport) { 133 | for (const key of Object.keys(data.books)) { 134 | const book = data.books[key]; 135 | const blob = new Blob([JSON.stringify(book)], { type:'text' }); 136 | const file = new File([blob], `${key}.json`); 137 | await importWorldInfo(file); 138 | } 139 | } 140 | } 141 | }; 142 | 143 | /** 144 | * 145 | * @param {FileList} files 146 | */ 147 | const importPreset = async(files)=>{ 148 | for (let i = 0; i < files.length; i++) { 149 | await importSinglePreset(files.item(i)); 150 | } 151 | }; 152 | /** 153 | * 154 | * @param {File} file 155 | */ 156 | const importSinglePreset = async(file)=>{ 157 | try { 158 | const text = await file.text(); 159 | const data = JSON.parse(text); 160 | let old = settings.presetList.find(it=>it.name.toLowerCase() == data.name.toLowerCase()); 161 | while (old) { 162 | const popupText = ` 163 |

Import World Info Preset: "${data.name}" 164 |

165 | A preset by that name already exists. Change the name to import under a new name, 166 | or keep the name to ovewrite the existing preset. 167 |

168 | `; 169 | const newName = await callPopup(popupText, 'input', data.name); 170 | if (newName == data.name) { 171 | const overwrite = await callPopup(`

Overwrite World Info Preset "${newName}"?

`, 'confirm'); 172 | if (overwrite) { 173 | old.worldList = data.worldList; 174 | await importBooks(data); 175 | if (settings.preset == old) { 176 | activatePreset(old); 177 | saveSettingsDebounced(); 178 | } 179 | } 180 | return; 181 | } else { 182 | data.name = newName; 183 | old = settings.presetList.find(it=>it.name.toLowerCase() == data.name.toLowerCase()); 184 | } 185 | } 186 | const preset = new Preset(); 187 | preset.name = data.name; 188 | preset.worldList = data.worldList; 189 | settings.presetList.push(preset); 190 | await importBooks(data); 191 | updateSelect(); 192 | saveSettingsDebounced(); 193 | } catch (ex) { 194 | toastr.error(`Failed to import "${file.name}":\n\n${ex.message}`); 195 | } 196 | }; 197 | 198 | const createPreset = async()=>{ 199 | const name = await callPopup('

Preset Name:

', 'input', settings.presetName); 200 | if (!name) return; 201 | const preset = new Preset(); 202 | preset.name = name; 203 | preset.worldList = [...world_info.globalSelect]; 204 | settings.presetList.push(preset); 205 | settings.presetName = name; 206 | updateSelect(); 207 | saveSettingsDebounced(); 208 | }; 209 | 210 | 211 | 212 | const init = ()=>{ 213 | const container = document.querySelector('#WorldInfo > div > h3'); 214 | const dom = document.createElement('div'); { 215 | dom.classList.add('stwip--container'); 216 | presetSelect = document.createElement('select'); { 217 | presetSelect.classList.add('stwip--preset'); 218 | const blank = document.createElement('option'); { 219 | blank.value = ''; 220 | blank.textContent = '--- Pick a Preset ---'; 221 | presetSelect.append(blank); 222 | } 223 | for (const preset of settings.presetList.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase()))) { 224 | const opt = document.createElement('option'); { 225 | opt.value = preset.name; 226 | opt.textContent = preset.name; 227 | opt.title = preset.worldList.join(', '); 228 | presetSelect.append(opt); 229 | } 230 | } 231 | presetSelect.value = settings.presetName ?? ''; 232 | presetSelect.addEventListener('change', async()=>{ 233 | await activatePresetByName(presetSelect.value); 234 | }); 235 | dom.append(presetSelect); 236 | } 237 | const actions = document.createElement('div'); { 238 | actions.classList.add('stwip--actions'); 239 | const btnRename = document.createElement('div'); { 240 | btnRename.classList.add('stwip--action'); 241 | btnRename.classList.add('menu_button'); 242 | btnRename.classList.add('fa-solid', 'fa-pencil'); 243 | btnRename.title = 'Rename current preset'; 244 | btnRename.addEventListener('click', async()=>{ 245 | const name = await callPopup('

Rename Preset:

', 'input', settings.presetName); 246 | if (!name) return; 247 | settings.preset.name = name; 248 | settings.presetName = name; 249 | updateSelect(); 250 | saveSettingsDebounced(); 251 | }); 252 | actions.append(btnRename); 253 | } 254 | const btnUpdate = document.createElement('div'); { 255 | btnUpdate.classList.add('stwip--action'); 256 | btnUpdate.classList.add('menu_button'); 257 | btnUpdate.classList.add('fa-solid', 'fa-save'); 258 | btnUpdate.title = 'Update current preset'; 259 | btnUpdate.addEventListener('click', ()=>{ 260 | if (!settings.preset) return createPreset(); 261 | settings.preset.worldList = [...world_info.globalSelect]; 262 | saveSettingsDebounced(); 263 | }); 264 | actions.append(btnUpdate); 265 | } 266 | const btnCreate = document.createElement('div'); { 267 | btnCreate.classList.add('stwip--action'); 268 | btnCreate.classList.add('menu_button'); 269 | btnCreate.classList.add('fa-solid', 'fa-file-circle-plus'); 270 | btnCreate.title = 'Save current preset as'; 271 | btnCreate.addEventListener('click', async()=>createPreset()); 272 | actions.append(btnCreate); 273 | } 274 | const btnRestore = document.createElement('div'); { 275 | btnRestore.classList.add('stwip--action'); 276 | btnRestore.classList.add('menu_button'); 277 | btnRestore.classList.add('fa-solid', 'fa-rotate-left'); 278 | btnRestore.title = 'Restore current preset'; 279 | btnRestore.addEventListener('click', ()=>activatePreset(settings.preset)); 280 | actions.append(btnRestore); 281 | } 282 | const importFile = document.createElement('input'); { 283 | importFile.classList.add('stwip--importFile'); 284 | importFile.type = 'file'; 285 | importFile.addEventListener('change', async()=>{ 286 | await importPreset(importFile.files); 287 | importFile.value = null; 288 | }); 289 | } 290 | const btnImport = document.createElement('div'); { 291 | btnImport.classList.add('stwip--action'); 292 | btnImport.classList.add('menu_button'); 293 | btnImport.classList.add('fa-solid', 'fa-file-import'); 294 | btnImport.title = 'Import preset'; 295 | btnImport.addEventListener('click', ()=>importFile.click()); 296 | actions.append(btnImport); 297 | } 298 | const btnExport = document.createElement('div'); { 299 | btnExport.classList.add('stwip--action'); 300 | btnExport.classList.add('menu_button'); 301 | btnExport.classList.add('fa-solid', 'fa-file-export'); 302 | btnExport.title = 'Export the current preset'; 303 | btnExport.addEventListener('click', async()=>{ 304 | const popupText = ` 305 |

Export World Info Preset: "${settings.presetName}"

306 |

Include the books' contents in the exported file?

307 | `; 308 | const includeBooks = await callPopup(popupText, 'confirm'); 309 | const data = settings.preset.toJSON(); 310 | if (includeBooks) { 311 | let names = world_info.globalSelect; 312 | const books = {}; 313 | for (const book of names) { 314 | books[book] = await loadBook(book); 315 | } 316 | data.books = books; 317 | } 318 | const blob = new Blob([JSON.stringify(data)], { type:'text' }); 319 | const url = URL.createObjectURL(blob); 320 | const a = document.createElement('a'); { 321 | a.href = url; 322 | const name = `SillyTavern-WorldInfoPreset-${settings.presetName}`; 323 | const ext = 'json'; 324 | a.download = `${name}.${ext}`; 325 | a.click(); 326 | } 327 | }); 328 | actions.append(btnExport); 329 | } 330 | const btnDelete = document.createElement('div'); { 331 | btnDelete.classList.add('stwip--action'); 332 | btnDelete.classList.add('menu_button'); 333 | btnDelete.classList.add('redWarningBG'); 334 | btnDelete.classList.add('fa-solid', 'fa-trash-can'); 335 | btnDelete.title = 'Delete the current preset'; 336 | btnDelete.addEventListener('click', async()=>{ 337 | if (settings.presetName == '') return; 338 | const confirmed = await callPopup(`

Delete World Info Preset "${settings.presetName}"?

`, 'confirm'); 339 | if (confirmed) { 340 | settings.presetList.splice(settings.presetList.indexOf(settings.preset), 1); 341 | settings.presetName = ''; 342 | updateSelect(); 343 | saveSettingsDebounced(); 344 | } 345 | }); 346 | actions.append(btnDelete); 347 | } 348 | dom.append(actions); 349 | } 350 | container.insertAdjacentElement('afterend', dom); 351 | } 352 | 353 | const sel = document.querySelector('#world_editor_select'); 354 | let bookNames = Array.from(sel.children).map(it=>it.textContent); 355 | const mo = new MutationObserver(async(muts)=>{ 356 | console.log('[WIP]', '[BOOKS CHANGED]', muts); 357 | const newNames = Array.from(sel.children).map(it=>it.textContent); 358 | const added = []; 359 | const removed = []; 360 | for (const nn of newNames) { 361 | if (!bookNames.includes(nn)) added.push(nn); 362 | } 363 | for (const bn of bookNames) { 364 | if (!newNames.includes(bn)) removed.push(bn); 365 | } 366 | if (added.length == 1 && removed.length == 1) { 367 | const oldName = removed[0]; 368 | const newName = added[0]; 369 | const presets = settings.presetList.filter(preset=>preset.worldList.includes(oldName)); 370 | if (presets.length > 0) { 371 | // oldName has probably been renamed to newName 372 | const popupText = ` 373 |
374 |

World Info Renamed

375 |

It looks like you renamed the World Info book "${oldName}" to "${newName}".

376 |

The following presets currently include the World Info book "${oldName}":

377 | 380 |

381 | Do you want to update all ${presets.length} presets that include "${oldName}" to now include "${newName}" instead? 382 |

383 |
384 | `; 385 | const dlg = new Popup(popupText, POPUP_TYPE.CONFIRM); 386 | await dlg.show(); 387 | if (dlg.result == POPUP_RESULT.AFFIRMATIVE) { 388 | for (const preset of presets) { 389 | preset.worldList.splice(preset.worldList.indexOf(oldName), 1, newName); 390 | } 391 | saveSettingsDebounced(); 392 | } 393 | } else { 394 | // toastr.info(`World Info book renamed, but not included in any presets: "${oldName}" => "${newName}"`); 395 | } 396 | } 397 | bookNames = [...newNames]; 398 | }); 399 | mo.observe(sel, { childList: true }); 400 | }; 401 | init(); 402 | 403 | 404 | 405 | registerSlashCommand('wipreset', 406 | (args, value)=>{ 407 | activatePresetByName(value); 408 | }, 409 | [], 410 | '(optional preset name) – Activate a World Info preset. Leave name blank to deactivate current preset (unload all WI books).', 411 | true, 412 | true, 413 | ); 414 | 415 | 416 | 417 | 418 | const initTransfer = ()=>{ 419 | const alterTemplate = ()=>{ 420 | const tpl = document.querySelector('#entry_edit_template'); 421 | const transferBtn = document.createElement('i'); { 422 | transferBtn.classList.add('stwip--transfer'); 423 | transferBtn.classList.add('menu_button'); 424 | transferBtn.classList.add('fa-solid'); 425 | transferBtn.classList.add('fa-truck-arrow-right'); 426 | transferBtn.title = 'Transfer or copy world info entry into another book'; 427 | tpl.querySelector('.duplicate_entry_button').insertAdjacentElement('beforebegin', transferBtn); 428 | } 429 | }; 430 | alterTemplate(); 431 | 432 | 433 | const mo = new MutationObserver(muts=>{ 434 | for (const entry of [...document.querySelectorAll('#world_popup_entries_list .world_entry:not(.stwip--)')]) { 435 | const uid = entry.getAttribute('uid'); 436 | entry.classList.add('stwip--'); 437 | const transferBtn = entry.querySelector('.stwip--transfer'); 438 | transferBtn.addEventListener('click', async(evt)=>{ 439 | evt.stopPropagation(); 440 | let sel; 441 | let isCopy = false; 442 | const dom = document.createElement('div'); { 443 | dom.classList.add('stwip--transferModal'); 444 | const title = document.createElement('h3'); { 445 | title.textContent = 'Transfer World Info Entry'; 446 | dom.append(title); 447 | } 448 | const subTitle = document.createElement('h4'); { 449 | const entryName = transferBtn.closest('.world_entry').querySelector('[name="comment"]').value ?? transferBtn.closest('.world_entry').querySelector('[name="key"]').value; 450 | const bookName = document.querySelector('#world_editor_select').selectedOptions[0].textContent; 451 | subTitle.textContent = `${bookName}: ${entryName}`; 452 | dom.append(subTitle); 453 | } 454 | sel = document.querySelector('#world_editor_select').cloneNode(true); { 455 | sel.classList.add('stwip--worldSelect'); 456 | sel.value = document.querySelector('#world_editor_select').value; 457 | sel.addEventListener('keyup', (evt)=>{ 458 | if (evt.key == 'Shift') { 459 | (dlg.dom ?? dlg.dlg).classList.remove('stwip--isCopy'); 460 | return; 461 | } 462 | }); 463 | sel.addEventListener('keydown', (evt)=>{ 464 | if (evt.key == 'Shift') { 465 | (dlg.dom ?? dlg.dlg).classList.add('stwip--isCopy'); 466 | return; 467 | } 468 | if (!evt.ctrlKey && !evt.altKey && evt.key == 'Enter') { 469 | evt.preventDefault(); 470 | if (evt.shiftKey) isCopy = true; 471 | dlg.completeAffirmative(); 472 | } 473 | }); 474 | dom.append(sel); 475 | } 476 | const hintP = document.createElement('p'); { 477 | const hint = document.createElement('small'); { 478 | hint.textContent = 'Type to select book. Enter to transfer. Shift+Enter to copy.'; 479 | hintP.append(hint); 480 | } 481 | dom.append(hintP); 482 | } 483 | } 484 | const dlg = new Popup(dom, POPUP_TYPE.CONFIRM, null, { okButton:'Transfer', cancelButton:'Cancel' }); 485 | const copyBtn = document.createElement('div'); { 486 | copyBtn.classList.add('stwip--copy'); 487 | copyBtn.classList.add('menu_button'); 488 | copyBtn.textContent = 'Copy'; 489 | copyBtn.addEventListener('click', ()=>{ 490 | isCopy = true; 491 | dlg.completeAffirmative(); 492 | }); 493 | (dlg.ok ?? dlg.okButton).insertAdjacentElement('afterend', copyBtn); 494 | } 495 | const prom = dlg.show(); 496 | sel.focus(); 497 | await prom; 498 | if (dlg.result == POPUP_RESULT.AFFIRMATIVE) { 499 | toastr.info('Transferring WI Entry'); 500 | console.log('TRANSFER TO', sel.value); 501 | const srcName = document.querySelector('#world_editor_select').selectedOptions[0].textContent; 502 | const dstName = sel.selectedOptions[0].textContent; 503 | if (srcName == dstName) { 504 | toastr.warning(`Entry is already in book "${dstName}"`); 505 | return; 506 | } 507 | let page = document.querySelector('#world_info_pagination .paginationjs-prev[data-num]')?.getAttribute('data-num'); 508 | if (page === undefined) { 509 | page = document.querySelector('#world_info_pagination .paginationjs-next[data-num]')?.getAttribute('data-num'); 510 | if (page !== undefined) { 511 | page = (Number(page) - 1).toString(); 512 | } 513 | } else { 514 | page = (Number(page) + 1).toString(); 515 | } 516 | const [srcBook, dstBook] = await Promise.all([ 517 | loadWorldInfo(srcName), 518 | loadWorldInfo(dstName), 519 | ]); 520 | if (srcBook && dstBook) { 521 | const srcEntry = srcBook.entries[uid]; 522 | const oData = Object.assign({}, srcEntry); 523 | delete oData.uid; 524 | const dstEntry = createWorldInfoEntry(null, dstBook); 525 | Object.assign(dstEntry, oData); 526 | await saveWorldInfo(dstName, dstBook, true); 527 | if (!isCopy) { 528 | const deleted = await deleteWorldInfoEntry(srcBook, uid, { silent:true }); 529 | if (deleted) { 530 | deleteWIOriginalDataValue(srcBook, uid); 531 | await saveWorldInfo(srcName, srcBook, true); 532 | } 533 | } 534 | toastr.info('Almost transferred...'); 535 | document.querySelector('#world_editor_select').value = ''; 536 | document.querySelector('#world_editor_select').dispatchEvent(new Event('change', { bubbles:true })); 537 | await delay(100); 538 | document.querySelector('#world_editor_select').value = [...document.querySelector('#world_editor_select').children].find(it=>it.textContent == srcName).value; 539 | let saveProm = new Promise(resolve=>eventSource.once(event_types.WORLDINFO_UPDATED, resolve)); 540 | document.querySelector('#world_editor_select').dispatchEvent(new Event('change', { bubbles:true })); 541 | await saveProm; 542 | if (page !== undefined) { 543 | saveProm = new Promise(resolve=>eventSource.once(event_types.WORLDINFO_UPDATED, resolve)); 544 | document.querySelector('#world_info_pagination .paginationjs-next').setAttribute('data-num', page.toString()); 545 | document.querySelector('#world_info_pagination .paginationjs-next').click(); 546 | await saveProm; 547 | } 548 | toastr.success('Transferred WI Entry'); 549 | } else { 550 | toastr.error('Something went wrong'); 551 | } 552 | } 553 | }); 554 | } 555 | }); 556 | mo.observe(document.querySelector('#world_popup_entries_list'), { childList:true, subtree:true }); 557 | 558 | const loadBook = async(name)=>{ 559 | const result = await fetch('/api/worldinfo/get', { 560 | method: 'POST', 561 | headers: getRequestHeaders(), 562 | body: JSON.stringify({ name }), 563 | }); 564 | if (result.ok) { 565 | return await result.json(); 566 | } else { 567 | toastr.warning(`Failed to load World Info book: ${name}`); 568 | } 569 | }; 570 | const saveBook = async(name, data)=>{ 571 | await fetch('/api/worldinfo/edit', { 572 | method: 'POST', 573 | headers: getRequestHeaders(), 574 | body: JSON.stringify({ name, data }), 575 | }); 576 | eventSource.emit(event_types.WORLDINFO_UPDATED, name, data); 577 | }; 578 | }; 579 | initTransfer(); 580 | --------------------------------------------------------------------------------