|
import { hljs, morphdom } from '../../../../lib.js'; |
|
import { POPUP_RESULT, POPUP_TYPE, Popup } from '../../../popup.js'; |
|
import { setSlashCommandAutoComplete } from '../../../slash-commands.js'; |
|
import { SlashCommandAbortController } from '../../../slash-commands/SlashCommandAbortController.js'; |
|
import { SlashCommandBreakPoint } from '../../../slash-commands/SlashCommandBreakPoint.js'; |
|
import { SlashCommandClosure } from '../../../slash-commands/SlashCommandClosure.js'; |
|
import { SlashCommandClosureResult } from '../../../slash-commands/SlashCommandClosureResult.js'; |
|
import { SlashCommandDebugController } from '../../../slash-commands/SlashCommandDebugController.js'; |
|
import { SlashCommandExecutor } from '../../../slash-commands/SlashCommandExecutor.js'; |
|
import { SlashCommandParser } from '../../../slash-commands/SlashCommandParser.js'; |
|
import { SlashCommandParserError } from '../../../slash-commands/SlashCommandParserError.js'; |
|
import { SlashCommandScope } from '../../../slash-commands/SlashCommandScope.js'; |
|
import { accountStorage } from '../../../util/AccountStorage.js'; |
|
import { debounce, delay, getSortableDelay, showFontAwesomePicker } from '../../../utils.js'; |
|
import { log, quickReplyApi, warn } from '../index.js'; |
|
import { QuickReplyContextLink } from './QuickReplyContextLink.js'; |
|
import { QuickReplySet } from './QuickReplySet.js'; |
|
import { ContextMenu } from './ui/ctx/ContextMenu.js'; |
|
|
|
export class QuickReply { |
|
|
|
|
|
|
|
static from(props) { |
|
props.contextList = (props.contextList ?? []).map(( it)=>QuickReplyContextLink.from(it)); |
|
return Object.assign(new this(), props); |
|
} |
|
|
|
|
|
|
|
|
|
id; |
|
icon; |
|
label = ''; |
|
showLabel = false; |
|
title = ''; |
|
message = ''; |
|
|
|
contextList; |
|
|
|
preventAutoExecute = true; |
|
isHidden = false; |
|
executeOnStartup = false; |
|
executeOnUser = false; |
|
executeOnAi = false; |
|
executeOnChatChange = false; |
|
executeOnGroupMemberDraft = false; |
|
executeOnNewChat = false; |
|
automationId = ''; |
|
|
|
onExecute; |
|
onDebug; |
|
onDelete; |
|
onUpdate; |
|
onInsertBefore; |
|
onTransfer; |
|
|
|
|
|
dom; |
|
domIcon; |
|
domLabel; |
|
settingsDom; |
|
settingsDomIcon; |
|
settingsDomLabel; |
|
settingsDomMessage; |
|
|
|
editorPopup; |
|
editorDom; |
|
|
|
editorMessage; |
|
editorMessageLabel; |
|
editorSyntax; |
|
editorExecuteBtn; |
|
editorExecuteBtnPause; |
|
editorExecuteBtnStop; |
|
editorExecuteProgress; |
|
editorExecuteErrors; |
|
editorExecuteResult; |
|
editorDebugState; |
|
editorExecutePromise; |
|
isExecuting; |
|
abortController; |
|
debugController; |
|
|
|
|
|
get hasContext() { |
|
return this.contextList && this.contextList.filter(it => it.set).length > 0; |
|
} |
|
|
|
|
|
|
|
|
|
unrender() { |
|
this.dom?.remove(); |
|
this.dom = null; |
|
} |
|
updateRender() { |
|
if (!this.dom) return; |
|
this.dom.title = this.title || this.message; |
|
if (this.icon) { |
|
this.domIcon.classList.remove('qr--hidden'); |
|
if (this.showLabel) this.domLabel.classList.remove('qr--hidden'); |
|
else this.domLabel.classList.add('qr--hidden'); |
|
} else { |
|
this.domIcon.classList.add('qr--hidden'); |
|
this.domLabel.classList.remove('qr--hidden'); |
|
} |
|
this.domLabel.textContent = this.label; |
|
this.dom.classList[this.hasContext ? 'add' : 'remove']('qr--hasCtx'); |
|
} |
|
render() { |
|
this.unrender(); |
|
if (!this.dom) { |
|
const root = document.createElement('div'); { |
|
this.dom = root; |
|
root.classList.add('qr--button'); |
|
root.classList.add('menu_button'); |
|
if (this.hasContext) { |
|
root.classList.add('qr--hasCtx'); |
|
} |
|
root.title = this.title || this.message; |
|
root.addEventListener('contextmenu', (evt) => { |
|
log('contextmenu', this, this.hasContext); |
|
if (this.hasContext) { |
|
evt.preventDefault(); |
|
evt.stopPropagation(); |
|
const menu = new ContextMenu(this); |
|
menu.show(evt); |
|
} |
|
}); |
|
root.addEventListener('click', (evt)=>{ |
|
if (evt.ctrlKey) { |
|
this.showEditor(); |
|
return; |
|
} |
|
this.execute(); |
|
}); |
|
const icon = document.createElement('div'); { |
|
this.domIcon = icon; |
|
icon.classList.add('qr--button-icon'); |
|
icon.classList.add('fa-solid'); |
|
if (!this.icon) icon.classList.add('qr--hidden'); |
|
else icon.classList.add(this.icon); |
|
root.append(icon); |
|
} |
|
const lbl = document.createElement('div'); { |
|
this.domLabel = lbl; |
|
lbl.classList.add('qr--button-label'); |
|
if (this.icon && !this.showLabel) lbl.classList.add('qr--hidden'); |
|
lbl.textContent = this.label; |
|
root.append(lbl); |
|
} |
|
const expander = document.createElement('div'); { |
|
expander.classList.add('qr--button-expander'); |
|
expander.textContent = '⋮'; |
|
expander.title = 'Open context menu'; |
|
expander.addEventListener('click', (evt) => { |
|
evt.stopPropagation(); |
|
evt.preventDefault(); |
|
const menu = new ContextMenu(this); |
|
menu.show(evt); |
|
}); |
|
root.append(expander); |
|
} |
|
} |
|
} |
|
return this.dom; |
|
} |
|
|
|
|
|
|
|
|
|
renderSettings(idx) { |
|
if (!this.settingsDom) { |
|
const item = document.createElement('div'); { |
|
this.settingsDom = item; |
|
item.classList.add('qr--set-item'); |
|
item.setAttribute('data-order', String(idx)); |
|
item.setAttribute('data-id', String(this.id)); |
|
const adder = document.createElement('div'); { |
|
adder.classList.add('qr--set-itemAdder'); |
|
const actions = document.createElement('div'); { |
|
actions.classList.add('qr--actions'); |
|
const addNew = document.createElement('div'); { |
|
addNew.classList.add('qr--action'); |
|
addNew.classList.add('qr--add'); |
|
addNew.classList.add('menu_button'); |
|
addNew.classList.add('menu_button_icon'); |
|
addNew.classList.add('fa-solid'); |
|
addNew.classList.add('fa-plus'); |
|
addNew.title = 'Add quick reply'; |
|
addNew.addEventListener('click', ()=>this.onInsertBefore()); |
|
actions.append(addNew); |
|
} |
|
const paste = document.createElement('div'); { |
|
paste.classList.add('qr--action'); |
|
paste.classList.add('qr--paste'); |
|
paste.classList.add('menu_button'); |
|
paste.classList.add('menu_button_icon'); |
|
paste.classList.add('fa-solid'); |
|
paste.classList.add('fa-paste'); |
|
paste.title = 'Add quick reply from clipboard'; |
|
paste.addEventListener('click', async()=>{ |
|
const text = await navigator.clipboard.readText(); |
|
this.onInsertBefore(text); |
|
}); |
|
actions.append(paste); |
|
} |
|
const importFile = document.createElement('div'); { |
|
importFile.classList.add('qr--action'); |
|
importFile.classList.add('qr--importFile'); |
|
importFile.classList.add('menu_button'); |
|
importFile.classList.add('menu_button_icon'); |
|
importFile.classList.add('fa-solid'); |
|
importFile.classList.add('fa-file-import'); |
|
importFile.title = 'Add quick reply from JSON file'; |
|
importFile.addEventListener('click', async()=>{ |
|
const inp = document.createElement('input'); { |
|
inp.type = 'file'; |
|
inp.accept = '.json'; |
|
inp.addEventListener('change', async()=>{ |
|
if (inp.files.length > 0) { |
|
for (const file of inp.files) { |
|
const text = await file.text(); |
|
this.onInsertBefore(text); |
|
} |
|
} |
|
}); |
|
inp.click(); |
|
} |
|
}); |
|
actions.append(importFile); |
|
} |
|
adder.append(actions); |
|
} |
|
item.append(adder); |
|
} |
|
const itemContent = document.createElement('div'); { |
|
itemContent.classList.add('qr--content'); |
|
const drag = document.createElement('div'); { |
|
drag.classList.add('drag-handle'); |
|
drag.classList.add('ui-sortable-handle'); |
|
drag.textContent = '☰'; |
|
itemContent.append(drag); |
|
} |
|
const lblContainer = document.createElement('div'); { |
|
lblContainer.classList.add('qr--set-itemLabelContainer'); |
|
const icon = document.createElement('div'); { |
|
this.settingsDomIcon = icon; |
|
icon.title = 'Click to change icon'; |
|
icon.classList.add('qr--set-itemIcon'); |
|
icon.classList.add('menu_button'); |
|
icon.classList.add('fa-fw'); |
|
if (this.icon) { |
|
icon.classList.add('fa-solid'); |
|
icon.classList.add(this.icon); |
|
} |
|
icon.addEventListener('click', async()=>{ |
|
let value = await showFontAwesomePicker(); |
|
this.updateIcon(value); |
|
}); |
|
lblContainer.append(icon); |
|
} |
|
const lbl = document.createElement('input'); { |
|
this.settingsDomLabel = lbl; |
|
lbl.classList.add('qr--set-itemLabel'); |
|
lbl.classList.add('text_pole'); |
|
lbl.value = this.label; |
|
lbl.addEventListener('input', ()=>this.updateLabel(lbl.value)); |
|
lblContainer.append(lbl); |
|
} |
|
itemContent.append(lblContainer); |
|
} |
|
item.append(itemContent); |
|
} |
|
const optContainer = document.createElement('div'); { |
|
optContainer.classList.add('qr--set-optionsContainer'); |
|
const opt = document.createElement('div'); { |
|
opt.classList.add('qr--action'); |
|
opt.classList.add('menu_button'); |
|
opt.classList.add('fa-fw'); |
|
opt.classList.add('fa-solid'); |
|
opt.textContent = '⁝'; |
|
opt.title = 'Additional options:\n - large editor\n - context menu\n - auto-execution\n - tooltip'; |
|
opt.addEventListener('click', ()=>this.showEditor()); |
|
optContainer.append(opt); |
|
} |
|
itemContent.append(optContainer); |
|
} |
|
const mes = document.createElement('textarea'); { |
|
this.settingsDomMessage = mes; |
|
mes.id = `qr--set--item${this.id}`; |
|
mes.classList.add('qr--set-itemMessage'); |
|
mes.value = this.message; |
|
|
|
$(mes).on('input', ()=>this.updateMessage(mes.value)); |
|
itemContent.append(mes); |
|
} |
|
const actions = document.createElement('div'); { |
|
actions.classList.add('qr--actions'); |
|
const move = document.createElement('div'); { |
|
move.classList.add('qr--action'); |
|
move.classList.add('menu_button'); |
|
move.classList.add('fa-fw'); |
|
move.classList.add('fa-solid'); |
|
move.classList.add('fa-truck-arrow-right'); |
|
move.title = 'Move quick reply to other set'; |
|
move.addEventListener('click', ()=>this.onTransfer(this)); |
|
actions.append(move); |
|
} |
|
const copy = document.createElement('div'); { |
|
copy.classList.add('qr--action'); |
|
copy.classList.add('menu_button'); |
|
copy.classList.add('fa-fw'); |
|
copy.classList.add('fa-solid'); |
|
copy.classList.add('fa-copy'); |
|
copy.title = 'Copy quick reply to clipboard'; |
|
copy.addEventListener('click', async()=>{ |
|
await navigator.clipboard.writeText(JSON.stringify(this)); |
|
copy.classList.add('qr--success'); |
|
await delay(3010); |
|
copy.classList.remove('qr--success'); |
|
}); |
|
actions.append(copy); |
|
} |
|
const cut = document.createElement('div'); { |
|
cut.classList.add('qr--action'); |
|
cut.classList.add('menu_button'); |
|
cut.classList.add('fa-fw'); |
|
cut.classList.add('fa-solid'); |
|
cut.classList.add('fa-cut'); |
|
cut.title = 'Cut quick reply to clipboard (copy and remove)'; |
|
cut.addEventListener('click', async()=>{ |
|
await navigator.clipboard.writeText(JSON.stringify(this)); |
|
this.delete(); |
|
}); |
|
actions.append(cut); |
|
} |
|
const exp = document.createElement('div'); { |
|
exp.classList.add('qr--action'); |
|
exp.classList.add('menu_button'); |
|
exp.classList.add('fa-fw'); |
|
exp.classList.add('fa-solid'); |
|
exp.classList.add('fa-file-export'); |
|
exp.title = 'Export quick reply as file'; |
|
exp.addEventListener('click', ()=>{ |
|
const blob = new Blob([JSON.stringify(this)], { type:'text' }); |
|
const url = URL.createObjectURL(blob); |
|
const a = document.createElement('a'); { |
|
a.href = url; |
|
a.download = `${this.label}.qr.json`; |
|
a.click(); |
|
} |
|
}); |
|
actions.append(exp); |
|
} |
|
const del = document.createElement('div'); { |
|
del.classList.add('qr--action'); |
|
del.classList.add('menu_button'); |
|
del.classList.add('fa-fw'); |
|
del.classList.add('fa-solid'); |
|
del.classList.add('fa-trash-can'); |
|
del.classList.add('redWarningBG'); |
|
del.title = 'Remove Quick Reply\n---\nShift+Click to skip confirmation'; |
|
del.addEventListener('click', async(evt)=>{ |
|
if (!evt.shiftKey) { |
|
const result = await Popup.show.confirm( |
|
'Remove Quick Reply', |
|
'Are you sure you want to remove this Quick Reply?', |
|
); |
|
if (result != POPUP_RESULT.AFFIRMATIVE) { |
|
return; |
|
} |
|
} |
|
this.delete(); |
|
}); |
|
actions.append(del); |
|
} |
|
itemContent.append(actions); |
|
} |
|
} |
|
} |
|
return this.settingsDom; |
|
} |
|
unrenderSettings() { |
|
this.settingsDom?.remove(); |
|
} |
|
|
|
async showEditor() { |
|
const response = await fetch('/scripts/extensions/quick-reply/html/qrEditor.html', { cache: 'no-store' }); |
|
if (response.ok) { |
|
this.template = document.createRange().createContextualFragment(await response.text()).querySelector('#qr--modalEditor'); |
|
|
|
|
|
const dom = this.template.cloneNode(true); |
|
this.editorDom = dom; |
|
this.editorPopup = new Popup(dom, POPUP_TYPE.TEXT, undefined, { okButton: 'OK', wide: true, large: true, rows: 1 }); |
|
const popupResult = this.editorPopup.show(); |
|
|
|
|
|
|
|
const icon = dom.querySelector('#qr--modal-icon'); |
|
if (this.icon) { |
|
icon.classList.add('fa-solid'); |
|
icon.classList.add(this.icon); |
|
} |
|
else { |
|
icon.textContent = '…'; |
|
} |
|
icon.addEventListener('click', async()=>{ |
|
let value = await showFontAwesomePicker(); |
|
if (value === null) return; |
|
if (this.icon) icon.classList.remove(this.icon); |
|
if (value == '') { |
|
icon.classList.remove('fa-solid'); |
|
icon.textContent = '…'; |
|
} else { |
|
icon.textContent = ''; |
|
icon.classList.add('fa-solid'); |
|
icon.classList.add(value); |
|
} |
|
this.updateIcon(value); |
|
}); |
|
|
|
const showLabel = dom.querySelector('#qr--modal-showLabel'); |
|
showLabel.checked = this.showLabel; |
|
showLabel.addEventListener('click', ()=>{ |
|
this.updateShowLabel(showLabel.checked); |
|
}); |
|
|
|
const label = dom.querySelector('#qr--modal-label'); |
|
label.value = this.label; |
|
label.addEventListener('input', ()=>{ |
|
this.updateLabel(label.value); |
|
}); |
|
let switcherList; |
|
dom.querySelector('#qr--modal-switcher').addEventListener('click', (evt)=>{ |
|
if (switcherList) { |
|
switcherList.remove(); |
|
switcherList = null; |
|
return; |
|
} |
|
const list = document.createElement('ul'); { |
|
switcherList = list; |
|
list.classList.add('qr--modal-switcherList'); |
|
const makeList = (qrs)=>{ |
|
const setItem = document.createElement('li'); { |
|
setItem.classList.add('qr--modal-switcherItem'); |
|
setItem.addEventListener('click', ()=>{ |
|
list.innerHTML = ''; |
|
for (const qrs of quickReplyApi.listSets()) { |
|
const item = document.createElement('li'); { |
|
item.classList.add('qr--modal-switcherItem'); |
|
item.addEventListener('click', ()=>{ |
|
list.innerHTML = ''; |
|
makeList(quickReplyApi.getSetByName(qrs)); |
|
}); |
|
const lbl = document.createElement('div'); { |
|
lbl.classList.add('qr--label'); |
|
lbl.textContent = qrs; |
|
item.append(lbl); |
|
} |
|
list.append(item); |
|
} |
|
} |
|
}); |
|
const lbl = document.createElement('div'); { |
|
lbl.classList.add('qr--label'); |
|
const icon = document.createElement('i'); { |
|
icon.classList.add('fa-solid'); |
|
icon.classList.add('fa-arrow-alt-circle-right'); |
|
icon.classList.add('menu_button'); |
|
lbl.append(icon); |
|
} |
|
const text = document.createElement('span'); { |
|
text.textContent = 'Switch QR Sets...'; |
|
lbl.append(text); |
|
} |
|
setItem.append(lbl); |
|
} |
|
list.append(setItem); |
|
} |
|
const addItem = document.createElement('li'); { |
|
addItem.classList.add('qr--modal-switcherItem'); |
|
addItem.addEventListener('click', ()=>{ |
|
const qr = quickReplyApi.getSetByQr(this).addQuickReply(); |
|
this.editorPopup.completeAffirmative(); |
|
qr.showEditor(); |
|
}); |
|
const lbl = document.createElement('div'); { |
|
lbl.classList.add('qr--label'); |
|
const icon = document.createElement('i'); { |
|
icon.classList.add('fa-solid'); |
|
icon.classList.add('fa-plus'); |
|
icon.classList.add('menu_button'); |
|
lbl.append(icon); |
|
} |
|
const text = document.createElement('span'); { |
|
text.textContent = 'Add QR'; |
|
lbl.append(text); |
|
} |
|
addItem.append(lbl); |
|
} |
|
list.append(addItem); |
|
} |
|
for (const qr of qrs.qrList.toSorted((a,b)=>a.label.toLowerCase().localeCompare(b.label.toLowerCase()))) { |
|
const item = document.createElement('li'); { |
|
item.classList.add('qr--modal-switcherItem'); |
|
if (qr == this) item.classList.add('qr--current'); |
|
else item.addEventListener('click', ()=>{ |
|
this.editorPopup.completeAffirmative(); |
|
qr.showEditor(); |
|
}); |
|
const lbl = document.createElement('div'); { |
|
lbl.classList.add('qr--label'); |
|
lbl.textContent = qr.label; |
|
item.append(lbl); |
|
} |
|
const id = document.createElement('div'); { |
|
id.classList.add('qr--id'); |
|
id.textContent = qr.id.toString(); |
|
item.append(id); |
|
} |
|
const mes = document.createElement('div'); { |
|
mes.classList.add('qr--message'); |
|
mes.textContent = qr.message; |
|
item.append(mes); |
|
} |
|
list.append(item); |
|
} |
|
} |
|
}; |
|
makeList(quickReplyApi.getSetByQr(this)); |
|
} |
|
label.parentElement.append(list); |
|
}); |
|
|
|
const title = dom.querySelector('#qr--modal-title'); |
|
title.value = this.title; |
|
title.addEventListener('input', () => { |
|
this.updateTitle(title.value); |
|
}); |
|
|
|
const messageSyntaxInner = dom.querySelector('#qr--modal-messageSyntaxInner'); |
|
this.editorSyntax = messageSyntaxInner; |
|
|
|
const wrap = dom.querySelector('#qr--modal-wrap'); |
|
wrap.checked = JSON.parse(accountStorage.getItem('qr--wrap') ?? 'false'); |
|
wrap.addEventListener('click', () => { |
|
accountStorage.setItem('qr--wrap', JSON.stringify(wrap.checked)); |
|
updateWrap(); |
|
}); |
|
const updateWrap = () => { |
|
if (wrap.checked) { |
|
message.style.whiteSpace = 'pre-wrap'; |
|
messageSyntaxInner.style.whiteSpace = 'pre-wrap'; |
|
if (this.clone) { |
|
this.clone.style.whiteSpace = 'pre-wrap'; |
|
} |
|
} else { |
|
message.style.whiteSpace = 'pre'; |
|
messageSyntaxInner.style.whiteSpace = 'pre'; |
|
if (this.clone) { |
|
this.clone.style.whiteSpace = 'pre'; |
|
} |
|
} |
|
updateScrollDebounced(); |
|
}; |
|
const updateScroll = (evt) => { |
|
let left = message.scrollLeft; |
|
let top = message.scrollTop; |
|
if (evt) { |
|
evt.preventDefault(); |
|
left = message.scrollLeft + evt.deltaX; |
|
top = message.scrollTop + evt.deltaY; |
|
message.scrollTo({ |
|
behavior: 'instant', |
|
left, |
|
top, |
|
}); |
|
} |
|
messageSyntaxInner.scrollTo({ |
|
behavior: 'instant', |
|
left, |
|
top, |
|
}); |
|
}; |
|
const updateScrollDebounced = updateScroll; |
|
const updateSyntaxEnabled = ()=>{ |
|
if (syntax.checked) { |
|
dom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); |
|
} else { |
|
dom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); |
|
} |
|
}; |
|
|
|
const tabSize = dom.querySelector('#qr--modal-tabSize'); |
|
tabSize.value = JSON.parse(accountStorage.getItem('qr--tabSize') ?? '4'); |
|
const updateTabSize = () => { |
|
message.style.tabSize = tabSize.value; |
|
messageSyntaxInner.style.tabSize = tabSize.value; |
|
updateScrollDebounced(); |
|
}; |
|
tabSize.addEventListener('change', () => { |
|
accountStorage.setItem('qr--tabSize', JSON.stringify(Number(tabSize.value))); |
|
updateTabSize(); |
|
}); |
|
|
|
const executeShortcut = dom.querySelector('#qr--modal-executeShortcut'); |
|
executeShortcut.checked = JSON.parse(accountStorage.getItem('qr--executeShortcut') ?? 'true'); |
|
executeShortcut.addEventListener('click', () => { |
|
accountStorage.setItem('qr--executeShortcut', JSON.stringify(executeShortcut.checked)); |
|
}); |
|
|
|
const syntax = dom.querySelector('#qr--modal-syntax'); |
|
syntax.checked = JSON.parse(accountStorage.getItem('qr--syntax') ?? 'true'); |
|
syntax.addEventListener('click', () => { |
|
accountStorage.setItem('qr--syntax', JSON.stringify(syntax.checked)); |
|
updateSyntaxEnabled(); |
|
}); |
|
if (navigator.keyboard) { |
|
navigator.keyboard.getLayoutMap().then(it=>dom.querySelector('#qr--modal-commentKey').textContent = it.get('Backslash')); |
|
} else { |
|
dom.querySelector('#qr--modal-commentKey').closest('small').remove(); |
|
} |
|
this.editorMessageLabel = dom.querySelector('label[for="qr--modal-message"]'); |
|
|
|
const message = dom.querySelector('#qr--modal-message'); |
|
this.editorMessage = message; |
|
message.value = this.message; |
|
const updateMessageDebounced = debounce((value)=>this.updateMessage(value), 10); |
|
message.addEventListener('input', () => { |
|
updateMessageDebounced(message.value); |
|
updateScrollDebounced(); |
|
}, { passive:true }); |
|
const getLineStart = ()=>{ |
|
const start = message.selectionStart; |
|
let lineStart; |
|
if (start == 0 || message.value[start - 1] == '\n') { |
|
|
|
|
|
lineStart = start; |
|
} else { |
|
|
|
|
|
lineStart = message.value.lastIndexOf('\n', start - 1) + 1; |
|
} |
|
return lineStart; |
|
}; |
|
message.addEventListener('keydown', async(evt) => { |
|
if (this.isExecuting) return; |
|
if (evt.key == 'Tab' && !evt.shiftKey && !evt.ctrlKey && !evt.altKey) { |
|
|
|
evt.preventDefault(); |
|
const start = message.selectionStart; |
|
const end = message.selectionEnd; |
|
if (end - start > 0 && message.value.substring(start, end).includes('\n')) { |
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
const lineStart = getLineStart(); |
|
message.selectionStart = lineStart; |
|
const affectedLines = message.value.substring(lineStart, end).split('\n'); |
|
|
|
document.execCommand('insertText', false, `\t${affectedLines.join('\n\t')}`); |
|
message.selectionStart = start + 1; |
|
message.selectionEnd = end + affectedLines.length; |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
} else if (!(ac.isReplaceable && ac.isActive)) { |
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
|
|
document.execCommand('insertText', false, '\t'); |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
} |
|
} else if (evt.key == 'Tab' && evt.shiftKey && !evt.ctrlKey && !evt.altKey) { |
|
|
|
evt.preventDefault(); |
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
const start = message.selectionStart; |
|
const end = message.selectionEnd; |
|
const lineStart = getLineStart(); |
|
message.selectionStart = lineStart; |
|
const affectedLines = message.value.substring(lineStart, end).split('\n'); |
|
const newText = affectedLines.map(it=>it.replace(/^\t/, '')).join('\n'); |
|
const delta = affectedLines.join('\n').length - newText.length; |
|
|
|
if (delta > 0) { |
|
if (newText == '') { |
|
document.execCommand('delete', false); |
|
} else { |
|
document.execCommand('insertText', false, newText); |
|
} |
|
message.selectionStart = start - (affectedLines[0].startsWith('\t') ? 1 : 0); |
|
message.selectionEnd = end - delta; |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
} else { |
|
message.selectionStart = start; |
|
} |
|
} else if (evt.key == 'Enter' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey && !(ac.isReplaceable && ac.isActive)) { |
|
|
|
const start = message.selectionStart; |
|
let lineStart = getLineStart(); |
|
const indent = /^([^\S\n]*)/.exec(message.value.slice(lineStart))[1] ?? ''; |
|
if (indent.length) { |
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
evt.preventDefault(); |
|
|
|
document.execCommand('insertText', false, `\n${indent}`); |
|
message.selectionStart = start + 1 + indent.length; |
|
message.selectionEnd = message.selectionStart; |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
} |
|
} else if (evt.key == 'Enter' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { |
|
if (executeShortcut.checked) { |
|
|
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
evt.preventDefault(); |
|
const selectionStart = message.selectionStart; |
|
const selectionEnd = message.selectionEnd; |
|
message.blur(); |
|
await this.executeFromEditor(); |
|
if (document.activeElement != message) { |
|
message.focus(); |
|
message.selectionStart = selectionStart; |
|
message.selectionEnd = selectionEnd; |
|
} |
|
} |
|
} else if (evt.key == 'F9' && !evt.ctrlKey && !evt.shiftKey && !evt.altKey) { |
|
|
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
evt.preventDefault(); |
|
preBreakPointStart = message.selectionStart; |
|
preBreakPointEnd = message.selectionEnd; |
|
toggleBreakpoint(); |
|
} else if (evt.code == 'Backslash' && evt.ctrlKey && !evt.shiftKey && !evt.altKey) { |
|
|
|
|
|
evt.stopImmediatePropagation(); |
|
evt.stopPropagation(); |
|
evt.preventDefault(); |
|
|
|
const parser = new SlashCommandParser(); |
|
parser.parse(message.value, false); |
|
const start = message.selectionStart; |
|
const end = message.selectionEnd; |
|
const comment = parser.commandIndex.findLast(it=>it.name == '*' && (it.start <= start && it.end >= start || it.start <= end && it.end >= end)); |
|
if (comment) { |
|
|
|
let content = message.value.slice(comment.start + 1, comment.end - 1); |
|
let len = content.length; |
|
content = content.replace(/^ /, ''); |
|
const offsetStart = len - content.length; |
|
len = content.length; |
|
content = content.replace(/ $/, ''); |
|
const offsetEnd = len - content.length; |
|
message.selectionStart = comment.start - 1; |
|
message.selectionEnd = comment.end + 1; |
|
|
|
document.execCommand('insertText', false, content); |
|
message.selectionStart = start - (start >= comment.start ? 2 + offsetStart : 0); |
|
message.selectionEnd = end - 2 - offsetStart - (end >= comment.end ? 2 + offsetEnd : 0); |
|
} else { |
|
|
|
const lineStart = getLineStart(); |
|
const lineEnd = message.value.indexOf('\n', end); |
|
message.selectionStart = lineStart; |
|
message.selectionEnd = lineEnd; |
|
|
|
document.execCommand('insertText', false, `/* ${message.value.slice(lineStart, lineEnd)} *|`); |
|
message.selectionStart = start + 3; |
|
message.selectionEnd = end + 3; |
|
} |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
} |
|
}); |
|
const ac = await setSlashCommandAutoComplete(message, true); |
|
message.addEventListener('wheel', (evt)=>{ |
|
updateScrollDebounced(evt); |
|
}); |
|
message.addEventListener('scroll', (evt)=>{ |
|
updateScrollDebounced(); |
|
}); |
|
let preBreakPointStart; |
|
let preBreakPointEnd; |
|
|
|
|
|
|
|
const removeBreakpoint = (bp)=>{ |
|
|
|
let start = bp.start - 1; |
|
|
|
while (message.value[start] != '/') start--; |
|
|
|
while (/[^\S\n]/.test(message.value[start - 1])) start--; |
|
|
|
if (message.value[start - 1] == '\n') start--; |
|
let end = bp.end; |
|
|
|
while (/\s/.test(message.value[end])) end++; |
|
|
|
if (message.value[end] == '|') end++; |
|
message.selectionStart = start; |
|
message.selectionEnd = end; |
|
|
|
document.execCommand('insertText', false, ''); |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
let postStart = preBreakPointStart; |
|
let postEnd = preBreakPointEnd; |
|
|
|
if (preBreakPointStart <= start) { |
|
|
|
} else if (preBreakPointStart > start && preBreakPointEnd < end) { |
|
|
|
postStart = start; |
|
} else if (preBreakPointStart >= end) { |
|
|
|
postStart = preBreakPointStart - (end - start); |
|
} |
|
if (preBreakPointEnd <= start) { |
|
|
|
} else if (preBreakPointEnd > start && preBreakPointEnd < end) { |
|
|
|
postEnd = start; |
|
} else if (preBreakPointEnd >= end) { |
|
|
|
postEnd = preBreakPointEnd - (end - start); |
|
} |
|
return { start:postStart, end:postEnd }; |
|
}; |
|
|
|
|
|
|
|
const addBreakpoint = (cmd)=>{ |
|
|
|
let start = cmd.start - 1; |
|
let indent = ''; |
|
|
|
while (message.value[start] != '/') start--; |
|
|
|
while (/[^\S\n]/.test(message.value[start - 1])) { |
|
start--; |
|
indent += message.value[start]; |
|
} |
|
|
|
if (message.value[start - 1] == '\n') { |
|
start--; |
|
indent = `\n${indent}`; |
|
} |
|
const breakpointText = `${indent}/breakpoint |`; |
|
message.selectionStart = start; |
|
message.selectionEnd = start; |
|
|
|
document.execCommand('insertText', false, breakpointText); |
|
message.dispatchEvent(new Event('input', { bubbles:true })); |
|
return breakpointText.length; |
|
}; |
|
const toggleBreakpoint = ()=>{ |
|
const idx = message.selectionStart; |
|
let postStart = preBreakPointStart; |
|
let postEnd = preBreakPointEnd; |
|
const parser = new SlashCommandParser(); |
|
parser.parse(message.value, false); |
|
const cmdIdx = parser.commandIndex.findLastIndex(it=>it.start <= idx); |
|
if (cmdIdx > -1) { |
|
const cmd = parser.commandIndex[cmdIdx]; |
|
if (cmd instanceof SlashCommandBreakPoint) { |
|
const bp = cmd; |
|
const { start, end } = removeBreakpoint(bp); |
|
postStart = start; |
|
postEnd = end; |
|
} else if (parser.commandIndex[cmdIdx - 1] instanceof SlashCommandBreakPoint) { |
|
const bp = parser.commandIndex[cmdIdx - 1]; |
|
const { start, end } = removeBreakpoint(bp); |
|
postStart = start; |
|
postEnd = end; |
|
} else { |
|
const len = addBreakpoint(cmd); |
|
postStart += len; |
|
postEnd += len; |
|
} |
|
message.selectionStart = postStart; |
|
message.selectionEnd = postEnd; |
|
} |
|
}; |
|
message.addEventListener('pointerdown', (evt)=>{ |
|
if (!evt.ctrlKey || !evt.altKey) return; |
|
preBreakPointStart = message.selectionStart; |
|
preBreakPointEnd = message.selectionEnd; |
|
}); |
|
message.addEventListener('pointerup', async(evt)=>{ |
|
if (!evt.ctrlKey || !evt.altKey || message.selectionStart != message.selectionEnd) return; |
|
toggleBreakpoint(); |
|
}); |
|
|
|
const resizeListener = debounce((evt) => { |
|
updateScrollDebounced(evt); |
|
if (document.activeElement == message) { |
|
message.blur(); |
|
message.focus(); |
|
} |
|
}); |
|
window.addEventListener('resize', resizeListener); |
|
updateSyntaxEnabled(); |
|
const updateSyntax = ()=>{ |
|
if (messageSyntaxInner && syntax.checked) { |
|
morphdom( |
|
messageSyntaxInner, |
|
`<div>${hljs.highlight(`${message.value}${message.value.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value}</div>`, |
|
{ childrenOnly: true }, |
|
); |
|
updateScrollDebounced(); |
|
} |
|
}; |
|
let lastSyntaxUpdate = 0; |
|
const fpsTime = 1000 / 30; |
|
let lastMessageValue = null; |
|
let wasSyntax = null; |
|
const updateSyntaxLoop = ()=>{ |
|
const now = Date.now(); |
|
|
|
if (now - lastSyntaxUpdate < fpsTime) return requestAnimationFrame(updateSyntaxLoop); |
|
|
|
if (!messageSyntaxInner || !message) return requestAnimationFrame(updateSyntaxLoop); |
|
|
|
if (!messageSyntaxInner.closest('body')) return; |
|
|
|
if (this.isExecuting) { |
|
lastMessageValue = null; |
|
return requestAnimationFrame(updateSyntaxLoop); |
|
} |
|
|
|
if (wasSyntax == syntax.checked && lastMessageValue == message.value) return requestAnimationFrame(updateSyntaxLoop); |
|
wasSyntax = syntax.checked; |
|
lastSyntaxUpdate = now; |
|
lastMessageValue = message.value; |
|
updateSyntax(); |
|
requestAnimationFrame(updateSyntaxLoop); |
|
}; |
|
requestAnimationFrame(()=>updateSyntaxLoop()); |
|
message.style.setProperty('text-shadow', 'none', 'important'); |
|
updateWrap(); |
|
updateTabSize(); |
|
|
|
|
|
|
|
const tpl = dom.querySelector('#qr--ctxItem'); |
|
const linkList = dom.querySelector('#qr--ctxEditor'); |
|
const fillQrSetSelect = (select, link) => { |
|
[{ name: 'Select a QR set' }, ...QuickReplySet.list.toSorted((a,b)=>a.name.toLowerCase().localeCompare(b.name.toLowerCase()))].forEach(qrs => { |
|
const opt = document.createElement('option'); { |
|
opt.value = qrs.name; |
|
opt.textContent = qrs.name; |
|
opt.selected = qrs.name == link.set?.name; |
|
select.append(opt); |
|
} |
|
}); |
|
}; |
|
const addCtxItem = (link, idx) => { |
|
|
|
|
|
const itemDom = tpl.content.querySelector('.qr--ctxItem').cloneNode(true); { |
|
itemDom.setAttribute('data-order', String(idx)); |
|
|
|
|
|
const select = itemDom.querySelector('.qr--set'); |
|
fillQrSetSelect(select, link); |
|
select.addEventListener('change', () => { |
|
link.set = QuickReplySet.get(select.value); |
|
this.updateContext(); |
|
}); |
|
|
|
|
|
const chain = itemDom.querySelector('.qr--isChained'); |
|
chain.checked = link.isChained; |
|
chain.addEventListener('click', () => { |
|
link.isChained = chain.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
itemDom.querySelector('.qr--delete').addEventListener('click', () => { |
|
itemDom.remove(); |
|
this.contextList.splice(this.contextList.indexOf(link), 1); |
|
this.updateContext(); |
|
}); |
|
|
|
linkList.append(itemDom); |
|
} |
|
}; |
|
[...this.contextList].forEach((link, idx) => addCtxItem(link, idx)); |
|
dom.querySelector('#qr--ctxAdd').addEventListener('click', () => { |
|
const link = new QuickReplyContextLink(); |
|
this.contextList.push(link); |
|
addCtxItem(link, this.contextList.length - 1); |
|
}); |
|
const onContextSort = () => { |
|
this.contextList = Array.from(linkList.querySelectorAll('.qr--ctxItem')).map((it,idx) => { |
|
const link = this.contextList[Number(it.getAttribute('data-order'))]; |
|
it.setAttribute('data-order', String(idx)); |
|
return link; |
|
}); |
|
this.updateContext(); |
|
}; |
|
|
|
$(linkList).sortable({ |
|
delay: getSortableDelay(), |
|
stop: () => onContextSort(), |
|
}); |
|
|
|
|
|
|
|
const preventAutoExecute = dom.querySelector('#qr--preventAutoExecute'); |
|
preventAutoExecute.checked = this.preventAutoExecute; |
|
preventAutoExecute.addEventListener('click', ()=>{ |
|
this.preventAutoExecute = preventAutoExecute.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const isHidden = dom.querySelector('#qr--isHidden'); |
|
isHidden.checked = this.isHidden; |
|
isHidden.addEventListener('click', ()=>{ |
|
this.isHidden = isHidden.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const executeOnStartup = dom.querySelector('#qr--executeOnStartup'); |
|
executeOnStartup.checked = this.executeOnStartup; |
|
executeOnStartup.addEventListener('click', ()=>{ |
|
this.executeOnStartup = executeOnStartup.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const executeOnUser = dom.querySelector('#qr--executeOnUser'); |
|
executeOnUser.checked = this.executeOnUser; |
|
executeOnUser.addEventListener('click', ()=>{ |
|
this.executeOnUser = executeOnUser.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const executeOnAi = dom.querySelector('#qr--executeOnAi'); |
|
executeOnAi.checked = this.executeOnAi; |
|
executeOnAi.addEventListener('click', ()=>{ |
|
this.executeOnAi = executeOnAi.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const executeOnChatChange = dom.querySelector('#qr--executeOnChatChange'); |
|
executeOnChatChange.checked = this.executeOnChatChange; |
|
executeOnChatChange.addEventListener('click', ()=>{ |
|
this.executeOnChatChange = executeOnChatChange.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const executeOnGroupMemberDraft = dom.querySelector('#qr--executeOnGroupMemberDraft'); |
|
executeOnGroupMemberDraft.checked = this.executeOnGroupMemberDraft; |
|
executeOnGroupMemberDraft.addEventListener('click', ()=>{ |
|
this.executeOnGroupMemberDraft = executeOnGroupMemberDraft.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const executeOnNewChat = dom.querySelector('#qr--executeOnNewChat'); |
|
executeOnNewChat.checked = this.executeOnNewChat; |
|
executeOnNewChat.addEventListener('click', ()=>{ |
|
this.executeOnNewChat = executeOnNewChat.checked; |
|
this.updateContext(); |
|
}); |
|
|
|
const automationId = dom.querySelector('#qr--automationId'); |
|
automationId.value = this.automationId; |
|
automationId.addEventListener('input', () => { |
|
this.automationId = automationId.value; |
|
this.updateContext(); |
|
}); |
|
|
|
|
|
const executeProgress = dom.querySelector('#qr--modal-executeProgress'); |
|
this.editorExecuteProgress = executeProgress; |
|
|
|
const executeErrors = dom.querySelector('#qr--modal-executeErrors'); |
|
this.editorExecuteErrors = executeErrors; |
|
|
|
const executeResult = dom.querySelector('#qr--modal-executeResult'); |
|
this.editorExecuteResult = executeResult; |
|
|
|
const debugState = dom.querySelector('#qr--modal-debugState'); |
|
this.editorDebugState = debugState; |
|
|
|
const executeBtn = dom.querySelector('#qr--modal-execute'); |
|
this.editorExecuteBtn = executeBtn; |
|
executeBtn.addEventListener('click', async()=>{ |
|
await this.executeFromEditor(); |
|
}); |
|
|
|
const executeBtnPause = dom.querySelector('#qr--modal-pause'); |
|
this.editorExecuteBtnPause = executeBtnPause; |
|
executeBtnPause.addEventListener('click', async()=>{ |
|
if (this.abortController) { |
|
if (this.abortController.signal.paused) { |
|
this.abortController.continue('Continue button clicked'); |
|
this.editorExecuteProgress.classList.remove('qr--paused'); |
|
} else { |
|
this.abortController.pause('Pause button clicked'); |
|
this.editorExecuteProgress.classList.add('qr--paused'); |
|
} |
|
} |
|
}); |
|
|
|
const executeBtnStop = dom.querySelector('#qr--modal-stop'); |
|
this.editorExecuteBtnStop = executeBtnStop; |
|
executeBtnStop.addEventListener('click', async()=>{ |
|
this.abortController?.abort('Stop button clicked'); |
|
}); |
|
|
|
|
|
const inputOg = document.querySelector('#send_textarea'); |
|
const inputMirror = dom.querySelector('#qr--modal-send_textarea'); |
|
inputMirror.value = inputOg.value; |
|
const inputOgMo = new MutationObserver(muts=>{ |
|
if (muts.find(it=>[...it.removedNodes].includes(inputMirror) || [...it.removedNodes].find(n=>n.contains(inputMirror)))) { |
|
inputOg.removeEventListener('input', inputOgListener); |
|
} |
|
}); |
|
inputOgMo.observe(document.body, { childList:true }); |
|
const inputOgListener = ()=>{ |
|
inputMirror.value = inputOg.value; |
|
}; |
|
inputOg.addEventListener('input', inputOgListener); |
|
inputMirror.addEventListener('input', ()=>{ |
|
inputOg.value = inputMirror.value; |
|
}); |
|
|
|
|
|
const resumeBtn = dom.querySelector('#qr--modal-resume'); |
|
resumeBtn.addEventListener('click', ()=>{ |
|
this.debugController?.resume(); |
|
}); |
|
|
|
const stepBtn = dom.querySelector('#qr--modal-step'); |
|
stepBtn.addEventListener('click', ()=>{ |
|
this.debugController?.step(); |
|
}); |
|
|
|
const stepIntoBtn = dom.querySelector('#qr--modal-stepInto'); |
|
stepIntoBtn.addEventListener('click', ()=>{ |
|
this.debugController?.stepInto(); |
|
}); |
|
|
|
const stepOutBtn = dom.querySelector('#qr--modal-stepOut'); |
|
stepOutBtn.addEventListener('click', ()=>{ |
|
this.debugController?.stepOut(); |
|
}); |
|
|
|
const minimizeBtn = dom.querySelector('#qr--modal-minimize'); |
|
minimizeBtn.addEventListener('click', ()=>{ |
|
this.editorDom.classList.add('qr--minimized'); |
|
}); |
|
const maximizeBtn = dom.querySelector('#qr--modal-maximize'); |
|
maximizeBtn.addEventListener('click', ()=>{ |
|
this.editorDom.classList.remove('qr--minimized'); |
|
}); |
|
|
|
let isResizing = false; |
|
let resizeStart; |
|
let wStart; |
|
|
|
const resizeHandle = dom.querySelector('#qr--resizeHandle'); |
|
resizeHandle.addEventListener('pointerdown', (evt)=>{ |
|
if (isResizing) return; |
|
isResizing = true; |
|
evt.preventDefault(); |
|
resizeStart = evt.x; |
|
wStart = dom.querySelector('#qr--qrOptions').offsetWidth; |
|
const dragListener = debounce((evt)=>{ |
|
const w = wStart + resizeStart - evt.x; |
|
dom.querySelector('#qr--qrOptions').style.setProperty('--width', `${w}px`); |
|
}, 5); |
|
window.addEventListener('pointerup', ()=>{ |
|
window.removeEventListener('pointermove', dragListener); |
|
isResizing = false; |
|
}, { once:true }); |
|
window.addEventListener('pointermove', dragListener); |
|
}); |
|
|
|
await popupResult; |
|
|
|
window.removeEventListener('resize', resizeListener); |
|
} else { |
|
warn('failed to fetch qrEditor template'); |
|
} |
|
} |
|
|
|
getEditorPosition(start, end, message = null) { |
|
const inputRect = this.editorMessage.getBoundingClientRect(); |
|
const style = window.getComputedStyle(this.editorMessage); |
|
if (!this.clone) { |
|
this.clone = document.createElement('div'); |
|
for (const key of style) { |
|
this.clone.style[key] = style[key]; |
|
} |
|
this.clone.style.position = 'fixed'; |
|
this.clone.style.visibility = 'hidden'; |
|
const mo = new MutationObserver(muts=>{ |
|
if (muts.find(it=>[...it.removedNodes].includes(this.editorMessage) || [...it.removedNodes].find(n=>n.contains(this.editorMessage)))) { |
|
this.clone?.remove(); |
|
this.clone = null; |
|
} |
|
}); |
|
mo.observe(document.body, { childList:true }); |
|
} |
|
document.body.append(this.clone); |
|
this.clone.style.width = `${inputRect.width}px`; |
|
this.clone.style.height = `${inputRect.height}px`; |
|
this.clone.style.left = `${inputRect.left}px`; |
|
this.clone.style.top = `${inputRect.top}px`; |
|
this.clone.style.whiteSpace = style.whiteSpace; |
|
this.clone.style.tabSize = style.tabSize; |
|
const text = message ?? this.editorMessage.value; |
|
const before = text.slice(0, start); |
|
this.clone.textContent = before; |
|
const locator = document.createElement('span'); |
|
locator.textContent = text.slice(start, end); |
|
this.clone.append(locator); |
|
this.clone.append(text.slice(end)); |
|
this.clone.scrollTop = this.editorSyntax.scrollTop; |
|
this.clone.scrollLeft = this.editorSyntax.scrollLeft; |
|
const locatorRect = locator.getBoundingClientRect(); |
|
const bodyRect = document.body.getBoundingClientRect(); |
|
const location = { |
|
left: locatorRect.left - bodyRect.left, |
|
right: locatorRect.right - bodyRect.left, |
|
top: locatorRect.top - bodyRect.top, |
|
bottom: locatorRect.bottom - bodyRect.top, |
|
}; |
|
|
|
return location; |
|
} |
|
async executeFromEditor() { |
|
if (this.isExecuting) return; |
|
this.editorPopup.onClosing = ()=>false; |
|
const uuidCheck = /^[0-9a-z]{8}(-[0-9a-z]{4}){3}-[0-9a-z]{12}$/; |
|
const oText = this.message; |
|
this.isExecuting = true; |
|
this.editorDom.classList.add('qr--isExecuting'); |
|
const noSyntax = this.editorDom.querySelector('#qr--modal-messageHolder').classList.contains('qr--noSyntax'); |
|
if (noSyntax) { |
|
this.editorDom.querySelector('#qr--modal-messageHolder').classList.remove('qr--noSyntax'); |
|
} |
|
this.editorExecuteBtn.classList.add('qr--busy'); |
|
this.editorExecuteProgress.style.setProperty('--prog', '0'); |
|
this.editorExecuteErrors.classList.remove('qr--hasErrors'); |
|
this.editorExecuteResult.classList.remove('qr--hasResult'); |
|
this.editorExecuteProgress.classList.remove('qr--error'); |
|
this.editorExecuteProgress.classList.remove('qr--success'); |
|
this.editorExecuteProgress.classList.remove('qr--paused'); |
|
this.editorExecuteProgress.classList.remove('qr--aborted'); |
|
this.editorExecuteErrors.innerHTML = ''; |
|
this.editorExecuteResult.innerHTML = ''; |
|
const syntax = this.editorDom.querySelector('#qr--modal-messageSyntaxInner'); |
|
const updateScroll = (evt) => { |
|
let left = syntax.scrollLeft; |
|
let top = syntax.scrollTop; |
|
if (evt) { |
|
evt.preventDefault(); |
|
left = syntax.scrollLeft + evt.deltaX; |
|
top = syntax.scrollTop + evt.deltaY; |
|
syntax.scrollTo({ |
|
behavior: 'instant', |
|
left, |
|
top, |
|
}); |
|
} |
|
this.editorMessage.scrollTo({ |
|
behavior: 'instant', |
|
left, |
|
top, |
|
}); |
|
}; |
|
const updateScrollDebounced = updateScroll; |
|
syntax.addEventListener('wheel', (evt)=>{ |
|
updateScrollDebounced(evt); |
|
}); |
|
syntax.addEventListener('scroll', (evt)=>{ |
|
updateScrollDebounced(); |
|
}); |
|
try { |
|
this.abortController = new SlashCommandAbortController(); |
|
this.debugController = new SlashCommandDebugController(); |
|
this.debugController.onBreakPoint = async(closure, executor)=>{ |
|
this.editorDom.classList.add('qr--isPaused'); |
|
syntax.innerHTML = hljs.highlight(`${closure.fullText}${closure.fullText.slice(-1) == '\n' ? ' ' : ''}`, { language:'stscript', ignoreIllegals:true })?.value; |
|
this.editorMessageLabel.innerHTML = ''; |
|
if (uuidCheck.test(closure.source)) { |
|
const p0 = document.createElement('span'); { |
|
p0.textContent = 'anonymous: '; |
|
this.editorMessageLabel.append(p0); |
|
} |
|
const p1 = document.createElement('strong'); { |
|
p1.textContent = executor.source.slice(0,5); |
|
this.editorMessageLabel.append(p1); |
|
} |
|
const p2 = document.createElement('span'); { |
|
p2.textContent = executor.source.slice(5, -5); |
|
this.editorMessageLabel.append(p2); |
|
} |
|
const p3 = document.createElement('strong'); { |
|
p3.textContent = executor.source.slice(-5); |
|
this.editorMessageLabel.append(p3); |
|
} |
|
} else { |
|
this.editorMessageLabel.textContent = executor.source; |
|
} |
|
const source = closure.source; |
|
this.editorDebugState.innerHTML = ''; |
|
let ci = -1; |
|
const varNames = []; |
|
const macroNames = []; |
|
|
|
|
|
|
|
const buildVars = (scope, isCurrent = false)=>{ |
|
if (!isCurrent) { |
|
ci--; |
|
} |
|
const c = this.debugController.stack.slice(ci)[0]; |
|
const wrap = document.createElement('div'); { |
|
wrap.classList.add('qr--scope'); |
|
if (isCurrent) { |
|
const executor = this.debugController.cmdStack.slice(-1)[0]; |
|
{ |
|
const namedTitle = document.createElement('div'); { |
|
namedTitle.classList.add('qr--title'); |
|
namedTitle.textContent = `Named Args - /${executor.name}`; |
|
if (executor.command.name == 'run') { |
|
namedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; |
|
} |
|
wrap.append(namedTitle); |
|
} |
|
const keys = new Set([...Object.keys(this.debugController.namedArguments ?? {}), ...(executor.namedArgumentList ?? []).map(it=>it.name)]); |
|
for (const key of keys) { |
|
if (key[0] == '_') continue; |
|
const item = document.createElement('div'); { |
|
item.classList.add('qr--var'); |
|
const k = document.createElement('div'); { |
|
k.classList.add('qr--key'); |
|
k.textContent = key; |
|
item.append(k); |
|
} |
|
const vUnresolved = document.createElement('div'); { |
|
vUnresolved.classList.add('qr--val'); |
|
vUnresolved.classList.add('qr--singleCol'); |
|
const val = executor.namedArgumentList.find(it=>it.name == key)?.value; |
|
if (val instanceof SlashCommandClosure) { |
|
vUnresolved.classList.add('qr--closure'); |
|
vUnresolved.title = val.rawText; |
|
vUnresolved.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
vUnresolved.classList.add('qr--undefined'); |
|
vUnresolved.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
vUnresolved.textContent = val; |
|
vUnresolved.classList.add('qr--simple'); |
|
} |
|
} |
|
item.append(vUnresolved); |
|
} |
|
const vResolved = document.createElement('div'); { |
|
vResolved.classList.add('qr--val'); |
|
vResolved.classList.add('qr--singleCol'); |
|
if (this.debugController.namedArguments === undefined) { |
|
vResolved.classList.add('qr--unresolved'); |
|
} else { |
|
const val = this.debugController.namedArguments?.[key]; |
|
if (val instanceof SlashCommandClosure) { |
|
vResolved.classList.add('qr--closure'); |
|
vResolved.title = val.rawText; |
|
vResolved.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
vResolved.classList.add('qr--undefined'); |
|
vResolved.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
vResolved.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
vResolved.textContent = val; |
|
vResolved.classList.add('qr--simple'); |
|
} |
|
} |
|
} |
|
item.append(vResolved); |
|
} |
|
wrap.append(item); |
|
} |
|
} |
|
} |
|
{ |
|
const unnamedTitle = document.createElement('div'); { |
|
unnamedTitle.classList.add('qr--title'); |
|
unnamedTitle.textContent = `Unnamed Args - /${executor.name}`; |
|
if (executor.command.name == 'run') { |
|
unnamedTitle.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; |
|
} |
|
wrap.append(unnamedTitle); |
|
} |
|
let i = 0; |
|
let unnamed = this.debugController.unnamedArguments ?? []; |
|
if (!Array.isArray(unnamed)) unnamed = [unnamed]; |
|
while (unnamed.length < executor.unnamedArgumentList?.length ?? 0) unnamed.push(undefined); |
|
unnamed = unnamed.map((it,idx)=>[executor.unnamedArgumentList?.[idx], it]); |
|
for (const arg of unnamed) { |
|
i++; |
|
const item = document.createElement('div'); { |
|
item.classList.add('qr--var'); |
|
const k = document.createElement('div'); { |
|
k.classList.add('qr--key'); |
|
k.textContent = i.toString(); |
|
item.append(k); |
|
} |
|
const vUnresolved = document.createElement('div'); { |
|
vUnresolved.classList.add('qr--val'); |
|
vUnresolved.classList.add('qr--singleCol'); |
|
const val = arg[0]?.value; |
|
if (val instanceof SlashCommandClosure) { |
|
vUnresolved.classList.add('qr--closure'); |
|
vUnresolved.title = val.rawText; |
|
vUnresolved.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
vUnresolved.classList.add('qr--undefined'); |
|
vUnresolved.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
vUnresolved.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
vUnresolved.textContent = val; |
|
vUnresolved.classList.add('qr--simple'); |
|
} |
|
} |
|
item.append(vUnresolved); |
|
} |
|
const vResolved = document.createElement('div'); { |
|
vResolved.classList.add('qr--val'); |
|
vResolved.classList.add('qr--singleCol'); |
|
if (this.debugController.unnamedArguments === undefined) { |
|
vResolved.classList.add('qr--unresolved'); |
|
} else if ((Array.isArray(this.debugController.unnamedArguments) ? this.debugController.unnamedArguments : [this.debugController.unnamedArguments]).length < i) { |
|
|
|
} else { |
|
const val = arg[1]; |
|
if (val instanceof SlashCommandClosure) { |
|
vResolved.classList.add('qr--closure'); |
|
vResolved.title = val.rawText; |
|
vResolved.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
vResolved.classList.add('qr--undefined'); |
|
vResolved.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
vResolved.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
vResolved.textContent = val; |
|
vResolved.classList.add('qr--simple'); |
|
} |
|
} |
|
} |
|
item.append(vResolved); |
|
} |
|
wrap.append(item); |
|
} |
|
} |
|
} |
|
} |
|
|
|
const title = document.createElement('div'); { |
|
title.classList.add('qr--title'); |
|
title.textContent = isCurrent ? 'Current Scope' : 'Parent Scope'; |
|
if (c.source == source) { |
|
let hi; |
|
title.addEventListener('pointerenter', ()=>{ |
|
const loc = this.getEditorPosition(Math.max(0, c.executorList[0].start - 1), c.executorList.slice(-1)[0].end, c.fullText); |
|
const layer = syntax.getBoundingClientRect(); |
|
hi = document.createElement('div'); |
|
hi.classList.add('qr--highlight-secondary'); |
|
hi.style.left = `${loc.left - layer.left}px`; |
|
hi.style.width = `${loc.right - loc.left}px`; |
|
hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; |
|
hi.style.height = `${loc.bottom - loc.top}px`; |
|
syntax.append(hi); |
|
}); |
|
title.addEventListener('pointerleave', ()=>hi?.remove()); |
|
} |
|
wrap.append(title); |
|
} |
|
for (const key of Object.keys(scope.variables)) { |
|
const isHidden = varNames.includes(key); |
|
if (!isHidden) varNames.push(key); |
|
const item = document.createElement('div'); { |
|
item.classList.add('qr--var'); |
|
if (isHidden) item.classList.add('qr--isHidden'); |
|
const k = document.createElement('div'); { |
|
k.classList.add('qr--key'); |
|
k.textContent = key; |
|
item.append(k); |
|
} |
|
const v = document.createElement('div'); { |
|
v.classList.add('qr--val'); |
|
const val = scope.variables[key]; |
|
if (val instanceof SlashCommandClosure) { |
|
v.classList.add('qr--closure'); |
|
v.title = val.rawText; |
|
v.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
v.classList.add('qr--undefined'); |
|
v.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
v.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
v.textContent = val; |
|
v.classList.add('qr--simple'); |
|
} |
|
} |
|
item.append(v); |
|
} |
|
wrap.append(item); |
|
} |
|
} |
|
for (const key of Object.keys(scope.macros)) { |
|
const isHidden = macroNames.includes(key); |
|
if (!isHidden) macroNames.push(key); |
|
const item = document.createElement('div'); { |
|
item.classList.add('qr--macro'); |
|
if (isHidden) item.classList.add('qr--isHidden'); |
|
const k = document.createElement('div'); { |
|
k.classList.add('qr--key'); |
|
k.textContent = key; |
|
item.append(k); |
|
} |
|
const v = document.createElement('div'); { |
|
v.classList.add('qr--val'); |
|
const val = scope.macros[key]; |
|
if (val instanceof SlashCommandClosure) { |
|
v.classList.add('qr--closure'); |
|
v.title = val.rawText; |
|
v.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
v.classList.add('qr--undefined'); |
|
v.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
v.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
v.textContent = val; |
|
v.classList.add('qr--simple'); |
|
} |
|
} |
|
item.append(v); |
|
} |
|
wrap.append(item); |
|
} |
|
} |
|
const pipeItem = document.createElement('div'); { |
|
pipeItem.classList.add('qr--pipe'); |
|
const k = document.createElement('div'); { |
|
k.classList.add('qr--key'); |
|
k.textContent = 'pipe'; |
|
pipeItem.append(k); |
|
} |
|
const v = document.createElement('div'); { |
|
v.classList.add('qr--val'); |
|
const val = scope.pipe; |
|
if (val instanceof SlashCommandClosure) { |
|
v.classList.add('qr--closure'); |
|
v.title = val.rawText; |
|
v.textContent = val.toString(); |
|
} else if (val === undefined) { |
|
v.classList.add('qr--undefined'); |
|
v.textContent = 'undefined'; |
|
} else { |
|
let jsonVal; |
|
try { jsonVal = JSON.parse(val); } catch { } |
|
if (jsonVal && typeof jsonVal == 'object') { |
|
v.textContent = JSON.stringify(jsonVal, null, 2); |
|
} else { |
|
v.textContent = val; |
|
v.classList.add('qr--simple'); |
|
} |
|
} |
|
pipeItem.append(v); |
|
} |
|
wrap.append(pipeItem); |
|
} |
|
if (scope.parent) { |
|
wrap.append(buildVars(scope.parent)); |
|
} |
|
} |
|
return wrap; |
|
}; |
|
const buildStack = ()=>{ |
|
const wrap = document.createElement('div'); { |
|
wrap.classList.add('qr--stack'); |
|
const title = document.createElement('div'); { |
|
title.classList.add('qr--title'); |
|
title.textContent = 'Call Stack'; |
|
wrap.append(title); |
|
} |
|
let ei = -1; |
|
for (const executor of this.debugController.cmdStack.toReversed()) { |
|
ei++; |
|
const c = this.debugController.stack.toReversed()[ei]; |
|
const item = document.createElement('div'); { |
|
item.classList.add('qr--item'); |
|
if (executor.source == source) { |
|
let hi; |
|
item.addEventListener('pointerenter', ()=>{ |
|
const loc = this.getEditorPosition(Math.max(0, executor.start - 1), executor.end, c.fullText); |
|
const layer = syntax.getBoundingClientRect(); |
|
hi = document.createElement('div'); |
|
hi.classList.add('qr--highlight-secondary'); |
|
hi.style.left = `${loc.left - layer.left}px`; |
|
hi.style.width = `${loc.right - loc.left}px`; |
|
hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; |
|
hi.style.height = `${loc.bottom - loc.top}px`; |
|
syntax.append(hi); |
|
}); |
|
item.addEventListener('pointerleave', ()=>hi?.remove()); |
|
} |
|
const cmd = document.createElement('div'); { |
|
cmd.classList.add('qr--cmd'); |
|
cmd.textContent = `/${executor.name}`; |
|
if (executor.command.name == 'run') { |
|
cmd.textContent += `${(executor.name == ':' ? '' : ' ')}${executor.unnamedArgumentList[0]?.value}`; |
|
} |
|
item.append(cmd); |
|
} |
|
const src = document.createElement('div'); { |
|
src.classList.add('qr--source'); |
|
const line = closure.fullText.slice(0, executor.start).split('\n').length; |
|
if (uuidCheck.test(executor.source)) { |
|
const p1 = document.createElement('span'); { |
|
p1.classList.add('qr--fixed'); |
|
p1.textContent = executor.source.slice(0,5); |
|
src.append(p1); |
|
} |
|
const p2 = document.createElement('span'); { |
|
p2.classList.add('qr--truncated'); |
|
p2.textContent = '…'; |
|
src.append(p2); |
|
} |
|
const p3 = document.createElement('span'); { |
|
p3.classList.add('qr--fixed'); |
|
p3.textContent = `${executor.source.slice(-5)}:${line}`; |
|
src.append(p3); |
|
} |
|
src.title = `anonymous: ${executor.source}`; |
|
} else { |
|
src.textContent = `${executor.source}:${line}`; |
|
} |
|
item.append(src); |
|
} |
|
wrap.append(item); |
|
} |
|
} |
|
} |
|
return wrap; |
|
}; |
|
this.editorDebugState.append(buildVars(closure.scope, true)); |
|
this.editorDebugState.append(buildStack()); |
|
this.editorDebugState.classList.add('qr--active'); |
|
const loc = this.getEditorPosition(Math.max(0, executor.start - 1), executor.end, closure.fullText); |
|
const layer = syntax.getBoundingClientRect(); |
|
const hi = document.createElement('div'); |
|
hi.classList.add('qr--highlight'); |
|
if (this.debugController.namedArguments === undefined) { |
|
hi.classList.add('qr--unresolved'); |
|
} |
|
hi.style.left = `${loc.left - layer.left}px`; |
|
hi.style.width = `${loc.right - loc.left}px`; |
|
hi.style.top = `${loc.top - layer.top + syntax.scrollTop}px`; |
|
hi.style.height = `${loc.bottom - loc.top}px`; |
|
syntax.append(hi); |
|
const isStepping = await this.debugController.awaitContinue(); |
|
hi.remove(); |
|
this.editorDebugState.textContent = ''; |
|
this.editorDebugState.classList.remove('qr--active'); |
|
this.editorDom.classList.remove('qr--isPaused'); |
|
return isStepping; |
|
}; |
|
const result = await this.onDebug(this); |
|
if (this.abortController?.signal?.aborted) { |
|
this.editorExecuteProgress.classList.add('qr--aborted'); |
|
} else { |
|
this.editorExecuteResult.textContent = result?.toString(); |
|
this.editorExecuteResult.classList.add('qr--hasResult'); |
|
this.editorExecuteProgress.classList.add('qr--success'); |
|
} |
|
this.editorExecuteProgress.classList.remove('qr--paused'); |
|
} catch (ex) { |
|
this.editorExecuteErrors.classList.add('qr--hasErrors'); |
|
this.editorExecuteProgress.classList.add('qr--error'); |
|
this.editorExecuteProgress.classList.remove('qr--paused'); |
|
if (ex instanceof SlashCommandParserError) { |
|
this.editorExecuteErrors.innerHTML = ` |
|
<div>${ex.message}</div> |
|
<div>Line: ${ex.line} Column: ${ex.column}</div> |
|
<pre style="text-align:left;">${ex.hint}</pre> |
|
`; |
|
} else { |
|
this.editorExecuteErrors.innerHTML = ` |
|
<div>${ex.message}</div> |
|
`; |
|
} |
|
} |
|
if (noSyntax) { |
|
this.editorDom.querySelector('#qr--modal-messageHolder').classList.add('qr--noSyntax'); |
|
} |
|
this.editorMessageLabel.innerHTML = ''; |
|
this.editorMessageLabel.textContent = 'Message / Command: '; |
|
this.editorMessage.value = oText; |
|
this.editorMessage.dispatchEvent(new Event('input', { bubbles:true })); |
|
this.editorExecutePromise = null; |
|
this.editorExecuteBtn.classList.remove('qr--busy'); |
|
this.editorDom.classList.remove('qr--isExecuting'); |
|
this.isExecuting = false; |
|
this.editorPopup.onClosing = null; |
|
} |
|
|
|
updateEditorProgress(done, total) { |
|
this.editorExecuteProgress.style.setProperty('--prog', `${done / total * 100}`); |
|
} |
|
|
|
|
|
|
|
|
|
delete() { |
|
if (this.onDelete) { |
|
this.unrender(); |
|
this.unrenderSettings(); |
|
this.onDelete(this); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
updateMessage(value) { |
|
if (this.onUpdate) { |
|
if (this.settingsDomMessage && this.settingsDomMessage.value != value) { |
|
this.settingsDomMessage.value = value; |
|
} |
|
this.message = value; |
|
this.updateRender(); |
|
this.onUpdate(this); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
updateIcon(value) { |
|
if (this.onUpdate) { |
|
if (value === null) return; |
|
if (this.settingsDomIcon) { |
|
if (this.icon != value) { |
|
if (value == '') { |
|
if (this.icon) { |
|
this.settingsDomIcon.classList.remove(this.icon); |
|
} |
|
this.settingsDomIcon.textContent = '…'; |
|
this.settingsDomIcon.classList.remove('fa-solid'); |
|
} else { |
|
if (this.icon) { |
|
this.settingsDomIcon.classList.remove(this.icon); |
|
} else { |
|
this.settingsDomIcon.classList.add('fa-solid'); |
|
} |
|
this.settingsDomIcon.classList.add(value); |
|
} |
|
} |
|
} |
|
this.icon = value; |
|
this.updateRender(); |
|
this.onUpdate(this); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
updateShowLabel(value) { |
|
if (this.onUpdate) { |
|
this.showLabel = value; |
|
this.updateRender(); |
|
this.onUpdate(this); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
updateLabel(value) { |
|
if (this.onUpdate) { |
|
if (this.settingsDomLabel && this.settingsDomLabel.value != value) { |
|
this.settingsDomLabel.value = value; |
|
} |
|
this.label = value; |
|
this.updateRender(); |
|
this.onUpdate(this); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
updateTitle(value) { |
|
if (this.onUpdate) { |
|
this.title = 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(); |
|
} |
|
} |
|
|
|
|
|
async execute(args = {}, isEditor = false, isRun = false, options = {}) { |
|
if (this.message?.length > 0 && this.onExecute) { |
|
const scope = new SlashCommandScope(); |
|
for (const key of Object.keys(args)) { |
|
if (key[0] == '_') continue; |
|
if (key == 'isAutoExecute') continue; |
|
scope.setMacro(`arg::${key}`, args[key]); |
|
} |
|
scope.setMacro('arg::*', ''); |
|
if (isEditor) { |
|
this.abortController = new SlashCommandAbortController(); |
|
} |
|
return await this.onExecute(this, { |
|
message: this.message, |
|
isAutoExecute: args.isAutoExecute ?? false, |
|
isEditor, |
|
isRun, |
|
scope, |
|
executionOptions: options, |
|
}); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
toJSON() { |
|
return { |
|
id: this.id, |
|
icon: this.icon, |
|
showLabel: this.showLabel, |
|
label: this.label, |
|
title: this.title, |
|
message: this.message, |
|
contextList: this.contextList, |
|
preventAutoExecute: this.preventAutoExecute, |
|
isHidden: this.isHidden, |
|
executeOnStartup: this.executeOnStartup, |
|
executeOnUser: this.executeOnUser, |
|
executeOnAi: this.executeOnAi, |
|
executeOnChatChange: this.executeOnChatChange, |
|
executeOnGroupMemberDraft: this.executeOnGroupMemberDraft, |
|
executeOnNewChat: this.executeOnNewChat, |
|
automationId: this.automationId, |
|
}; |
|
} |
|
} |
|
|