restructuring

This commit is contained in:
LenAnderson 2023-12-20 15:49:27 +00:00
parent 34decf1c05
commit 2648b3c801
9 changed files with 338 additions and 184 deletions

View File

@ -14,47 +14,54 @@
<hr>
<div class="qr--head">
<div class="qr--title">Global Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--global-setListAdd" title="Add quick reply set"></div>
<div id="qr--global">
<div class="qr--head">
<div class="qr--title">Global Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--global-setListAdd" title="Add quick reply set"></div>
</div>
</div>
<div id="qr--global-setList" class="qr--setList"></div>
</div>
<div id="qr--global-setList" class="qr--setList"></div>
<hr>
<div class="qr--head">
<div class="qr--title">Chat Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--chat-setListAdd" title="Add quick reply set"></div>
<div id="qr--chat">
<div class="qr--head">
<div class="qr--title">Chat Quick Reply Sets</div>
<div class="qr--actions">
<div class="qr--setListAdd menu_button menu_button_icon fa-solid fa-plus" id="qr--chat-setListAdd" title="Add quick reply set"></div>
</div>
</div>
<div id="qr--chat-setList" class="qr--setList"></div>
</div>
<div id="qr--chat-setList" class="qr--setList"></div>
<hr>
<div class="qr--head">
<div class="qr--title">Edit Quick Replies</div>
<div class="qr--actions">
<select id="qr--set" class="text_pole"></select>
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
<div id="qr--editor">
<div class="qr--head">
<div class="qr--title">Edit Quick Replies</div>
<div class="qr--actions">
<select id="qr--set" class="text_pole"></select>
<div class="qr--del menu_button menu_button_icon fa-solid fa-trash" id="qr--set-delete" title="Delete quick reply set"></div>
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-new" title="Create new quick reply set"></div>
</div>
</div>
<div id="qr--set-settings">
<label class="flex-container">
<input type="checkbox" id="qr--disableSend"> <span>Disable send (insert into input field)</span>
</label>
<label class="flex-container">
<input type="checkbox" id="qr--placeBeforeInput"> <span>Place quick reply before input</span>
</label>
<label class="flex-container" id="qr--injectInputContainer">
<input type="checkbox" id="qr--injectInput"> <span>Inject user input automatically <small>(if disabled, use <code>{{input}}</code> macro for manual injection)</small></span>
</label>
</div>
<div id="qr--set-qrList" class="qr--qrList"></div>
<div class="qr--set-qrListActions">
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div>
</div>
</div>
<div id="qr--set-settings">
<label class="flex-container">
<input type="checkbox" id="qr--disableSend"> <span>Disable send (insert into input field)</span>
</label>
<label class="flex-container">
<input type="checkbox" id="qr--placeBeforeInput"> <span>Place quick reply before input</span>
</label>
<label class="flex-container" id="qr--injectInputContainer">
<input type="checkbox" id="qr--injectInput"> <span>Inject user input automatically <small>(if disabled, use <code>{{input}}</code> macro for manual injection)</small></span>
</label>
</div>
<div id="qr--set-qrList" class="qr--qrList"></div>
<div class="qr--set-qrListActions">
<div class="qr--add menu_button menu_button_icon fa-solid fa-plus" id="qr--set-add" title="Add quick reply"></div>
</div>
</div>
</div>

View File

@ -16,6 +16,7 @@ import { SettingsUi } from './src/ui/SettingsUi.js';
//TODO move advanced QR options into own UI class
//TODO slash commands
//TODO delete QR set
//TODO handle replacing QR set (create->overwrite): update configs that use this set
//TODO easy way to CRUD QRs and sets
//TODO easy way to set global and chat sets

View File

