├── 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 | 
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}"3>
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 |
378 | ${presets.map(it=>`- ${it.name}
`).join('')}
379 |
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 |
--------------------------------------------------------------------------------