Merge pull request #3290 from SillyTavern/qr-set-rename QR Set buttons for rename and duplicate

f4624364503caeb1a9cd94881c43eb8edf1bddd6

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

Signed
2 files changed, +84 -6Ignore whitespace
public/scripts/extensions/quick-reply/html/settings.html+2 -0
@@ -46,10 +46,12 @@
4646 <div class="qr--title" data-i18n="Edit Quick Replies">Edit Quick Replies</div>
4747 <div class="qr--actions">
4848 <select id="qr--set" class="text_pole"></select>
49+ <div class="qr--add menu_button menu_button_icon fa-solid fa-pencil" id="qr--set-rename" title="Rename quick reply set"></div>
4950 <div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
5051 <div class="qr--add menu_button menu_button_icon fa-solid fa-file-import" id="qr--set-import" title="Import quick reply set"></div>
5152 <input type="file" id="qr--set-importFile" accept=".json" hidden>
5253 <div class="qr--add menu_button menu_button_icon fa-solid fa-file-export" id="qr--set-export" title="Export quick reply set"></div>
54+ <div class="qr-add menu_button menu_button_icon fa-solid fa-paste" id="qr--set-duplicate" title="Duplicate quick reply set"></div>
5355 <div class="qr--del menu_button menu_button_icon fa-solid fa-trash redWarningBG" id="qr--set-delete" title="Delete quick reply set"></div>
5456 </div>
5557 </div>
public/scripts/extensions/quick-reply/src/ui/SettingsUi.js+82 -6
@@ -1,4 +1,4 @@
11import { callPopupPopup } from '../../../../../scriptpopup.js';
22import { getSortableDelay } from '../../../../utils.js';
33import { log, warn } from '../../index.js';
44import { QuickReply } from '../QuickReply.js';
@@ -111,6 +111,7 @@ export class SettingsUi {
111111
112112 prepareQrEditor() {
113113 // qr editor
114+ this.dom.querySelector('#qr--set-rename').addEventListener('click', async () => this.renameQrSet());
114115 this.dom.querySelector('#qr--set-new').addEventListener('click', async()=>this.addQrSet());
115116 /**@type {HTMLInputElement}*/
116117 const importFile = this.dom.querySelector('#qr--set-importFile');
@@ -119,7 +120,8 @@ export class SettingsUi {
119120 importFile.value = null;
120121 });
121122 this.dom.querySelector('#qr--set-import').addEventListener('click', ()=>importFile.click());
122123 this.dom.querySelector('#qr--set-export').addEventListener('click', async () => this.exportQrSet());
124+ this.dom.querySelector('#qr--set-duplicate').addEventListener('click', async () => this.duplicateQrSet());
123125 this.dom.querySelector('#qr--set-delete').addEventListener('click', async()=>this.deleteQrSet());
124126 this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
125127 this.currentQrSet.addQuickReply();
@@ -279,7 +281,7 @@ export class SettingsUi {
279281 }
280282
281283 async deleteQrSet() {
282284 const confirmed = await callPopupPopup.show.confirm('Delete Quick Reply Set', `Are you sure you want to delete the Quick Reply Set "${this.currentQrSet.name}"?<br>This cannot be undone.`, 'confirm');
283285 if (confirmed) {
284286 await this.doDeleteQrSet(this.currentQrSet);
285287 this.rerender();
@@ -303,12 +305,52 @@ export class SettingsUi {
303305 this.settings.save();
304306 }
305307
308+ async renameQrSet() {
309+ const newName = await Popup.show.input('Rename Quick Reply Set', 'Enter a new name:', this.currentQrSet.name);
310+ if (newName && newName.length > 0) {
311+ const existingSet = QuickReplySet.get(newName);
312+ if (existingSet) {
313+ toastr.error(`A Quick Reply Set named "${newName}" already exists.`);
314+ return;
315+ }
316+ const oldName = this.currentQrSet.name;
317+ this.currentQrSet.name = newName;
318+ await this.currentQrSet.save();
319+
320+ // Update it in both set lists
321+ this.settings.config.setList.forEach(set => {
322+ if (set.set.name === oldName) {
323+ set.set.name = newName;
324+ }
325+ });
326+ this.settings.chatConfig?.setList.forEach(set => {
327+ if (set.set.name === oldName) {
328+ set.set.name = newName;
329+ }
330+ });
331+ this.settings.save();
332+
333+ // Update the option in the current selected QR dropdown. All others will be refreshed via the prepare calls below.
334+ /** @type {HTMLOptionElement} */
335+ const option = this.currentSet.querySelector(`#qr--set option[value="${oldName}"]`);
336+ option.value = newName;
337+ option.textContent = newName;
338+
339+ this.currentSet.value = newName;
340+ this.onQrSetChange();
341+ this.prepareGlobalSetList();
342+ this.prepareChatSetList();
343+
344+ console.info(`Quick Reply Set renamed from ""${oldName}" to "${newName}".`);
345+ }
346+ }
347+
306348 async addQrSet() {
307349 const name = await callPopupPopup.show.input('QuickCreate Replya Setnew Name:World Info', 'inputEnter a name for the new Quick Reply Set:');
308350 if (name && name.length > 0) {
309351 const oldQrs = QuickReplySet.get(name);
310352 if (oldQrs) {
311353 const replace = await callPopupPopup.show.confirm('Replace existing World Info', `A Quick Reply Set named "${name}" already exists.<br>Do you want to overwrite the existing Quick Reply Set?<br>The existing set will be deleted. This cannot be undone.`, 'confirm');
312354 if (replace) {
313355 const idx = QuickReplySet.list.indexOf(oldQrs);
314356 await this.doDeleteQrSet(oldQrs);
@@ -369,7 +411,7 @@ export class SettingsUi {
369411 qrs.init();
370412 const oldQrs = QuickReplySet.get(props.name);
371413 if (oldQrs) {
372414 const replace = await callPopupPopup.show.confirm('Replace existing World Info', `A Quick Reply Set named "${qrs.name}" already exists.<br>Do you want to overwrite the existing Quick Reply Set?<br>The existing set will be deleted. This cannot be undone.`, 'confirm');
373415 if (replace) {
374416 const idx = QuickReplySet.list.indexOf(oldQrs);
375417 await this.doDeleteQrSet(oldQrs);
@@ -421,6 +463,40 @@ export class SettingsUi {
421463 URL.revokeObjectURL(url);
422464 }
423465
466+ async duplicateQrSet() {
467+ const newName = await Popup.show.input('Duplicate Quick Reply Set', 'Enter a name for the new Quick Reply Set:', `${this.currentQrSet.name} (Copy)`);
468+ if (newName && newName.length > 0) {
469+ const existingSet = QuickReplySet.get(newName);
470+ if (existingSet) {
471+ toastr.error(`A Quick Reply Set named "${newName}" already exists.`);
472+ return;
473+ }
474+ const newQrSet = QuickReplySet.from(JSON.parse(JSON.stringify(this.currentQrSet)));
475+ newQrSet.name = newName;
476+ newQrSet.qrList = this.currentQrSet.qrList.map(qr => QuickReply.from(JSON.parse(JSON.stringify(qr))));
477+ newQrSet.addQuickReply();
478+ const idx = QuickReplySet.list.findIndex(it => it.name.toLowerCase().localeCompare(newName.toLowerCase()) == 1);
479+ if (idx > -1) {
480+ QuickReplySet.list.splice(idx, 0, newQrSet);
481+ } else {
482+ QuickReplySet.list.push(newQrSet);
483+ }
484+ const opt = document.createElement('option'); {
485+ opt.value = newQrSet.name;
486+ opt.textContent = newQrSet.name;
487+ if (idx > -1) {
488+ this.currentSet.children[idx].insertAdjacentElement('beforebegin', opt);
489+ } else {
490+ this.currentSet.append(opt);
491+ }
492+ }
493+ this.currentSet.value = newName;
494+ this.onQrSetChange();
495+ this.prepareGlobalSetList();
496+ this.prepareChatSetList();
497+ }
498+ }
499+
424500 selectQrSet(qrs) {
425501 this.currentSet.value = qrs.name;
426502 this.onQrSetChange();