diff --git a/public/scripts/extensions/quick-reply/index.js b/public/scripts/extensions/quick-reply/index.js
index 461e918b3..b2c714b07 100644
--- a/public/scripts/extensions/quick-reply/index.js
+++ b/public/scripts/extensions/quick-reply/index.js
@@ -5,6 +5,7 @@ import { QuickReplyConfig } from './src/QuickReplyConfig.js';
import { QuickReplyContextLink } from './src/QuickReplyContextLink.js';
import { QuickReplySet } from './src/QuickReplySet.js';
import { QuickReplySettings } from './src/QuickReplySettings.js';
+import { SlashCommandHandler } from './src/SlashCommandHandler.js';
import { ButtonUi } from './src/ui/ButtonUi.js';
import { SettingsUi } from './src/ui/SettingsUi.js';
@@ -12,7 +13,6 @@ import { SettingsUi } from './src/ui/SettingsUi.js';
//TODO move advanced QR options into own UI class
-//TODO slash commands
//TODO easy way to CRUD QRs and sets
//TODO easy way to set global and chat sets
@@ -151,6 +151,9 @@ const init = async () => {
await qr.onExecute();
}
}
+
+ const slash = new SlashCommandHandler(settings);
+ slash.init();
};
eventSource.on(event_types.APP_READY, init);
diff --git a/public/scripts/extensions/quick-reply/src/QuickReply.js b/public/scripts/extensions/quick-reply/src/QuickReply.js
index 8e262cee2..7fbb85422 100644
--- a/public/scripts/extensions/quick-reply/src/QuickReply.js
+++ b/public/scripts/extensions/quick-reply/src/QuickReply.js
@@ -108,9 +108,6 @@ export class QuickReply {
- /**
- * @param {any} idx
- */
renderSettings(idx) {
if (!this.settingsDom) {
const item = document.createElement('div'); {
@@ -194,44 +191,6 @@ export class QuickReply {
this.settingsDom?.remove();
}
-
-
-
- delete() {
- if (this.onDelete) {
- this.unrender();
- this.unrenderSettings();
- this.onDelete(this);
- }
- }
- /**
- * @param {string} value
- */
- updateMessage(value) {
- if (this.onUpdate) {
- this.message = value;
- this.updateRender();
- this.onUpdate(this);
- }
- }
- /**
- * @param {string} value
- */
- updateLabel(value) {
- if (this.onUpdate) {
- this.label = value;
- this.updateRender();
- this.onUpdate(this);
- }
- }
-
- updateContext() {
- if (this.onUpdate) {
- this.updateRender();
- this.onUpdate(this);
- }
- }
-
async showOptions() {
const response = await fetch('/scripts/extensions/quick-reply/html/qrOptions.html', { cache: 'no-store' });
if (response.ok) {
@@ -359,6 +318,63 @@ export class QuickReply {
+ delete() {
+ if (this.onDelete) {
+ this.unrender();
+ this.unrenderSettings();
+ this.onDelete(this);
+ }
+ }
+
+ /**
+ * @param {string} value
+ */
+ updateMessage(value) {
+ if (this.onUpdate) {
+ this.message = value;
+ this.updateRender();
+ this.onUpdate(this);
+ }
+ }
+
+ /**
+ * @param {string} value
+ */
+ updateLabel(value) {
+ if (this.onUpdate) {
+ this.label = value;
+ this.updateRender();
+ this.onUpdate(this);
+ }
+ }
+
+ updateContext() {
+ if (this.onUpdate) {
+ this.updateRender();
+ this.onUpdate(this);
+ }
+ }
+ addContextLink(cl) {
+ this.contextList.push(cl);
+ this.updateContext();
+ }
+ removeContextLink(setName) {
+ const idx = this.contextList.findIndex(it=>it.set.name == setName);
+ if (idx > -1) {
+ this.contextList.splice(idx, 1);
+ this.updateContext();
+ }
+ }
+ clearContextLinks() {
+ if (this.contextList.length) {
+ this.contextList.splice(0, this.contextList.length);
+ this.updateContext();
+ }
+ }
+
+
+
+
toJSON() {
return {
id: this.id,
diff --git a/public/scripts/extensions/quick-reply/src/QuickReplyConfig.js b/public/scripts/extensions/quick-reply/src/QuickReplyConfig.js
index c7ca95c5a..872e33e30 100644
--- a/public/scripts/extensions/quick-reply/src/QuickReplyConfig.js
+++ b/public/scripts/extensions/quick-reply/src/QuickReplyConfig.js
@@ -30,27 +30,52 @@ export class QuickReplyConfig {
}
+ hasSet(qrs) {
+ return this.setList.find(it=>it.set == qrs) != null;
+ }
+ addSet(qrs, isVisible = true) {
+ if (!this.hasSet(qrs)) {
+ const qrl = new QuickReplySetLink();
+ qrl.set = qrs;
+ qrl.isVisible = isVisible;
+ this.setList.push(qrl);
+ this.update();
+ this.updateSetListDom();
+ }
+ }
+ removeSet(qrs) {
+ const idx = this.setList.findIndex(it=>it.set == qrs);
+ if (idx > -1) {
+ this.setList.splice(idx, 1);
+ this.update();
+ this.updateSetListDom();
+ }
+ }
+
+
renderSettingsInto(/**@type {HTMLElement}*/root) {
/**@type {HTMLElement}*/
- const setList = root.querySelector('.qr--setList');
- this.setListDom = setList;
- setList.innerHTML = '';
+ this.setListDom = root.querySelector('.qr--setList');
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.setListDom.append(qrl.renderSettings(this.setList.length - 1));
this.update();
});
+ this.updateSetListDom();
+ }
+ updateSetListDom() {
+ this.setListDom.innerHTML = '';
// @ts-ignore
- $(setList).sortable({
+ $(this.setListDom).sortable({
delay: getSortableDelay(),
stop: ()=>this.onSetListSort(),
});
- this.setList.filter(it=>!it.set.isDeleted).forEach((qrl,idx)=>setList.append(qrl.renderSettings(idx)));
+ this.setList.filter(it=>!it.set.isDeleted).forEach((qrl,idx)=>this.setListDom.append(qrl.renderSettings(idx)));
}
diff --git a/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js
new file mode 100644
index 000000000..a522730bd
--- /dev/null
+++ b/public/scripts/extensions/quick-reply/src/SlashCommandHandler.js
@@ -0,0 +1,217 @@
+import { registerSlashCommand } from '../../../slash-commands.js';
+import { QuickReplyContextLink } from './QuickReplyContextLink.js';
+import { QuickReplySet } from './QuickReplySet.js';
+import { QuickReplySettings } from './QuickReplySettings.js';
+
+export class SlashCommandHandler {
+ /**@type {QuickReplySettings}*/ settings;
+
+
+
+
+ constructor(/**@type {QuickReplySettings}*/settings) {
+ this.settings = settings;
+ }
+
+
+
+
+ init() {
+ registerSlashCommand('qr', (_, value) => this.executeQuickReplyByIndex(Number(value)), [], '(number) – activates the specified Quick Reply', true, true);
+ registerSlashCommand('qrset', ()=>toastr.warning('The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.'), [], 'DEPRECATED – The command /qrset has been deprecated. Use /qr-set, /qr-set-on, and /qr-set-off instead.', true, true);
+ registerSlashCommand('qr-set', (args, value)=>this.toggleGlobalSet(value, args), [], '[visible=true] (number) – toggle global QR set', true, true);
+ registerSlashCommand('qr-set-on', (args, value)=>this.addGlobalSet(value, args), [], '[visible=true] (number) – activate global QR set', true, true);
+ registerSlashCommand('qr-set-off', (_, value)=>this.removeGlobalSet(value), [], '(number) – deactivate global QR set', true, true);
+ registerSlashCommand('qr-chat-set', (args, value)=>this.toggleChatSet(value, args), [], '[visible=true] (number) – toggle chat QR set', true, true);
+ registerSlashCommand('qr-chat-set-on', (args, value)=>this.addChatSet(value, args), [], '[visible=true] (number) – activate chat QR set', true, true);
+ registerSlashCommand('qr-chat-set-off', (_, value)=>this.removeChatSet(value), [], '(number) – deactivate chat QR set', true, true);
+
+ const qrArgs = `
+ label - string - text on the button, e.g., label=MyButton
+ set - string - name of the QR set, e.g., set=PresetName1
+ hidden - bool - whether the button should be hidden, e.g., hidden=true
+ startup - bool - auto execute on app startup, e.g., startup=true
+ user - bool - auto execute on user message, e.g., user=true
+ bot - bool - auto execute on AI message, e.g., bot=true
+ load - bool - auto execute on chat load, e.g., load=true
+ title - bool - title / tooltip to be shown on button, e.g., title="My Fancy Button"
+ `.trim();
+ const qrUpdateArgs = `
+ newlabel - string - new text fort the button, e.g. newlabel=MyRenamedButton
+ ${qrArgs}
+ `.trim();
+ registerSlashCommand('qr-create', (args, message)=>this.createQuickReply(args, message), [], `(arguments [message])\n arguments:\n ${qrArgs} – creates a new Quick Reply, example: /qr-create set=MyPreset label=MyButton /echo 123`, true, true);
+ registerSlashCommand('qr-update', (args, message)=>this.updateQuickReply(args, message), [], `(arguments [message])\n arguments:\n ${qrUpdateArgs} – updates Quick Reply, example: /qr-update set=MyPreset label=MyButton newlabel=MyRenamedButton /echo 123`, true, true);
+ registerSlashCommand('qr-delete', (args, name)=>this.deleteQuickReply(args, name), [], '(set=string [label]) – deletes Quick Reply', true, true);
+ registerSlashCommand('qr-contextadd', (args, name)=>this.createContextItem(args, name), [], '(set=string label=string chain=bool [preset name]) – add context menu preset to a QR, example: /qr-contextadd set=MyPreset label=MyButton chain=true MyOtherPreset', true, true);
+ registerSlashCommand('qr-contextdel', (args, name)=>this.deleteContextItem(args, name), [], '(set=string label=string [preset name]) – remove context menu preset from a QR, example: /qr-contextdel set=MyPreset label=MyButton MyOtherPreset', true, true);
+ registerSlashCommand('qr-contextclear', (args, label)=>this.clearContextMenu(args, label), [], '(set=string [label]) – remove all context menu presets from a QR, example: /qr-contextclear set=MyPreset MyButton', true, true);
+ const presetArgs = `
+ enabled - bool - enable or disable the preset
+ nosend - bool - disable send / insert in user input (invalid for slash commands)
+ before - bool - place QR before user input
+ slots - int - number of slots
+ inject - bool - inject user input automatically (if disabled use {{input}})
+ `.trim();
+ registerSlashCommand('qr-set-create', (args, name)=>this.createSet(name, args), ['qr-presetadd'], `(arguments [label])\n arguments:\n ${presetArgs} – create a new preset (overrides existing ones), example: /qr-presetadd slots=3 MyNewPreset`, true, true);
+ registerSlashCommand('qr-set-update', (args, name)=>this.updateSet(name, args), ['qr-presetupdate'], `(arguments [label])\n arguments:\n ${presetArgs} – update an existing preset, example: /qr-presetupdate enabled=false MyPreset`, true, true);
+ }
+
+
+
+
+ getSetByName(name) {
+ const set = QuickReplySet.get(name);
+ if (!set) {
+ toastr.error(`No Quick Reply Set with the name "${name}" could be found.`);
+ }
+ return set;
+ }
+
+ getQrByLabel(setName, label) {
+ const set = this.getSetByName(setName);
+ if (!set) return;
+ const qr = set.qrList.find(it=>it.label == label);
+ if (!qr) {
+ toastr.error(`No Quick Reply with the label "${label}" could be found in the set "${set.name}"`);
+ }
+ return qr;
+ }
+
+
+
+
+ async executeQuickReplyByIndex(idx) {
+ const qr = [...this.settings.config.setList, ...(this.settings.chatConfig?.setList ?? [])]
+ .map(it=>it.set.qrList)
+ .flat()[idx]
+ ;
+ if (qr) {
+ return await qr.onExecute();
+ } else {
+ toastr.error(`No Quick Reply at index "${idx}"`);
+ }
+ }
+
+
+ toggleGlobalSet(name, args = {}) {
+ const set = this.getSetByName(name);
+ if (!set) return;
+ if (this.settings.config.hasSet(set)) {
+ this.settings.config.removeSet(set);
+ } else {
+ this.settings.config.addSet(set, JSON.parse(args.visible ?? 'true'));
+ }
+ }
+ addGlobalSet(name, args = {}) {
+ const set = this.getSetByName(name);
+ if (!set) return;
+ this.settings.config.addSet(set, JSON.parse(args.visible ?? 'true'));
+ }
+ removeGlobalSet(name) {
+ const set = this.getSetByName(name);
+ if (!set) return;
+ this.settings.config.removeSet(set);
+ }
+
+
+ toggleChatSet(name, args = {}) {
+ if (!this.settings.chatConfig) return;
+ const set = this.getSetByName(name);
+ if (!set) return;
+ if (this.settings.chatConfig.hasSet(set)) {
+ this.settings.chatConfig.removeSet(set);
+ } else {
+ this.settings.chatConfig.addSet(set, JSON.parse(args.visible ?? 'true'));
+ }
+ }
+ addChatSet(name, args = {}) {
+ if (!this.settings.chatConfig) return;
+ const set = this.getSetByName(name);
+ if (!set) return;
+ this.settings.chatConfig.addSet(set, JSON.parse(args.visible ?? 'true'));
+ }
+ removeChatSet(name) {
+ if (!this.settings.chatConfig) return;
+ const set = this.getSetByName(name);
+ if (!set) return;
+ this.settings.chatConfig.removeSet(set);
+ }
+
+
+ createQuickReply(args, message) {
+ const set = this.getSetByName(args.set);
+ if (!set) return;
+ const qr = set.addQuickReply();
+ qr.label = args.label ?? '';
+ qr.message = message ?? '';
+ qr.title = args.title ?? '';
+ qr.isHidden = JSON.parse(args.hidden ?? 'false') === true;
+ qr.executeOnStartup = JSON.parse(args.startup ?? 'false') === true;
+ qr.executeOnUser = JSON.parse(args.user ?? 'false') === true;
+ qr.executeOnAi = JSON.parse(args.bot ?? 'false') === true;
+ qr.executeOnChatChange = JSON.parse(args.load ?? 'false') === true;
+ qr.onUpdate();
+ }
+ updateQuickReply(args, message) {
+ const qr = this.getQrByLabel(args.set, args.label);
+ if (!qr) return;
+ qr.message = (message ?? '').trim().length > 0 ? message : qr.message;
+ qr.label = args.newlabel !== undefined ? (args.newlabel ?? '') : qr.label;
+ qr.title = args.title !== undefined ? (args.title ?? '') : qr.title;
+ qr.isHidden = args.hidden !== undefined ? (JSON.parse(args.hidden ?? 'false') === true) : qr.isHidden;
+ qr.executeOnStartup = args.startup !== undefined ? (JSON.parse(args.startup ?? 'false') === true) : qr.executeOnStartup;
+ qr.executeOnUser = args.user !== undefined ? (JSON.parse(args.user ?? 'false') === true) : qr.executeOnUser;
+ qr.executeOnAi = args.bot !== undefined ? (JSON.parse(args.bot ?? 'false') === true) : qr.executeOnAi;
+ qr.executeOnChatChange = args.load !== undefined ? (JSON.parse(args.load ?? 'false') === true) : qr.executeOnChatChange;
+ qr.onUpdate();
+ }
+ deleteQuickReply(args, label) {
+ const qr = this.getQrByLabel(args.set, args.label ?? label);
+ if (!qr) return;
+ qr.delete();
+ }
+
+
+ createContextItem(args, name) {
+ const qr = this.getQrByLabel(args.set, args.label);
+ const set = this.getSetByName(name);
+ if (!qr || !set) return;
+ const cl = new QuickReplyContextLink();
+ cl.set = set;
+ cl.isChained = JSON.parse(args.chain ?? 'false') ?? false;
+ qr.addContextLink(cl);
+ }
+ deleteContextItem(args, name) {
+ const qr = this.getQrByLabel(args.set, args.label);
+ const set = this.getSetByName(name);
+ if (!qr || !set) return;
+ qr.removeContextLink(set.name);
+ }
+ clearContextMenu(args, label) {
+ const qr = this.getQrByLabel(args.set, args.label ?? label);
+ if (!qr) return;
+ qr.clearContextLinks();
+ }
+
+
+ createSet(name, args) {
+ const set = new QuickReplySet();
+ set.name = args.name ?? name;
+ set.disableSend = JSON.parse(args.nosend ?? 'false') === true;
+ set.placeBeforeInput = JSON.parse(args.before ?? 'false') === true;
+ set.injectInput = JSON.parse(args.inject ?? 'false') === true;
+ QuickReplySet.list.push(set);
+ set.save();
+ //TODO settings UI must be updated
+ }
+ updateSet(name, args) {
+ const set = this.getSetByName(args.name ?? name);
+ if (!set) return;
+ set.disableSend = args.nosend !== undefined ? (JSON.parse(args.nosend ?? 'false') === true) : set.disableSend;
+ set.placeBeforeInput = args.before !== undefined ? (JSON.parse(args.before ?? 'false') === true) : set.placeBeforeInput;
+ set.injectInput = args.inject !== undefined ? (JSON.parse(args.inject ?? 'false') === true) : set.injectInput;
+ set.save();
+ //TODO settings UI must be updated
+ }
+}