@ -1,19 +1,98 @@
import { QuickReplyLink } from './QuickReplyLink.js';
import { getSortableDelay } from '../../../utils.js';
import { QuickReplySetLink } from './QuickReplySetLink.js';
import { QuickReplySet } from './QuickReplySet.js';
export class QuickReplyConfig {
/**@type {QuickReplyLink[]}*/ setList = [];
/**@type {QuickReplySetLink[]}*/ setList = [];
/**@type {Boolean}*/ isGlobal;
/**@type {Function}*/ onUpdate;
/**@type {Function}*/ onRequestEditSet;
/**@type {HTMLElement}*/ dom;
/**@type {HTMLElement}*/ setListDom;
static from(props) {
props.setList = props.setList?.map(it=>QuickReplyLink.from(it)) ?? [];
return Object.assign(new this(), props);
props.setList = props.setList?.map(it=>QuickReplySetLink.from(it)) ?? [];
const instance = Object.assign(new this(), props);
instance.init();
return instance;
}
init() {
this.setList.forEach(it=>this.hookQuickReplyLink(it));
}
renderSettingsInto(/**@type {HTMLElement}*/root) {
/**@type {HTMLElement}*/
const setList = root.querySelector('.qr--setList');
this.setListDom = setList;
setList.innerHTML = '';
root.querySelector('.qr--setListAdd').addEventListener('click', ()=>{
const qrl = new QuickReplySetLink();
qrl.set = QuickReplySet.list[0];
this.hookQuickReplyLink(qrl);
this.setList.push(qrl);
setList.append(qrl.renderSettings(this.setList.length - 1));
this.update();
});
// @ts-ignore
$(setList).sortable({
delay: getSortableDelay(),
stop: ()=>this.onSetListSort(),
});
this.setList.forEach((qrl,idx)=>setList.append(qrl.renderSettings(idx)));
}
onSetListSort() {
this.setList = Array.from(this.setListDom.children).map((it,idx)=>{
const qrl = this.setList[Number(it.getAttribute('data-order'))];
qrl.index = idx;
it.setAttribute('data-order', String(idx));
return qrl;
});
this.update();
}
/**
* @param {QuickReplySetLink} qrl
*/
hookQuickReplyLink(qrl) {
qrl.onDelete = ()=>this.deleteQuickReplyLink(qrl);
qrl.onUpdate = ()=>this.update();
qrl.onRequestEditSet = ()=>this.requestEditSet(qrl.set);
}
deleteQuickReplyLink(qrl) {
this.setList.splice(this.setList.indexOf(qrl), 1);
this.update();
}
update() {
if (this.onUpdate) {
this.onUpdate(this);
}
}
requestEditSet(qrs) {
if (this.onRequestEditSet) {
this.onRequestEditSet(qrs);
}
}
toJSON() {
return {
setList: this.setList,

View File

@ -13,9 +13,6 @@ export class QuickReplyContextLink {
/**@type {QuickReplySet}*/ set;
/**@type {Boolean}*/ isChained = false;
toJSON() {
return {
set: this.set?.name,

View File

@ -1,24 +0,0 @@
import { QuickReplySet } from './QuickReplySet.js';
export class QuickReplyLink {
/**@type {QuickReplySet}*/ set;
/**@type {Boolean}*/ isVisible = true;
static from(props) {
props.set = QuickReplySet.get(props.set);
return Object.assign(new this(), props);
}
toJSON() {
return {
set: this.set.name,
isVisible: this.isVisible,
};
}
}

View File

@ -189,4 +189,20 @@ export class QuickReplySet {
warn(`Failed to save Quick Reply Set: ${this.name}`);
}
}
async delete() {
const response = await fetch('/deletequickreply', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify(this),
});
if (response.ok) {
this.unrender();
const idx = QuickReplySet.list.indexOf(this);
QuickReplySet.list.splice(idx, 1);
} else {
warn(`Failed to delete Quick Reply Set: ${this.name}`);
}
}
}

View File

@ -0,0 +1,126 @@
import { QuickReplySet } from './QuickReplySet.js';
export class QuickReplySetLink {
static from(props) {
props.set = QuickReplySet.get(props.set);
return Object.assign(new this(), props);
}
/**@type {QuickReplySet}*/ set;
/**@type {Boolean}*/ isVisible = true;
/**@type {Number}*/ index;
/**@type {Function}*/ onUpdate;
/**@type {Function}*/ onRequestEditSet;
/**@type {Function}*/ onDelete;
/**@type {HTMLElement}*/ settingsDom;
renderSettings(idx) {
if (!this.settingsDom || idx != this.index) {
this.index = idx;
const item = document.createElement('div'); {
this.settingsDom = item;
item.classList.add('qr--item');
item.setAttribute('data-order', String(this.index));
const drag = document.createElement('div'); {
drag.classList.add('drag-handle');
drag.classList.add('ui-sortable-handle');
drag.textContent = '☰';
item.append(drag);
}
const set = document.createElement('select'); {
set.classList.add('qr--set');
set.addEventListener('change', ()=>{
this.set = QuickReplySet.get(set.value);
this.update();
});
QuickReplySet.list.forEach(qrs=>{
const opt = document.createElement('option'); {
opt.value = qrs.name;
opt.textContent = qrs.name;
opt.selected = qrs == this.set;
set.append(opt);
}
});
item.append(set);
}
const visible = document.createElement('label'); {
visible.classList.add('qr--visible');
const cb = document.createElement('input'); {
cb.type = 'checkbox';
cb.checked = this.isVisible;
cb.addEventListener('click', ()=>{
this.isVisible = cb.checked;
this.update();
});
visible.append(cb);
}
visible.append('Show buttons');
item.append(visible);
}
const edit = document.createElement('div'); {
edit.classList.add('menu_button');
edit.classList.add('menu_button_icon');
edit.classList.add('fa-solid');
edit.classList.add('fa-pencil');
edit.title = 'Edit quick reply set';
edit.addEventListener('click', ()=>this.requestEditSet());
item.append(edit);
}
const del = document.createElement('div'); {
del.classList.add('qr--del');
del.classList.add('menu_button');
del.classList.add('menu_button_icon');
del.classList.add('fa-solid');
del.classList.add('fa-trash-can');
del.title = 'Remove quick reply set';
del.addEventListener('click', ()=>this.delete());
item.append(del);
}
}
}
return this.settingsDom;
}
unrenderSettings() {
this.settingsDom?.remove();
this.settingsDom = null;
}
update() {
if (this.onUpdate) {
this.onUpdate(this);
}
}
requestEditSet() {
if (this.onRequestEditSet) {
this.onRequestEditSet(this.set);
}
}
delete() {
this.unrenderSettings();
if (this.onDelete) {
this.onDelete();
}
}
toJSON() {
return {
set: this.set.name,
isVisible: this.isVisible,
};
}
}

View File

@ -5,7 +5,9 @@ import { QuickReplyConfig } from './QuickReplyConfig.js';
export class QuickReplySettings {
static from(props) {
props.config = QuickReplyConfig.from(props.config);
return Object.assign(new this(), props);
const instance = Object.assign(new this(), props);
instance.init();
return instance;
}
@ -14,9 +16,41 @@ export class QuickReplySettings {
/**@type {Boolean}*/ isEnabled = false;
/**@type {Boolean}*/ isCombined = false;
/**@type {QuickReplyConfig}*/ config;
/**@type {QuickReplyConfig}*/ chatConfig;
/**@type {QuickReplyConfig}*/ _chatConfig;
get chatConfig() {
return this._chatConfig;
}
set chatConfig(value) {
if (this._chatConfig != value) {
this.unhookConfig(this._chatConfig);
this._chatConfig = value;
this.hookConfig(this._chatConfig);
}
}
/**@type {Function}*/ onSave;
/**@type {Function}*/ onRequestEditSet;
init() {
this.hookConfig(this.config);
this.hookConfig(this.chatConfig);
}
hookConfig(config) {
if (config) {
config.onUpdate = ()=>this.save();
config.onRequestEditSet = (qrs)=>this.requestEditSet(qrs);
}
}
unhookConfig(config) {
if (config) {
config.onUpdate = null;
config.onRequestEditSet = null;
}
}
@ -33,6 +67,12 @@ export class QuickReplySettings {
}
}
requestEditSet(qrs) {
if (this.onRequestEditSet) {
this.onRequestEditSet(qrs);
}
}
toJSON() {
return {
isEnabled: this.isEnabled,

View File

@ -1,7 +1,6 @@
import { callPopup } from '../../../../../script.js';
import { getSortableDelay } from '../../../../utils.js';
import { warn } from '../../index.js';
import { QuickReplyLink } from '../QuickReplyLink.js';
import { QuickReplySet } from '../QuickReplySet.js';
// eslint-disable-next-line no-unused-vars
import { QuickReplySettings } from '../QuickReplySettings.js';
@ -30,6 +29,7 @@ export class SettingsUi {
constructor(/**@type {QuickReplySettings}*/settings) {
this.settings = settings;
settings.onRequestEditSet = (qrs) => this.selectQrSet(qrs);
}
@ -78,135 +78,23 @@ export class SettingsUi {
this.isCombined.addEventListener('click', ()=>this.onIsCombined());
}
/**
* @param {QuickReplyLink} qrl
* @param {Number} idx
*/
renderQrLinkItem(qrl, idx, isGlobal) {
const item = document.createElement('div'); {
item.classList.add('qr--item');
item.setAttribute('data-order', String(idx));
const drag = document.createElement('div'); {
drag.classList.add('drag-handle');
drag.classList.add('ui-sortable-handle');
drag.textContent = '☰';
item.append(drag);
}
const set = document.createElement('select'); {
set.addEventListener('change', ()=>{
qrl.set = QuickReplySet.get(set.value);
this.settings.save();
});
QuickReplySet.list.forEach(qrs=>{
const opt = document.createElement('option'); {
opt.value = qrs.name;
opt.textContent = qrs.name;
opt.selected = qrs == qrl.set;
set.append(opt);
}
});
item.append(set);
}
const visible = document.createElement('label'); {
visible.classList.add('qr--visible');
const cb = document.createElement('input'); {
cb.type = 'checkbox';
cb.checked = qrl.isVisible;
cb.addEventListener('click', ()=>{
qrl.isVisible = cb.checked;
this.settings.save();
});
visible.append(cb);
}
visible.append('Show buttons');
item.append(visible);
}
const edit = document.createElement('div'); {
edit.classList.add('menu_button');
edit.classList.add('menu_button_icon');
edit.classList.add('fa-solid');
edit.classList.add('fa-pencil');
edit.title = 'Edit quick reply set';
edit.addEventListener('click', ()=>{
this.currentSet.value = qrl.set.name;
this.onQrSetChange();
});
item.append(edit);
}
const del = document.createElement('div'); {
del.classList.add('menu_button');
del.classList.add('menu_button_icon');
del.classList.add('fa-solid');
del.classList.add('fa-trash-can');
del.title = 'Remove quick reply set';
del.addEventListener('click', ()=>{
item.remove();
if (isGlobal) {
this.settings.config.setList.splice(this.settings.config.setList.indexOf(qrl), 1);
this.updateOrder(this.globalSetList);
} else {
this.settings.chatConfig.setList.splice(this.settings.chatConfig.setList.indexOf(qrl), 1);
this.updateOrder(this.chatSetList);
}
this.settings.save();
});
item.append(del);
}
}
return item;
}
prepareGlobalSetList() {
// global set list
this.dom.querySelector('#qr--global-setListAdd').addEventListener('click', ()=>{
const qrl = new QuickReplyLink();
qrl.set = QuickReplySet.list[0];
this.settings.config.setList.push(qrl);
this.globalSetList.append(this.renderQrLinkItem(qrl, this.settings.config.setList.length - 1, true));
this.settings.save();
});
this.globalSetList = this.dom.querySelector('#qr--global-setList');
// @ts-ignore
$(this.globalSetList).sortable({
delay: getSortableDelay(),
stop: ()=>this.onGlobalSetListSort(),
});
this.settings.config.setList.forEach((qrl,idx)=>{
this.globalSetList.append(this.renderQrLinkItem(qrl, idx, true));
});
this.settings.config.renderSettingsInto(this.dom.querySelector('#qr--global'));
}
prepareChatSetList() {
// chat set list
this.dom.querySelector('#qr--chat-setListAdd').addEventListener('click', ()=>{
if (!this.settings.chatConfig) {
toastr.warning('No active chat.');
return;
}
const qrl = new QuickReplyLink();
qrl.set = QuickReplySet.list[0];
this.settings.chatConfig.setList.push(qrl);
this.chatSetList.append(this.renderQrLinkItem(qrl, this.settings.chatConfig.setList.length - 1, false));
this.settings.save();
});
this.chatSetList = this.dom.querySelector('#qr--chat-setList');
if (!this.settings.chatConfig) {
const info = document.createElement('small'); {
if (this.settings.chatConfig) {
this.settings.chatConfig.renderSettingsInto(this.dom.querySelector('#qr--chat'));
} else {
const info = document.createElement('div'); {
info.textContent = 'No active chat.';
this.chatSetList.append(info);
}
this.dom.querySelector('#qr--chat').append(info);
}
// @ts-ignore
$(this.chatSetList).sortable({
delay: getSortableDelay(),
stop: ()=>this.onChatSetListSort(),
});
this.settings.chatConfig?.setList?.forEach((qrl,idx)=>{
this.chatSetList.append(this.renderQrLinkItem(qrl, idx, false));
});
}
prepareQrEditor() {
// qr editor
this.dom.querySelector('#qr--set-delete').addEventListener('click', async()=>this.deleteQrSet());
this.dom.querySelector('#qr--set-new').addEventListener('click', async()=>this.addQrSet());
this.dom.querySelector('#qr--set-add').addEventListener('click', async()=>{
this.currentQrSet.addQuickReply();
@ -310,12 +198,31 @@ export class SettingsUi {
this.currentQrSet.save();
}
async deleteQrSet() {
const confirmed = await callPopup(`Are you sure you want to delete the Quick Reply Set "${this.currentQrSet.name}"? This cannot be undone.`, 'confirm');
if (confirmed) {
const idx = QuickReplySet.list.indexOf(this.currentQrSet);
await this.currentQrSet.delete();
this.currentSet.children[idx].remove();
[
...Array.from(this.globalSetList.querySelectorAll('.qr--item > .qr--set')),
...Array.from(this.chatSetList.querySelectorAll('.qr--item > .qr--set')),
]
// @ts-ignore
.filter(it=>it.value == this.currentQrSet.name)
// @ts-ignore
.forEach(it=>it.closest('.qr--item').querySelector('.qr--del').click())
;
this.onQrSetChange();
}
}
async addQrSet() {
const name = await callPopup('Quick Reply Set Name:', 'input');
if (name && name.length > 0) {
const oldQrs = QuickReplySet.get(name);
if (oldQrs) {
const replace = await callPopup(`A Quick Reply Set named "${name} already exists.\nDo you want to overwrite the existing Quick Reply Set?\nThe existing set will be deleted. This cannot be undone.`, 'confirm');
const replace = await callPopup(`A Quick Reply Set named "${name}" already exists.\nDo you want to overwrite the existing Quick Reply Set?\nThe existing set will be deleted. This cannot be undone.`, 'confirm');
if (replace) {
const qrs = new QuickReplySet();
qrs.name = name;
@ -340,4 +247,9 @@ export class SettingsUi {
}
}
}
selectQrSet(qrs) {
this.currentSet.value = qrs.name;
this.onQrSetChange();
}
}